Skip to content

Commit 480ea66

Browse files
committed
Restore the ast-grep skill 1:1 with GaaS
Replace the Corbits run_shell intro and prettier-aligned tables with Guy's GaaS body. Slash /ast-grep remains. run_shell mapping stays on native-integration. Ignore GaaS ast-grep SKILL.md in prettier so table alignment stays 1:1.
1 parent 8eb06c9 commit 480ea66

5 files changed

Lines changed: 64 additions & 53 deletions

File tree

.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ CHANGELOG.md
77
plugins/corbits-skills/skills/opsh/SKILL.md
88
plugins/corbits-skills/skills/refactor/SKILL.md
99
plugins/corbits-skills/skills/scribe/SKILL.md
10+
plugins/corbits-skills/skills/ast-grep/SKILL.md
1011

1112
tmp/
1213
.claude/

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,11 @@ parallel copies under `docs/` or `scripts/notes/`. At cut time: rename
2020
### Changed
2121

2222
- Restore the scribe skill 1:1 with GaaS. ask_operator mapping stays on native-integration. Slash /scribe remains.
23+
- Restore the ast-grep skill 1:1 with GaaS. run_shell mapping stays on native-integration. Slash /ast-grep remains.
2324
- Restore the opsh skill 1:1 with GaaS. Tool/shell mapping stays on native-integration. user-invocable: false stays so it remains use_skill-only.
2425
- Restore the pull-request-review skill 1:1 with GaaS. ask_operator, /review mapping, and GitHub posting stay on native-integration. Slash /pull-request-review remains.
2526
- Restore the refactor skill 1:1 with GaaS. ask_operator mapping stays on native-integration. Slash /refactor remains.
26-
- Ignore GaaS opsh, refactor, and scribe SKILL.md in prettier so table/list alignment stays 1:1.
27+
- Ignore GaaS opsh, refactor, scribe, and ast-grep SKILL.md in prettier so table/list alignment stays 1:1.
2728
- Restore the git-rebase skill body 1:1 with GaaS. Intern execution recipe stays on native-integration. user-invocable: false stays so it remains use_skill-only.
2829
- Restore the linear-issue-workflow skill body 1:1 with GaaS. Claim-first, In Review, and git-worktrees extras stay on native-integration. user-invocable: false stays so it remains use_skill-only.
2930
- Restore the interview skill body 1:1 with GaaS (AskUserQuestion). Operator-ask mapping stays on native-integration. Slash /interview remains.

plugins/corbits-skills/skills/ast-grep/SKILL.md

Lines changed: 43 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ description: Bulk code refactoring using AST patterns instead of manual read-edi
55

66
# ast-grep
77

8-
Use `ast-grep` (CLI: `sg`) for structural code search and rewriting. Invoke `sg` via `run_shell`. It matches and transforms code using Abstract Syntax Tree patterns rather than text, so it understands code structure and handles formatting, whitespace, and nesting correctly.
8+
Use `ast-grep` (CLI: `sg`) for structural code search and rewriting. It matches and transforms code using Abstract Syntax Tree patterns rather than text, so it understands code structure and handles formatting, whitespace, and nesting correctly.
99

1010
If you can describe the change as "rename X to Y" or "change all A-shaped code to B-shaped code," use ast-grep — even if you already know some of the locations. Knowing where the definitions are does not mean you know where all the access sites are.
1111

@@ -36,12 +36,12 @@ Patterns are code snippets in the target language with metavariable placeholders
3636

3737
### Metavariables
3838

39-
| Syntax | Meaning |
40-
| --------- | ------------------------------------------------------ |
41-
| `$NAME` | Matches exactly one AST node, captured as `NAME` |
42-
| `$_` | Matches one node, not captured |
39+
| Syntax | Meaning |
40+
|---|---|
41+
| `$NAME` | Matches exactly one AST node, captured as `NAME` |
42+
| `$_` | Matches one node, not captured |
4343
| `$$$NAME` | Matches zero or more sibling nodes, captured as `NAME` |
44-
| `$$$` | Matches zero or more siblings, not captured |
44+
| `$$$` | Matches zero or more siblings, not captured |
4545

4646
**Same-name constraint:** Two occurrences of the same metavariable in one pattern must match identical text. `foo($X, $X)` matches `foo(a, a)` but not `foo(a, b)`.
4747

@@ -79,24 +79,22 @@ The `-U` (`--update-all`) flag applies changes to files without prompting. Witho
7979

8080
### Key flags
8181

