@oh-my-pi/pi-coding-agent 13.9.5 → 13.9.6
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 +14 -0
- package/package.json +7 -7
- package/src/prompts/tools/ast-edit.md +20 -5
- package/src/prompts/tools/ast-grep.md +26 -9
- package/src/tools/ast-edit.ts +8 -7
- package/src/tools/ast-grep.ts +16 -18
- package/src/tools/grep.ts +2 -7
- package/src/tools/path-utils.ts +10 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [13.9.6] - 2026-03-08
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added `glob` parameter to `ast_grep` and `ast_edit` tools for additional glob filtering relative to the `path` parameter
|
|
10
|
+
- Added `combineSearchGlobs` utility function to merge glob patterns from `path` and `glob` parameters
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Renamed `patterns` parameter to `pat` in `ast_grep` tool for consistency
|
|
15
|
+
- Renamed `selector` parameter to `sel` in `ast_grep` and `ast_edit` tools for brevity
|
|
16
|
+
- Updated tool documentation with expanded guidance on AST pattern syntax, metavariable usage, and contextual matching strategies
|
|
17
|
+
- Updated `grep` tool to combine glob patterns from `path` and `glob` parameters instead of throwing an error when both are provided
|
|
18
|
+
|
|
5
19
|
## [13.9.4] - 2026-03-07
|
|
6
20
|
### Added
|
|
7
21
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
4
|
-
"version": "13.9.
|
|
4
|
+
"version": "13.9.6",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://github.com/can1357/oh-my-pi",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -41,12 +41,12 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@mozilla/readability": "^0.6",
|
|
44
|
-
"@oh-my-pi/omp-stats": "13.9.
|
|
45
|
-
"@oh-my-pi/pi-agent-core": "13.9.
|
|
46
|
-
"@oh-my-pi/pi-ai": "13.9.
|
|
47
|
-
"@oh-my-pi/pi-natives": "13.9.
|
|
48
|
-
"@oh-my-pi/pi-tui": "13.9.
|
|
49
|
-
"@oh-my-pi/pi-utils": "13.9.
|
|
44
|
+
"@oh-my-pi/omp-stats": "13.9.6",
|
|
45
|
+
"@oh-my-pi/pi-agent-core": "13.9.6",
|
|
46
|
+
"@oh-my-pi/pi-ai": "13.9.6",
|
|
47
|
+
"@oh-my-pi/pi-natives": "13.9.6",
|
|
48
|
+
"@oh-my-pi/pi-tui": "13.9.6",
|
|
49
|
+
"@oh-my-pi/pi-utils": "13.9.6",
|
|
50
50
|
"@sinclair/typebox": "^0.34",
|
|
51
51
|
"@xterm/headless": "^6.0",
|
|
52
52
|
"ajv": "^8.18",
|
|
@@ -2,10 +2,16 @@ Performs structural AST-aware rewrites via native ast-grep.
|
|
|
2
2
|
|
|
3
3
|
<instruction>
|
|
4
4
|
- Use for codemods and structural rewrites where plain text replace is unsafe
|
|
5
|
-
- Narrow scope with `path` before replacing (`path` accepts files, directories, or glob patterns)
|
|
6
|
-
- Default to language-scoped rewrites in mixed repositories: set `lang` and keep `path` narrow
|
|
7
|
-
- Treat parse issues as a scoping signal: tighten `path`/`lang
|
|
5
|
+
- Narrow scope with `path` before replacing (`path` accepts files, directories, or glob patterns; use `glob` for an additional filter relative to `path`)
|
|
6
|
+
- Default to language-scoped rewrites in mixed repositories: set `lang` and keep `path`/`glob` narrow
|
|
7
|
+
- Treat parse issues as a scoping or pattern-shape signal: tighten `path`/`lang`, or rewrite the pattern into valid syntax before retrying
|
|
8
8
|
- Metavariables captured in each rewrite pattern (`$A`, `$$$ARGS`) are substituted into that entry's rewrite template
|
|
9
|
+
- For variadic captures, use `$$$NAME` (not `$$NAME`)
|
|
10
|
+
- Rewrite patterns must parse as valid AST for the target language; if a method or declaration does not parse standalone, wrap it in valid context or switch to a contextual `sel`
|
|
11
|
+
- When using contextual `sel`, the match and replacement target the selected node, not the outer wrapper you used to make the pattern parse
|
|
12
|
+
- For TypeScript declarations and methods, prefer patterns that tolerate annotations you do not care about, e.g. `async function $NAME($$$ARGS): $_ { $$$BODY }` or `class $_ { method($$$ARGS): $_ { $$$BODY } }`
|
|
13
|
+
- Metavariables must be the sole content of an AST node; partial-text metavariables like `prefix$VAR` or `"hello $NAME"` do NOT work in patterns or rewrites
|
|
14
|
+
- To delete matched code, use an empty `out` string: `{"pat":"console.log($$$)","out":""}`
|
|
9
15
|
- Each matched rewrite is a 1:1 structural substitution; you cannot split one capture into multiple nodes or merge multiple captures into one node
|
|
10
16
|
</instruction>
|
|
11
17
|
|
|
@@ -17,14 +23,23 @@ Performs structural AST-aware rewrites via native ast-grep.
|
|
|
17
23
|
<examples>
|
|
18
24
|
- Rename a call site across a directory:
|
|
19
25
|
`{"ops":[{"pat":"oldApi($$$ARGS)","out":"newApi($$$ARGS)"}],"lang":"typescript","path":"src/"}`
|
|
20
|
-
-
|
|
21
|
-
`{"ops":[{"pat":"
|
|
26
|
+
- Delete all matching calls (empty `out` removes the matched node):
|
|
27
|
+
`{"ops":[{"pat":"console.log($$$ARGS)","out":""}],"lang":"typescript","path":"src/"}`
|
|
28
|
+
- Rewrite an import source path:
|
|
29
|
+
`{"ops":[{"pat":"import { $$$IMPORTS } from \"old-package\"","out":"import { $$$IMPORTS } from \"new-package\""}],"lang":"typescript","path":"src/"}`
|
|
30
|
+
- Modernize to optional chaining (same metavariable enforces identity):
|
|
31
|
+
`{"ops":[{"pat":"$A && $A()","out":"$A?.()"}],"lang":"typescript","path":"src/"}`
|
|
22
32
|
- Swap two arguments using captures:
|
|
23
33
|
`{"ops":[{"pat":"assertEqual($A, $B)","out":"assertEqual($B, $A)"}],"lang":"typescript","path":"tests/"}`
|
|
34
|
+
- Rename a TypeScript function declaration while tolerating any return type annotation:
|
|
35
|
+
`{"ops":[{"pat":"async function fetchData($$$ARGS): $_ { $$$BODY }","out":"async function loadData($$$ARGS): $_ { $$$BODY }"}],"sel":"function_declaration","lang":"typescript","path":"src/api.ts"}`
|
|
36
|
+
- Convert Python print calls to logging:
|
|
37
|
+
`{"ops":[{"pat":"print($$$ARGS)","out":"logger.info($$$ARGS)"}],"lang":"python","path":"src/"}`
|
|
24
38
|
</examples>
|
|
25
39
|
|
|
26
40
|
<critical>
|
|
27
41
|
- `ops` **MUST** contain at least one concrete `{ pat, out }` entry
|
|
28
42
|
- If the path pattern spans multiple languages, set `lang` explicitly for deterministic rewrites
|
|
43
|
+
- Parse issues mean the rewrite request is malformed or mis-scoped; do not assume a clean no-op until the pattern parses successfully
|
|
29
44
|
- For one-off local text edits, prefer the Edit tool instead of AST edit
|
|
30
45
|
</critical>
|
|
@@ -2,14 +2,22 @@ Performs structural code search using AST matching via native ast-grep.
|
|
|
2
2
|
|
|
3
3
|
<instruction>
|
|
4
4
|
- Use this when syntax shape matters more than raw text (calls, declarations, specific language constructs)
|
|
5
|
-
- Prefer a precise `path` scope to keep results targeted and deterministic (`path` accepts files, directories, or glob patterns)
|
|
6
|
-
- Default to language-scoped search in mixed repositories: pair `path` glob + explicit `lang` to avoid parse-noise from non-source files
|
|
7
|
-
- `
|
|
5
|
+
- Prefer a precise `path` scope to keep results targeted and deterministic (`path` accepts files, directories, or glob patterns; use `glob` for an additional filter relative to `path`)
|
|
6
|
+
- Default to language-scoped search in mixed repositories: pair `path` + `glob` + explicit `lang` to avoid parse-noise from non-source files
|
|
7
|
+
- `pat` is required and must include at least one non-empty AST pattern; `lang` is optional (`lang` is inferred per file extension when omitted)
|
|
8
8
|
- Multiple patterns run in one native pass; results are merged and then `offset`/`limit` are applied to the combined match set
|
|
9
|
-
- Use `
|
|
9
|
+
- Use `sel` only for contextual pattern mode; otherwise provide direct patterns
|
|
10
|
+
- In contextual pattern mode, results are returned for the selected node (`sel`), not the outer wrapper used to make the pattern parse
|
|
10
11
|
- For variadic arguments/fields, use `$$$NAME` (not `$$NAME`)
|
|
12
|
+
- Patterns must parse as a single valid AST node for the target language; if a bare pattern fails, wrap it in valid context or use `sel`
|
|
11
13
|
- Patterns match AST structure, not text — whitespace/formatting differences are ignored
|
|
12
14
|
- When the same metavariable appears multiple times, all occurrences must match identical code
|
|
15
|
+
- For TypeScript declarations and methods, prefer shapes that tolerate annotations you do not care about, e.g. `async function $NAME($$$ARGS): $_ { $$$BODY }` or `class $_ { method($$$ARGS): $_ { $$$BODY } }` instead of omitting the return type entirely
|
|
16
|
+
- Metavariables must be the sole content of an AST node; partial-text metavariables like `prefix$VAR`, `"hello $NAME"`, or `a $OP b` do NOT work — match the whole node instead
|
|
17
|
+
- `$$$` captures are lazy (non-greedy): they stop when the next element in the pattern can match; place the most specific node after `$$$` to control where capture ends
|
|
18
|
+
- `$_` is a non-capturing wildcard (matches any single node without binding); use it when you need to tolerate a node but don't need its value
|
|
19
|
+
- Search the right declaration form before concluding absence: top-level function, class method, and variable-assigned function are different AST shapes
|
|
20
|
+
- If you only need to prove a symbol exists, prefer a looser contextual search such as `pat: ["executeBash"]` with `sel: "identifier"`
|
|
13
21
|
</instruction>
|
|
14
22
|
|
|
15
23
|
<output>
|
|
@@ -19,16 +27,25 @@ Performs structural code search using AST matching via native ast-grep.
|
|
|
19
27
|
|
|
20
28
|
<examples>
|
|
21
29
|
- Find all console logging calls in one pass (multi-pattern, scoped):
|
|
22
|
-
`{"
|
|
23
|
-
-
|
|
24
|
-
`{"
|
|
30
|
+
`{"pat":["console.log($$$)","console.error($$$)"],"lang":"typescript","path":"src/"}`
|
|
31
|
+
- Find all named imports from a specific package:
|
|
32
|
+
`{"pat":["import { $$$IMPORTS } from \"react\""],"lang":"typescript","path":"src/"}`
|
|
33
|
+
- Match arrow functions assigned to a const (different AST shape than function declarations):
|
|
34
|
+
`{"pat":["const $NAME = ($$$ARGS) => $BODY"],"lang":"typescript","path":"src/utils/"}`
|
|
35
|
+
- Match any method call on an object using wildcard `$_` (ignores method name):
|
|
36
|
+
`{"pat":["logger.$_($$$ARGS)"],"lang":"typescript","path":"src/"}`
|
|
25
37
|
- Contextual pattern with selector — match only the identifier `foo`, not the whole call:
|
|
26
|
-
`{"
|
|
38
|
+
`{"pat":["foo()"],"sel":"identifier","lang":"typescript","path":"src/utils.ts"}`
|
|
39
|
+
- Match a TypeScript function declaration without caring about its exact return type:
|
|
40
|
+
`{"pat":["async function processItems($$$ARGS): $_ { $$$BODY }"],"sel":"function_declaration","lang":"typescript","path":"src/worker.ts"}`
|
|
41
|
+
- Loosest existence check for a symbol in one file:
|
|
42
|
+
`{"pat":["processItems"],"sel":"identifier","lang":"typescript","path":"src/worker.ts"}`
|
|
27
43
|
</examples>
|
|
28
44
|
|
|
29
45
|
<critical>
|
|
30
|
-
- `
|
|
46
|
+
- `pat` is required
|
|
31
47
|
- Set `lang` explicitly to constrain matching when path pattern spans mixed-language trees
|
|
32
48
|
- Avoid repo-root AST scans when the target is language-specific; narrow `path` first
|
|
49
|
+
- Treat parse issues as query failure, not evidence of absence: repair the pattern or tighten `path`/`glob`/`lang` before concluding "no matches"
|
|
33
50
|
- If exploration is broad/open-ended across subsystems, use Task tool with explore subagent first
|
|
34
51
|
</critical>
|
package/src/tools/ast-edit.ts
CHANGED
|
@@ -14,7 +14,7 @@ import { Ellipsis, Hasher, type RenderCache, renderStatusLine, renderTreeList, t
|
|
|
14
14
|
import { resolveFileDisplayMode } from "../utils/file-display-mode";
|
|
15
15
|
import type { ToolSession } from ".";
|
|
16
16
|
import type { OutputMeta } from "./output-meta";
|
|
17
|
-
import { hasGlobPathChars, parseSearchPath, resolveToCwd } from "./path-utils";
|
|
17
|
+
import { combineSearchGlobs, hasGlobPathChars, parseSearchPath, resolveToCwd } from "./path-utils";
|
|
18
18
|
import {
|
|
19
19
|
dedupeParseErrors,
|
|
20
20
|
formatCount,
|
|
@@ -38,7 +38,8 @@ const astEditSchema = Type.Object({
|
|
|
38
38
|
}),
|
|
39
39
|
lang: Type.Optional(Type.String({ description: "Language override" })),
|
|
40
40
|
path: Type.Optional(Type.String({ description: "File, directory, or glob pattern to rewrite (default: cwd)" })),
|
|
41
|
-
|
|
41
|
+
glob: Type.Optional(Type.String({ description: "Optional glob filter relative to path" })),
|
|
42
|
+
sel: Type.Optional(Type.String({ description: "Optional selector for contextual pattern mode" })),
|
|
42
43
|
limit: Type.Optional(Type.Number({ description: "Max total replacements" })),
|
|
43
44
|
});
|
|
44
45
|
|
|
@@ -98,7 +99,7 @@ export class AstEditTool implements AgentTool<typeof astEditSchema, AstEditToolD
|
|
|
98
99
|
const maxFiles = parseInt(process.env.PI_MAX_AST_FILES ?? "", 10) || 1000;
|
|
99
100
|
|
|
100
101
|
let searchPath: string | undefined;
|
|
101
|
-
let globFilter
|
|
102
|
+
let globFilter = params.glob?.trim() || undefined;
|
|
102
103
|
const rawPath = params.path?.trim();
|
|
103
104
|
if (rawPath) {
|
|
104
105
|
const internalRouter = this.session.internalRouter;
|
|
@@ -114,7 +115,7 @@ export class AstEditTool implements AgentTool<typeof astEditSchema, AstEditToolD
|
|
|
114
115
|
} else {
|
|
115
116
|
const parsedPath = parseSearchPath(rawPath);
|
|
116
117
|
searchPath = resolveToCwd(parsedPath.basePath, this.session.cwd);
|
|
117
|
-
globFilter = parsedPath.glob;
|
|
118
|
+
globFilter = combineSearchGlobs(parsedPath.glob, globFilter);
|
|
118
119
|
}
|
|
119
120
|
}
|
|
120
121
|
|
|
@@ -133,7 +134,7 @@ export class AstEditTool implements AgentTool<typeof astEditSchema, AstEditToolD
|
|
|
133
134
|
lang: params.lang?.trim(),
|
|
134
135
|
path: resolvedSearchPath,
|
|
135
136
|
glob: globFilter,
|
|
136
|
-
selector: params.
|
|
137
|
+
selector: params.sel?.trim(),
|
|
137
138
|
dryRun: true,
|
|
138
139
|
maxReplacements,
|
|
139
140
|
maxFiles,
|
|
@@ -277,7 +278,7 @@ export class AstEditTool implements AgentTool<typeof astEditSchema, AstEditToolD
|
|
|
277
278
|
lang: params.lang?.trim(),
|
|
278
279
|
path: resolvedSearchPath,
|
|
279
280
|
glob: globFilter,
|
|
280
|
-
selector: params.
|
|
281
|
+
selector: params.sel?.trim(),
|
|
281
282
|
dryRun: false,
|
|
282
283
|
maxReplacements,
|
|
283
284
|
maxFiles,
|
|
@@ -320,7 +321,7 @@ interface AstEditRenderArgs {
|
|
|
320
321
|
ops?: Array<{ pat?: string; out?: string }>;
|
|
321
322
|
lang?: string;
|
|
322
323
|
path?: string;
|
|
323
|
-
|
|
324
|
+
sel?: string;
|
|
324
325
|
limit?: number;
|
|
325
326
|
}
|
|
326
327
|
|
package/src/tools/ast-grep.ts
CHANGED
|
@@ -14,7 +14,7 @@ import { Ellipsis, Hasher, type RenderCache, renderStatusLine, renderTreeList, t
|
|
|
14
14
|
import { resolveFileDisplayMode } from "../utils/file-display-mode";
|
|
15
15
|
import type { ToolSession } from ".";
|
|
16
16
|
import type { OutputMeta } from "./output-meta";
|
|
17
|
-
import { hasGlobPathChars, parseSearchPath, resolveToCwd } from "./path-utils";
|
|
17
|
+
import { combineSearchGlobs, hasGlobPathChars, parseSearchPath, resolveToCwd } from "./path-utils";
|
|
18
18
|
import {
|
|
19
19
|
dedupeParseErrors,
|
|
20
20
|
formatCount,
|
|
@@ -28,10 +28,11 @@ import { ToolError } from "./tool-errors";
|
|
|
28
28
|
import { toolResult } from "./tool-result";
|
|
29
29
|
|
|
30
30
|
const astGrepSchema = Type.Object({
|
|
31
|
-
|
|
31
|
+
pat: Type.Array(Type.String(), { minItems: 1, description: "AST patterns to match" }),
|
|
32
32
|
lang: Type.Optional(Type.String({ description: "Language override" })),
|
|
33
33
|
path: Type.Optional(Type.String({ description: "File, directory, or glob pattern to search (default: cwd)" })),
|
|
34
|
-
|
|
34
|
+
glob: Type.Optional(Type.String({ description: "Optional glob filter relative to path" })),
|
|
35
|
+
sel: Type.Optional(Type.String({ description: "Optional selector for contextual pattern mode" })),
|
|
35
36
|
limit: Type.Optional(Type.Number({ description: "Max matches (default: 50)" })),
|
|
36
37
|
offset: Type.Optional(Type.Number({ description: "Skip first N matches (default: 0)" })),
|
|
37
38
|
context: Type.Optional(Type.Number({ description: "Context lines around each match" })),
|
|
@@ -68,11 +69,9 @@ export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolD
|
|
|
68
69
|
_context?: AgentToolContext,
|
|
69
70
|
): Promise<AgentToolResult<AstGrepToolDetails>> {
|
|
70
71
|
return untilAborted(signal, async () => {
|
|
71
|
-
const patterns = [
|
|
72
|
-
...new Set(params.patterns.map(pattern => pattern.trim()).filter(pattern => pattern.length > 0)),
|
|
73
|
-
];
|
|
72
|
+
const patterns = [...new Set(params.pat.map(pattern => pattern.trim()).filter(pattern => pattern.length > 0))];
|
|
74
73
|
if (patterns.length === 0) {
|
|
75
|
-
throw new ToolError("`
|
|
74
|
+
throw new ToolError("`pat` must include at least one non-empty pattern");
|
|
76
75
|
}
|
|
77
76
|
const limit = params.limit === undefined ? 50 : Math.floor(params.limit);
|
|
78
77
|
if (!Number.isFinite(limit) || limit < 1) {
|
|
@@ -88,7 +87,7 @@ export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolD
|
|
|
88
87
|
}
|
|
89
88
|
|
|
90
89
|
let searchPath: string | undefined;
|
|
91
|
-
let globFilter
|
|
90
|
+
let globFilter = params.glob?.trim() || undefined;
|
|
92
91
|
const rawPath = params.path?.trim();
|
|
93
92
|
if (rawPath) {
|
|
94
93
|
const internalRouter = this.session.internalRouter;
|
|
@@ -104,7 +103,7 @@ export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolD
|
|
|
104
103
|
} else {
|
|
105
104
|
const parsedPath = parseSearchPath(rawPath);
|
|
106
105
|
searchPath = resolveToCwd(parsedPath.basePath, this.session.cwd);
|
|
107
|
-
globFilter = parsedPath.glob;
|
|
106
|
+
globFilter = combineSearchGlobs(parsedPath.glob, globFilter);
|
|
108
107
|
}
|
|
109
108
|
}
|
|
110
109
|
|
|
@@ -126,7 +125,7 @@ export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolD
|
|
|
126
125
|
lang: params.lang?.trim(),
|
|
127
126
|
path: resolvedSearchPath,
|
|
128
127
|
glob: globFilter,
|
|
129
|
-
selector: params.
|
|
128
|
+
selector: params.sel?.trim(),
|
|
130
129
|
limit,
|
|
131
130
|
offset,
|
|
132
131
|
context,
|
|
@@ -273,10 +272,10 @@ export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolD
|
|
|
273
272
|
// =============================================================================
|
|
274
273
|
|
|
275
274
|
interface AstGrepRenderArgs {
|
|
276
|
-
|
|
275
|
+
pat?: string[];
|
|
277
276
|
lang?: string;
|
|
278
277
|
path?: string;
|
|
279
|
-
|
|
278
|
+
sel?: string;
|
|
280
279
|
limit?: number;
|
|
281
280
|
offset?: number;
|
|
282
281
|
context?: number;
|
|
@@ -290,14 +289,13 @@ export const astGrepToolRenderer = {
|
|
|
290
289
|
const meta: string[] = [];
|
|
291
290
|
if (args.lang) meta.push(`lang:${args.lang}`);
|
|
292
291
|
if (args.path) meta.push(`in ${args.path}`);
|
|
293
|
-
if (args.
|
|
292
|
+
if (args.sel) meta.push("selector");
|
|
294
293
|
if (args.limit !== undefined && args.limit > 0) meta.push(`limit:${args.limit}`);
|
|
295
294
|
if (args.offset !== undefined && args.offset > 0) meta.push(`offset:${args.offset}`);
|
|
296
295
|
if (args.context !== undefined) meta.push(`context:${args.context}`);
|
|
297
|
-
if (args.
|
|
296
|
+
if (args.pat && args.pat.length > 1) meta.push(`${args.pat.length} patterns`);
|
|
298
297
|
|
|
299
|
-
const description =
|
|
300
|
-
args.patterns?.length === 1 ? args.patterns[0] : args.patterns ? `${args.patterns.length} patterns` : "?";
|
|
298
|
+
const description = args.pat?.length === 1 ? args.pat[0] : args.pat ? `${args.pat.length} patterns` : "?";
|
|
301
299
|
const text = renderStatusLine({ icon: "pending", title: "AST Grep", description, meta }, uiTheme);
|
|
302
300
|
return new Text(text, 0, 0);
|
|
303
301
|
},
|
|
@@ -321,7 +319,7 @@ export const astGrepToolRenderer = {
|
|
|
321
319
|
const limitReached = details?.limitReached ?? false;
|
|
322
320
|
|
|
323
321
|
if (matchCount === 0) {
|
|
324
|
-
const description = args?.
|
|
322
|
+
const description = args?.pat?.length === 1 ? args.pat[0] : undefined;
|
|
325
323
|
const meta = ["0 matches"];
|
|
326
324
|
if (details?.scopePath) meta.push(`in ${details.scopePath}`);
|
|
327
325
|
if (filesSearched > 0) meta.push(`searched ${filesSearched}`);
|
|
@@ -344,7 +342,7 @@ export const astGrepToolRenderer = {
|
|
|
344
342
|
if (details?.scopePath) meta.push(`in ${details.scopePath}`);
|
|
345
343
|
meta.push(`searched ${filesSearched}`);
|
|
346
344
|
if (limitReached) meta.push(uiTheme.fg("warning", "limit reached"));
|
|
347
|
-
const description = args?.
|
|
345
|
+
const description = args?.pat?.length === 1 ? args.pat[0] : undefined;
|
|
348
346
|
const header = renderStatusLine(
|
|
349
347
|
{ icon: limitReached ? "warning" : "success", title: "AST Grep", description, meta },
|
|
350
348
|
uiTheme,
|
package/src/tools/grep.ts
CHANGED
|
@@ -16,7 +16,7 @@ import { Ellipsis, Hasher, type RenderCache, renderStatusLine, renderTreeList, t
|
|
|
16
16
|
import { resolveFileDisplayMode } from "../utils/file-display-mode";
|
|
17
17
|
import type { ToolSession } from ".";
|
|
18
18
|
import { formatFullOutputReference, type OutputMeta } from "./output-meta";
|
|
19
|
-
import { hasGlobPathChars, parseSearchPath, resolveToCwd } from "./path-utils";
|
|
19
|
+
import { combineSearchGlobs, hasGlobPathChars, parseSearchPath, resolveToCwd } from "./path-utils";
|
|
20
20
|
import { formatCount, formatEmptyMessage, formatErrorMessage, PREVIEW_LIMITS } from "./render-utils";
|
|
21
21
|
import { ToolError } from "./tool-errors";
|
|
22
22
|
import { toolResult } from "./tool-result";
|
|
@@ -125,12 +125,7 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
125
125
|
const parsedPath = parseSearchPath(rawPath);
|
|
126
126
|
searchPath = resolveToCwd(parsedPath.basePath, this.session.cwd);
|
|
127
127
|
if (parsedPath.glob) {
|
|
128
|
-
|
|
129
|
-
throw new ToolError(
|
|
130
|
-
"When path already includes glob characters, omit the separate glob parameter",
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
globFilter = parsedPath.glob;
|
|
128
|
+
globFilter = combineSearchGlobs(parsedPath.glob, globFilter);
|
|
134
129
|
}
|
|
135
130
|
}
|
|
136
131
|
} else {
|
package/src/tools/path-utils.ts
CHANGED
|
@@ -128,6 +128,16 @@ export function parseSearchPath(filePath: string): ParsedSearchPath {
|
|
|
128
128
|
};
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
export function combineSearchGlobs(prefixGlob?: string, suffixGlob?: string): string | undefined {
|
|
132
|
+
if (!prefixGlob) return suffixGlob;
|
|
133
|
+
if (!suffixGlob) return prefixGlob;
|
|
134
|
+
|
|
135
|
+
const normalizedPrefix = prefixGlob.replace(/\/+$/, "");
|
|
136
|
+
const normalizedSuffix = suffixGlob.replace(/^\/+/, "");
|
|
137
|
+
|
|
138
|
+
return `${normalizedPrefix}/${normalizedSuffix}`;
|
|
139
|
+
}
|
|
140
|
+
|
|
131
141
|
export function resolveReadPath(filePath: string, cwd: string): string {
|
|
132
142
|
const resolved = resolveToCwd(filePath, cwd);
|
|
133
143
|
const shellEscapedVariant = tryShellEscapedPath(resolved);
|