pi-shorthand 0.9.0 → 0.10.0
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/README.md +11 -3
- package/package.json +6 -13
- package/src/index.ts +23 -2
- package/skills/shorthand/SKILL.md +0 -117
- package/skills/shorthand/advanced-refactors.md +0 -146
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# pi-shorthand
|
|
2
2
|
|
|
3
|
-

|
|
4
4
|
|
|
5
5
|
A [Pi](https://github.com/earendil-works/pi) tool for editing repositories with Bun programs, with first-class support for TypeScript and JavaScript.
|
|
6
6
|
|
|
@@ -36,10 +36,18 @@ sudo apt install bubblewrap build-essential lsof
|
|
|
36
36
|
|
|
37
37
|
pi-shorthand gives Pi a programming environment instead of a patch format. This makes multi-file and structural edits possible in one call, but does not guarantee that Pi will find it easier or more reliable than its built-in edit tool. Which works better depends on the model and the task.
|
|
38
38
|
|
|
39
|
-
Programs edit a private workspace, with host files outside the repository kept read-only.
|
|
39
|
+
Programs edit a private workspace, with host files outside the repository kept read-only. Agents are good at improving things a step at a time, so changes are applied a whole file at a time and the agent is told which worked and which didn't. Concurrent edits can cause a run to be rejected; conflict detection is best-effort, not an atomic commit.
|
|
40
40
|
|
|
41
41
|
The `code` tool uses Pi's working directory by default. Pass `cwd` to target another checkout; relative paths are resolved from Pi's working directory. For example, when Pi starts in a bare worktree container, `cwd: "child"` targets its `child` worktree. The chosen directory must be inside a Git worktree.
|
|
42
42
|
|
|
43
43
|
## Without Pi
|
|
44
44
|
|
|
45
|
-
The engine is published separately as
|
|
45
|
+
The engine is published separately as [shorthand-code](https://github.com/sebinsua/shortsight/tree/main/packages/shorthand-code), with a `shorthand` command that runs a program from a file or stdin and prints the same result. See `shorthand --help`.
|
|
46
|
+
|
|
47
|
+
## Code graph (optional)
|
|
48
|
+
|
|
49
|
+
Install [`sightread`](https://github.com/sebinsua/shortsight/tree/main/packages/sightread) and programs can also ask the TypeScript compiler what calls what, with exact line ranges, before they edit:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
npm i -g sightread
|
|
53
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-shorthand",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Pi tool for editing repositories with Bun programs, with first-class support for TypeScript and JavaScript.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ast-grep",
|
|
@@ -11,26 +11,22 @@
|
|
|
11
11
|
"pi-extension",
|
|
12
12
|
"pi-package"
|
|
13
13
|
],
|
|
14
|
-
"homepage": "https://github.com/sebinsua/
|
|
15
|
-
"bugs": "https://github.com/sebinsua/
|
|
14
|
+
"homepage": "https://github.com/sebinsua/shortsight/tree/main/packages/pi-shorthand#readme",
|
|
15
|
+
"bugs": "https://github.com/sebinsua/shortsight/issues",
|
|
16
16
|
"license": "MIT",
|
|
17
17
|
"author": "Seb Insua",
|
|
18
18
|
"repository": {
|
|
19
19
|
"type": "git",
|
|
20
|
-
"url": "git+https://github.com/sebinsua/
|
|
20
|
+
"url": "git+https://github.com/sebinsua/shortsight.git",
|
|
21
21
|
"directory": "packages/pi-shorthand"
|
|
22
22
|
},
|
|
23
23
|
"files": [
|
|
24
24
|
"src",
|
|
25
|
-
"skills",
|
|
26
25
|
"README.md"
|
|
27
26
|
],
|
|
28
27
|
"type": "module",
|
|
29
|
-
"scripts": {
|
|
30
|
-
"prepack": "cp ../../README.md README.md"
|
|
31
|
-
},
|
|
32
28
|
"dependencies": {
|
|
33
|
-
"shorthand-code": "0.
|
|
29
|
+
"shorthand-code": "0.10.0"
|
|
34
30
|
},
|
|
35
31
|
"peerDependencies": {
|
|
36
32
|
"@earendil-works/pi-ai": "*",
|
|
@@ -59,9 +55,6 @@
|
|
|
59
55
|
"extensions": [
|
|
60
56
|
"./src/index.ts"
|
|
61
57
|
],
|
|
62
|
-
"
|
|
63
|
-
"./skills"
|
|
64
|
-
],
|
|
65
|
-
"image": "https://raw.githubusercontent.com/sebinsua/pi-shorthand/main/docs/screenshot.png?v=2"
|
|
58
|
+
"image": "https://raw.githubusercontent.com/sebinsua/shortsight/main/docs/shorthand.png"
|
|
66
59
|
}
|
|
67
60
|
}
|
package/src/index.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Its writes go to a copy-on-write overlay; if it succeeds, they're applied and the diff is returned.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { createRequire } from "node:module";
|
|
6
7
|
import * as path from "node:path";
|
|
7
8
|
import { StringEnum } from "@earendil-works/pi-ai";
|
|
8
9
|
import { type ExtensionAPI, type Theme, truncateHead, truncateTail } from "@earendil-works/pi-coding-agent";
|
|
@@ -16,6 +17,7 @@ import {
|
|
|
16
17
|
diagnosticLines,
|
|
17
18
|
runFailed,
|
|
18
19
|
runWithBun,
|
|
20
|
+
sightreadAvailable,
|
|
19
21
|
textForModel,
|
|
20
22
|
} from "shorthand-code";
|
|
21
23
|
import { callLine, resultLines, unstructuredResultText } from "./display.ts";
|
|
@@ -29,12 +31,30 @@ Common operations:
|
|
|
29
31
|
- sg.one(pattern, files?) selects exactly one match; sg.find returns an array. sg.rewrite also accepts a selected match or array without a file scope.
|
|
30
32
|
- A rewrite callback receives a match and returns text, a native node.replace(text) edit, or null to skip. Return native edits to apply them. Pass selected arrays together for independent edits; select again after changing their file.
|
|
31
33
|
- await refactor.rename({ file, symbol, to }) renames one resolved TypeScript symbol across the project without changing unrelated names.
|
|
34
|
+
- await refactor.references({ file, symbol }) returns resolved identifier matches that pass straight to sg.rewrite(matches, callback).
|
|
32
35
|
- await refactor.renameFile({ from, to }) moves a TypeScript file and updates module paths that resolve to it.
|
|
33
36
|
- await refactor.move({ file, symbol, to }) moves a top-level declaration to another file and updates the imports that follow it.
|
|
34
37
|
|
|
35
38
|
See the shorthand skill for renames, moves and call-site migrations. Read its advanced-refactors.md guide only to extract code, move syntax or use rule objects. The default timeout is two seconds; request more for longer programs. Time spent inside the helpers above does not count toward it, up to 60 extra seconds.`;
|
|
36
39
|
|
|
37
|
-
|
|
40
|
+
const GRAPH_DESCRIPTION =
|
|
41
|
+
"- await graph.query(request | request[]) queries symbols and relationships; graph results can be passed to sg as scope. The graph shows the repository before this program's edits.\n";
|
|
42
|
+
|
|
43
|
+
export const SKILLS_DIRECTORY = path.join(
|
|
44
|
+
path.dirname(createRequire(import.meta.url).resolve("shorthand-code/package.json")),
|
|
45
|
+
"skills",
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
export function codeDescription(graphAvailable: boolean): string {
|
|
49
|
+
return graphAvailable
|
|
50
|
+
? DESCRIPTION.replace("\n\nSee the shorthand skill", `\n${GRAPH_DESCRIPTION}\nSee the shorthand skill`)
|
|
51
|
+
: DESCRIPTION;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export default async function (pi: ExtensionAPI, findGraph: () => Promise<unknown> = sightreadAvailable) {
|
|
55
|
+
// The shorthand skill ships with shorthand-code, whose `shorthand --skill` prints it for other agents.
|
|
56
|
+
pi.on("resources_discover", () => ({ skillPaths: [SKILLS_DIRECTORY] }));
|
|
57
|
+
const graphAvailable = Boolean(await findGraph());
|
|
38
58
|
// A failed run is an error, both for the model and for how Pi shows it. (execute() returns its details
|
|
39
59
|
// rather than throwing, since a thrown error loses them.)
|
|
40
60
|
pi.on("tool_result", async (event) => {
|
|
@@ -45,7 +65,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
45
65
|
pi.registerTool({
|
|
46
66
|
name: "code",
|
|
47
67
|
label: "Code",
|
|
48
|
-
description:
|
|
68
|
+
description: codeDescription(graphAvailable),
|
|
49
69
|
promptSnippet:
|
|
50
70
|
"Make multi-file, repetitive or rename/move changes with one Bun program; a small change to one file is quicker as a direct edit. Run verification separately afterward",
|
|
51
71
|
|
|
@@ -91,6 +111,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
91
111
|
program: params.program,
|
|
92
112
|
timeoutMs: (params.timeout ?? DEFAULT_TIMEOUT_SECONDS) * 1000,
|
|
93
113
|
rollback: params.rollback ?? "file",
|
|
114
|
+
graph: graphAvailable,
|
|
94
115
|
},
|
|
95
116
|
signal,
|
|
96
117
|
(step) => (latest = step),
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: shorthand
|
|
3
|
-
description: Edit repository files with Bun programs using plain text edits or structural matching. Covers renames, file and declaration moves, and call-site migrations; the advanced guide covers extracting code, moving syntax and rule objects.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Shorthand
|
|
7
|
-
|
|
8
|
-
Use text replacement for known source; use structural matching when it saves enumerating
|
|
9
|
-
occurrences or preserves varying syntax. Ordinary JavaScript strings, loops and Bun APIs work.
|
|
10
|
-
Run tests, type-checks and builds separately with the shell tool after editing.
|
|
11
|
-
|
|
12
|
-
## Replace known text
|
|
13
|
-
|
|
14
|
-
```ts
|
|
15
|
-
edit({ path: "src/config.ts", oldText: "timeoutMs: 1000", newText: "timeoutMs: 3000" });
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
`edit` replaces exactly one literal occurrence, throwing if it is missing or ambiguous. Include
|
|
19
|
-
surrounding text to distinguish repeated occurrences. Replacement text is literal, including `$`.
|
|
20
|
-
Line-ending differences are accepted when matching.
|
|
21
|
-
`edit` calls are synchronous and can be combined in one program; later calls see earlier changes.
|
|
22
|
-
|
|
23
|
-
## Refactors
|
|
24
|
-
|
|
25
|
-
Rename symbols, move files, or move a declaration to another file, updating the project to match:
|
|
26
|
-
|
|
27
|
-
```ts
|
|
28
|
-
await refactor.rename({ file: "src/users.ts", symbol: "parseUser", to: "decodeUser" });
|
|
29
|
-
await refactor.renameFile({ from: "src/users.ts", to: "src/models/users.ts" });
|
|
30
|
-
await refactor.move({ file: "src/api.ts", symbol: "parseUser", to: "src/users/parse.ts" });
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
`refactor.rename` requires the declaration name to be unique in its file and leaves unrelated symbols
|
|
34
|
-
alone. `refactor.renameFile` moves the file and updates imports and exports that resolve to it. Read
|
|
35
|
-
[Semantic TypeScript refactors](advanced-refactors.md#semantic-typescript-refactors) for selection
|
|
36
|
-
rules, updated paths and failure conditions.
|
|
37
|
-
|
|
38
|
-
`refactor.move` appends the top-level declaration of `symbol` to `to`, which may be a new file. The
|
|
39
|
-
target imports what the declaration uses, exporting helpers from the source when needed; the source
|
|
40
|
-
imports it back if it still uses it; and files importing it from the source import it from the target.
|
|
41
|
-
Importers keep their style, including `tsconfig` path aliases. Default exports, overloads, namespace
|
|
42
|
-
imports that use it and dynamic imports of the source are refused before anything is written.
|
|
43
|
-
|
|
44
|
-
## Insert before or after a statement
|
|
45
|
-
|
|
46
|
-
Validate an order immediately before saving it:
|
|
47
|
-
|
|
48
|
-
```ts
|
|
49
|
-
const save = sg.one("await saveOrder(order);", "checkout.ts");
|
|
50
|
-
sg.insert("validateOrder(order);", { before: save });
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
Use `{ after: save }` to insert after it instead. `sg.one(pattern, files?)` requires exactly one
|
|
54
|
-
match. Select the whole statement, including its semicolon, for before/after insertion in JS/TS.
|
|
55
|
-
|
|
56
|
-
## Replace calls while keeping their arguments
|
|
57
|
-
|
|
58
|
-
Switch logging calls without reproducing their arguments:
|
|
59
|
-
|
|
60
|
-
```ts
|
|
61
|
-
sg.rewrite("console.log($$$ARGS)", "logger.info($$$ARGS)", "src");
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
`sg.rewrite(pattern, replacement, files?)` discovers, parses and writes matching files. Omit the
|
|
65
|
-
scope for the working directory, or pass a file, directory or glob. `$X` captures one syntax node;
|
|
66
|
-
`$$$X` captures a sequence. Captures can appear in the replacement text.
|
|
67
|
-
|
|
68
|
-
## Change one argument and preserve the rest
|
|
69
|
-
|
|
70
|
-
Migrate numeric retry limits to options objects, leaving existing options alone:
|
|
71
|
-
|
|
72
|
-
```ts
|
|
73
|
-
sg.rewrite("connect($URL, $RETRIES)", (m) => {
|
|
74
|
-
const retries = m.node.getMatch("RETRIES")!;
|
|
75
|
-
return retries.kind() === "number" ? retries.replace(`{ retries: ${retries.text()} }`) : null;
|
|
76
|
-
});
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
A callback returns text to replace the whole match, a native edit for part of it, or `null` to skip.
|
|
80
|
-
Rewrites apply one after another. A pattern rewrite skips places an earlier rewrite produced, so one
|
|
81
|
-
rewrite per call shape is safe in any order; select them with `sg.find` to rewrite them again.
|
|
82
|
-
**`node.replace()` constructs an edit; return it from the callback so `sg.rewrite` applies it.**
|
|
83
|
-
|
|
84
|
-
## Replace an implementation while keeping its signature
|
|
85
|
-
|
|
86
|
-
Make `calculateTotal` sum its prices, preserving parameter and return types:
|
|
87
|
-
|
|
88
|
-
```ts
|
|
89
|
-
const fn = sg.one(
|
|
90
|
-
{ rule: { kind: "function_declaration", has: { field: "name", regex: "^calculateTotal$" } } },
|
|
91
|
-
"prices.ts",
|
|
92
|
-
);
|
|
93
|
-
sg.rewrite(fn, (m) => m.node.field("body")!.replace("{ return prices.reduce((total, price) => total + price, 0); }"));
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
Use `kind: "method_definition"` with the same `has` to select a class method; match names with
|
|
97
|
-
`regex`, since a method's name is not an identifier pattern. `sg.rewrite(match, "new source")`
|
|
98
|
-
replaces the whole selected node instead. `sg.find` returns an
|
|
99
|
-
array: pass it once to `sg.rewrite(matches, callback)` for independent edits. Each call writes
|
|
100
|
-
immediately within the editing workspace; select again if a later edit depends on that write.
|
|
101
|
-
|
|
102
|
-
## Write files or replace plain text
|
|
103
|
-
|
|
104
|
-
```ts
|
|
105
|
-
await Bun.write("src/defaults.ts", "export const retryLimit = 3;\n");
|
|
106
|
-
const source = await Bun.file("src/config.ts").text();
|
|
107
|
-
await Bun.write("src/config.ts", source.replace("timeoutMs: 1000", "timeoutMs: 3000"));
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
Use repository-relative paths. Changes apply on successful exit by default; the tool reports the
|
|
111
|
-
diff, preserves existing UTF-8 BOMs and uniform line endings across write methods, then uses a
|
|
112
|
-
detected project formatter. Counters and console summaries aren't required.
|
|
113
|
-
|
|
114
|
-
This page covers renames, file and declaration moves, and call-site migrations. Read
|
|
115
|
-
[advanced-refactors.md](advanced-refactors.md) only to extract code into a new function or file,
|
|
116
|
-
insert, move or remove other statements, or use rule objects or the native ast-grep API. The bundled
|
|
117
|
-
TypeScript 7 package has no legacy compiler API; use the supplied structural tools instead.
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
# Advanced refactors
|
|
2
|
-
|
|
3
|
-
Use this guide for extraction, complex structural rewrites, syntax placement or other languages.
|
|
4
|
-
The everyday file and replacement operations are in [SKILL.md](SKILL.md).
|
|
5
|
-
|
|
6
|
-
## Semantic TypeScript refactors
|
|
7
|
-
|
|
8
|
-
Prefer `refactor.rename` to a structural rewrite when changing a TypeScript symbol. It asks the TypeScript
|
|
9
|
-
language server to rename the resolved symbol across the project, so unrelated names and strings are
|
|
10
|
-
left alone. `file` is the declaration's file, and `symbol` must name exactly one declaration there.
|
|
11
|
-
Missing names, repeated declarations and overloads are rejected without writing.
|
|
12
|
-
|
|
13
|
-
Use `refactor.renameFile` to move a whole TypeScript file. It updates imports and exports that resolve to
|
|
14
|
-
the file, as well as relative module paths inside the moved file, then performs the move. The source
|
|
15
|
-
must exist, the destination must not exist, and both paths must remain inside the repository.
|
|
16
|
-
|
|
17
|
-
Await each operation; when it resolves, all of its edits are complete. Use `sg.move` below for
|
|
18
|
-
moving syntax between files; it does not repair imports or bindings. `refactor.move` moves a top-level
|
|
19
|
-
declaration with its imports.
|
|
20
|
-
|
|
21
|
-
## Extract existing source
|
|
22
|
-
|
|
23
|
-
Select and reuse the implementation rather than copying it into the program or searching for braces.
|
|
24
|
-
This extracts a state-independent method, leaves a delegate, and updates its direct caller:
|
|
25
|
-
|
|
26
|
-
```ts
|
|
27
|
-
const pattern = { rule: { kind: "method_definition", has: { field: "name", regex: "^format$" } } };
|
|
28
|
-
const method = sg.one(pattern, "writer.ts");
|
|
29
|
-
await Bun.write(
|
|
30
|
-
"table.ts",
|
|
31
|
-
'import type { Cell, FormatOptions } from "./types";\n' +
|
|
32
|
-
method.text.replace(/^format\b/, "export function renderTable"),
|
|
33
|
-
);
|
|
34
|
-
sg.rewrite(method, (m) => m.node.field("body")!.replace("{ return renderTable(rows, options); }"));
|
|
35
|
-
await Bun.write("writer.ts", 'import { renderTable } from "./table";\n' + (await Bun.file("writer.ts").text()));
|
|
36
|
-
sg.rewrite('import { TableWriter } from "./writer"', 'import { renderTable } from "./table"', "export.ts");
|
|
37
|
-
sg.rewrite("new TableWriter().format($$$ARGS)", "renderTable($$$ARGS)", "export.ts");
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
Preserve imports and dependencies; moving source does not remove its dependence on instance state.
|
|
41
|
-
`field("body")` selects the actual body even when defaults or comments contain braces.
|
|
42
|
-
|
|
43
|
-
## Structural matching details
|
|
44
|
-
|
|
45
|
-
Patterns must parse as one syntax node; a standalone class method such as
|
|
46
|
-
`format($$$PARAMS) { $$$BODY }` is matched as a method. `$X` captures one node, `$$$X` a sequence, and `$_`
|
|
47
|
-
matches without capturing. Capture text is available as `m.X` or `m.vars.X`.
|
|
48
|
-
|
|
49
|
-
Callbacks are synchronous. They can return an array of native edits, or `null`/`undefined`/`false`
|
|
50
|
-
to skip. `sg.rewrite` counts matches producing edits, not individual edits. Overlaps are rejected;
|
|
51
|
-
an empty selection returns zero. Syntax fields and node kinds depend on the language.
|
|
52
|
-
|
|
53
|
-
`sg.find` and `sg.rewrite` handle JS, TS, TSX, HTML and CSS. Scopes accept files, directories,
|
|
54
|
-
globs or arrays. Paths may be relative or absolute within the editing workspace; results use
|
|
55
|
-
repository-relative paths. String scopes use Git's tracked and non-ignored files. An explicit
|
|
56
|
-
`sg.file()` target can also select an ignored JS/TS file; only files Git sees are applied afterward.
|
|
57
|
-
|
|
58
|
-
## Inserting, moving and removing syntax
|
|
59
|
-
|
|
60
|
-
For JS/TS, use matches from `sg.find` or `sg.one(pattern, files)`, which requires exactly one match.
|
|
61
|
-
`sg.file(path)` selects a JS/TS file root for placement or for scoping `find`, `one` and `rewrite`.
|
|
62
|
-
Pass paths or file targets individually or in mixed arrays. Missing targets are valid insertion destinations,
|
|
63
|
-
but cannot be searched.
|
|
64
|
-
|
|
65
|
-
```ts
|
|
66
|
-
sg.insert("initialize();", { before: sg.one("run();", "src/app.ts") });
|
|
67
|
-
sg.move(sg.one("function helper() { $$$BODY }", "src/old.ts"), {
|
|
68
|
-
endOf: sg.file("src/new.ts"),
|
|
69
|
-
});
|
|
70
|
-
sg.remove(sg.find("obsolete();", "src")); // single match or array; validates the batch before writing
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
Choose one destination: `before`/`after` a whole statement or declaration (`"run();"`), or
|
|
74
|
-
`startOf`/`endOf` a file root or `statement_block`. For a function, select its body explicitly.
|
|
75
|
-
Argument lists and class bodies aren't supported.
|
|
76
|
-
|
|
77
|
-
To copy, use `sg.insert(source.text, destination)`. `sg.move(source, destination, transform?)`
|
|
78
|
-
accepts an optional `(text) => string` returning non-empty replacement text.
|
|
79
|
-
|
|
80
|
-
**Rematch placement targets after each edit**; use array removal for matches from one search. Adjacent comments stay in
|
|
81
|
-
place, and interior whitespace is preserved. Imports and bindings aren't repaired; to move a top-level
|
|
82
|
-
declaration with its imports, use `refactor.move`. If placement
|
|
83
|
-
rejects joined statement boundaries, add explicit semicolons.
|
|
84
|
-
|
|
85
|
-
## Rule objects
|
|
86
|
-
|
|
87
|
-
When a pattern alone can't say it, pass a rule instead:
|
|
88
|
-
|
|
89
|
-
```ts
|
|
90
|
-
sg.find({ rule: { kind: "import_statement" } }, "src");
|
|
91
|
-
sg.find({ rule: { pattern: "console.log($$$A)", inside: { kind: "function_declaration", stopBy: "end" } } }, "src");
|
|
92
|
-
sg.find(
|
|
93
|
-
{ rule: { pattern: "console.log($$$A)", not: { inside: { kind: "function_declaration", stopBy: "end" } } } },
|
|
94
|
-
"src",
|
|
95
|
-
);
|
|
96
|
-
// constraints restrict what a capture may match
|
|
97
|
-
sg.rewrite({ rule: { pattern: "$F($X)" }, constraints: { F: { regex: "^(oldApi|legacyApi)$" } } }, "newApi($X)", "src");
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
`kind` names come from tree-sitter (`function_declaration`, `call_expression`, `import_statement`,
|
|
101
|
-
…). `stopBy: "end"` searches all ancestors, not just the parent.
|
|
102
|
-
|
|
103
|
-
## Native ast-grep API
|
|
104
|
-
|
|
105
|
-
For transformations outside file-backed rewrites, ast-grep's JavaScript API is exposed on `sg`:
|
|
106
|
-
|
|
107
|
-
```ts
|
|
108
|
-
const root = sg.parse(sg.Lang.TypeScript, sourceText).root();
|
|
109
|
-
const edits = root.findAll("oldApi($$$ARGS)").map((node) => node.replace("newApi()"));
|
|
110
|
-
await Bun.write("src/app.ts", root.commitEdits(edits));
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
`import { parse, Lang } from "@ast-grep/napi"` also works. Native `replace` constructs edits;
|
|
114
|
-
`commitEdits` returns updated source text. Neither writes files by itself.
|
|
115
|
-
The bundled TypeScript 7 package does not expose the legacy compiler API (`createSourceFile`,
|
|
116
|
-
`ScriptTarget`); changing import syntax won't make it available.
|
|
117
|
-
|
|
118
|
-
## File discovery and commands
|
|
119
|
-
|
|
120
|
-
```ts
|
|
121
|
-
glob("src/**/*.ts"); // sorted paths Git sees
|
|
122
|
-
grep("oldApi(", "src"); // [{ file, line, text }]; accepts a RegExp too
|
|
123
|
-
const files = await $`git ls-files`.text();
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
`glob`, `grep` and `sg` are synchronous. Bun's `$` needs `await`; interpolated values are
|
|
127
|
-
quoted as single arguments, and arrays become multiple arguments. These globals and bundled CLIs
|
|
128
|
-
belong to `code`, not necessarily ordinary shell calls. `node:fs` and ordinary Bun APIs also work.
|
|
129
|
-
|
|
130
|
-
## Execution options
|
|
131
|
-
|
|
132
|
-
Programs run in an isolated repository workspace. Use relative paths: the live checkout's absolute
|
|
133
|
-
path is inaccessible on macOS. On both platforms, host paths outside the workspace are read-only
|
|
134
|
-
(including external symlink targets). `$TMPDIR` is private to the run.
|
|
135
|
-
Writes to `.git` are blocked.
|
|
136
|
-
|
|
137
|
-
The default timeout is two seconds; request more for longer transformations. Time inside helpers
|
|
138
|
-
(`edit`, `glob`, `grep`, `sg`, `refactor`) does not count toward it, up to 60 extra seconds per run.
|
|
139
|
-
By default, rollback happens per file: files involved in failed or interrupted edits are discarded,
|
|
140
|
-
while the others are retained, including after exceptions. Unattributed failures
|
|
141
|
-
(such as failed checks) preserve completed edits and report the failure. A failed file loses all
|
|
142
|
-
its edits from this run, even if the error is caught. Multi-file operations such as moves share
|
|
143
|
-
one outcome. Arbitrary shell failures cannot identify failed closed
|
|
144
|
-
files. Open writers are inspected before failure exits and timeout termination; inspection failures
|
|
145
|
-
or crashes that bypass exit handling retain nothing. Cancellation still discards everything.
|
|
146
|
-
Use `rollback: "all"` when the whole change must be atomic.
|