82-
| Flag | Purpose |
83-
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
84-
| `-p, --pattern` | AST pattern to match |
85-
| `-r, --rewrite` | Replacement template using captured metavariables |
86-
| `-l, --lang` | Target language |
87-
| `-U, --update-all` | Apply rewrites in place |
88-
| `--globs` | Filter files by glob (prefix `!` to exclude) |
89-
| `--json` | Structured JSON output |
82+
| Flag | Purpose |
83+
|---|---|
84+
| `-p, --pattern` | AST pattern to match |
85+
| `-r, --rewrite` | Replacement template using captured metavariables |
86+
| `-l, --lang` | Target language |
87+
| `-U, --update-all` | Apply rewrites in place |
88+
| `--globs` | Filter files by glob (prefix `!` to exclude) |
89+
| `--json` | Structured JSON output |
9090
| `--debug-query=<mode>` | Show AST structure; modes: `pattern` (pattern parse tree), `ast` (named nodes), `cst` (full tree), `sexp` (S-expression). Requires `--lang`. |
9191

9292
### Common inline recipes
9393

9494
**Rename a function call site:**
95-
9695
```bash
9796
sg run -p 'oldName($$$ARGS)' -r 'newName($$$ARGS)' -l js -U src/
9897
```
99-
10098
This pattern only matches `identifier` nodes in call-expression position. It will not catch the name where it appears as a type annotation (`type_identifier`), an interface or object field (`property_identifier`), a destructured binding (`shorthand_property_identifier_pattern`), or an object literal shorthand (`shorthand_property_identifier`). For a name that appears in more than one syntactic position, use the multi-kind rename recipe below.
10199

102100
**Rename an identifier across all syntactic positions (TypeScript):**
@@ -126,35 +124,29 @@ fix: NewName
126124
This is the default approach for renaming a type, class, interface, or any identifier that may surface in more than just call-site position. The inline `sg run -p` form is the shortcut for call-site-only renames.
127125

128126
**Change an import source:**
129-
130127
```bash
131128
sg run -p 'import $$$ITEMS from "old-package"' -r 'import $$$ITEMS from "new-package"' -l ts -U src/
132129
```
133-
134130
Use `$$$ITEMS` (not `$ITEMS`) because `import type` inserts an extra `type` node as a sibling before the import clause. `$ITEMS` expects exactly one node in that position and fails when two are present.
135131

136132
The symmetric export form does not work inline. `sg run -p 'export $$$ITEMS from "old-package"' -r '...'` fails with "Multiple AST nodes are detected" — the re-export does not parse as a single AST node. For re-export source rewrites, use a YAML rule keyed on `kind: export_statement` with a `has` constraint on the source string, or fall back to manual edits when the file count is small.
137133

138134
**Add an argument to a call:**
139-
140135
```bash
141136
sg run -p 'client.get($URL)' -r 'client.get($URL, { timeout: 5000 })' -l ts -U src/
142137
```
143138

144139
**Wrap a call with an additional outer call:**
145-
146140
```bash
147141
sg run -p 'fetchData($$$ARGS)' -r 'withRetry(() => fetchData($$$ARGS))' -l ts -U src/
148142
```
149143

150144
**Unwrap a wrapper (Rust):**
151-
152145
```bash
153146
sg run -p '$EXPR.unwrap()' -r '$EXPR?' -l rust -U src/
154147
```
155148

156149
**Remove a function call, keep the argument:**
157-
158150
```bash
159151
sg run -p 'deprecated($VALUE)' -r '$VALUE' -l js -U src/
160152
```
@@ -174,7 +166,6 @@ fix: logger.info($$$ARGS)
174166
```
175167
176168
Run a single rule file:
177-
178169
```bash
179170
sg scan --rule my-rule.yaml src/
180171
sg scan --rule my-rule.yaml -U src/
@@ -183,7 +174,6 @@ sg scan --rule my-rule.yaml -U src/
183174
### Inline YAML rules
184175

