@vybestack/llxprt-code-tools 0.10.0-nightly.260613.1adad3b34
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/LICENSE +202 -0
- package/README.md +294 -0
- package/dist/.last_build +0 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/src/__tests__/fixtures/filesystem-tool-fixtures.d.ts +29 -0
- package/dist/src/__tests__/fixtures/filesystem-tool-fixtures.js +30 -0
- package/dist/src/__tests__/fixtures/filesystem-tool-fixtures.js.map +1 -0
- package/dist/src/__tests__/fixtures/key-storage-fixtures.d.ts +39 -0
- package/dist/src/__tests__/fixtures/key-storage-fixtures.js +53 -0
- package/dist/src/__tests__/fixtures/key-storage-fixtures.js.map +1 -0
- package/dist/src/__tests__/fixtures/provider-formatting-fixtures.d.ts +140 -0
- package/dist/src/__tests__/fixtures/provider-formatting-fixtures.js +157 -0
- package/dist/src/__tests__/fixtures/provider-formatting-fixtures.js.map +1 -0
- package/dist/src/__tests__/red-test-helpers.d.ts +14 -0
- package/dist/src/__tests__/red-test-helpers.js +18 -0
- package/dist/src/__tests__/red-test-helpers.js.map +1 -0
- package/dist/src/formatters/IToolFormatter.d.ts +84 -0
- package/dist/src/formatters/IToolFormatter.js +6 -0
- package/dist/src/formatters/IToolFormatter.js.map +1 -0
- package/dist/src/formatters/ToolFormatter.d.ts +94 -0
- package/dist/src/formatters/ToolFormatter.js +379 -0
- package/dist/src/formatters/ToolFormatter.js.map +1 -0
- package/dist/src/formatters/ToolIdStrategy.d.ts +79 -0
- package/dist/src/formatters/ToolIdStrategy.js +173 -0
- package/dist/src/formatters/ToolIdStrategy.js.map +1 -0
- package/dist/src/formatters/doubleEscapeUtils.d.ts +46 -0
- package/dist/src/formatters/doubleEscapeUtils.js +223 -0
- package/dist/src/formatters/doubleEscapeUtils.js.map +1 -0
- package/dist/src/formatters/index.d.ts +21 -0
- package/dist/src/formatters/index.js +12 -0
- package/dist/src/formatters/index.js.map +1 -0
- package/dist/src/formatters/toolIdNormalization.d.ts +28 -0
- package/dist/src/formatters/toolIdNormalization.js +97 -0
- package/dist/src/formatters/toolIdNormalization.js.map +1 -0
- package/dist/src/formatters/toolNameUtils.d.ts +43 -0
- package/dist/src/formatters/toolNameUtils.js +143 -0
- package/dist/src/formatters/toolNameUtils.js.map +1 -0
- package/dist/src/index.d.ts +88 -0
- package/dist/src/index.js +68 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/interfaces/IAsyncTaskService.d.ts +57 -0
- package/dist/src/interfaces/IAsyncTaskService.js +6 -0
- package/dist/src/interfaces/IAsyncTaskService.js.map +1 -0
- package/dist/src/interfaces/IIdeService.d.ts +62 -0
- package/dist/src/interfaces/IIdeService.js +6 -0
- package/dist/src/interfaces/IIdeService.js.map +1 -0
- package/dist/src/interfaces/ILspService.d.ts +54 -0
- package/dist/src/interfaces/ILspService.js +6 -0
- package/dist/src/interfaces/ILspService.js.map +1 -0
- package/dist/src/interfaces/IMcpToolService.d.ts +47 -0
- package/dist/src/interfaces/IMcpToolService.js +6 -0
- package/dist/src/interfaces/IMcpToolService.js.map +1 -0
- package/dist/src/interfaces/IPromptRegistryService.d.ts +51 -0
- package/dist/src/interfaces/IPromptRegistryService.js +6 -0
- package/dist/src/interfaces/IPromptRegistryService.js.map +1 -0
- package/dist/src/interfaces/ISettingsService.d.ts +50 -0
- package/dist/src/interfaces/ISettingsService.js +6 -0
- package/dist/src/interfaces/ISettingsService.js.map +1 -0
- package/dist/src/interfaces/IShellExecutionService.d.ts +55 -0
- package/dist/src/interfaces/IShellExecutionService.js +6 -0
- package/dist/src/interfaces/IShellExecutionService.js.map +1 -0
- package/dist/src/interfaces/IShellToolHost.d.ts +176 -0
- package/dist/src/interfaces/IShellToolHost.js +6 -0
- package/dist/src/interfaces/IShellToolHost.js.map +1 -0
- package/dist/src/interfaces/ISkillService.d.ts +69 -0
- package/dist/src/interfaces/ISkillService.js +6 -0
- package/dist/src/interfaces/ISkillService.js.map +1 -0
- package/dist/src/interfaces/IStorageService.d.ts +42 -0
- package/dist/src/interfaces/IStorageService.js +6 -0
- package/dist/src/interfaces/IStorageService.js.map +1 -0
- package/dist/src/interfaces/ISubagentService.d.ts +112 -0
- package/dist/src/interfaces/ISubagentService.js +6 -0
- package/dist/src/interfaces/ISubagentService.js.map +1 -0
- package/dist/src/interfaces/ITaskToolHost.d.ts +185 -0
- package/dist/src/interfaces/ITaskToolHost.js +6 -0
- package/dist/src/interfaces/ITaskToolHost.js.map +1 -0
- package/dist/src/interfaces/ITodoService.d.ts +79 -0
- package/dist/src/interfaces/ITodoService.js +6 -0
- package/dist/src/interfaces/ITodoService.js.map +1 -0
- package/dist/src/interfaces/IToolHost.d.ts +91 -0
- package/dist/src/interfaces/IToolHost.js +6 -0
- package/dist/src/interfaces/IToolHost.js.map +1 -0
- package/dist/src/interfaces/IToolKeyStorage.d.ts +62 -0
- package/dist/src/interfaces/IToolKeyStorage.js +6 -0
- package/dist/src/interfaces/IToolKeyStorage.js.map +1 -0
- package/dist/src/interfaces/IToolMessageBus.d.ts +63 -0
- package/dist/src/interfaces/IToolMessageBus.js +6 -0
- package/dist/src/interfaces/IToolMessageBus.js.map +1 -0
- package/dist/src/interfaces/IToolRegistryHost.d.ts +43 -0
- package/dist/src/interfaces/IToolRegistryHost.js +6 -0
- package/dist/src/interfaces/IToolRegistryHost.js.map +1 -0
- package/dist/src/interfaces/IWebSearchService.d.ts +16 -0
- package/dist/src/interfaces/IWebSearchService.js +7 -0
- package/dist/src/interfaces/IWebSearchService.js.map +1 -0
- package/dist/src/interfaces/index.d.ts +33 -0
- package/dist/src/interfaces/index.js +6 -0
- package/dist/src/interfaces/index.js.map +1 -0
- package/dist/src/tools/activate-skill.d.ts +26 -0
- package/dist/src/tools/activate-skill.js +121 -0
- package/dist/src/tools/activate-skill.js.map +1 -0
- package/dist/src/tools/apply-patch.d.ts +65 -0
- package/dist/src/tools/apply-patch.js +528 -0
- package/dist/src/tools/apply-patch.js.map +1 -0
- package/dist/src/tools/ast-edit/ast-config.d.ts +67 -0
- package/dist/src/tools/ast-edit/ast-config.js +72 -0
- package/dist/src/tools/ast-edit/ast-config.js.map +1 -0
- package/dist/src/tools/ast-edit/ast-edit-invocation.d.ts +40 -0
- package/dist/src/tools/ast-edit/ast-edit-invocation.js +410 -0
- package/dist/src/tools/ast-edit/ast-edit-invocation.js.map +1 -0
- package/dist/src/tools/ast-edit/ast-query-extractor.d.ts +21 -0
- package/dist/src/tools/ast-edit/ast-query-extractor.js +178 -0
- package/dist/src/tools/ast-edit/ast-query-extractor.js.map +1 -0
- package/dist/src/tools/ast-edit/ast-read-file-invocation.d.ts +26 -0
- package/dist/src/tools/ast-edit/ast-read-file-invocation.js +149 -0
- package/dist/src/tools/ast-edit/ast-read-file-invocation.js.map +1 -0
- package/dist/src/tools/ast-edit/constants.d.ts +30 -0
- package/dist/src/tools/ast-edit/constants.js +36 -0
- package/dist/src/tools/ast-edit/constants.js.map +1 -0
- package/dist/src/tools/ast-edit/context-collector.d.ts +25 -0
- package/dist/src/tools/ast-edit/context-collector.js +115 -0
- package/dist/src/tools/ast-edit/context-collector.js.map +1 -0
- package/dist/src/tools/ast-edit/context-optimizer.d.ts +29 -0
- package/dist/src/tools/ast-edit/context-optimizer.js +86 -0
- package/dist/src/tools/ast-edit/context-optimizer.js.map +1 -0
- package/dist/src/tools/ast-edit/cross-file-analyzer.d.ts +41 -0
- package/dist/src/tools/ast-edit/cross-file-analyzer.js +294 -0
- package/dist/src/tools/ast-edit/cross-file-analyzer.js.map +1 -0
- package/dist/src/tools/ast-edit/edit-calculator.d.ts +71 -0
- package/dist/src/tools/ast-edit/edit-calculator.js +249 -0
- package/dist/src/tools/ast-edit/edit-calculator.js.map +1 -0
- package/dist/src/tools/ast-edit/edit-helpers.d.ts +22 -0
- package/dist/src/tools/ast-edit/edit-helpers.js +36 -0
- package/dist/src/tools/ast-edit/edit-helpers.js.map +1 -0
- package/dist/src/tools/ast-edit/language-analysis.d.ts +19 -0
- package/dist/src/tools/ast-edit/language-analysis.js +123 -0
- package/dist/src/tools/ast-edit/language-analysis.js.map +1 -0
- package/dist/src/tools/ast-edit/local-context-analyzer.d.ts +84 -0
- package/dist/src/tools/ast-edit/local-context-analyzer.js +267 -0
- package/dist/src/tools/ast-edit/local-context-analyzer.js.map +1 -0
- package/dist/src/tools/ast-edit/repository-context-provider.d.ts +22 -0
- package/dist/src/tools/ast-edit/repository-context-provider.js +139 -0
- package/dist/src/tools/ast-edit/repository-context-provider.js.map +1 -0
- package/dist/src/tools/ast-edit/types.d.ts +155 -0
- package/dist/src/tools/ast-edit/types.js +7 -0
- package/dist/src/tools/ast-edit/types.js.map +1 -0
- package/dist/src/tools/ast-edit/workspace-context-provider.d.ts +22 -0
- package/dist/src/tools/ast-edit/workspace-context-provider.js +39 -0
- package/dist/src/tools/ast-edit/workspace-context-provider.js.map +1 -0
- package/dist/src/tools/ast-edit.d.ts +43 -0
- package/dist/src/tools/ast-edit.js +183 -0
- package/dist/src/tools/ast-edit.js.map +1 -0
- package/dist/src/tools/ast-grep.d.ts +22 -0
- package/dist/src/tools/ast-grep.js +291 -0
- package/dist/src/tools/ast-grep.js.map +1 -0
- package/dist/src/tools/check-async-tasks.d.ts +46 -0
- package/dist/src/tools/check-async-tasks.js +241 -0
- package/dist/src/tools/check-async-tasks.js.map +1 -0
- package/dist/src/tools/codesearch.d.ts +25 -0
- package/dist/src/tools/codesearch.js +153 -0
- package/dist/src/tools/codesearch.js.map +1 -0
- package/dist/src/tools/delete_line_range.d.ts +41 -0
- package/dist/src/tools/delete_line_range.js +238 -0
- package/dist/src/tools/delete_line_range.js.map +1 -0
- package/dist/src/tools/direct-web-fetch.d.ts +22 -0
- package/dist/src/tools/direct-web-fetch.js +215 -0
- package/dist/src/tools/direct-web-fetch.js.map +1 -0
- package/dist/src/tools/edit-utils.d.ts +53 -0
- package/dist/src/tools/edit-utils.js +250 -0
- package/dist/src/tools/edit-utils.js.map +1 -0
- package/dist/src/tools/edit.d.ts +70 -0
- package/dist/src/tools/edit.js +816 -0
- package/dist/src/tools/edit.js.map +1 -0
- package/dist/src/tools/exa-web-search.d.ts +27 -0
- package/dist/src/tools/exa-web-search.js +153 -0
- package/dist/src/tools/exa-web-search.js.map +1 -0
- package/dist/src/tools/glob.d.ts +55 -0
- package/dist/src/tools/glob.js +284 -0
- package/dist/src/tools/glob.js.map +1 -0
- package/dist/src/tools/google-web-fetch.d.ts +34 -0
- package/dist/src/tools/google-web-fetch.js +417 -0
- package/dist/src/tools/google-web-fetch.js.map +1 -0
- package/dist/src/tools/google-web-search-invocation.d.ts +53 -0
- package/dist/src/tools/google-web-search-invocation.js +180 -0
- package/dist/src/tools/google-web-search-invocation.js.map +1 -0
- package/dist/src/tools/google-web-search.d.ts +16 -0
- package/dist/src/tools/google-web-search.js +34 -0
- package/dist/src/tools/google-web-search.js.map +1 -0
- package/dist/src/tools/grep.d.ts +56 -0
- package/dist/src/tools/grep.js +883 -0
- package/dist/src/tools/grep.js.map +1 -0
- package/dist/src/tools/insert_at_line.d.ts +41 -0
- package/dist/src/tools/insert_at_line.js +287 -0
- package/dist/src/tools/insert_at_line.js.map +1 -0
- package/dist/src/tools/list-subagents.d.ts +31 -0
- package/dist/src/tools/list-subagents.js +122 -0
- package/dist/src/tools/list-subagents.js.map +1 -0
- package/dist/src/tools/ls.d.ts +71 -0
- package/dist/src/tools/ls.js +238 -0
- package/dist/src/tools/ls.js.map +1 -0
- package/dist/src/tools/memoryTool.d.ts +66 -0
- package/dist/src/tools/memoryTool.js +435 -0
- package/dist/src/tools/memoryTool.js.map +1 -0
- package/dist/src/tools/modifiable-tool.d.ts +37 -0
- package/dist/src/tools/modifiable-tool.js +120 -0
- package/dist/src/tools/modifiable-tool.js.map +1 -0
- package/dist/src/tools/read-file.d.ts +49 -0
- package/dist/src/tools/read-file.js +279 -0
- package/dist/src/tools/read-file.js.map +1 -0
- package/dist/src/tools/read-many-files.d.ts +60 -0
- package/dist/src/tools/read-many-files.js +529 -0
- package/dist/src/tools/read-many-files.js.map +1 -0
- package/dist/src/tools/read_line_range.d.ts +47 -0
- package/dist/src/tools/read_line_range.js +248 -0
- package/dist/src/tools/read_line_range.js.map +1 -0
- package/dist/src/tools/ripGrep.d.ts +41 -0
- package/dist/src/tools/ripGrep.js +395 -0
- package/dist/src/tools/ripGrep.js.map +1 -0
- package/dist/src/tools/shell.d.ts +60 -0
- package/dist/src/tools/shell.js +735 -0
- package/dist/src/tools/shell.js.map +1 -0
- package/dist/src/tools/structural-analysis.d.ts +27 -0
- package/dist/src/tools/structural-analysis.js +1089 -0
- package/dist/src/tools/structural-analysis.js.map +1 -0
- package/dist/src/tools/stubs.d.ts +10 -0
- package/dist/src/tools/stubs.js +6 -0
- package/dist/src/tools/stubs.js.map +1 -0
- package/dist/src/tools/task.d.ts +41 -0
- package/dist/src/tools/task.js +195 -0
- package/dist/src/tools/task.js.map +1 -0
- package/dist/src/tools/todo-events.d.ts +22 -0
- package/dist/src/tools/todo-events.js +24 -0
- package/dist/src/tools/todo-events.js.map +1 -0
- package/dist/src/tools/todo-pause.d.ts +29 -0
- package/dist/src/tools/todo-pause.js +172 -0
- package/dist/src/tools/todo-pause.js.map +1 -0
- package/dist/src/tools/todo-read.d.ts +18 -0
- package/dist/src/tools/todo-read.js +98 -0
- package/dist/src/tools/todo-read.js.map +1 -0
- package/dist/src/tools/todo-store.d.ts +35 -0
- package/dist/src/tools/todo-store.js +124 -0
- package/dist/src/tools/todo-store.js.map +1 -0
- package/dist/src/tools/todo-write.d.ts +29 -0
- package/dist/src/tools/todo-write.js +366 -0
- package/dist/src/tools/todo-write.js.map +1 -0
- package/dist/src/tools/tool-registry.d.ts +156 -0
- package/dist/src/tools/tool-registry.js +623 -0
- package/dist/src/tools/tool-registry.js.map +1 -0
- package/dist/src/tools/tools.d.ts +403 -0
- package/dist/src/tools/tools.js +519 -0
- package/dist/src/tools/tools.js.map +1 -0
- package/dist/src/tools/write-file.d.ts +45 -0
- package/dist/src/tools/write-file.js +320 -0
- package/dist/src/tools/write-file.js.map +1 -0
- package/dist/src/types/index.d.ts +17 -0
- package/dist/src/types/index.js +9 -0
- package/dist/src/types/index.js.map +1 -0
- package/dist/src/types/provider-content-types.d.ts +56 -0
- package/dist/src/types/provider-content-types.js +6 -0
- package/dist/src/types/provider-content-types.js.map +1 -0
- package/dist/src/types/todo-schemas.d.ts +263 -0
- package/dist/src/types/todo-schemas.js +32 -0
- package/dist/src/types/todo-schemas.js.map +1 -0
- package/dist/src/types/tool-confirmation-types.d.ts +34 -0
- package/dist/src/types/tool-confirmation-types.js +29 -0
- package/dist/src/types/tool-confirmation-types.js.map +1 -0
- package/dist/src/types/tool-context.d.ts +31 -0
- package/dist/src/types/tool-context.js +6 -0
- package/dist/src/types/tool-context.js.map +1 -0
- package/dist/src/types/tool-error.d.ts +69 -0
- package/dist/src/types/tool-error.js +94 -0
- package/dist/src/types/tool-error.js.map +1 -0
- package/dist/src/types/tool-names.d.ts +57 -0
- package/dist/src/types/tool-names.js +64 -0
- package/dist/src/types/tool-names.js.map +1 -0
- package/dist/src/utils/EmojiFilter.d.ts +134 -0
- package/dist/src/utils/EmojiFilter.js +370 -0
- package/dist/src/utils/EmojiFilter.js.map +1 -0
- package/dist/src/utils/ast-grep-utils.d.ts +42 -0
- package/dist/src/utils/ast-grep-utils.js +140 -0
- package/dist/src/utils/ast-grep-utils.js.map +1 -0
- package/dist/src/utils/debugLogger.d.ts +11 -0
- package/dist/src/utils/debugLogger.js +16 -0
- package/dist/src/utils/debugLogger.js.map +1 -0
- package/dist/src/utils/diffOptions.d.ts +14 -0
- package/dist/src/utils/diffOptions.js +46 -0
- package/dist/src/utils/diffOptions.js.map +1 -0
- package/dist/src/utils/editor.d.ts +39 -0
- package/dist/src/utils/editor.js +212 -0
- package/dist/src/utils/editor.js.map +1 -0
- package/dist/src/utils/ensure-dirs.d.ts +10 -0
- package/dist/src/utils/ensure-dirs.js +16 -0
- package/dist/src/utils/ensure-dirs.js.map +1 -0
- package/dist/src/utils/errors.d.ts +59 -0
- package/dist/src/utils/errors.js +178 -0
- package/dist/src/utils/errors.js.map +1 -0
- package/dist/src/utils/fetch.d.ts +11 -0
- package/dist/src/utils/fetch.js +74 -0
- package/dist/src/utils/fetch.js.map +1 -0
- package/dist/src/utils/fileUtils.d.ts +32 -0
- package/dist/src/utils/fileUtils.js +363 -0
- package/dist/src/utils/fileUtils.js.map +1 -0
- package/dist/src/utils/fuzzy-replacer.d.ts +61 -0
- package/dist/src/utils/fuzzy-replacer.js +492 -0
- package/dist/src/utils/fuzzy-replacer.js.map +1 -0
- package/dist/src/utils/gitLineChanges.d.ts +12 -0
- package/dist/src/utils/gitLineChanges.js +171 -0
- package/dist/src/utils/gitLineChanges.js.map +1 -0
- package/dist/src/utils/gitUtils.d.ts +6 -0
- package/dist/src/utils/gitUtils.js +31 -0
- package/dist/src/utils/gitUtils.js.map +1 -0
- package/dist/src/utils/lsp-diagnostics-helper.d.ts +27 -0
- package/dist/src/utils/lsp-diagnostics-helper.js +62 -0
- package/dist/src/utils/lsp-diagnostics-helper.js.map +1 -0
- package/dist/src/utils/mediaUtils.d.ts +38 -0
- package/dist/src/utils/mediaUtils.js +22 -0
- package/dist/src/utils/mediaUtils.js.map +1 -0
- package/dist/src/utils/pathValidation.d.ts +7 -0
- package/dist/src/utils/pathValidation.js +39 -0
- package/dist/src/utils/pathValidation.js.map +1 -0
- package/dist/src/utils/paths.d.ts +7 -0
- package/dist/src/utils/paths.js +158 -0
- package/dist/src/utils/paths.js.map +1 -0
- package/dist/src/utils/resolveTextSearchTarget.d.ts +17 -0
- package/dist/src/utils/resolveTextSearchTarget.js +45 -0
- package/dist/src/utils/resolveTextSearchTarget.js.map +1 -0
- package/dist/src/utils/retry.d.ts +17 -0
- package/dist/src/utils/retry.js +185 -0
- package/dist/src/utils/retry.js.map +1 -0
- package/dist/src/utils/ripgrepPathResolver.d.ts +23 -0
- package/dist/src/utils/ripgrepPathResolver.js +179 -0
- package/dist/src/utils/ripgrepPathResolver.js.map +1 -0
- package/dist/src/utils/safeJsonStringify.d.ts +9 -0
- package/dist/src/utils/safeJsonStringify.js +21 -0
- package/dist/src/utils/safeJsonStringify.js.map +1 -0
- package/dist/src/utils/schemaValidator.d.ts +13 -0
- package/dist/src/utils/schemaValidator.js +275 -0
- package/dist/src/utils/schemaValidator.js.map +1 -0
- package/dist/src/utils/terminalSerializer.d.ts +33 -0
- package/dist/src/utils/terminalSerializer.js +6 -0
- package/dist/src/utils/terminalSerializer.js.map +1 -0
- package/dist/src/utils/todoContextTracker.d.ts +12 -0
- package/dist/src/utils/todoContextTracker.js +28 -0
- package/dist/src/utils/todoContextTracker.js.map +1 -0
- package/dist/src/utils/todoFormatter.d.ts +24 -0
- package/dist/src/utils/todoFormatter.js +157 -0
- package/dist/src/utils/todoFormatter.js.map +1 -0
- package/dist/src/utils/todoReminderService.d.ts +22 -0
- package/dist/src/utils/todoReminderService.js +43 -0
- package/dist/src/utils/todoReminderService.js.map +1 -0
- package/dist/src/utils/tool-key-storage-facade.d.ts +32 -0
- package/dist/src/utils/tool-key-storage-facade.js +56 -0
- package/dist/src/utils/tool-key-storage-facade.js.map +1 -0
- package/dist/src/utils/tool-key-storage-types.d.ts +56 -0
- package/dist/src/utils/tool-key-storage-types.js +56 -0
- package/dist/src/utils/tool-key-storage-types.js.map +1 -0
- package/dist/src/utils/toolOutputLimiter.d.ts +29 -0
- package/dist/src/utils/toolOutputLimiter.js +107 -0
- package/dist/src/utils/toolOutputLimiter.js.map +1 -0
- package/dist/src/utils/unicodeUtils.d.ts +55 -0
- package/dist/src/utils/unicodeUtils.js +129 -0
- package/dist/src/utils/unicodeUtils.js.map +1 -0
- package/package.json +75 -0
|
@@ -0,0 +1,883 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
/* eslint-disable complexity, sonarjs/cognitive-complexity, max-lines -- Phase 5: legacy core boundary retained while larger decomposition continues. */
|
|
7
|
+
import fsPromises from 'fs/promises';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { EOL } from 'os';
|
|
10
|
+
import { spawn } from 'child_process';
|
|
11
|
+
import { globStream } from 'glob';
|
|
12
|
+
import { BaseDeclarativeTool, BaseToolInvocation, Kind, } from './tools.js';
|
|
13
|
+
import { makeRelative, shortenPath } from '../utils/paths.js';
|
|
14
|
+
import { getErrorMessage, isNodeError } from '../utils/errors.js';
|
|
15
|
+
import { isGitRepository } from '../utils/gitUtils.js';
|
|
16
|
+
import { resolveTextSearchTarget, } from '../utils/resolveTextSearchTarget.js';
|
|
17
|
+
import { ToolErrorType } from '../types/tool-error.js';
|
|
18
|
+
import { limitOutputTokens, formatLimitedOutput, } from '../utils/toolOutputLimiter.js';
|
|
19
|
+
import { debugLogger } from '../utils/debugLogger.js';
|
|
20
|
+
/**
|
|
21
|
+
* Checks if a glob pattern contains brace expansion syntax that git grep doesn't support.
|
|
22
|
+
* Git grep pathspecs don't support shell-style brace expansion like {ts,tsx,js}.
|
|
23
|
+
* Uses indexOf for O(n) complexity instead of regex to avoid ReDoS vulnerability.
|
|
24
|
+
*/
|
|
25
|
+
function hasBraceExpansion(pattern) {
|
|
26
|
+
const braceStart = pattern.indexOf('{');
|
|
27
|
+
if (braceStart === -1)
|
|
28
|
+
return false;
|
|
29
|
+
const braceEnd = pattern.indexOf('}', braceStart);
|
|
30
|
+
if (braceEnd === -1)
|
|
31
|
+
return false;
|
|
32
|
+
const commaPos = pattern.indexOf(',', braceStart);
|
|
33
|
+
return commaPos !== -1 && commaPos < braceEnd;
|
|
34
|
+
}
|
|
35
|
+
// --- Interfaces ---
|
|
36
|
+
/**
|
|
37
|
+
* Default timeout for grep operations in milliseconds (1 minute)
|
|
38
|
+
*/
|
|
39
|
+
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
40
|
+
/**
|
|
41
|
+
* Maximum allowed timeout for grep operations in milliseconds (5 minutes)
|
|
42
|
+
*/
|
|
43
|
+
const MAX_TIMEOUT_MS = 300_000;
|
|
44
|
+
class GrepToolInvocation extends BaseToolInvocation {
|
|
45
|
+
host;
|
|
46
|
+
fileExclusions;
|
|
47
|
+
constructor(host, params, messageBus) {
|
|
48
|
+
super(params, messageBus);
|
|
49
|
+
this.host = host;
|
|
50
|
+
this.fileExclusions = host.getFileExclusions();
|
|
51
|
+
}
|
|
52
|
+
getDirPath() {
|
|
53
|
+
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- intentional falsy coalescing: dir_path and path are optional strings, empty string should fall through
|
|
54
|
+
return this.params.dir_path || this.params.path;
|
|
55
|
+
}
|
|
56
|
+
resolveTarget(relativePath) {
|
|
57
|
+
return resolveTextSearchTarget(this.host.getTargetDir(), this.host.getWorkspaceRoots(), relativePath);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Executes a single-file search and returns a formatted ToolResult.
|
|
61
|
+
*/
|
|
62
|
+
async executeSingleFileSearch(resolved, combinedSignal, searchDirDisplay) {
|
|
63
|
+
const fileResult = await this.performSingleFileSearch(this.params.pattern, resolved.filePath, resolved.basename, combinedSignal);
|
|
64
|
+
let includeNote = '';
|
|
65
|
+
if (this.params.include) {
|
|
66
|
+
includeNote =
|
|
67
|
+
'\nNote: include filter ignored because a specific file path was provided.';
|
|
68
|
+
}
|
|
69
|
+
if (fileResult.length === 0) {
|
|
70
|
+
const noMatchMsg = `No matches found for pattern "${this.params.pattern}" in file "${searchDirDisplay}".${includeNote}`;
|
|
71
|
+
return { llmContent: noMatchMsg, returnDisplay: 'No matches found' };
|
|
72
|
+
}
|
|
73
|
+
const matchTerm = fileResult.length === 1 ? 'match' : 'matches';
|
|
74
|
+
let llmContent = `Found ${fileResult.length} ${matchTerm} for pattern "${this.params.pattern}" in file "${searchDirDisplay}":${includeNote}
|
|
75
|
+
---
|
|
76
|
+
File: ${resolved.basename}
|
|
77
|
+
`;
|
|
78
|
+
for (const match of fileResult) {
|
|
79
|
+
llmContent += `L${match.lineNumber}: ${match.line.trim()}
|
|
80
|
+
`;
|
|
81
|
+
}
|
|
82
|
+
llmContent += '---';
|
|
83
|
+
const limited = limitOutputTokens(llmContent.trim(), this.host, 'SearchText');
|
|
84
|
+
if (limited.wasTruncated) {
|
|
85
|
+
const formatted = formatLimitedOutput(limited);
|
|
86
|
+
return {
|
|
87
|
+
llmContent: formatted.llmContent,
|
|
88
|
+
returnDisplay: formatted.returnDisplay,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
llmContent: llmContent.trim(),
|
|
93
|
+
returnDisplay: `Found ${fileResult.length} ${matchTerm}`,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Collects matches across multiple search directories.
|
|
98
|
+
*/
|
|
99
|
+
async collectDirectoryMatches(searchDirectories, combinedSignal, maxResults, maxFiles, maxPerFile, filesWithMatches) {
|
|
100
|
+
let allMatches = [];
|
|
101
|
+
let totalMatchesFound = 0;
|
|
102
|
+
let wasLimited = false;
|
|
103
|
+
for (const searchDir of searchDirectories) {
|
|
104
|
+
if (allMatches.length >= maxResults) {
|
|
105
|
+
wasLimited = true;
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
const matches = await this.performGrepSearch({
|
|
109
|
+
pattern: this.params.pattern,
|
|
110
|
+
path: searchDir,
|
|
111
|
+
include: this.params.include,
|
|
112
|
+
signal: combinedSignal,
|
|
113
|
+
maxResults: maxResults - allMatches.length,
|
|
114
|
+
maxFiles: maxFiles - filesWithMatches.size,
|
|
115
|
+
maxPerFile,
|
|
116
|
+
});
|
|
117
|
+
if (matches.wasLimited === true) {
|
|
118
|
+
wasLimited = true;
|
|
119
|
+
}
|
|
120
|
+
if (searchDirectories.length > 1) {
|
|
121
|
+
const dirName = path.basename(searchDir);
|
|
122
|
+
matches.results.forEach((match) => {
|
|
123
|
+
match.filePath = path.join(dirName, match.filePath);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
matches.results.forEach((match) => {
|
|
127
|
+
filesWithMatches.add(match.filePath);
|
|
128
|
+
});
|
|
129
|
+
totalMatchesFound += matches.totalFound ?? matches.results.length;
|
|
130
|
+
allMatches = allMatches.concat(matches.results);
|
|
131
|
+
}
|
|
132
|
+
return { allMatches, totalMatchesFound, wasLimited };
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Applies max_files and max_per_file limits, returning grouped matches,
|
|
136
|
+
* match count, and any limit message.
|
|
137
|
+
*/
|
|
138
|
+
applyFileLimits(allMatches, filesWithMatches, maxFiles, maxPerFile) {
|
|
139
|
+
let limitedMatches = allMatches;
|
|
140
|
+
let limitMessage = '';
|
|
141
|
+
let wasLimited = false;
|
|
142
|
+
if (filesWithMatches.size > maxFiles) {
|
|
143
|
+
const filesToKeep = Array.from(filesWithMatches).slice(0, maxFiles);
|
|
144
|
+
limitedMatches = allMatches.filter((match) => filesToKeep.includes(match.filePath));
|
|
145
|
+
limitMessage = `
|
|
146
|
+
|
|
147
|
+
**Note: Results limited to ${maxFiles} files out of ${filesWithMatches.size} files with matches.**`;
|
|
148
|
+
wasLimited = true;
|
|
149
|
+
}
|
|
150
|
+
const matchesByFile = limitedMatches.reduce((acc, match) => {
|
|
151
|
+
const fileKey = match.filePath;
|
|
152
|
+
if (!(fileKey in acc)) {
|
|
153
|
+
acc[fileKey] = [];
|
|
154
|
+
}
|
|
155
|
+
if (acc[fileKey].length < maxPerFile) {
|
|
156
|
+
acc[fileKey].push(match);
|
|
157
|
+
}
|
|
158
|
+
acc[fileKey].sort((a, b) => a.lineNumber - b.lineNumber);
|
|
159
|
+
return acc;
|
|
160
|
+
}, {});
|
|
161
|
+
const matchCount = Object.values(matchesByFile).reduce((sum, matches) => sum + matches.length, 0);
|
|
162
|
+
return {
|
|
163
|
+
matchesByFile,
|
|
164
|
+
matchCount,
|
|
165
|
+
limitedMatches,
|
|
166
|
+
limitMessage,
|
|
167
|
+
wasLimited,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Builds the formatted LLM content string from grouped matches.
|
|
172
|
+
*/
|
|
173
|
+
formatMatchOutput(matchesByFile, limitMessage, totalMatchesFound, matchCount, wasLimited, searchLocationDescription) {
|
|
174
|
+
let llmContent = '';
|
|
175
|
+
if (wasLimited || totalMatchesFound > matchCount) {
|
|
176
|
+
llmContent = `Found ${totalMatchesFound} total matches, showing ${matchCount} for pattern "${this.params.pattern}" ${searchLocationDescription}${this.params.include ? ` (filter: "${this.params.include}")` : ''}:
|
|
177
|
+
---
|
|
178
|
+
`;
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
const matchTerm = matchCount === 1 ? 'match' : 'matches';
|
|
182
|
+
llmContent = `Found ${matchCount} ${matchTerm} for pattern "${this.params.pattern}" ${searchLocationDescription}${this.params.include ? ` (filter: "${this.params.include}")` : ''}:
|
|
183
|
+
---
|
|
184
|
+
`;
|
|
185
|
+
}
|
|
186
|
+
for (const filePath in matchesByFile) {
|
|
187
|
+
llmContent += `File: ${filePath}
|
|
188
|
+
`;
|
|
189
|
+
matchesByFile[filePath].forEach((match) => {
|
|
190
|
+
const trimmedLine = match.line.trim();
|
|
191
|
+
llmContent += `L${match.lineNumber}: ${trimmedLine}
|
|
192
|
+
`;
|
|
193
|
+
});
|
|
194
|
+
llmContent += '---\n';
|
|
195
|
+
}
|
|
196
|
+
if (limitMessage) {
|
|
197
|
+
llmContent += limitMessage;
|
|
198
|
+
}
|
|
199
|
+
return llmContent;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Applies file and per-file limits to matches and builds the output content.
|
|
203
|
+
*/
|
|
204
|
+
buildDirectorySearchResult(allMatches, totalMatchesFound, wasLimited, filesWithMatches, searchLocationDescription, maxFiles, maxPerFile) {
|
|
205
|
+
if (allMatches.length === 0) {
|
|
206
|
+
const noMatchMsg = `No matches found for pattern "${this.params.pattern}" ${searchLocationDescription}${this.params.include ? ` (filter: "${this.params.include}")` : ''}.`;
|
|
207
|
+
return { llmContent: noMatchMsg, returnDisplay: `No matches found` };
|
|
208
|
+
}
|
|
209
|
+
const { matchesByFile, matchCount, limitMessage, wasLimited: limited, } = this.applyFileLimits(allMatches, filesWithMatches, maxFiles, maxPerFile);
|
|
210
|
+
const effectiveWasLimited = wasLimited || limited;
|
|
211
|
+
const llmContent = this.formatMatchOutput(matchesByFile, limitMessage, totalMatchesFound, matchCount, effectiveWasLimited, searchLocationDescription);
|
|
212
|
+
// Apply token limiting as final safety check
|
|
213
|
+
const limitedOutput = limitOutputTokens(llmContent.trim(), this.host, 'SearchText');
|
|
214
|
+
if (limitedOutput.wasTruncated) {
|
|
215
|
+
const formatted = formatLimitedOutput(limitedOutput);
|
|
216
|
+
return {
|
|
217
|
+
llmContent: formatted.llmContent,
|
|
218
|
+
returnDisplay: formatted.returnDisplay,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
const displayCount = effectiveWasLimited || totalMatchesFound > matchCount
|
|
222
|
+
? `Found ${totalMatchesFound} matches (showing ${matchCount})`
|
|
223
|
+
: // eslint-disable-next-line sonarjs/no-nested-conditional -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
|
|
224
|
+
`Found ${matchCount} ${matchCount === 1 ? 'match' : 'matches'}`;
|
|
225
|
+
return {
|
|
226
|
+
llmContent: llmContent.trim(),
|
|
227
|
+
returnDisplay: displayCount,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Handles abort/timeout errors from execute, returning appropriate ToolResult.
|
|
232
|
+
*/
|
|
233
|
+
handleExecuteError(error, timeoutController, timeoutMs, signal) {
|
|
234
|
+
const isAbortError = error instanceof Error &&
|
|
235
|
+
(error.name === 'AbortError' ||
|
|
236
|
+
error.message.includes('aborted') ||
|
|
237
|
+
error.message.includes('This operation was aborted'));
|
|
238
|
+
if (isAbortError) {
|
|
239
|
+
// Check if it was a timeout (our controller aborted but user's didn't)
|
|
240
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- caller AbortSignal can change asynchronously outside this stack
|
|
241
|
+
if (timeoutController.signal.aborted && !signal.aborted) {
|
|
242
|
+
const timeoutMessage = `Search operation timed out after ${timeoutMs}ms. To resolve this, you can either:
|
|
243
|
+
` +
|
|
244
|
+
`1. Increase the timeout (max ${MAX_TIMEOUT_MS}ms) by adding timeout_ms parameter
|
|
245
|
+
` +
|
|
246
|
+
`2. Use a more specific pattern to reduce search scope
|
|
247
|
+
` +
|
|
248
|
+
`3. Use a narrower path or include filter`;
|
|
249
|
+
return {
|
|
250
|
+
llmContent: timeoutMessage,
|
|
251
|
+
returnDisplay: `Timed out after ${timeoutMs}ms`,
|
|
252
|
+
error: {
|
|
253
|
+
message: timeoutMessage,
|
|
254
|
+
type: ToolErrorType.TIMEOUT,
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
return {
|
|
259
|
+
llmContent: 'Search operation was cancelled by user.',
|
|
260
|
+
returnDisplay: 'Cancelled',
|
|
261
|
+
error: {
|
|
262
|
+
message: 'Search operation was cancelled by user.',
|
|
263
|
+
type: ToolErrorType.EXECUTION_FAILED,
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
debugLogger.error(`Error during GrepLogic execution: ${error}`);
|
|
268
|
+
const errorMessage = getErrorMessage(error);
|
|
269
|
+
return {
|
|
270
|
+
llmContent: `Error during grep search operation: ${errorMessage}`,
|
|
271
|
+
returnDisplay: `Error: ${errorMessage}`,
|
|
272
|
+
error: {
|
|
273
|
+
message: errorMessage,
|
|
274
|
+
type: ToolErrorType.GREP_EXECUTION_ERROR,
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Executes the directory search after resolving the target.
|
|
280
|
+
*/
|
|
281
|
+
async executeDirectorySearch(resolved, workspaceContext, combinedSignal, searchDirDisplay) {
|
|
282
|
+
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- intentional falsy coalescing: dirPath is optional string, empty string should fall through
|
|
283
|
+
const ephemeralSettings = this.host.getEphemeralSettings();
|
|
284
|
+
const maxResults = this.params.max_results ??
|
|
285
|
+
ephemeralSettings['tool-output-max-items'] ??
|
|
286
|
+
1000;
|
|
287
|
+
const maxFiles = this.params.max_files ?? 100;
|
|
288
|
+
const maxPerFile = this.params.max_per_file ?? 50;
|
|
289
|
+
if (resolved.kind === 'file') {
|
|
290
|
+
return this.executeSingleFileSearch(resolved, combinedSignal, searchDirDisplay);
|
|
291
|
+
}
|
|
292
|
+
// Determine which directories to search
|
|
293
|
+
let searchDirectories;
|
|
294
|
+
if (resolved.kind === 'all-workspaces') {
|
|
295
|
+
searchDirectories = workspaceContext;
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
searchDirectories = [resolved.searchDir];
|
|
299
|
+
}
|
|
300
|
+
const filesWithMatches = new Set();
|
|
301
|
+
const { allMatches, totalMatchesFound, wasLimited } = await this.collectDirectoryMatches(searchDirectories, combinedSignal, maxResults, maxFiles, maxPerFile, filesWithMatches);
|
|
302
|
+
let searchLocationDescription;
|
|
303
|
+
if (resolved.kind === 'all-workspaces') {
|
|
304
|
+
const numDirs = workspaceContext.length;
|
|
305
|
+
searchLocationDescription =
|
|
306
|
+
numDirs > 1
|
|
307
|
+
? `across ${numDirs} workspace directories`
|
|
308
|
+
: `in the workspace directory`;
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
searchLocationDescription = `in path "${searchDirDisplay}"`;
|
|
312
|
+
}
|
|
313
|
+
return this.buildDirectorySearchResult(allMatches, totalMatchesFound, wasLimited, filesWithMatches, searchLocationDescription, maxFiles, maxPerFile);
|
|
314
|
+
}
|
|
315
|
+
async execute(signal) {
|
|
316
|
+
// Set up timeout handling
|
|
317
|
+
const timeoutMs = Math.min(this.params.timeout_ms ?? DEFAULT_TIMEOUT_MS, MAX_TIMEOUT_MS);
|
|
318
|
+
const timeoutController = new AbortController();
|
|
319
|
+
const timeoutId = setTimeout(() => timeoutController.abort(), timeoutMs);
|
|
320
|
+
// Combine user abort with timeout abort
|
|
321
|
+
const onUserAbort = () => {
|
|
322
|
+
clearTimeout(timeoutId);
|
|
323
|
+
timeoutController.abort();
|
|
324
|
+
};
|
|
325
|
+
if (signal.aborted) {
|
|
326
|
+
clearTimeout(timeoutId);
|
|
327
|
+
timeoutController.abort();
|
|
328
|
+
return {
|
|
329
|
+
llmContent: 'Search operation was cancelled by user.',
|
|
330
|
+
returnDisplay: 'Cancelled',
|
|
331
|
+
error: {
|
|
332
|
+
message: 'Search operation was cancelled by user.',
|
|
333
|
+
type: ToolErrorType.EXECUTION_FAILED,
|
|
334
|
+
},
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
signal.addEventListener('abort', onUserAbort, { once: true });
|
|
338
|
+
const combinedSignal = timeoutController.signal;
|
|
339
|
+
try {
|
|
340
|
+
const workspaceContext = this.host.getWorkspaceRoots();
|
|
341
|
+
const dirPath = this.getDirPath();
|
|
342
|
+
const resolved = this.resolveTarget(dirPath);
|
|
343
|
+
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- intentional falsy coalescing: dirPath is optional string, empty string should fall through
|
|
344
|
+
const searchDirDisplay = dirPath || '.';
|
|
345
|
+
return await this.executeDirectorySearch(resolved, workspaceContext, combinedSignal, searchDirDisplay);
|
|
346
|
+
}
|
|
347
|
+
catch (error) {
|
|
348
|
+
return this.handleExecuteError(error, timeoutController, timeoutMs, signal);
|
|
349
|
+
}
|
|
350
|
+
finally {
|
|
351
|
+
clearTimeout(timeoutId);
|
|
352
|
+
signal.removeEventListener('abort', onUserAbort);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Checks if a command is available in the system's PATH.
|
|
357
|
+
* @param {string} command The command name (e.g., 'git', 'grep').
|
|
358
|
+
* @returns {Promise<boolean>} True if the command is available, false otherwise.
|
|
359
|
+
*/
|
|
360
|
+
isCommandAvailable(command) {
|
|
361
|
+
return new Promise((resolve) => {
|
|
362
|
+
const checkCommand = process.platform === 'win32' ? 'where' : 'command';
|
|
363
|
+
const checkArgs = process.platform === 'win32' ? [command] : ['-v', command];
|
|
364
|
+
try {
|
|
365
|
+
// eslint-disable-next-line sonarjs/os-command -- Project intentionally invokes platform tooling at this trusted boundary; arguments remain explicit and behavior is preserved.
|
|
366
|
+
const child = spawn(checkCommand, checkArgs, {
|
|
367
|
+
stdio: 'ignore',
|
|
368
|
+
shell: true,
|
|
369
|
+
});
|
|
370
|
+
child.on('close', (code) => resolve(code === 0));
|
|
371
|
+
child.on('error', (err) => {
|
|
372
|
+
debugLogger.debug(`[GrepTool] Failed to start process for '${command}':`, err.message);
|
|
373
|
+
resolve(false);
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
catch {
|
|
377
|
+
resolve(false);
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Apply limits to search results
|
|
383
|
+
*/
|
|
384
|
+
applyLimits(matches, maxResults, maxFiles, maxPerFile) {
|
|
385
|
+
const filesWithMatches = new Map();
|
|
386
|
+
const totalFound = matches.length;
|
|
387
|
+
// Group by file and apply per-file limits
|
|
388
|
+
for (const match of matches) {
|
|
389
|
+
if (!filesWithMatches.has(match.filePath)) {
|
|
390
|
+
filesWithMatches.set(match.filePath, []);
|
|
391
|
+
}
|
|
392
|
+
const fileMatches = filesWithMatches.get(match.filePath);
|
|
393
|
+
if (fileMatches.length < maxPerFile) {
|
|
394
|
+
fileMatches.push(match);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
// Apply file limit
|
|
398
|
+
const limitedFiles = Array.from(filesWithMatches.entries()).slice(0, maxFiles);
|
|
399
|
+
// Flatten and apply total results limit
|
|
400
|
+
const results = [];
|
|
401
|
+
for (const [, fileMatches] of limitedFiles) {
|
|
402
|
+
for (const match of fileMatches) {
|
|
403
|
+
if (results.length >= maxResults) {
|
|
404
|
+
break;
|
|
405
|
+
}
|
|
406
|
+
results.push(match);
|
|
407
|
+
}
|
|
408
|
+
if (results.length >= maxResults) {
|
|
409
|
+
break;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return {
|
|
413
|
+
results,
|
|
414
|
+
wasLimited: results.length < totalFound || filesWithMatches.size > maxFiles,
|
|
415
|
+
totalFound: totalFound > results.length ? totalFound : undefined,
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Parses the standard output of grep-like commands (git grep, system grep).
|
|
420
|
+
* Expects format: filePath:lineNumber:lineContent
|
|
421
|
+
* Handles colons within file paths and line content correctly.
|
|
422
|
+
* @param {string} output The raw stdout string.
|
|
423
|
+
* @param {string} basePath The absolute directory the search was run from, for relative paths.
|
|
424
|
+
* @returns {GrepMatch[]} Array of match objects.
|
|
425
|
+
*/
|
|
426
|
+
parseGrepOutput(output, basePath) {
|
|
427
|
+
const results = [];
|
|
428
|
+
if (!output)
|
|
429
|
+
return results;
|
|
430
|
+
const lines = output.split(EOL); // Use OS-specific end-of-line
|
|
431
|
+
// eslint-disable-next-line sonarjs/too-many-break-or-continue-in-loop -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
|
|
432
|
+
for (const line of lines) {
|
|
433
|
+
if (!line.trim())
|
|
434
|
+
continue;
|
|
435
|
+
// Find the index of the first colon.
|
|
436
|
+
const firstColonIndex = line.indexOf(':');
|
|
437
|
+
if (firstColonIndex === -1)
|
|
438
|
+
continue; // Malformed
|
|
439
|
+
// Find the index of the second colon, searching *after* the first one.
|
|
440
|
+
const secondColonIndex = line.indexOf(':', firstColonIndex + 1);
|
|
441
|
+
if (secondColonIndex === -1)
|
|
442
|
+
continue; // Malformed
|
|
443
|
+
// Extract parts based on the found colon indices
|
|
444
|
+
const filePathRaw = line.substring(0, firstColonIndex);
|
|
445
|
+
const lineNumberStr = line.substring(firstColonIndex + 1, secondColonIndex);
|
|
446
|
+
const lineContent = line.substring(secondColonIndex + 1);
|
|
447
|
+
const lineNumber = parseInt(lineNumberStr, 10);
|
|
448
|
+
if (!isNaN(lineNumber)) {
|
|
449
|
+
const absoluteFilePath = path.resolve(basePath, filePathRaw);
|
|
450
|
+
const relativeFilePath = path.relative(basePath, absoluteFilePath);
|
|
451
|
+
results.push({
|
|
452
|
+
filePath: relativeFilePath || path.basename(absoluteFilePath),
|
|
453
|
+
lineNumber,
|
|
454
|
+
line: lineContent,
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
return results;
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Gets a description of the grep operation
|
|
462
|
+
* @returns A string describing the grep
|
|
463
|
+
*/
|
|
464
|
+
getDescription() {
|
|
465
|
+
let description = `'${this.params.pattern}'`;
|
|
466
|
+
if (this.params.include) {
|
|
467
|
+
description += ` in ${this.params.include}`;
|
|
468
|
+
}
|
|
469
|
+
const dirPath = this.getDirPath();
|
|
470
|
+
if (dirPath) {
|
|
471
|
+
try {
|
|
472
|
+
const resolved = this.resolveTarget(dirPath);
|
|
473
|
+
if (resolved.kind === 'file') {
|
|
474
|
+
const relativePath = makeRelative(resolved.filePath, this.host.getTargetDir());
|
|
475
|
+
description += ` in ${shortenPath(relativePath)}`;
|
|
476
|
+
return description;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
catch {
|
|
480
|
+
// Fall through to default path display on validation errors
|
|
481
|
+
}
|
|
482
|
+
const resolvedPath = path.resolve(this.host.getTargetDir(), dirPath);
|
|
483
|
+
if (resolvedPath === this.host.getTargetDir() || dirPath === '.') {
|
|
484
|
+
description += ` within ./`;
|
|
485
|
+
}
|
|
486
|
+
else {
|
|
487
|
+
const relativePath = makeRelative(resolvedPath, this.host.getTargetDir());
|
|
488
|
+
description += ` within ${shortenPath(relativePath)}`;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
const workspaceContext = this.host.getWorkspaceRoots();
|
|
493
|
+
const directories = workspaceContext;
|
|
494
|
+
if (directories.length > 1) {
|
|
495
|
+
description += ` across all workspace directories`;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return description;
|
|
499
|
+
}
|
|
500
|
+
async performSingleFileSearch(pattern, filePath, _basename, signal) {
|
|
501
|
+
if (signal.aborted) {
|
|
502
|
+
return [];
|
|
503
|
+
}
|
|
504
|
+
const regex = new RegExp(pattern, 'i');
|
|
505
|
+
const content = await fsPromises.readFile(filePath, 'utf8');
|
|
506
|
+
const lines = content.split(/\r?\n/);
|
|
507
|
+
const matches = [];
|
|
508
|
+
for (let i = 0; i < lines.length; i++) {
|
|
509
|
+
if (regex.test(lines[i])) {
|
|
510
|
+
matches.push({
|
|
511
|
+
filePath: path.basename(filePath),
|
|
512
|
+
lineNumber: i + 1,
|
|
513
|
+
line: lines[i],
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
return matches;
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Runs git grep as Strategy 1.
|
|
521
|
+
*/
|
|
522
|
+
async tryGitGrep(pattern, absolutePath, include, abortSignal, maxResults, maxFiles, maxPerFile, hasBracePattern) {
|
|
523
|
+
const isGit = !hasBracePattern && isGitRepository(absolutePath);
|
|
524
|
+
const gitAvailable = isGit && (await this.isCommandAvailable('git'));
|
|
525
|
+
if (!gitAvailable)
|
|
526
|
+
return null;
|
|
527
|
+
const gitArgs = [
|
|
528
|
+
'grep',
|
|
529
|
+
'--untracked',
|
|
530
|
+
'-n',
|
|
531
|
+
'-E',
|
|
532
|
+
'--ignore-case',
|
|
533
|
+
pattern,
|
|
534
|
+
];
|
|
535
|
+
if (include) {
|
|
536
|
+
gitArgs.push('--', include);
|
|
537
|
+
}
|
|
538
|
+
try {
|
|
539
|
+
const output = await new Promise((resolve, reject) => {
|
|
540
|
+
// eslint-disable-next-line sonarjs/no-os-command-from-path -- Project intentionally invokes platform tooling at this trusted boundary; arguments remain explicit and behavior is preserved.
|
|
541
|
+
const child = spawn('git', gitArgs, {
|
|
542
|
+
cwd: absolutePath,
|
|
543
|
+
windowsHide: true,
|
|
544
|
+
});
|
|
545
|
+
const stdoutChunks = [];
|
|
546
|
+
const stderrChunks = [];
|
|
547
|
+
const abortHandler = () => {
|
|
548
|
+
// eslint-disable-next-line sonarjs/nested-control-flow -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
|
|
549
|
+
if (!child.killed) {
|
|
550
|
+
child.kill('SIGTERM');
|
|
551
|
+
}
|
|
552
|
+
reject(new Error('git grep aborted'));
|
|
553
|
+
};
|
|
554
|
+
abortSignal.addEventListener('abort', abortHandler);
|
|
555
|
+
child.stdout.on('data', (chunk) => stdoutChunks.push(chunk));
|
|
556
|
+
child.stderr.on('data', (chunk) => stderrChunks.push(chunk));
|
|
557
|
+
child.on('error', (err) => {
|
|
558
|
+
abortSignal.removeEventListener('abort', abortHandler);
|
|
559
|
+
reject(new Error(`Failed to start git grep: ${err.message}`));
|
|
560
|
+
});
|
|
561
|
+
child.on('close', (code) => {
|
|
562
|
+
abortSignal.removeEventListener('abort', abortHandler);
|
|
563
|
+
const stdoutData = Buffer.concat(stdoutChunks).toString('utf8');
|
|
564
|
+
const stderrData = Buffer.concat(stderrChunks).toString('utf8');
|
|
565
|
+
// eslint-disable-next-line sonarjs/nested-control-flow -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
|
|
566
|
+
if (code === 0)
|
|
567
|
+
resolve(stdoutData);
|
|
568
|
+
else if (code === 1)
|
|
569
|
+
resolve(''); // No matches
|
|
570
|
+
else
|
|
571
|
+
reject(new Error(`git grep exited with code ${code}: ${stderrData}`));
|
|
572
|
+
});
|
|
573
|
+
});
|
|
574
|
+
const matches = this.parseGrepOutput(output, absolutePath);
|
|
575
|
+
return this.applyLimits(matches, maxResults, maxFiles, maxPerFile);
|
|
576
|
+
}
|
|
577
|
+
catch (gitError) {
|
|
578
|
+
debugLogger.debug(`GrepLogic: git grep failed: ${getErrorMessage(gitError)}. Falling back...`);
|
|
579
|
+
return null;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Builds the grep args for system grep, including exclusion patterns.
|
|
584
|
+
*/
|
|
585
|
+
buildSystemGrepArgs(pattern, include) {
|
|
586
|
+
const grepArgs = ['-r', '-n', '-H', '-E', '-I'];
|
|
587
|
+
const globExcludes = this.fileExclusions;
|
|
588
|
+
const commonExcludes = globExcludes
|
|
589
|
+
.map((pattern) => {
|
|
590
|
+
let dir = pattern;
|
|
591
|
+
if (dir.startsWith('**/')) {
|
|
592
|
+
dir = dir.substring(3);
|
|
593
|
+
}
|
|
594
|
+
if (dir.endsWith('/**')) {
|
|
595
|
+
dir = dir.slice(0, -3);
|
|
596
|
+
}
|
|
597
|
+
else if (dir.endsWith('/')) {
|
|
598
|
+
dir = dir.slice(0, -1);
|
|
599
|
+
}
|
|
600
|
+
// Only consider patterns that are likely directories
|
|
601
|
+
if (dir && !dir.includes('/') && !dir.includes('*')) {
|
|
602
|
+
return dir;
|
|
603
|
+
}
|
|
604
|
+
return null;
|
|
605
|
+
})
|
|
606
|
+
.filter((dir) => !!dir);
|
|
607
|
+
commonExcludes.forEach((dir) => grepArgs.push(`--exclude-dir=${dir}`));
|
|
608
|
+
if (include) {
|
|
609
|
+
grepArgs.push(`--include=${include}`);
|
|
610
|
+
}
|
|
611
|
+
grepArgs.push(pattern);
|
|
612
|
+
grepArgs.push('.');
|
|
613
|
+
return grepArgs;
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Sets up event handlers for a spawned grep child process.
|
|
617
|
+
*/
|
|
618
|
+
setupSystemGrepHandlers(child, abortSignal, stdoutChunks, stderrChunks, resolve, reject) {
|
|
619
|
+
const abortHandler = () => {
|
|
620
|
+
if (!child.killed) {
|
|
621
|
+
child.kill('SIGTERM');
|
|
622
|
+
}
|
|
623
|
+
cleanup();
|
|
624
|
+
reject(new Error('system grep aborted'));
|
|
625
|
+
};
|
|
626
|
+
abortSignal.addEventListener('abort', abortHandler);
|
|
627
|
+
const onData = (chunk) => stdoutChunks.push(chunk);
|
|
628
|
+
const onStderr = (chunk) => {
|
|
629
|
+
const stderrStr = chunk.toString();
|
|
630
|
+
if (!stderrStr.includes('Permission denied') &&
|
|
631
|
+
!/grep:.*: Is a directory/i.test(stderrStr)) {
|
|
632
|
+
stderrChunks.push(chunk);
|
|
633
|
+
}
|
|
634
|
+
};
|
|
635
|
+
const onError = (err) => {
|
|
636
|
+
cleanup();
|
|
637
|
+
reject(new Error(`Failed to start system grep: ${err.message}`));
|
|
638
|
+
};
|
|
639
|
+
const onClose = (code) => {
|
|
640
|
+
const stdoutData = Buffer.concat(stdoutChunks).toString('utf8');
|
|
641
|
+
const stderrData = Buffer.concat(stderrChunks).toString('utf8').trim();
|
|
642
|
+
cleanup();
|
|
643
|
+
if (code === 0)
|
|
644
|
+
resolve(stdoutData);
|
|
645
|
+
else if (code === 1)
|
|
646
|
+
resolve(''); // No matches
|
|
647
|
+
else if (stderrData)
|
|
648
|
+
reject(new Error(`System grep exited with code ${code}: ${stderrData}`));
|
|
649
|
+
else
|
|
650
|
+
resolve(''); // Exit code > 1 but no stderr, likely just suppressed errors
|
|
651
|
+
};
|
|
652
|
+
const cleanup = () => {
|
|
653
|
+
abortSignal.removeEventListener('abort', abortHandler);
|
|
654
|
+
child.stdout.removeListener('data', onData);
|
|
655
|
+
child.stderr.removeListener('data', onStderr);
|
|
656
|
+
child.removeListener('error', onError);
|
|
657
|
+
child.removeListener('close', onClose);
|
|
658
|
+
if (child.connected) {
|
|
659
|
+
child.disconnect();
|
|
660
|
+
}
|
|
661
|
+
};
|
|
662
|
+
child.stdout.on('data', onData);
|
|
663
|
+
child.stderr.on('data', onStderr);
|
|
664
|
+
child.on('error', onError);
|
|
665
|
+
child.on('close', onClose);
|
|
666
|
+
return cleanup;
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Runs system grep as Strategy 2.
|
|
670
|
+
*/
|
|
671
|
+
async trySystemGrep(grepArgs, absolutePath, abortSignal, maxResults, maxFiles, maxPerFile) {
|
|
672
|
+
try {
|
|
673
|
+
const output = await new Promise((resolve, reject) => {
|
|
674
|
+
// eslint-disable-next-line sonarjs/no-os-command-from-path -- Project intentionally invokes platform tooling at this trusted boundary; arguments remain explicit and behavior is preserved.
|
|
675
|
+
const child = spawn('grep', grepArgs, {
|
|
676
|
+
cwd: absolutePath,
|
|
677
|
+
windowsHide: true,
|
|
678
|
+
});
|
|
679
|
+
const stdoutChunks = [];
|
|
680
|
+
const stderrChunks = [];
|
|
681
|
+
this.setupSystemGrepHandlers(child, abortSignal, stdoutChunks, stderrChunks, resolve, reject);
|
|
682
|
+
});
|
|
683
|
+
const matches = this.parseGrepOutput(output, absolutePath);
|
|
684
|
+
return this.applyLimits(matches, maxResults, maxFiles, maxPerFile);
|
|
685
|
+
}
|
|
686
|
+
catch (grepError) {
|
|
687
|
+
debugLogger.debug(`GrepLogic: System grep failed: ${getErrorMessage(grepError)}. Falling back...`);
|
|
688
|
+
return null;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Extracts matches from a single file's content lines.
|
|
693
|
+
*/
|
|
694
|
+
extractMatchesFromFile(lines, fileAbsolutePath, absolutePath, regex, maxPerFile, maxResults, allMatches, filesWithMatches) {
|
|
695
|
+
let matchesInFile = 0;
|
|
696
|
+
let totalFound = 0;
|
|
697
|
+
lines.forEach((line, index) => {
|
|
698
|
+
if (regex.test(line)) {
|
|
699
|
+
totalFound++;
|
|
700
|
+
if (matchesInFile < maxPerFile && allMatches.length < maxResults) {
|
|
701
|
+
allMatches.push({
|
|
702
|
+
filePath: path.relative(absolutePath, fileAbsolutePath) ||
|
|
703
|
+
path.basename(fileAbsolutePath),
|
|
704
|
+
lineNumber: index + 1,
|
|
705
|
+
line,
|
|
706
|
+
});
|
|
707
|
+
matchesInFile++;
|
|
708
|
+
filesWithMatches.add(fileAbsolutePath);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
});
|
|
712
|
+
return totalFound;
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Pure JavaScript fallback for grep (Strategy 3).
|
|
716
|
+
*/
|
|
717
|
+
async javascriptGrepFallback(pattern, absolutePath, include, abortSignal, maxResults, maxFiles, maxPerFile) {
|
|
718
|
+
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- intentional falsy coalescing: include is optional string, empty string should fall through to default glob
|
|
719
|
+
const globPattern = include ? include : '**/*';
|
|
720
|
+
const ignorePatterns = this.fileExclusions;
|
|
721
|
+
const filesStream = globStream(globPattern, {
|
|
722
|
+
cwd: absolutePath,
|
|
723
|
+
dot: true,
|
|
724
|
+
ignore: ignorePatterns,
|
|
725
|
+
absolute: true,
|
|
726
|
+
nodir: true,
|
|
727
|
+
signal: abortSignal,
|
|
728
|
+
});
|
|
729
|
+
const regex = new RegExp(pattern, 'i');
|
|
730
|
+
const allMatches = [];
|
|
731
|
+
const filesWithMatches = new Set();
|
|
732
|
+
let totalFound = 0;
|
|
733
|
+
// eslint-disable-next-line sonarjs/too-many-break-or-continue-in-loop -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
|
|
734
|
+
for await (const filePath of filesStream) {
|
|
735
|
+
// Check if we've hit file limit
|
|
736
|
+
if (filesWithMatches.size >= maxFiles &&
|
|
737
|
+
!filesWithMatches.has(filePath)) {
|
|
738
|
+
continue;
|
|
739
|
+
}
|
|
740
|
+
// Check if we've hit total results limit
|
|
741
|
+
if (allMatches.length >= maxResults) {
|
|
742
|
+
break;
|
|
743
|
+
}
|
|
744
|
+
const fileAbsolutePath = filePath;
|
|
745
|
+
try {
|
|
746
|
+
const content = await fsPromises.readFile(fileAbsolutePath, 'utf8');
|
|
747
|
+
const lines = content.split(/\r?\n/);
|
|
748
|
+
totalFound += this.extractMatchesFromFile(lines, fileAbsolutePath, absolutePath, regex, maxPerFile, maxResults, allMatches, filesWithMatches);
|
|
749
|
+
}
|
|
750
|
+
catch (readError) {
|
|
751
|
+
// Ignore errors like permission denied or file gone during read
|
|
752
|
+
if (!isNodeError(readError) || readError.code !== 'ENOENT') {
|
|
753
|
+
debugLogger.debug(`GrepLogic: Could not read/process ${fileAbsolutePath}: ${getErrorMessage(readError)}`);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
return {
|
|
758
|
+
results: allMatches,
|
|
759
|
+
wasLimited: totalFound > allMatches.length,
|
|
760
|
+
totalFound: totalFound > allMatches.length ? totalFound : undefined,
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Attempts system grep (Strategy 2), returning null to fall through.
|
|
765
|
+
*/
|
|
766
|
+
async trySystemGrepStrategy(pattern, absolutePath, include, signal, maxResults, maxFiles, maxPerFile) {
|
|
767
|
+
debugLogger.debug('GrepLogic: System grep is being considered as fallback strategy.');
|
|
768
|
+
const grepAvailable = await this.isCommandAvailable('grep');
|
|
769
|
+
if (!grepAvailable)
|
|
770
|
+
return null;
|
|
771
|
+
const grepArgs = this.buildSystemGrepArgs(pattern, include);
|
|
772
|
+
return this.trySystemGrep(grepArgs, absolutePath, signal, maxResults, maxFiles, maxPerFile);
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* Performs the actual search using the prioritized strategies.
|
|
776
|
+
* @param options Search options including pattern, absolute path, and include glob.
|
|
777
|
+
* @returns A promise resolving to search results with limit information.
|
|
778
|
+
*/
|
|
779
|
+
async performGrepSearch(options) {
|
|
780
|
+
const { pattern, path: absolutePath, include, maxResults = 1000, maxFiles = 100, maxPerFile = 50, } = options;
|
|
781
|
+
let strategyUsed = 'none';
|
|
782
|
+
try {
|
|
783
|
+
// --- Strategy 1: git grep ---
|
|
784
|
+
const hasBracePattern = typeof include === 'string' &&
|
|
785
|
+
include.length > 0 &&
|
|
786
|
+
hasBraceExpansion(include);
|
|
787
|
+
const gitResult = await this.tryGitGrep(pattern, absolutePath, include, options.signal, maxResults, maxFiles, maxPerFile, hasBracePattern);
|
|
788
|
+
if (gitResult !== null) {
|
|
789
|
+
return gitResult;
|
|
790
|
+
}
|
|
791
|
+
// --- Strategy 2: System grep ---
|
|
792
|
+
strategyUsed = 'system grep';
|
|
793
|
+
const sysResult = await this.trySystemGrepStrategy(pattern, absolutePath, include, options.signal, maxResults, maxFiles, maxPerFile);
|
|
794
|
+
if (sysResult !== null) {
|
|
795
|
+
return sysResult;
|
|
796
|
+
}
|
|
797
|
+
// --- Strategy 3: Pure JavaScript Fallback ---
|
|
798
|
+
debugLogger.debug('GrepLogic: Falling back to JavaScript grep implementation.');
|
|
799
|
+
strategyUsed = 'javascript fallback';
|
|
800
|
+
return await this.javascriptGrepFallback(pattern, absolutePath, include, options.signal, maxResults, maxFiles, maxPerFile);
|
|
801
|
+
}
|
|
802
|
+
catch (error) {
|
|
803
|
+
debugLogger.error(`GrepLogic: Error in performGrepSearch (Strategy: ${strategyUsed}): ${getErrorMessage(error)}`);
|
|
804
|
+
throw error; // Re-throw
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
// --- GrepLogic Class ---
|
|
809
|
+
/**
|
|
810
|
+
* Implementation of the Grep tool logic (moved from CLI)
|
|
811
|
+
*/
|
|
812
|
+
export class GrepTool extends BaseDeclarativeTool {
|
|
813
|
+
host;
|
|
814
|
+
static Name = 'search_file_content'; // Keep static name
|
|
815
|
+
constructor(host) {
|
|
816
|
+
super(GrepTool.Name, 'SearchText', 'Searches for a regular expression pattern within the content of files in a specified directory (or current working directory). Can filter files by a glob pattern. Returns the lines containing matches, along with their file paths and line numbers.', Kind.Search, {
|
|
817
|
+
properties: {
|
|
818
|
+
pattern: {
|
|
819
|
+
description: "The regular expression (regex) pattern to search for within file contents (e.g., 'function\\s+myFunction', 'import\\s+\\{.*\\}\\s+from\\s+.*').",
|
|
820
|
+
type: 'string',
|
|
821
|
+
},
|
|
822
|
+
dir_path: {
|
|
823
|
+
description: 'Optional: The absolute path to the directory to search within. If omitted, searches the current working directory. Can also be a path to a specific file (will search only that file).',
|
|
824
|
+
type: 'string',
|
|
825
|
+
},
|
|
826
|
+
path: {
|
|
827
|
+
description: 'Alternative parameter name for dir_path (for backward compatibility).',
|
|
828
|
+
type: 'string',
|
|
829
|
+
},
|
|
830
|
+
include: {
|
|
831
|
+
description: "Optional: A glob pattern to filter which files are searched (e.g., '*.js', '*.{ts,tsx}', 'src/**'). If omitted, searches all files (respecting potential global ignores).",
|
|
832
|
+
type: 'string',
|
|
833
|
+
},
|
|
834
|
+
max_results: {
|
|
835
|
+
description: 'Optional: Maximum number of total matches to return. Defaults to tool-output-max-items setting or 1000.',
|
|
836
|
+
type: 'number',
|
|
837
|
+
},
|
|
838
|
+
max_files: {
|
|
839
|
+
description: 'Optional: Maximum number of files to include in results. Defaults to 100.',
|
|
840
|
+
type: 'number',
|
|
841
|
+
},
|
|
842
|
+
max_per_file: {
|
|
843
|
+
description: 'Optional: Maximum number of matches per file to return. Defaults to 50.',
|
|
844
|
+
type: 'number',
|
|
845
|
+
},
|
|
846
|
+
timeout_ms: {
|
|
847
|
+
description: 'Optional: Timeout in milliseconds (default: 60000ms = 1 minute, max: 300000ms = 5 minutes). If the operation times out, an error is returned with suggestions.',
|
|
848
|
+
type: 'number',
|
|
849
|
+
},
|
|
850
|
+
},
|
|
851
|
+
required: ['pattern'],
|
|
852
|
+
type: 'object',
|
|
853
|
+
});
|
|
854
|
+
this.host = host;
|
|
855
|
+
}
|
|
856
|
+
validateToolParamValues(params) {
|
|
857
|
+
try {
|
|
858
|
+
new RegExp(params.pattern);
|
|
859
|
+
}
|
|
860
|
+
catch (error) {
|
|
861
|
+
return `Invalid regular expression pattern provided: ${params.pattern}. Error: ${getErrorMessage(error)}`;
|
|
862
|
+
}
|
|
863
|
+
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- intentional falsy coalescing: dir_path and path are optional strings, empty string should fall through
|
|
864
|
+
const dirPath = params.dir_path || params.path;
|
|
865
|
+
if (dirPath) {
|
|
866
|
+
try {
|
|
867
|
+
resolveTextSearchTarget(this.host.getTargetDir(), this.host.getWorkspaceRoots(), dirPath);
|
|
868
|
+
}
|
|
869
|
+
catch (error) {
|
|
870
|
+
return getErrorMessage(error);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
return null;
|
|
874
|
+
}
|
|
875
|
+
createInvocation(params, messageBus) {
|
|
876
|
+
const normalizedParams = { ...params };
|
|
877
|
+
if (!normalizedParams.dir_path && normalizedParams.path) {
|
|
878
|
+
normalizedParams.dir_path = normalizedParams.path;
|
|
879
|
+
}
|
|
880
|
+
return new GrepToolInvocation(this.host, normalizedParams, messageBus);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
//# sourceMappingURL=grep.js.map
|