From 3edcc91cec69d1796f39c89ee83dc5bae5023945 Mon Sep 17 00:00:00 2001 From: Jakob Heuser Date: Mon, 24 Aug 2026 13:08:37 -0700 Subject: [PATCH 1/3] chore(deps): pin the Vale platform packages to 3.18.0 All six move from 3.17.1-20260810052605 to 3.18.0-20260824195610, the stamp published by release-vale.yml on the merge of the manifest PR. The six SHA256 digests in that manifest were verified against upstream's vale_3.18.0_checksums.txt before the merge, and all six packages carry the identical stamp, so no platform is out of step with another. This commit alone changes what the binary does without changing what the capability table says it does, which the table's own doc comment warns is the dangerous direction: `.typ` becomes converter-backed in 3.18.0 while the table still reads it as plaintext, so it would be handed to Vale and take the run down. The next commit re-measures every row. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Cyga14bww8rmazH2XrF8ms --- packages/cli/package.json | 12 ++++---- pnpm-lock.yaml | 60 +++++++++++++++++++-------------------- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index e14d94cd..990d2d27 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -70,11 +70,11 @@ "@ast-grep/cli-win32-arm64-msvc": "0.41.0", "@ast-grep/cli-win32-ia32-msvc": "0.41.0", "@ast-grep/cli-win32-x64-msvc": "0.41.0", - "@taskless/vale-darwin-arm64": "3.17.1-20260810052605", - "@taskless/vale-darwin-x64": "3.17.1-20260810052605", - "@taskless/vale-linux-arm64": "3.17.1-20260810052605", - "@taskless/vale-linux-x64": "3.17.1-20260810052605", - "@taskless/vale-win32-arm64": "3.17.1-20260810052605", - "@taskless/vale-win32-x64": "3.17.1-20260810052605" + "@taskless/vale-darwin-arm64": "3.18.0-20260824195610", + "@taskless/vale-darwin-x64": "3.18.0-20260824195610", + "@taskless/vale-linux-arm64": "3.18.0-20260824195610", + "@taskless/vale-linux-x64": "3.18.0-20260824195610", + "@taskless/vale-win32-arm64": "3.18.0-20260824195610", + "@taskless/vale-win32-x64": "3.18.0-20260824195610" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c0e399ae..97d57ab1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -152,23 +152,23 @@ importers: specifier: 0.41.0 version: 0.41.0 '@taskless/vale-darwin-arm64': - specifier: 3.17.1-20260810052605 - version: 3.17.1-20260810052605 + specifier: 3.18.0-20260824195610 + version: 3.18.0-20260824195610 '@taskless/vale-darwin-x64': - specifier: 3.17.1-20260810052605 - version: 3.17.1-20260810052605 + specifier: 3.18.0-20260824195610 + version: 3.18.0-20260824195610 '@taskless/vale-linux-arm64': - specifier: 3.17.1-20260810052605 - version: 3.17.1-20260810052605 + specifier: 3.18.0-20260824195610 + version: 3.18.0-20260824195610 '@taskless/vale-linux-x64': - specifier: 3.17.1-20260810052605 - version: 3.17.1-20260810052605 + specifier: 3.18.0-20260824195610 + version: 3.18.0-20260824195610 '@taskless/vale-win32-arm64': - specifier: 3.17.1-20260810052605 - version: 3.17.1-20260810052605 + specifier: 3.18.0-20260824195610 + version: 3.18.0-20260824195610 '@taskless/vale-win32-x64': - specifier: 3.17.1-20260810052605 - version: 3.17.1-20260810052605 + specifier: 3.18.0-20260824195610 + version: 3.18.0-20260824195610 packages/vale-darwin-arm64: {} @@ -831,33 +831,33 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - '@taskless/vale-darwin-arm64@3.17.1-20260810052605': - resolution: {integrity: sha512-182XIfZmbUaa8js7t11a7MB7Iw+tKsIUOXd8sDIKD7QhuEcdnrRt3dWHXRVR065tr86U5/z1tnHqenlWlx2BfA==} + '@taskless/vale-darwin-arm64@3.18.0-20260824195610': + resolution: {integrity: sha512-5CYshnFck8eFrvlDk33ObSFepFKlRJBeXZBLfDt5yLtjzHaBaOILwXlLMao4d1trbppN+ryb21btInJCteamVQ==} cpu: [arm64] os: [darwin] - '@taskless/vale-darwin-x64@3.17.1-20260810052605': - resolution: {integrity: sha512-NR3fWrTTu1Lj/nWOcKTO27pGv+wRTwbETcrxmDCxbsP5wNGPUHk/lhZIdvR4JE2PcIBg79mDOuE3CGt/G8nlnQ==} + '@taskless/vale-darwin-x64@3.18.0-20260824195610': + resolution: {integrity: sha512-WX2KWjE7/srXfIXrb2+p/KvB8wt76iQsB4r34c88GUiYidLmwjlN4sPcfCFyKgzcU9vsVwd9ycHpuACqtR51tw==} cpu: [x64] os: [darwin] - '@taskless/vale-linux-arm64@3.17.1-20260810052605': - resolution: {integrity: sha512-bxQgaTVxuxASzuLSVF/hm/blQnM8b3+2+y5swm91YbcCW8PW0/myjSCsNCazErfOwZzbdOdsSV4GJkNjmzSSsA==} + '@taskless/vale-linux-arm64@3.18.0-20260824195610': + resolution: {integrity: sha512-tAi4ys1wR+HJ6JQucExrGxg/DzVWc+Lx4+CixPLPgmIP+vw8E9DXdkY7vyIwMAMmxXUS3/cl7TNFv2J/Su3QhA==} cpu: [arm64] os: [linux] - '@taskless/vale-linux-x64@3.17.1-20260810052605': - resolution: {integrity: sha512-XByCGwrckU2vueP1XoqIaUD5sqPsgqhWAlVKHhTlTql/5wecQLT6c6/WGa8g8uN4uRYbl7MG12eJPBHWabOXiw==} + '@taskless/vale-linux-x64@3.18.0-20260824195610': + resolution: {integrity: sha512-Ae+fGfXGy6QlzuDLyh0FFkjrsbp52oxCQtRzKG6CfUIVc80O4Hh/yZIh+xi4UwTL9zJ69xS4LNDQ1MLoR/DQYQ==} cpu: [x64] os: [linux] - '@taskless/vale-win32-arm64@3.17.1-20260810052605': - resolution: {integrity: sha512-PxX0QZUTB1VR3h6T8A7/YJ8QtXCAEWnYifBrHHy0yYTPlgb7hQw69px26AAnlZy2ml7CClzt4LwF7Syq7gE+sw==} + '@taskless/vale-win32-arm64@3.18.0-20260824195610': + resolution: {integrity: sha512-FSYdG2KRuv2cI2ulJMVlRdtKI1rAd2ScqyERlF6MAnqCu/0TY95I/Po3YBpKA4Ms3l4YBk2dP1viphm68y/L1g==} cpu: [arm64] os: [win32] - '@taskless/vale-win32-x64@3.17.1-20260810052605': - resolution: {integrity: sha512-JBldnOID0Q7RM3jy9tr98ZI2DFav9UzLtRdR1bkoJ2i8X5XdkeEfKVD6Na5YNJ/C87I3z6tcZyoiIxcTKYcM9w==} + '@taskless/vale-win32-x64@3.18.0-20260824195610': + resolution: {integrity: sha512-xpIADyagcUUSL0+MXuR5557W2DzC7jYrttN6V6NqNjdYQ3/T2vxZ97B4+syMhbSgyfm9ratLLasuGfVNj+KS3w==} cpu: [x64] os: [win32] @@ -3087,22 +3087,22 @@ snapshots: '@standard-schema/spec@1.1.0': {} - '@taskless/vale-darwin-arm64@3.17.1-20260810052605': + '@taskless/vale-darwin-arm64@3.18.0-20260824195610': optional: true - '@taskless/vale-darwin-x64@3.17.1-20260810052605': + '@taskless/vale-darwin-x64@3.18.0-20260824195610': optional: true - '@taskless/vale-linux-arm64@3.17.1-20260810052605': + '@taskless/vale-linux-arm64@3.18.0-20260824195610': optional: true - '@taskless/vale-linux-x64@3.17.1-20260810052605': + '@taskless/vale-linux-x64@3.18.0-20260824195610': optional: true - '@taskless/vale-win32-arm64@3.17.1-20260810052605': + '@taskless/vale-win32-arm64@3.18.0-20260824195610': optional: true - '@taskless/vale-win32-x64@3.17.1-20260810052605': + '@taskless/vale-win32-x64@3.18.0-20260824195610': optional: true '@types/chai@5.2.3': From 9bede551fbe49784f8c93fc0c803bfc01c39cdec Mon Sep 17 00:00:00 2001 From: Jakob Heuser Date: Mon, 24 Aug 2026 13:09:03 -0700 Subject: [PATCH 2/3] fix(cli): re-measure every format row against Vale 3.18.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Seven rows moved, in both directions, and each was probed rather than read off the release notes. .mdx converter:mdx2vast -> markup now parsed natively .typ plaintext -> converter now needs typst2vast .rmd plaintext -> markup .qml plaintext -> comment .scss plaintext -> comment .qmd (new) -> markup .qdoc (new) -> comment `.typ` is the row that mattered. An extension missing from the table is read as prose, which is harmless; the moment Vale routes it to a converter, the same omission is an `E100` that takes down every Vale rule in the run. The bump alone would have shipped that. The recipes carried the inverse of both facts. They told an author MDX was unsupported, which now costs them a format Vale reads fine, and they used `[*.{md,mdx}]` as the worked example of a matcher that breaks `check` — which is now a perfectly good matcher. The cautionary example is `.typ`, and the recipe says why it changed, since the dangerous extension is whichever one the rendered list names rather than the one an agent remembers. Probing found one change the release notes do not mention: PHP comment extraction requires a real ` Claude-Session: https://claude.ai/code/session_01Cyga14bww8rmazH2XrF8ms --- .changeset/vale-3-18-0.md | 30 +++++++++++++++++++ packages/cli/src/agent/create-vale-rule.txt | 25 +++++++++------- packages/cli/src/agent/route.txt | 12 ++++---- packages/cli/src/rules/capabilities.ts | 16 +++++----- .../cli/test/recipe-cross-references.test.ts | 17 +++++++---- packages/cli/test/vale-formats.test.ts | 10 ++++++- .../cli/test/vale-vendor-contract.test.ts | 25 +++++++++++++--- 7 files changed, 103 insertions(+), 32 deletions(-) create mode 100644 .changeset/vale-3-18-0.md diff --git a/.changeset/vale-3-18-0.md b/.changeset/vale-3-18-0.md new file mode 100644 index 00000000..7b826af7 --- /dev/null +++ b/.changeset/vale-3-18-0.md @@ -0,0 +1,30 @@ +--- +"@taskless/cli": patch +--- + +Move the Vale engine to 3.18.0, and re-measure what it can read. + +Seven rows of the format table changed, in both directions. `.mdx` gains a +native parser and moves out of the unsupported tier, so a matcher like +`[*.{md,mdx}]` — the worked example the recipes previously warned against — +is legitimate again. `.typ` moved the opposite way: Typst now parses through +`typst2vast`, an external program this build does not ship, so a Typst file +is excluded from the run rather than read as prose the way 3.17.1 read it. +`.rmd` and the new `.qmd` are parsed as markup, and `.qml`, `.scss` and the +new `.qdoc` are now comment-aware where they previously fell through to +plain text. + +The `.typ` move is the one that mattered to get right. An extension missing +from the table is read as prose, which is harmless — but the moment Vale +routes it to a converter, that same omission is a crash that takes down +every Vale rule in the run. Bumping the binary without re-measuring would +have introduced exactly the failure this table exists to prevent, under an +extension nobody was watching. + +Every row was re-probed against the new binary by its own discriminating +property rather than carried over: markup by a construct only a parser +skips, comment-aware by a bare non-comment line yielding nothing, plaintext +by that same line yielding a finding, and converter-backed by the `E100` +exit and its checker tag. That caught one change the release notes do not +mention: PHP comment extraction now requires a real `> = { ".html": "markup", ".markdown": "markup", ".md": "markup", + ".mdx": "markup", ".mdown": "markup", ".org": "markup", + ".qmd": "markup", + ".rmd": "markup", ".xhtml": "markup", // comment text only — the code body is invisible ".c": "comment", @@ -212,6 +215,8 @@ export const VALE_FORMAT_TIERS: Readonly> = { ".pod": "comment", ".proto": "comment", ".ps1": "comment", + ".qdoc": "comment", + ".qml": "comment", ".py": "comment", ".py3": "comment", ".pyw": "comment", @@ -220,6 +225,7 @@ export const VALE_FORMAT_TIERS: Readonly> = { ".rb": "comment", ".rs": "comment", ".sass": "comment", + ".scss": "comment", ".sbt": "comment", ".scala": "comment", ".swift": "comment", @@ -228,23 +234,19 @@ export const VALE_FORMAT_TIERS: Readonly> = { // plaintext, and surprising about it — these look parsed and are not ".mkd": "plaintext", ".mkdn": "plaintext", - ".rmd": "plaintext", ".tex": "plaintext", - ".typ": "plaintext", // plaintext HERE, though Vale's own docs list them as comment-tier. The docs // describe the CURRENT Vale; we pin 3.17.1. Measured on the pinned binary a // bare non-comment line lints, which is the plaintext signature. Transcribing // the docs would have shipped these as comment-tier and been wrong for this // build — the case for probing rather than copying. ".pyi": "plaintext", - ".qml": "plaintext", - ".scss": "plaintext", // converter-dependent — Vale supports the format, we ship no converter ".adoc": "converter:asciidoctor", ".asc": "converter:asciidoctor", ".asciidoc": "converter:asciidoctor", ".dita": "converter:dita", - ".mdx": "converter:mdx2vast", + ".typ": "converter:typst2vast", ".rest": "converter:rst2html", ".rst": "converter:rst2html", ".xml": "converter:xsltproc and an XSLT stylesheet", @@ -369,7 +371,7 @@ export const VALE_CONVERTER_CHECKERS: Readonly> = { ".asc": "lintAdoc", ".xml": "lintXML", ".dita": "lintDITA", - ".mdx": "lintMDX", + ".typ": "lintTypst", }; /** Every converter-dependent extension, flattened. */ diff --git a/packages/cli/test/recipe-cross-references.test.ts b/packages/cli/test/recipe-cross-references.test.ts index 4319f727..3f2438f5 100644 --- a/packages/cli/test/recipe-cross-references.test.ts +++ b/packages/cli/test/recipe-cross-references.test.ts @@ -380,10 +380,12 @@ describe("recipes state engine reach from the pinned versions", () => { // The consequence, not just the list. A recipe that names `.mdx` without // saying it takes the whole pass down has not conveyed the hazard. expect(route).toContain("E100"); - // MDX is a "not yet", not a "never" — Vale 3.18.0 parses it natively. An - // agent told only that it is unreadable would tell a user MDX is - // unsupported, full stop. - expect(route).toContain("MDX is not supported yet"); + // The pair that swapped in 3.18.0, asserted by name because getting either + // backwards is a user-visible error in opposite directions: telling an + // author `.mdx` is unsupported costs them a format Vale reads fine, and + // telling them `.typ` is readable takes their whole Vale pass down. + expect(route).toContain("`.mdx` is supported"); + expect(route).toContain("`.typ`"); }); it("names ast-grep's languages in rendered create-sg-rule.txt", async () => { @@ -414,7 +416,12 @@ describe("recipes state engine reach from the pinned versions", () => { // this pins the warning rather than the absence of the string. expect(recipe).toContain("Never put one of those extensions in a glob."); expect(recipe).toContain(valePlaintextList()); - expect(recipe).toContain("MDX is not supported yet"); + expect(recipe).toContain("`.mdx` is supported"); + // The cautionary glob must name a currently-dangerous extension. It said + // `[*.{md,mdx}]` until 3.18.0 made that matcher legitimate, which is + // exactly the kind of staleness a version bump introduces silently. + expect(recipe).toContain("[*.{md,typ}]"); + expect(recipe).not.toContain("[*.{md,mdx}] is not"); // No date. The bump is expected, not scheduled, and a recipe that implies // otherwise is stale the moment it slips. expect(recipe).not.toMatch(/\b20\d\d-\d\d\b/); diff --git a/packages/cli/test/vale-formats.test.ts b/packages/cli/test/vale-formats.test.ts index 355407b4..27c2450c 100644 --- a/packages/cli/test/vale-formats.test.ts +++ b/packages/cli/test/vale-formats.test.ts @@ -126,9 +126,17 @@ describe("the format tier table", () => { // tier. Being wrong about the tier is survivable; being wrong about needing // a converter is not, because it excludes a file Vale would have linted // perfectly well. This is that half of the claim. - for (const extension of [".tex", ".rmd", ".mkd", ".mkdn", ".typ"]) { + for (const extension of [".tex", ".mkd", ".mkdn", ".pyi"]) { expect(converterFor(`doc${extension}`)).toBeUndefined(); } + // The other half, and the reason this list is re-derived rather than + // remembered: `.typ` was plaintext until Vale 3.18.0 gave Typst a parser + // that shells out, so the same extension that must NOT be excluded on one + // version must be excluded on the next. + expect(converterFor("doc.typ")).toBe("typst2vast"); + // `.mdx` went the other way in the same release — native now, so excluding + // it would drop a file Vale reads perfectly well. + expect(converterFor("doc.mdx")).toBeUndefined(); }); }); diff --git a/packages/cli/test/vale-vendor-contract.test.ts b/packages/cli/test/vale-vendor-contract.test.ts index 9079c2a8..503fefb2 100644 --- a/packages/cli/test/vale-vendor-contract.test.ts +++ b/packages/cli/test/vale-vendor-contract.test.ts @@ -417,6 +417,11 @@ function comment(extension: string): string { ]); if (HASH.has(extension)) return "# simply\n"; if (extension === ".css") return "/* simply */\n"; + // Vale 3.18.0 parses PHP with tree-sitter, so a comment only counts inside + // real PHP — on 3.17.1 a bare `// simply` was linted without the open tag. + if (extension === ".php") return " { skipped: "Fine.\n\n```\nsimply\n```\n", }, ".org": { prose: "We simply do it.\n", skipped: "# simply\nFine.\n" }, + // Native as of 3.18.0. `.mdx` arrived from the converter tier and `.rmd` + // and `.qmd` from plaintext, so all three are new claims, not carried over. + ".mdx": { + prose: "We simply do it.\n", + skipped: "Fine.\n\n```\nsimply\n```\n", + }, + ".qmd": { + prose: "We simply do it.\n", + skipped: "Fine.\n\n```{r}\nsimply <- 1\n```\n", + }, + ".rmd": { + prose: "We simply do it.\n", + skipped: "Fine.\n\n```{r}\nsimply <- 1\n```\n", + }, ".htm": { prose: "

We simply do it.

\n", skipped: "\n", @@ -551,14 +570,12 @@ withVale("Vale engine capabilities", () => { const PLAINTEXT_FIXTURES: Record = { ".mkd": "Fine.\n\n```\nsimply\n```\n", ".mkdn": "Fine.\n\n```\nsimply\n```\n", - ".rmd": "Fine.\n\n```{r}\nsimply <- 1\n```\n", ".tex": "% simply in a comment\nFine.\n", - ".typ": "// simply\nFine.\n", // Documented by Vale as comment-aware, measured as plaintext on the pinned // binary — the construct here is the comment a parser would have skipped. + // `.qml` and `.scss` were here until 3.18.0 gave them real parsers, which + // is why the tier is re-measured on every bump rather than carried over. ".pyi": "# simply\nFine.\n", - ".qml": "// simply\nFine.\n", - ".scss": "// simply\nFine.\n", }; it("covers every extension in VALE_PLAINTEXT_EXTENSIONS", () => { From dc2b88c558b3ba0859cc10dada6bd2bdd9198160 Mon Sep 17 00:00:00 2001 From: Jakob Heuser Date: Mon, 24 Aug 2026 13:32:03 -0700 Subject: [PATCH 3/3] docs(cli): retire the 3.18.0 forecast, and measure the MyST row it predicted MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The format table's own header insists on "MEASURED, NOT DOCUMENTED", and the bump left its prose describing a version we no longer pin: the block above VALE_FORMAT_TIERS still framed 3.18.0 as "the known incoming bump", the plaintext bullet still cited `.rmd`, the `.pyi` note still said "we pin 3.17.1", and VALE_CONVERTER_DEPENDENT still used `[*.{md,mdx}]` as the glob that takes down the whole pass — the one place the `.typ` swap was missed. The same `.rmd` staleness sat in two contract-test doc comments. The forecast also predicted a MyST row and none was added. Probed on the pinned binary, `.myst` skips a fenced code block (zero findings) where a bare line lints, so it is markup; it is now a row with its own fixture. The 3.18.0 changeset is folded into `.changeset/vale-converter-formats.md` rather than shipping beside it, per "a later PR extends the existing file" — and folding it caught two sentences there that the bump falsified. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Cyga14bww8rmazH2XrF8ms --- .changeset/vale-3-18-0.md | 30 --------- .changeset/vale-converter-formats.md | 35 +++++++--- packages/cli/src/rules/capabilities.ts | 65 ++++++++++++------- .../cli/test/recipe-cross-references.test.ts | 6 +- .../cli/test/vale-vendor-contract.test.ts | 31 +++++---- 5 files changed, 89 insertions(+), 78 deletions(-) delete mode 100644 .changeset/vale-3-18-0.md diff --git a/.changeset/vale-3-18-0.md b/.changeset/vale-3-18-0.md deleted file mode 100644 index 7b826af7..00000000 --- a/.changeset/vale-3-18-0.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -"@taskless/cli": patch ---- - -Move the Vale engine to 3.18.0, and re-measure what it can read. - -Seven rows of the format table changed, in both directions. `.mdx` gains a -native parser and moves out of the unsupported tier, so a matcher like -`[*.{md,mdx}]` — the worked example the recipes previously warned against — -is legitimate again. `.typ` moved the opposite way: Typst now parses through -`typst2vast`, an external program this build does not ship, so a Typst file -is excluded from the run rather than read as prose the way 3.17.1 read it. -`.rmd` and the new `.qmd` are parsed as markup, and `.qml`, `.scss` and the -new `.qdoc` are now comment-aware where they previously fell through to -plain text. - -The `.typ` move is the one that mattered to get right. An extension missing -from the table is read as prose, which is harmless — but the moment Vale -routes it to a converter, that same omission is a crash that takes down -every Vale rule in the run. Bumping the binary without re-measuring would -have introduced exactly the failure this table exists to prevent, under an -extension nobody was watching. - -Every row was re-probed against the new binary by its own discriminating -property rather than carried over: markup by a construct only a parser -skips, comment-aware by a bare non-comment line yielding nothing, plaintext -by that same line yielding a finding, and converter-backed by the `E100` -exit and its checker tag. That caught one change the release notes do not -mention: PHP comment extraction now requires a real `> = { // markup — parsed, the format's own constructs skipped @@ -183,6 +194,7 @@ export const VALE_FORMAT_TIERS: Readonly> = { ".md": "markup", ".mdx": "markup", ".mdown": "markup", + ".myst": "markup", ".org": "markup", ".qmd": "markup", ".rmd": "markup", @@ -235,11 +247,12 @@ export const VALE_FORMAT_TIERS: Readonly> = { ".mkd": "plaintext", ".mkdn": "plaintext", ".tex": "plaintext", - // plaintext HERE, though Vale's own docs list them as comment-tier. The docs - // describe the CURRENT Vale; we pin 3.17.1. Measured on the pinned binary a - // bare non-comment line lints, which is the plaintext signature. Transcribing - // the docs would have shipped these as comment-tier and been wrong for this - // build — the case for probing rather than copying. + // plaintext HERE, though Vale's own docs list it as comment-tier. Measured on + // the pinned 3.18.0 binary a bare non-comment line lints, which is the + // plaintext signature. `.qml` and `.scss` sat here for the same reason until + // 3.18.0 made the docs true for them; `.pyi` is the row where transcribing + // the docs would still ship the wrong tier — the case for probing rather than + // copying. ".pyi": "plaintext", // converter-dependent — Vale supports the format, we ship no converter ".adoc": "converter:asciidoctor", @@ -339,10 +352,12 @@ function groupByConverter(): ValeConverterFormat[] { * * The blast radius is what makes this worth surfacing at routing time rather * than at authoring time: Vale exits 2 with an `E100` runtime error and - * abandons the run, and `--no-exit` does not suppress it. One `.mdx` file + * abandons the run, and `--no-exit` does not suppress it. One `.typ` file * caught by a rule's glob takes down the entire Vale pass, including every - * other rule and every other file — so `[*.{md,mdx}]` is not a slightly wider - * matcher than `[*.md]`, it is a broken one. + * other rule and every other file — so `[*.{md,typ}]` is not a slightly wider + * matcher than `[*.md]`, it is a broken one. (`[*.{md,mdx}]` was that example + * until 3.18.0 gave MDX a native parser — the membership of this tier is a + * property of {@link VALE_VERSION}, and so is the worked example.) */ export const VALE_CONVERTER_DEPENDENT: readonly ValeConverterFormat[] = groupByConverter(); diff --git a/packages/cli/test/recipe-cross-references.test.ts b/packages/cli/test/recipe-cross-references.test.ts index 3f2438f5..966103c8 100644 --- a/packages/cli/test/recipe-cross-references.test.ts +++ b/packages/cli/test/recipe-cross-references.test.ts @@ -385,7 +385,11 @@ describe("recipes state engine reach from the pinned versions", () => { // author `.mdx` is unsupported costs them a format Vale reads fine, and // telling them `.typ` is readable takes their whole Vale pass down. expect(route).toContain("`.mdx` is supported"); - expect(route).toContain("`.typ`"); + // `.typ` alone would pass on the converter list rendered above, which names + // it whatever the prose says. Pin the hazard sentence instead, so dropping + // the warning fails here even while the extension is still mentioned. + expect(route).toContain("`.typ` moved the other way"); + expect(route).toContain("`[*.{md,typ}]`"); }); it("names ast-grep's languages in rendered create-sg-rule.txt", async () => { diff --git a/packages/cli/test/vale-vendor-contract.test.ts b/packages/cli/test/vale-vendor-contract.test.ts index 503fefb2..649e6866 100644 --- a/packages/cli/test/vale-vendor-contract.test.ts +++ b/packages/cli/test/vale-vendor-contract.test.ts @@ -388,8 +388,10 @@ withVale("Vale vendor contract", () => { * - **plaintext** is the tier that needs no separating — a bare line fires — * but the entries listed in it do: each names a construct a parser WOULD have * skipped, and the probe asserts Vale lints it. That is the assertion that - * `.tex` and `.rmd` are not markup, and it is the one the first hand-written - * table got backwards. + * `.tex`, `.mkd` and `.mkdn` are not markup, and it is the one the first + * hand-written table got backwards. It is also the assertion that expires: on + * 3.17.1 `.rmd` belonged here, and 3.18.0 gave it a real parser and moved it + * into MARKUP_FIXTURES. * - **converter-dependent** is separated from everything by failing. * * See taskless/cli#151. @@ -467,12 +469,17 @@ withVale("Vale engine capabilities", () => { skipped: "Fine.\n\n```\nsimply\n```\n", }, ".org": { prose: "We simply do it.\n", skipped: "# simply\nFine.\n" }, - // Native as of 3.18.0. `.mdx` arrived from the converter tier and `.rmd` - // and `.qmd` from plaintext, so all three are new claims, not carried over. + // Native as of 3.18.0. `.mdx` arrived from the converter tier, `.rmd` from + // plaintext, and `.qmd` and `.myst` are new rows — four new claims, none of + // them carried over from the 3.17.1 table. ".mdx": { prose: "We simply do it.\n", skipped: "Fine.\n\n```\nsimply\n```\n", }, + ".myst": { + prose: "We simply do it.\n", + skipped: "Fine.\n\n```\nsimply\n```\n", + }, ".qmd": { prose: "We simply do it.\n", skipped: "Fine.\n\n```{r}\nsimply <- 1\n```\n", @@ -498,11 +505,10 @@ withVale("Vale engine capabilities", () => { it("reports the pinned version", () => { // VALE_VERSION is rendered beside the reach lists in route.txt and // create-vale-rule.txt, so it is the attribution for every claim below. - // It also gates two of them, in opposite directions: Vale 3.18.0 parses MDX - // natively, so a bump past it moves `.mdx` from converter-dependent to - // markup — and the same release adds a Typst converter, so it moves `.typ` - // from plaintext to `converter:typst2vast`. A bump re-measures the whole - // table; these two are only the rows already known to move. + // 3.18.0 moved rows in both directions: `.mdx` gained a native parser and + // left the converter tier, while `.typ` gained a `typst2vast` converter and + // entered it. A bump re-measures the whole table — the fixtures below are + // the record of the last time that was done, not a forecast of the next. const result = spawnSync(binary as string, ["--version"], { encoding: "utf8", }); @@ -564,7 +570,7 @@ withVale("Vale engine capabilities", () => { * its spelling suggests would have skipped. * * Vale lints it, which is the whole finding: `.tex` is not TeX to Vale and - * `.rmd` is not R Markdown. The mirror image of the markup fixtures — same + * `.mkd` is not Markdown. The mirror image of the markup fixtures — same * documents, opposite expectation. */ const PLAINTEXT_FIXTURES: Record = { @@ -573,8 +579,9 @@ withVale("Vale engine capabilities", () => { ".tex": "% simply in a comment\nFine.\n", // Documented by Vale as comment-aware, measured as plaintext on the pinned // binary — the construct here is the comment a parser would have skipped. - // `.qml` and `.scss` were here until 3.18.0 gave them real parsers, which - // is why the tier is re-measured on every bump rather than carried over. + // `.qml` and `.scss` were here until 3.18.0 gave them real parsers, and + // `.rmd` left for MARKUP_FIXTURES in the same bump, which is why the tier is + // re-measured on every bump rather than carried over. ".pyi": "# simply\nFine.\n", };