185176
For quick one-offs that need rule features but not a file:
186-
187177
```bash
188178
sg scan --inline-rules '
189179
id: example
@@ -227,12 +217,12 @@ rule:
227217

228218
Available relational rules:
229219

230-
| Rule | Meaning |
231-
| ---------- | ------------------------------------------- |
232-
| `inside` | Node is a descendant of a matching ancestor |
233-
| `has` | Node has a descendant matching this |
234-
| `follows` | Node is preceded by a matching sibling |
235-
| `precedes` | Node is followed by a matching sibling |
220+
| Rule | Meaning |
221+
|---|---|
222+
| `inside` | Node is a descendant of a matching ancestor |
223+
| `has` | Node has a descendant matching this |
224+
| `follows` | Node is preceded by a matching sibling |
225+
| `precedes` | Node is followed by a matching sibling |
236226

237227
All accept `stopBy` with three valid forms: `neighbor` (only check adjacent — the default when omitted), `end` (traverse all the way to the root), or a rule object (e.g., `stopBy: { kind: function_declaration }` to stop at a specific node type).
238228

@@ -261,11 +251,11 @@ rule:
261251
pattern: logger.$_($$$)
262252
```
263253

264-
| Combinator | Meaning |
265-
| ---------- | ------------------------------ |
266-
| `all` | All sub-rules must match (AND) |
267-
| `any` | Any sub-rule must match (OR) |
268-
| `not` | Sub-rule must not match (NOT) |
254+
| Combinator | Meaning |
255+
|---|---|
256+
| `all` | All sub-rules must match (AND) |
257+
| `any` | Any sub-rule must match (OR) |
258+
| `not` | Sub-rule must not match (NOT) |
269259

270260
### Disambiguating same-named identifiers
271261

@@ -323,29 +313,26 @@ fix: $CAMEL_NAME($$$ARGS)
323313

324314
Available transforms:
325315

326-
| Transform | Purpose |
327-
| ----------- | ------------------------------------------------------------------------------------------- |
328-
| `convert` | Change case (`upperCase`, `lowerCase`, `camelCase`, `snakeCase`, `pascalCase`, `kebabCase`) |
329-
| `substring` | Extract a substring by char index |
330-
| `replace` | String find-and-replace within a metavar |
331-
| `rewrite` | Apply sub-rewriters to a metavar (for nested transformations) |
316+
| Transform | Purpose |
317+
|---|---|
318+
| `convert` | Change case (`upperCase`, `lowerCase`, `camelCase`, `snakeCase`, `pascalCase`, `kebabCase`) |
319+
| `substring` | Extract a substring by char index |
320+
| `replace` | String find-and-replace within a metavar |
321+
| `rewrite` | Apply sub-rewriters to a metavar (for nested transformations) |
332322

333323
## Debugging Non-Matching Patterns
334324

335325
When a pattern does not match what you expect:
336326

337327
1. **Inspect your pattern's AST.** Use `--debug-query=pattern` to see how ast-grep parses your pattern:
338-
339328
```bash
340329
sg run --pattern 'your_pattern($X)' --lang js --debug-query=pattern
341330
```
342331

343332
2. **Inspect the source code's AST.** Use the target code itself as the pattern to see its tree structure:
344-
345333
```bash
346334
sg run --pattern 'myFunc(arg1, arg2)' --lang js --debug-query=ast
347335
```
348-
349336
This shows you the node kinds in the source, which tells you what your real pattern needs to match against. Compare the AST of your pattern (step 1) with the AST of the source to find the mismatch.
350337

351338
3. **Common causes of non-matches:**
@@ -387,7 +374,7 @@ These steps are mandatory, not advisory. Skipping them is the single most common
387374
### After applying rewrites
388375

389376
5. **Format.** ast-grep rewrites can collapse multi-line formatting to single lines. Run the project's formatter (prettier, rustfmt, gofmt, etc.) after applying rewrites.
390-
6. **Check for stragglers.** Use `grep` for the old name across all file types — including comments, strings, docs, and test fixtures. ast-grep only matches code structure; occurrences in prose, JSDoc, string literals, and non-code files will be missed.
377+
6. **Check for stragglers.** Grep for the old name across all file types — including comments, strings, docs, and test fixtures. ast-grep only matches code structure; occurrences in prose, JSDoc, string literals, and non-code files will be missed.
391378
7. **Run the type checker.** ast-grep matches on syntax, not semantics — it cannot guarantee that every reference to a name has been caught across every syntactic context, and it cannot see scope. In typed languages, run the type checker before the test suite. It is the safety net that surfaces both kinds of miss: occurrences of the old name that the pattern did not anticipate (e.g., type annotations missed by a call-site-only rename), and scope collisions where the new name shadows an existing binding. Without a type checker, these gaps are silent and only show up at runtime.
392379
8. **Run the full build.** Run the project's build and test suite to catch anything ast-grep's structural matching could not anticipate.
393380

@@ -404,14 +391,14 @@ ast-grep handles code; prose requires separate attention. Skipping this pass lea
404391

