Skip to content

Upgrade ast-grep from 0.41.0 (post-0.11.0) #162

Description

@thecodedrift

Post-0.11.0. Not to be folded into the current release — see Why not 0.11.0 below.

We pin @ast-grep/cli@0.41.0 (published 2026-02-22). Latest is 0.45.2 (2026-08-23) — 11 releases and six months behind.

Languages: 26 → 28

New: Dart and Markdown.

Markdown is the interesting one and it needs care, so it has its own section below.

Fixes worth having, three of which touch code we just shipped

release fix why it matters here
0.45.0 stop using ignore files outside rule_dirs (#2803) intersects the --no-ignore hidden + --globs work in #153
0.44.0 reject root multi-metavariable patterns (#2727) validation in exactly the $$$ area of #152 / #156
0.44.0 don't leak metavar bindings across nthChild siblings (#2677); same from a negated not (#2676) silent wrong-match class
0.45.1 match root metavariables against comments (#2868) matching semantics
0.44.0 trailing ast-grep-ignore on a multi-line statement suppresses it (#2678) suppression correctness
0.45.2 exit quietly on broken pipe (#2887) runAstGrepScan reads stdout through readline

The metavariable-binding leaks are the real prize: they are the silent-wrong-answer class, where a rule matches something it shouldn't and nobody notices.

sg is deprecated as of 0.45.0

AST_GREP_BINARY.binaryNames is ["ast-grep", "sg"], ast-grep first, so the normal path is unaffected. Measured on 0.45.2:

ast-grep run … --json=stream   → stderr 0 bytes, clean JSON on stdout
sg       run … --json=stream   → stderr 275 bytes:
    ========================================================================
    WARNING: `sg` is deprecated. Use `ast-grep` instead.
    ========================================================================

The fallback still resolves, but on a host where only sg is found we decode stderr into user-facing messages, so that banner would surface as if it were an engine error. This half is independent of the upgrade and could land separately at any time — it is roughly five lines.

Markdown support is narrower than "supports Markdown" suggests

Measured against 0.45.2 on a document containing a heading, a subheading, a list, a fenced block and a link:

kind: atx_heading        → 2 matches    ✓
kind: fenced_code_block  → 1 match      ✓
kind: list_item          → 2 matches    ✓
kind: paragraph          → 4 matches    ✓
kind: inline             → 6 matches    (opaque)
kind: link               → 0 matches    ✗

Headings discriminate correctly — # $T matches only the h1 and ## $T only the h2, via atx_h1_marker in the tree.

But everything inside a line is a single opaque inline node. The document has a link and kind: link finds nothing, because tree-sitter-markdown splits block and inline grammars and ast-grep exposes only the block tree. So "no bare URLs", "link text must not say 'click here'", "no bold inside a heading" are not sg rules, despite feeling structural.

That is not guessable from "ast-grep supports Markdown", and getting it wrong produces a rule that silently matches nothing — the same failure shape as #152, in a new place.

It also does not do counts or absence

ast-grep matches presence; a rule fires once per match, not scopes to a node rather than a document, and there is no count assertion.

rule sg?
flag every setext heading ✓ presence
flag a code fence with no language ✓ presence
every doc must have an h1 ✗ absence
exactly 5 h1s / at most one h1 ✗ count

So the routing axis is not structure-vs-words. It is closer to: sg answers "does this block-level shape occur here", Vale answers "do these words appear in prose" — and neither answers "does this document have the right number of things", which stays with the runtime tier.

Routing consequence

Adding Markdown to AST_GREP_LANGUAGES is one line, but honest guidance in route.txt is three measured claims: block structure yes, inline constructs no, counts and absence no. It also creates the first case where both static engines have a legitimate claim on .md, so the recipe should say which question each answers rather than leaving an agent to choose by file extension.

Why not 0.11.0

  • Every one of the matching fixes can change what existing rules match. That needs the same measure-everything pass Vale 3.18 just got, not a version bump.
  • 0.11.0 already carries the Vale 3.18 move with seven tier changes, the hidden-directory fix, the sg fixture-coverage gate, and the converter-support policy. Adding six months of matching-semantics changes makes any behaviour change ambiguous to attribute.
  • None of these fixes a bug we have actually hit. In particular not the $$$ separator trap from [sg] Trailing $$$ breaks sg rules #152 — that is upstream's intended behaviour, and 0.45.2 was measured as identical to 0.41.0 on all four arity cases.

Scope when it happens

  • Pin all platform packages to a specific @ast-grep/cli version, as with Vale
  • Re-run ast-grep-vendor-contract.test.ts and re-measure every assumption it pins — especially the $$$ arity cases and strictness placement
  • Verify no shipped rule or curated example changes what it matches
  • Add Dart and Markdown to AST_GREP_LANGUAGES, with the Markdown caveats above in route.txt and create-sg-rule.txt
  • Confirm #2803's ignore-file change does not alter the --no-ignore hidden / --globs behaviour from fix(cli): let ast-grep rules see inside hidden directories #153
  • Handle the sg-fallback stderr banner (or land that separately, sooner)

Refs #151

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions