Skip to content

Commit 1ccf86a

Browse files
committed
chore(vale): take Vale 3.19.0, re-measured
Bumps VALE_VERSION to 3.19.0 and re-derives everything that is a claim about the binary rather than about our code. Every row of VALE_FORMAT_TIERS was re-probed against the 3.19.0 binary and none moved; the generated vocabulary regenerates byte-identical apart from its version string, and the corpus differential (panic shapes included) still holds. What moved is what a table of existing rows cannot report by construction: - `.ex`/`.exs` gained comment and `@doc` extraction, so they leave the unnamed plaintext fallback for the comment tier. Measured both ways: a bare non-comment line fires on 3.18.0 and is silent on 3.19.0. This is a NARROWING, and a silent one, so `agent update` gains a ledger entry telling a reader why their Elixir findings disappeared. - `.mdx` stays `markup` and changed underneath the tier: a JSX element's children are read as the Markdown they are, and carry the element name as a `text.class.<name>` scope. That family is already an open prefix in the schema, so it validates today. Also fixes a latent bug in two recipes, which wrote "`.mdx` is supported as of Vale v%(VALE_VERSION)s" with the version interpolated. Native MDX arrived in 3.18.0, so raising the pin would have silently turned a true sentence into a false one. Both now name the release that did it.
1 parent 81977c4 commit 1ccf86a

10 files changed

Lines changed: 116 additions & 37 deletions

File tree

‎.changeset/vale-3-19-0.md‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
"@taskless/cli": patch
3+
---
4+
5+
Vale moves to 3.19.0.
6+
7+
Elixir (`.ex`, `.exs`) gained comment and `@doc` extraction, so it leaves
8+
the plaintext fallback for the comment tier. This narrows what a Vale rule
9+
matching Elixir reports: the code body is no longer prose, and findings that
10+
came from it disappear silently. `taskless agent update` explains the drop and
11+
where to take a rule that was relying on it.
12+
13+
MDX gained coverage in the other direction — a JSX element's children are read
14+
as the Markdown they are, so prose inside a wrapping component such as
15+
`<Steps>` or `<Aside>` is linted where it previously was skipped, and carries
16+
the component name as a `text.class.<name>` scope a rule can target.
17+
18+
Every other row of the format-tier table was re-probed against the 3.19.0
19+
binary and none of them moved.

‎packages/cli/src/agent/create-vale-rule.txt‎

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -532,18 +532,34 @@ it.
532532
matcher that takes `check` down the first time the repo grows a
533533
`.typ` file. Never put one of those extensions in a glob.
534534

535-
That example changed with Vale v%(VALE_VERSION)s, which is the point:
536-
the dangerous extension is whichever one the list above says needs a
537-
program, not the one you remember. `.mdx` was the example until this
535+
That example changed with Vale v3.18.0, which is the point: the
536+
dangerous extension is whichever one the list above says needs a
537+
program, not the one you remember. `.mdx` was the example until that
538538
release parsed it natively, and `.typ` took its place.
539539

540-
**`.mdx` is supported** as of Vale v%(VALE_VERSION)s, which parses it
541-
natively rather than shelling out. `[*.{md,mdx}]` is a legitimate
542-
matcher again, the example this recipe used to warn about is no
543-
longer the broken one. Check the lists above rather than reaching for
544-
that memory: `.typ` moved the opposite way in the same release, so a
540+
**`.mdx` is supported** as of Vale v3.18.0, which parses it natively
541+
rather than shelling out. `[*.{md,mdx}]` is a legitimate matcher
542+
again, the example this recipe used to warn about is no longer the
543+
broken one. Check the lists above rather than reaching for that
544+
memory: `.typ` moved the opposite way in the same release, so a
545545
matcher covering Typst is now the one that takes the run down.
546546