405392
### Choosing inline vs YAML
406393

407-
| Situation | Use |
408-
| ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- |
409-
| Call-site-only rename or argument change | `sg run -p ... -r ...` |
394+
| Situation | Use |
395+
|---|---|
396+
| Call-site-only rename or argument change | `sg run -p ... -r ...` |
410397
| Renaming an identifier that may appear in type annotations, fields, or destructuring | YAML rule with `any:` over the relevant node kinds (see "Rename an identifier across all syntactic positions" above) |
411-
| Need to exclude certain matches | YAML rule with `not` or `constraints` |
412-
| Need positional context (inside a function, after an import) | YAML rule with `inside`/`follows`/`precedes` |
413-
| Need case conversion or string manipulation in the replacement | YAML rule with `transform` |
414-
| Applying multiple related transformations | Multiple `sg run` commands in sequence, or multiple YAML rules |
398+
| Need to exclude certain matches | YAML rule with `not` or `constraints` |
399+
| Need positional context (inside a function, after an import) | YAML rule with `inside`/`follows`/`precedes` |
400+
| Need case conversion or string manipulation in the replacement | YAML rule with `transform` |
401+
| Applying multiple related transformations | Multiple `sg run` commands in sequence, or multiple YAML rules |
415402

416403
### Combining with manual edits
417404

@@ -422,3 +409,7 @@ ast-grep handles the bulk structural transformation. Use manual edits for:
422409
- One-off fixups after a bulk rewrite (e.g., adjusting a special case that the pattern caught incorrectly)
423410

424411
The ideal workflow for a large refactor: ast-grep for the mechanical bulk, manual edits for the exceptions, build verification to confirm everything holds together.
412+
413+
## Acknowledgment
414+
415+
After reviewing this skill, state: "I have reviewed the ast-grep skill."

plugins/corbits-skills/skills/native-integration/SKILL.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ When a GaaS skill names a Claude/GaaS tool, use the Corbits equivalent. Do not c
3939

4040
`intent="general"` is not a Corbits spawn. Use a closed director id.
4141

42+
GaaS ast-grep invokes `sg` as a CLI. Corbits extras: run `sg` via `run_shell`. Do not fork the GaaS ast-grep body.
43+
4244
Slash names that differ from GaaS skill ids: `/review` is GaaS `code-review`; `/create-issue` is GaaS `linear-create`. Keep those Corbits names.
4345

4446
GaaS refactor says "ask clarifying questions" / "ask the user". Corbits extras: `ask_operator` (tool mapping above). Do not fork the GaaS refactor body.

tests/unit/corbits-skills-catalog.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,20 @@ test("scribe skill is 1:1 with GaaS scribe", async () => {
289289
expect(skill).not.toContain("## Acknowledgment");
290290
});
291291

292+
test("ast-grep skill is 1:1 with GaaS ast-grep", async () => {
293+
const skill = await Bun.file(join(pluginRoot, "skills/ast-grep/SKILL.md")).text();
294+
expect(skill).toContain("CLI: `sg`");
295+
expect(skill).toContain("|---|---|");
296+
expect(skill).toContain("sg run");
297+
expect(skill).toContain("sg scan --inline-rules");
298+
expect(skill).toContain("## Acknowledgment");
299+
expect(skill).toContain("I have reviewed the ast-grep skill.");
300+
expect(skill).not.toContain("run_shell");
301+
expect(skill).not.toContain("Invoke `sg` via `run_shell`");
302+
expect(skill).not.toContain(USER_INVOCABLE_FALSE);
303+
expect(skill).not.toContain(DISABLE_MODEL_INVOCATION);
304+
});
305+
292306
test("create-issue is Linear-first without restated MCP tool contracts", async () => {
293307
const skill = await Bun.file(join(pluginRoot, "skills/create-issue/SKILL.md")).text();
294308
expect(skill).toContain("mcp__linear__");
@@ -422,6 +436,8 @@ test("native-integration maps GaaS tool names and parks Corbits extras", async (
422436
expect(skill).toContain("Do not fork the GaaS pull-request-review body");
423437
expect(skill).toContain("Do not fork the GaaS refactor body");
424438
expect(skill).toContain("Do not fork the GaaS scribe body");
439+
expect(skill).toContain("Do not fork the GaaS ast-grep body");
440+
expect(skill).toContain("run `sg` via `run_shell`");
425441
expect(skill).toContain("prove");
426442
});
427443

0 commit comments

Comments
 (0)