You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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:
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
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:
DartandMarkdown.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
rule_dirs(#2803)--no-ignore hidden+--globswork in #153$$$area of #152 / #156nthChildsiblings (#2677); same from a negatednot(#2676)ast-grep-ignoreon a multi-line statement suppresses it (#2678)runAstGrepScanreads stdout throughreadlineThe 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.
sgis deprecated as of 0.45.0AST_GREP_BINARY.binaryNamesis["ast-grep", "sg"],ast-grepfirst, so the normal path is unaffected. Measured on 0.45.2:The fallback still resolves, but on a host where only
sgis 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:
Headings discriminate correctly —
# $Tmatches only the h1 and## $Tonly the h2, viaatx_h1_markerin the tree.But everything inside a line is a single opaque
inlinenode. The document has a link andkind: linkfinds 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,
notscopes to a node rather than a document, and there is no count assertion.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
MarkdowntoAST_GREP_LANGUAGESis one line, but honest guidance inroute.txtis 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
$$$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
@ast-grep/cliversion, as with Valeast-grep-vendor-contract.test.tsand re-measure every assumption it pins — especially the$$$arity cases andstrictnessplacementDartandMarkdowntoAST_GREP_LANGUAGES, with the Markdown caveats above inroute.txtandcreate-sg-rule.txt--no-ignore hidden/--globsbehaviour from fix(cli): let ast-grep rules see inside hidden directories #153sg-fallback stderr banner (or land that separately, sooner)Refs #151