547+
**In `.mdx`, a component's children are prose as of v3.19.0.** Vale
548+
reads a JSX element's children as the Markdown they are, so text
549+
inside a wrapping component (`<Steps>`, `<Tabs>`, `<Aside>`) is
550+
linted at its own source position. Only tags, attributes, `{...}`
551+
expressions, self-closing elements, and an element opened and closed
552+
on one standalone line are still treated as code.
553+
554+
Two consequences for a rule you write against MDX. Coverage grew,
555+
so a rule can now fire in prose it never reached before, which is a
556+
finding count that moves without the rule changing. And the children
557+
carry the element's name as a class scope, exactly as MyST and
558+
Quarto directives do, so `scope: text.class.Aside` targets one
559+
component's content. That is an open family: `verify` accepts any
560+
`text.class.<name>` tail, because the set of component names is the
561+
author's, not Vale's.
562+
547563
5. **Write the fixtures.** Two directories inside the rule, both flat.
548564
Vale lints the whole fixture tree, so a document nested a level
549565
deeper would be linted and never checked against either bucket, which

‎packages/cli/src/agent/route.txt‎

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -178,13 +178,18 @@ answered together.
178178
unreported. A matcher written as `[*.{md,typ}]` is not a wider
179179
`[*.md]`. It is a broken one.
180180

181-
**`.mdx` is supported** as of Vale v%(VALE_VERSION)s, which
182-
parses it natively. It needs no external program and belongs
183-
with the other markup formats above. `.typ` moved the other way
184-
in the same release: Typst now parses through `typst2vast`, so a
185-
Typst file is excluded rather than read as prose the way it was
181+
**`.mdx` is supported** as of Vale v3.18.0, which parses it
182+
natively. It needs no external program and belongs with the
183+
other markup formats above. `.typ` moved the other way in the
184+
same release: Typst now parses through `typst2vast`, so a Typst
185+
file is excluded rather than read as prose the way it was
186186
before. Both are measured, not assumed.
187187

188+
As of v3.19.0 an MDX component's children are read as Markdown
189+
too, so prose wrapped in `<Steps>` or `<Aside>` is linted where
190+
it previously was not. That widens what a Vale rule covers in
191+
`.mdx`; it does not change which extensions are safe to match.
192+
188193
**A language on neither list does not route to runtime by default.**
189194
Check `create-legacy-rule` first: the repo may already run a linter
190195
that speaks it, and that linter's own dialect is a local destination

‎packages/cli/src/agent/update.txt‎

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,21 @@ that you finished.
7575

7676
### Migrating to 0.11.x
7777

78-
The vendored ast-grep moves from 0.41.0 to 0.45.2, and Vale is at
79-
3.18.0. Four things follow for existing rules.
78+
The vendored ast-grep moves from 0.41.0 to 0.45.2, and Vale from 3.18.0
79+
to 3.19.0. Five things follow for existing rules.
80+
81+
**Elixir stopped being linted as prose.** Vale 3.19.0 reads `.ex` and
82+
`.exs` through a real parser, so it now sees comments and `@doc`
83+
attributes and nothing else. On 3.18.0 those files had no parser and
84+
were linted whole, so a Vale rule matching `[*.ex]` fired on function
85+
names, atoms, and string literals as well as on comments.
86+
87+
Those findings are gone, and nothing errors or warns. A rule that
88+
reported on Elixir code bodies now reports less, which is a drop you
89+
would otherwise notice only as a suspiciously clean run. If the rule was
90+
meant for prose, this is the behaviour you always wanted. If it was
91+
being used to catch something in the code itself, that is a job for an
92+
`sg` rule, and `%(TASKLESS_CLI)s rule create` will route it there.
8093

8194
**A rewriter now requires `fix`.** `SerializableRewriter.required` goes
8295
from `["rule","id"]` to `["id","fix","rule"]`, so a `rewriters:` entry

‎packages/cli/src/generated/vale-vocabulary-report.md‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
# Vale 3.18.0 vocabulary: divergence report
1+
# Vale 3.19.0 vocabulary: divergence report
22

