ast-grep

AST-aware structural code search and rewrite via ast-grep.

  • ast
  • codemod
  • refactoring
  • structural-search
  • code-search
  • rewrite
  • tree-sitter

Declared platforms: linux · macos · windows

Install
npx skills add 'https://github.com/NousResearch/hermes-agent/tree/main/optional-skills/software-development/ast-grep'
Download bundle ↓
main · 24fd22bScanned 2026-09-15

Contributors

GitHub-linked commit authors for this SKILL.md at the saved revision. Co-authors and history before file renames are not included.

File history ↗
View on GitHub
← Back to SKILL.md
#!/usr/bin/env bash# Smoke test for the ast-grep skill on POSIX (macOS / Linux / WSL / Git Bash).## Tests:#   1. helper --version#   2. helper langs                              (lists 25 languages)#   3. helper validate '\w+' --lang ts           (must exit 2 with hint)#   4. helper validate 'console.log($MSG)'       (must exit 0 plausible)#   5. helper validate 'def $F($$$):' --lang py  (must exit 2 - trailing colon)#   6. helper validate 'function $N' --lang ts   (must exit 2 - incomplete)#   7. helper validate 'foo|bar' --lang ts       (must exit 2 - alternation)#   8. helper doctor                             (informational; tolerates no-binary)#   9. helper search w/o binary => exit 3 with install hint#  10. install.sh --help                         (parses)#  11. SKILL.md frontmatter shape                (canonical)## Runs against the helper using ONLY stdlib python3 - no ast-grep needed. set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"SKILL_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"HELPER="python3 $SKILL_DIR/scripts/ast_grep_helper.py" OUTPUT_DIR="$(mktemp -d -t ast-grep-skill-smoke-XXXXXX)"trap 'rm -rf "$OUTPUT_DIR"' EXIT fail() { echo "FAIL: $*" >&2; exit 1; }pass() { echo "PASS: $*"; } omo_runtime_slug() {  case "$(uname -s)" in    Darwin) local os_slug="darwin" ;;    MINGW*|MSYS*|CYGWIN*) local os_slug="win32" ;;    *) local os_slug="linux" ;;  esac   case "$(uname -m | tr '[:upper:]' '[:lower:]')" in    arm64|aarch64) local arch_slug="arm64" ;;    *) local arch_slug="x64" ;;  esac   printf '%s-%s' "$os_slug" "$arch_slug"} fake_sg() {  local target="$1"  mkdir -p "$(dirname "$target")"  cat > "$target" <<'SH'#!/usr/bin/env bashif [ "${1:-}" = "--version" ]; then  printf 'ast-grep 0.45.0 fake\n'else  printf 'fake ast-grep\n'fiSH  chmod +x "$target"} # 1. --version$HELPER --version | grep -q "ast-grep-helper" || fail "--version output missing"pass "--version" # 2. langs (must list 25)LANG_LINES=$($HELPER langs | grep -E '^  [a-z]' | wc -l | tr -d ' ')[ "$LANG_LINES" -ge 25 ] || fail "langs listed only $LANG_LINES (expected >=25)"pass "langs lists at least 25 languages" # 3. regex misuse: \w+set +e$HELPER validate '\w+' --lang ts > "$OUTPUT_DIR/v1.out" 2>&1RC=$?set -e[ $RC -eq 2 ] || fail "validate '\\w+' should exit 2, got $RC (output: $(cat $OUTPUT_DIR/v1.out))"grep -qi 'regex' "$OUTPUT_DIR/v1.out" || fail "validate '\\w+' should mention regex (output: $(cat $OUTPUT_DIR/v1.out))"pass "validate detects \\w regex misuse" # 4. valid patternset +e$HELPER validate 'console.log($MSG)' --lang ts > "$OUTPUT_DIR/v2.out" 2>&1RC=$?set -e[ $RC -eq 0 ] || fail "validate 'console.log(\$MSG)' should exit 0, got $RC (output: $(cat $OUTPUT_DIR/v2.out))"pass "validate accepts plausible pattern" # 5. Python trailing colonset +e$HELPER validate 'def $F($$$):' --lang py > "$OUTPUT_DIR/v3.out" 2>&1RC=$?set -e[ $RC -eq 2 ] || fail "validate 'def \$F(\$\$\$):' should exit 2, got $RC"grep -qi 'colon\|trailing' "$OUTPUT_DIR/v3.out" || fail "validate should mention trailing colon"pass "validate detects Python trailing colon" # 6. Incomplete TS functionset +e$HELPER validate 'function $N' --lang ts > "$OUTPUT_DIR/v4.out" 2>&1RC=$?set -e[ $RC -eq 2 ] || fail "validate 'function \$N' should exit 2, got $RC"grep -qi 'incomplete\|params\|body' "$OUTPUT_DIR/v4.out" || fail "validate should hint about params/body"pass "validate detects incomplete TS function" # 7. Alternation pipeset +e$HELPER validate 'foo|bar' --lang ts > "$OUTPUT_DIR/v5.out" 2>&1RC=$?set -e[ $RC -eq 2 ] || fail "validate 'foo|bar' should exit 2, got $RC"grep -qi 'alternation\|regex' "$OUTPUT_DIR/v5.out" || fail "validate should mention alternation"pass "validate detects literal | alternation" # 8. doctor (informational)$HELPER doctor > "$OUTPUT_DIR/doc.out" 2>&1 || truegrep -q "ast-grep-helper" "$OUTPUT_DIR/doc.out" || fail "doctor missing helper version line"pass "doctor produces output" # Given OMO_AST_GREP_SG_PATH points at a fake executable.# When doctor resolves ast-grep, then it reports that exact path.FAKE_ENV_SG="$OUTPUT_DIR/fake-env/sg"fake_sg "$FAKE_ENV_SG"OMO_AST_GREP_SG_PATH="$FAKE_ENV_SG" $HELPER doctor > "$OUTPUT_DIR/omo-env.out" 2>&1grep -Fq "ast-grep binary: $FAKE_ENV_SG" "$OUTPUT_DIR/omo-env.out" || fail "OMO_AST_GREP_SG_PATH was not preferred: $(cat "$OUTPUT_DIR/omo-env.out")"pass "OMO_AST_GREP_SG_PATH resolves first" # Given HOME has an OMO runtime sg executable.# When doctor resolves ast-grep, then it reports the HOME runtime path.RUNTIME_HOME="$OUTPUT_DIR/home"RUNTIME_SLUG="$(omo_runtime_slug)"RUNTIME_BIN="sg"case "$RUNTIME_SLUG" in  win32-*) RUNTIME_BIN="sg.exe" ;;esacFAKE_RUNTIME_SG="$RUNTIME_HOME/.omo/runtime/ast-grep/$RUNTIME_SLUG/$RUNTIME_BIN"fake_sg "$FAKE_RUNTIME_SG"HOME="$RUNTIME_HOME" CODEX_HOME= OMO_AST_GREP_SG_PATH= $HELPER doctor > "$OUTPUT_DIR/omo-runtime.out" 2>&1grep -Fq "ast-grep binary: $FAKE_RUNTIME_SG" "$OUTPUT_DIR/omo-runtime.out" || fail "OMO HOME runtime was not resolved: $(cat "$OUTPUT_DIR/omo-runtime.out")"pass "OMO HOME runtime resolves before standalone fallback" # 9. search w/o binary => either runs successfully (binary found) OR exits 3 with hint# The CI may or may not have ast-grep installed; both are valid.set +e$HELPER -q search 'foo()' --lang ts /nonexistent-path-xyzzy > "$OUTPUT_DIR/s1.out" 2>&1RC=$?set -ecase "$RC" in  0|1|4)    # Binary present but no matches OR sg returned non-fatal error - both fine    pass "search runs (rc=$RC, ast-grep available)"    ;;  3)    grep -qi 'install' "$OUTPUT_DIR/s1.out" || fail "search rc=3 should print install hint"    pass "search without binary prints install hint"    ;;  *)    fail "search returned unexpected rc=$RC: $(cat $OUTPUT_DIR/s1.out)"    ;;esac # 10. install.sh syntax check + --helpbash -n "$SKILL_DIR/install.sh" || fail "install.sh has syntax errors"$SKILL_DIR/install.sh --help > "$OUTPUT_DIR/inst.out" 2>&1 || truegrep -qi 'install' "$OUTPUT_DIR/inst.out" || fail "install.sh --help missing keyword 'install'"pass "install.sh syntax + --help" # 11. SKILL.md frontmatterpython3 - <<PYimport re, syssrc = open("$SKILL_DIR/SKILL.md").read()assert src.startswith("---\n"), "SKILL.md must start with YAML frontmatter"end = src.find("\n---\n", 4)assert end > 0, "SKILL.md missing closing ---"fm = src[4:end]assert re.search(r"^name:\s*ast-grep\s*$", fm, re.M), "frontmatter missing name: ast-grep"assert re.search(r"^description:", fm, re.M), "frontmatter missing description"PYpass "SKILL.md frontmatter shape" # 12. All required reference files existfor f in references/install.md references/patterns.md references/pitfalls.md \         references/recipes.md references/cli.md references/yaml-rules.md \         references/sgconfig.md; do  test -f "$SKILL_DIR/$f" || fail "missing $f"donepass "all references present" python3 - "$SKILL_DIR" <<'PY' || fail "Korean characters found in skill content"import pathlib, re, sysroot = pathlib.Path(sys.argv[1])hangul = re.compile(r"[\uac00-\ud7a3]")targets = [root / "SKILL.md", root / "README.md"]for d in ("references", "scripts", "tests", ".github"):    targets.extend((root / d).rglob("*"))targets.extend([root / "install.sh", root / "install.ps1"])hits = 0for p in targets:    if not p.is_file():        continue    try:        text = p.read_text(encoding="utf-8")    except (UnicodeDecodeError, OSError):        continue    for n, line in enumerate(text.splitlines(), 1):        if hangul.search(line):            print(f"{p}:{n}: {line.rstrip()}")            hits += 1sys.exit(1 if hits else 0)PYpass "no Korean in skill content" echo ""echo "all smoke tests passed"