11 KiB
11 KiB
search
Search file contents with a regex across files, directories, globs, and internal URLs.
Source
- Entry:
packages/coding-agent/src/tools/search.ts - Model-facing prompt:
packages/coding-agent/src/prompts/tools/search.md - Key collaborators:
packages/coding-agent/src/tools/match-line-format.ts— model-facing anchor formatting.packages/coding-agent/src/tools/path-utils.ts— path normalization, glob splitting, internal URL resolution.packages/coding-agent/src/tools/file-recorder.ts— file ordering for grouped output.packages/coding-agent/src/tools/grouped-file-output.ts— grouped per-file text layout.packages/coding-agent/src/session/streaming-output.ts— line truncation and final byte truncation.packages/coding-agent/src/config/settings-schema.ts— default context lines.packages/natives/native/index.d.ts— nativegrep()types exposed to TS.crates/pi-natives/src/grep.rs— native regex/file search implementation.docs/natives-text-search-pipeline.md— native search pipeline overview.
Inputs
| Field | Type | Required | Description |
|---|---|---|---|
pattern |
string |
Yes | Regex pattern. search.ts trims it and rejects empty input. The native matcher enables multiline only when the pattern text contains a literal newline or the two-character sequence \\n. The model prompt explicitly documents literal-brace escaping such as interface\\{\\}, although the native layer also auto-escapes braces that cannot be valid repetition quantifiers. |
paths |
string[] |
Yes | One or more file paths, directory paths, glob-like paths, or internal URLs. Empty strings are rejected after trimming/quote stripping. Internal URLs must resolve to a backing file and cannot contain glob characters. |
i |
boolean |
No | Case-insensitive search. Defaults to false. Passed to native ignoreCase. |
gitignore |
boolean |
No | Respect .gitignore during directory scans. Defaults to true. Passed to native gitignore. |
skip |
number |
No | Global match offset. Defaults to 0. search.ts floors finite numbers and rejects negative or non-finite values. |
Outputs
The tool returns a single text block in content[0].text plus structured details.
- Match lines are formatted by
formatMatchLine()as*<anchor>|<line>for matches and<anchor>|<line>for context.- Hashline mode:
*5th|content,9x}|content. - Plain mode:
*5|content,9|content.
- Hashline mode:
- Directory results are grouped by file, with
# <path>headings and blank lines between groups. detailsmay include:scopePath— formatted search scope.matchCount,fileCount,files,fileMatches— counts for the returned page, not necessarily total corpus counts.matchLimitReached— visible-page limit hit (100).resultLimitReached— native preselection limit hit (500).linesTruncated— one or more matched lines were shortened to1024chars plus….truncatedandmeta.truncation— final text output was head-truncated bytruncateHead().displayContent— TUI-only rendering text with│gutters instead of model anchors.missingPaths— multi-path entries skipped because their base path did not exist.
- No-match result text is
No matches found, optionally followed bySkipped missing paths: ....
Flow
SearchTool.execute()validates and normalizes input inpackages/coding-agent/src/tools/search.ts:- trims
pattern, rejects empty patterns; - normalizes
skipto a non-negative integer; - reads
search.contextBeforeandsearch.contextAfterfrom session settings (1and3by default); - enables multiline only when
patterncontains\nor an actual newline.
- trims
- Each
pathsentry is normalized withnormalizePathLikeInput(). - Internal URLs are resolved through
session.internalRouter:- glob metacharacters (
*,?,[,{) are rejected for internal URLs; - URLs without
resource.sourcePathfail; - immutable sources are tracked so output can suppress editable hashline anchors per file.
- glob metacharacters (
- For multi-path calls,
partitionExistingPaths()skips only ENOENT entries. If every entry is missing, the tool errors. - Path resolution branches:
- one entry:
parseSearchPath()splitsbasePathand optional glob; - multiple entries:
resolveExplicitSearchPaths()computes a common base directory, brace-union glob, exact-file list, or degenerate-root target list.
- one entry:
search.tsstats the resolved base path to decide file vs directory behavior.- It calls native
grep()from@oh-my-pi/pi-nativeswith:pattern,ignoreCase,multiline,gitignore;hidden: true;cache: false;contextBefore/contextAfterfrom settings;maxColumns: 1024;mode: content.
- Native execution happens in
crates/pi-natives/src/grep.rs:build_matcher()sanitizes non-quantifier braces before regex compile;- if compile fails with unopened/unclosed-group errors, it retries after escaping previously unescaped parentheses;
- directory scans use the grep pipeline described in
docs/natives-text-search-pipeline.md.
- Search dispatch differs by resolved path set:
- exact explicit files or degenerate-root multi-targets: JS loops over targets and merges
grep()results itself; - single file/directory base: one
grep()call handles offset/limit natively.
- exact explicit files or degenerate-root multi-targets: JS loops over targets and merges
- JS output shaping then:
- round-robins directory matches down to
100visible matches so one file does not monopolize the page; - keeps the first
100file matches for single-file searches; - formats lines through
formatMatchLine()for the model andformatCodeFrameLine()for TUI; - records non-truncated matched/context lines into the session file-read cache with
recordSparse().
- Final text is passed through
truncateHead(rawOutput, { maxLines: Number.MAX_SAFE_INTEGER }), so the effective cap is the default byte cap fromstreaming-output.ts, not the default line cap. toolResult()attaches text plus limit/truncation metadata.
Modes / Variants
- Single file path
grep()searches one file.- Output is a flat list of match/context lines.
- Visible limit is the first
100matches after native offset handling.
- Single directory path or single glob-like path
parseSearchPath()may split the input intopath+glob.- One native
grep()scans the directory tree withgitignoreandhidden:true. - Native
offsethandlesskipglobally across files. - JS round-robins the returned matches to
100visible rows.
- Multiple explicit paths/globs
resolveExplicitSearchPaths()collapses them into a common base and either a brace-union glob, an explicit file list, or per-target searches when the only common base is the filesystem root.- Missing entries are skipped non-fatally unless all are missing.
- Internal URL paths
- Supported only when the internal resource resolves to a real backing file.
- No internal-URL globbing.
- Immutable sources switch to the immutable display mode when formatting anchors.
Side Effects
- Filesystem
- Stats resolved search roots and input paths.
- Reads matched files through native
grep(). - Records sparse matched/context lines into the session file-read cache via
getFileReadCache(...).recordSparse(...).
- Session state (transcript, memory, jobs, checkpoints, registries)
- Reads session settings for context defaults.
- Uses
session.internalRouterto resolve internal URLs. - Populates tool
details.metawith truncation/limit metadata.
- Background work / cancellation
- Wrapped in
untilAborted(signal, ...)at the JS level. search.tsdoes not passsignalortimeoutMsinto nativegrep(), so native grep cancellation/timeouts are not used by this tool.
- Wrapped in
Limits & Caps
- Visible page limit:
100matches (DEFAULT_MATCH_LIMITinpackages/coding-agent/src/tools/search.ts). - Native preselection limit:
500matches (internalLimit = Math.min(DEFAULT_MATCH_LIMIT * 5, 2000)inpackages/coding-agent/src/tools/search.ts). - Line truncation:
1024characters per emitted line (DEFAULT_MAX_COLUMNinpackages/coding-agent/src/session/streaming-output.ts). Native grep marks truncated lines; JS reportslinesTruncated. - Final text truncation:
truncateHead()default byte cap50 * 1024bytes (DEFAULT_MAX_BYTESinpackages/coding-agent/src/session/streaming-output.ts).search.tsoverridesmaxLinestoNumber.MAX_SAFE_INTEGER, so normal search output is byte-capped, not line-capped. - Context defaults:
search.contextBefore = 1,search.contextAfter = 3inpackages/coding-agent/src/config/settings-schema.ts. - Pagination:
skipis a global match offset. In single-base searches it is pushed into nativeoffset; in exact-file/multi-target aggregation it is applied in JS withmatches.slice(skip). - Native directory-scan cache: available in
grep.rs, but this tool always setscache: false.
Errors
Pattern must not be emptywhen trimmedpatternis empty.Skip must be a non-negative numberfor negative or non-finiteskip.`paths` must contain non-empty paths or globswhen any normalized path is empty.Glob patterns are not supported for internal URLs: ...for internal URL + glob metacharacters.Cannot search internal URL without a backing file: ...when the router resolves a virtual resource withoutsourcePath.Path not found: ...when the resolved base path is missing, or when every multi-path entry is missing.- Regex compile failures bubble from native
grep()as tool errors.search.tshas a special catch for messages beginning withregex parse error, then otherwise rethrows. - Multi-file native scans skip per-file open/search failures inside
grep.rs; the scan continues with surviving files.
Notes
- The model-facing prompt documents standard regex syntax plus two search-specific rules: escape literal braces, and use
\nor a literal newline for cross-line matching. - Native
build_matcher()already auto-escapes braces that cannot be valid quantifiers, so patterns like${platform}become searchable instead of failing. Valid quantifiers likea{2,4}remain unchanged. - Native compile retry also escapes unescaped literal parentheses only after an unopened/unclosed-group parse error. It is a fallback, not a general parser mode.
- Internal URLs are resolved before path existence checks. After resolution, the native layer sees ordinary filesystem paths.
hidden:trueis hard-coded insearch.ts; there is no model-facing flag to exclude dotfiles.gitignore:falseonly affects native directory traversal. It does not disable the tool's own path normalization or explicit-file handling.- When
pathsresolves to multiple exact files,search.tsdoes not apply the native500match cap and reportstotalMatchesinternally as the post-skip length for that branch. - The anchor suffix in hashline mode comes from
computeLineHash()inpackages/coding-agent/src/hashline/hash.ts;searchitself only formats it.