33
GENERATED FILE — DO NOT EDIT. Produced by `pnpm generate:vale-schema`
44
alongside `vale-vocabulary.ts`.
55

66
Every value in the vocabulary is the recorded answer of the vendored Vale
7-
3.18.0 binary. This file is what the binary said that its own
7+
3.19.0 binary. This file is what the binary said that its own
88
documentation does not, in both directions. A generator that dropped these
99
would be quietly deciding which of the two to believe.
1010

@@ -28,25 +28,25 @@ the worse failure.
2828

2929
### `scope: meta`
3030

31-
Vale 3.18.0 documents this operand and it never fired, on any fixture probed (.md).
31+
Vale 3.19.0 documents this operand and it never fired, on any fixture probed (.md).
3232

3333
**Consequence.** It is omitted from the vocabulary, so `verify` rejects it. A rule written from the documentation would otherwise load, run, and match nothing, with no error reported anywhere.
3434

3535
### `scope: meta.class.title`
3636

37-
Vale 3.18.0 documents this operand and it never fired, on any fixture probed (.md).
37+
Vale 3.19.0 documents this operand and it never fired, on any fixture probed (.md).
3838

3939
**Consequence.** It is omitted from the vocabulary, so `verify` rejects it. A rule written from the documentation would otherwise load, run, and match nothing, with no error reported anywhere.
4040

4141
### `scope: frontmatter`
4242

43-
This operand fired and Vale 3.18.0 documents it nowhere.
43+
This operand fired and Vale 3.19.0 documents it nowhere.
4444

4545
**Consequence.** It is included in the vocabulary. It is also the standing counterexample to trusting the candidate list: a real operand nobody proposes is simply absent, and the schema then rejects a rule the binary honors.
4646

4747
### `scope: frontmatter.title`
4848

49-
This operand fired and Vale 3.18.0 documents it nowhere.
49+
This operand fired and Vale 3.19.0 documents it nowhere.
5050

5151
**Consequence.** It is included in the vocabulary. It is also the standing counterexample to trusting the candidate list: a real operand nobody proposes is simply absent, and the schema then rejects a rule the binary honors.
5252

‎packages/cli/src/generated/vale-vocabulary.ts‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* `scripts/generate-vale-schema.ts`, and its header explains what each value
77
* below was measured with and what it is worth.
88
*
9-
* Derived against Vale 3.18.0. Every value here is the recorded answer
9+
* Derived against Vale 3.19.0. Every value here is the recorded answer
1010
* of that binary to a rule the generator wrote and ran; nothing is transcribed
1111
* from documentation. Where the binary and the documentation disagree, the
1212
* disagreement is in `vale-vocabulary-report.md` rather than dropped.
@@ -15,7 +15,7 @@
1515
*/
1616

1717
/** The binary this vocabulary was derived from. */
18-
export const VALE_VOCABULARY_VERSION = "3.18.0";
18+
export const VALE_VOCABULARY_VERSION = "3.19.0";
1919

2020
/**
2121
* Vale's check types, self-enumerated: an unknown `extends` makes the binary
@@ -193,7 +193,7 @@ export const VALE_SCOPE_OPERANDS = [
193193
export const VALE_SCOPE_PREFIXES = ["frontmatter.", "text.class."] as const;
194194

195195
/**
196-
* Where Vale 3.18.0 and its documentation disagree.
196+
* Where Vale 3.19.0 and its documentation disagree.
197197
*
198198
* Carried in the artifact rather than only in the report, so that a consumer
199199
* can render them and a reviewer cannot miss them in a diff.
@@ -202,26 +202,26 @@ export const VALE_DIVERGENCES = [
202202
{
203203
subject: "scope: meta",
204204
finding:
205-
"Vale 3.18.0 documents this operand and it never fired, on any fixture probed (.md).",
205+
"Vale 3.19.0 documents this operand and it never fired, on any fixture probed (.md).",
206206
consequence:
207207
"It is omitted from the vocabulary, so `verify` rejects it. A rule written from the documentation would otherwise load, run, and match nothing, with no error reported anywhere.",
208208
},
209209
{
210210
subject: "scope: meta.class.title",
211211
finding:
212-
"Vale 3.18.0 documents this operand and it never fired, on any fixture probed (.md).",
212+
"Vale 3.19.0 documents this operand and it never fired, on any fixture probed (.md).",
213213
consequence:
214214
"It is omitted from the vocabulary, so `verify` rejects it. A rule written from the documentation would otherwise load, run, and match nothing, with no error reported anywhere.",
215215
},
216216
{
217217
subject: "scope: frontmatter",
218-
finding: "This operand fired and Vale 3.18.0 documents it nowhere.",
218+
finding: "This operand fired and Vale 3.19.0 documents it nowhere.",
219219
consequence:
220220
"It is included in the vocabulary. It is also the standing counterexample to trusting the candidate list: a real operand nobody proposes is simply absent, and the schema then rejects a rule the binary honors.",
221221
},
222222
{
223223
subject: "scope: frontmatter.title",
224-
finding: "This operand fired and Vale 3.18.0 documents it nowhere.",
224+
finding: "This operand fired and Vale 3.19.0 documents it nowhere.",
225225
consequence:
226226
"It is included in the vocabulary. It is also the standing counterexample to trusting the candidate list: a real operand nobody proposes is simply absent, and the schema then rejects a rule the binary honors.",
227227
},

‎packages/cli/src/rules/capabilities.ts‎

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -196,12 +196,12 @@ export const AST_GREP_TSX_SPLIT: Readonly<
196196
/**
197197
* The Vale release carried by the `@taskless/vale-<platform>` packages pinned
198198
* in `packages/cli/package.json`. Their npm versions append a build stamp
199-
* (`3.17.1-20260810052605`); this is the version Vale itself reports.
199+
* (`3.18.0-20260824195610`); this is the version Vale itself reports.
200200
*
201201
* Pinned against the binary by `test/vale-vendor-contract.test.ts`
202202
* ("engine capabilities" → "reports the pinned version").
203203
*/
204-
export const VALE_VERSION = "3.18.0";
204+
export const VALE_VERSION = "3.19.0";
205205

206206
/**
207207
* Which tier Vale routes an extension to.
@@ -263,9 +263,31 @@ const CONVERTER_TIER_PREFIX = "converter:";
263263
* plain text today, but the moment Vale routes it to a converter the same
264264
* omission is a crash that takes down every Vale rule in the run.
265265
*
266-
* The 3.17.1 → 3.18.0 bump is what that warning looks like in practice. Every
267-
* row below was re-probed against the 3.18.0 binary, and eight moved, in three
268-
* different directions — which is why "re-measure" is not boilerplate here:
266+
* THE TABLE HELD ACROSS 3.18.0 → 3.19.0, AND THAT IS NOT THE SAME AS THE BUMP
267+
* BEING FREE. Every row below was re-probed against the 3.19.0 binary and not
268+
* one of them moved. What moved was a format Vale *learned*, which is precisely
269+
* the case a table of existing rows cannot report: `.ex` and `.exs` gained
270+
* comment and doc-attribute extraction, so they left the unnamed plaintext
271+
* fallback for the `comment` tier and are new rows below.
272+
*
273+
* Read that direction carefully, because it is a narrowing rather than a gain.
274+
* On 3.18.0 an Elixir file was linted as one block of prose, so a rule matching
275+
* `[*.ex]` fired on identifiers and string literals as readily as on comments.
276+
* On 3.19.0 the code body is invisible and only comments and `@doc` attributes
277+
* are read. Findings disappear, no error is raised, and nothing but a re-probe
278+
* would have told us. A format Vale learns is never a no-op: the benign version
279+
* of it is this one, and the dangerous version is a converter (see `.typ`).
280+
*
281+
* `.mdx` stayed `markup` and changed underneath the tier: a JSX element's
282+
* children are now read as the Markdown they are, so a Vale rule covers prose
283+
* inside `<Steps>` or `<Aside>` that it previously skipped, and those children
284+
* carry the element name as a `text.class.<name>` scope. The tier is the wrong
285+
* instrument for that kind of change — it says a parser exists, not what the
286+
* parser sees — which is the second reason a bump needs more than this table.
287+
*
288+
* The 3.17.1 → 3.18.0 bump is what the dangerous case looks like in practice.
289+
* Every row was re-probed against the 3.18.0 binary then too, and eight moved,
290+
* in three different directions — which is why "re-measure" is not boilerplate:
269291
*
270292
* - `.mdx` gained a native parser: `converter:mdx2vast` → `markup`. It is
271293
* supported now, and `[*.{md,mdx}]` is a legitimate matcher again.
@@ -315,6 +337,8 @@ export const VALE_FORMAT_TIERS: Readonly<Record<string, ValeFormatTier>> = {
315337
".css": "comment",
316338
".csx": "comment",
317339
".cxx": "comment",
340+
".ex": "comment",
341+
".exs": "comment",
318342
".go": "comment",
319343
".h": "comment",
320344
".h++": "comment",
@@ -353,7 +377,7 @@ export const VALE_FORMAT_TIERS: Readonly<Record<string, ValeFormatTier>> = {
353377
".mkdn": "plaintext",
354378
".tex": "plaintext",
355379
// plaintext HERE, though Vale's own docs list it as comment-tier. Measured on
356-
// the pinned 3.18.0 binary a bare non-comment line lints, which is the
380+
// the pinned 3.19.0 binary a bare non-comment line lints, which is the
357381
// plaintext signature. `.qml` and `.scss` sat here for the same reason until
358382
// 3.18.0 made the docs true for them; `.pyi` is the row where transcribing
359383
// the docs would still ship the wrong tier — the case for probing rather than

‎packages/cli/src/schemas/vale-rule.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,7 @@ const CHECK_FIELDS = VALE_CHECK_FIELDS;
458458
*
459459
* These are not field-table facts, which is why they sit apart from the union:
460460
* every key involved is a legal field of its check. It is the *shape* that is
461-
* fatal. Measured against Vale 3.18.0, each of the shapes below ends the
461+
* fatal. Measured against Vale 3.19.0, each of the shapes below ends the
462462
* process with a Go stack trace. The two `sequence` shapes fail while the rule
463463
* is compiled:
464464
*
@@ -511,7 +511,7 @@ function fatalShapeMessages(
511511

512512
if (rule.extends === "metric" && rule.formula !== undefined) {
513513
// Structural, like the `sequence` arm above, because "was the key
514-
// supplied" is the wrong question. Measured against 3.18.0, the panic
514+
// supplied" is the wrong question. Measured against 3.19.0, the panic
515515
// follows the *value*: an absent `condition`, a `condition:` written with
516516
// no value (YAML parses that to null, which is not `undefined`), and a
517517
// blank string all reach the same panic. Other types are not this guard's

‎packages/cli/test/reconcile-marker.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ describe("recording a rules reconciliation", () => {
8686
expect(rules?.reconciledTo).toBe(version);
8787
// Engine versions are the input a later differential needs. Recorded here
8888
// and nowhere else, so an upgrade cannot silently refresh them.
89-
expect(rules?.engines).toEqual({ sg: "0.45.2", vale: "3.18.0" });
89+
expect(rules?.engines).toEqual({ sg: "0.45.2", vale: "3.19.0" });
9090
});
9191

9292
it("reports the marker through info", async () => {

‎packages/cli/test/vale-vendor-contract.test.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,8 @@ withVale("Vale vendor contract", () => {
407407
*/
408408
function comment(extension: string): string {
409409
const HASH = new Set([
410+
".ex",
411+
".exs",
410412
".jl",
411413
".pl",
412414
".pm",

0 commit comments

Comments
 (0)