Skip to content

chore(vale): upgrade to Vale 3.23.0 - #427

Merged
theCodeDrift merged 3 commits into
mainfrom
vendor/vale/upgrade
Sep 30, 2026
Merged

theCodeDrift merged 3 commits into
mainfrom
vendor/vale/upgrade

Conversation

@github-actions

@github-actions github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Stack (root → tip):

The @taskless/vale-* packages are published ahead of what
@taskless/cli pins.

This moves all six pins from 3.22.0-20260921180930 to
3.23.0-20260930045834 — Vale 3.23.0 — and regenerates
pnpm-lock.yaml. The pins move together on purpose: the platform
packages are selected by optional dependency, so a straggler left at
the old version is a different Vale on one platform than on the others.

It also moves VALE_VERSION in capabilities.ts to
3.23.0 and regenerates src/generated/vale-vocabulary.ts
from the new binary, and the workflow ran
engine-version-consistency against the result before pushing. What
this does not carry is judgment: if vale-schema-contract or
vale-vendor-contract is red on this pull request, Vale
3.23.0 changed a measured behaviour, and that edit, the
update ledger, any migration, and the release note's prose belong
in a child pull request.

This is the half that reaches a user. Republishing the platform
packages changes nobody's install, because the CLI pins each one
exactly; merging this is what ships the new Vale.

This pull request rolls: if another set is published before it merges,
the branch, title, and body are rewritten to the newer version rather
than a second pull request being opened. Push a commit to the branch
and that stops — the workflow will not force-push over a commit it did
not write.


Upstream release notes — 3.23.0

https://github.com/vale-cli/vale/releases/tag/v3.23.0

Vale 3.23.0 reads a Sphinx project as Sphinx does, reads the markup inside source-code comments, and reads large files in linear time.

Sphinx

Docutils knows only its own directives and roles, and dropped the body of every one it did not know, without a word: the prose in every versionadded, seealso, tab, or grid was never linted. Vale now decides the rest by a rule that fits nearly every Sphinx construct: the body of a directive Docutils doesn't define is prose, and the text of a role it doesn't define is code. A :ref: written with a title is linted as its title, and :func:, :class:, and :doc: are left alone. An extension's additions that don't fit the rule go in one section, which the reStructuredText and MyST readers both honor:

[sphinx]
CodeDirectives = mermaid, plantuml
ProseRoles = kbd, samp

Nothing needs Sphinx installed. reStructuredText also reads and writes UTF-8 whatever the console's code page, and the Docutils pool now works on Windows.

Docs: Sphinx

The markup inside comments

A tree-sitter View's per-scope type is honored, so a Rust /// comment is read as Markdown, a Python docstring as reStructuredText, and a Javadoc block as HTML, each with its fenced blocks and inline code skipped:

engine: tree-sitter
scopes:
  - expr: (line_comment)+ @comment
    type: md

Each language's documentation convention is understood on top of that: the name a Go doc comment opens with and its [links], rustdoc and KDoc links, Javadoc and JSDoc tags and @example blocks, and a docstring's :param: fields are not prose. A comment addressed to a tool, //go:build, //nolint, # noqa, eslint-disable, is not read at all. Kotlin and TOML have grammars.

Docs: Code Views, Documentation conventions

Comments in data files

A YAML or TOML file's comments are linted beside the fields its View selects, under the scopes a source file's comments carry, and a # inside a string is not a comment.

Docs: Data Views

Named scopes and metric variables

A scope can be written once in config/scopes/ and used by name in any rule, and doc(...) accepts Selectors Level 4, relative selectors in :has() included:

# styles/config/scopes/Methods.yml
scope: 'doc(section:has(> h2:contains("Methods")))'

A metric formula has the readability scores as variables, quote_words, sentence_length_sd, and round(). A quote scope reaches the text between quotation marks. A rule can carry its own tests:, and vale test --coverage lists the rules without any.

Docs: Named scopes, Selections, metric variables

The package version this Vale supports

vale sync installs the release of a package that the running Vale supports, from a releases manifest in its meta.json or from the package's release feed, so a style that uses a newer key never breaks an older Vale. Built-in rules honor [param] settings, and YES and NO read as booleans.

Docs: The version Vale supports

Formats

  • HTML: a page's <meta name="description"> is prose, <samp> is code, and a figure's caption and alt text are no longer skipped with the figure.
  • Org: #+TITLE: and #+AUTHOR: are prose, headline tags are not, and a footnote is reported where it is written.
  • QDoc: an \omit ... \endomit on one line ends there.
  • Plain text: a manuscript's chapters are headings, and metric rules see the file's summary.
  • MDX: a code fence after a one-line element stays out of the HTML block.

Changelog

  • 2753160f spelling: Read faster on large files, and read figures and inline QDoc omissions
  • 75c04b37 feat: Read a data file's comments beside its View, and a page's description
  • 0ea6b7a1 fix: Honor a code View's scope type, mask doc-comment conventions, drop directives, and read Org keywords and footnotes in place
  • 521deac8 fix: scalar settings reach the built-in rules, and YES and NO read as bools
  • f8a01426 fix(rst): run the spawned rst2html in Python's UTF-8 mode
  • f43fcfc5 docs: add discord
  • 062bf1dc fix(rst): read and write UTF-8 whatever the console's code page
  • 9a192397 feat: sync installs the release this Vale supports
  • 27fa279c fix: give a plain-text file a summary, so metric rules and ls-metrics see it
  • 62d4da8e fix: pool Docutils on Windows, where rst2html is a launcher with no shebang
  • b66f8276 fix(mdx): keep a code fence after a one-line element out of an HTML block (#1194)
  • 559bfe17 feat: one [sphinx] section for reStructuredText and MyST
  • e435dab9 fix: place a script rule's matches by the block they index, not the file
  • 33087fea feat: read a plain-text manuscript's chapters, and measure speech and pace
  • 6ba35ce8 feat: read the body of a directive Docutils does not know as prose
  • 459d3cda feat: Selectors Level 4 in doc(...)
  • 0cc85df7 feat: named scopes, readability variables, and test coverage
  • cea3fcdd fix: read a YAML file's comments through a tree-sitter View (#1188)
  • 706f8582 Merge branch 'pr-1192' into v3
  • 9bcf8d08 feat(code): add Kotlin comment extraction (#1183)
  • 4bd1bc25 fix: place a paragraph that opens with inline content at its own text (#1186)
  • b970c752 fix: place a match that wraps into an indented or quoted line (#1185)
  • b9f2ee29 fix: locate a match past a multi-byte mask (#1184) (#1193)
  • 123e3f98 chore(deps): bump github.com/tomwright/dasel/v3 from 3.10.1 to 3.11.2 (#1190)
  • 64d2aa2c ci: allow benchstat to select its required Go toolchain
  • 70f3c136 fix: place a summary-scoped alert on the prose it counted
  • 275cd4c7 fix: metric positions in sentence scope
  • cac1949c feat: allow metrics to report scoped locations
  • 90eaca41 fix: put a fragment file back before the raw scope runs (#1182)
  • ba6a2c6a feat: add a quote scope for the text between quotation marks
  • 520962ef fix: gather a leaf element's own text for a doc(...) selection on it
  • 0904d198 feat: accept a selector group at the top level of doc(...)
  • 86032fc9 fix: find a package directory whose case differs from the URL basename

Contains #428

#427 moves the six @taskless/vale-* pins to Vale 3.23.0. Its Validate never ran (every workflow is sitting at action_required), and run locally it is red: three contract failures. This PR makes the upgrade correct and pins what 3.23.0 changed. It merges down into vendor/vale/upgrade, so pin, contract, and guidance reach main together.

Why #427 is red

One cause, three failures. A standalone scope: doc(<leaf>) (doc(h2)) now reads the leaf's own text, where 3.21.0 and 3.22.0 matched nothing. scope/doc-leaf-standalone in the corpus recorded that as a divergence, and the recipe taught text & doc(h2) around it. This closes a trap rather than opening one; the test's own comment anticipated it.

  • The corpus row now expects accepted and carries no divergence.
  • The 3.21.0 vendor test keeps only the chained half, which is still true.
  • A new Vale 3.23.0 block pins the standalone form firing.

What changed that no test caught

Each case below was run against both binaries on the same fixture and differs between them:

3.22.0 3.23.0
.kt, .kts whole file is prose comments only
//nolint (Go), # noqa (Py), eslint-disable (JS) linted not read
docstring :param x: field linted not read
doc(h2) alone nothing fires

The capabilities table says a bump re-measures everything, and every existing row held, so the suite was green. The format Vale learned only shows up in the source: v3.22.0...v3.23.0 adds internal/lint/code/kt.go, and internal/core/format.go routes .kt/.kts to code. .kt and .kts are new comment rows. .toml also gained a grammar, but routes to data, which needs a View, so it still reads as plaintext here, and that is recorded in the table note too.

tests: was already a Vale field

verify rejected tests: as "an E201 that suppresses every other Vale rule". Measured: 3.22.0 and 3.23.0 both load it with any value and no diagnostic. The key was simply missing from the generator's hand-seeded FIELD_CANDIDATES, which that list's own header calls the worse failure. It is added, and the regenerated vocabulary records it as a common field Vale reads literally. There is a corpus row for it.

Guidance

  • create-vale-rule (topic v15): the leaf paragraph no longer calls the standalone form inert, and the comment-tier bullet says tool-directive comments and :param: fields are not read.
  • update (topic v12): the 0.12.0 ledger now says 3.21.0 → 3.23.0 (by way of 3.22.0) and gains three 3.23.0 entries.
  • The changeset on chore(vale): upgrade to Vale 3.23.0 #427 is extended, not duplicated, and stays patch.

Checks

pnpm lint, pnpm typecheck, and pnpm test (1905 passed) are green locally on this branch, built.

The PR that adds CI checks for this class of drift stacks on top of this one.

Refs #427

Contains #429

Stacks on #428, which stacks on #427. Two checks for the drift #427 slipped through. Neither one gates.

Validate never runs on the upgrade PR

vale-upgrade.yml left the contract suites to Validate: "left to Validate, it is a red check on a pull request that already carries upstream's notes." That red check never appears. A pull_request run triggered by github-actions[bot] waits at action_required until a maintainer approves it:

2026-09-30 vendor/vale/upgrade      action_required  trig=github-actions[bot]   (#427)
2026-09-15 vendor/vale/upgrade      action_required  trig=github-actions[bot]
2026-09-15 vendor/ast-grep/upgrade  action_required  trig=github-actions[bot]

Every earlier upgrade got its first Validate run when a human pushed. #427 read as a PR with no checks while three contract tests were failing.

Now: the job runs vale-schema-contract and vale-vendor-contract itself, with continue-on-error so a changed verdict still proposes the upgrade, as the header intends. vale-upgrade-report.cjs then renders the verdict into the PR body.

A format Vale learns can't be caught by a test

VALE_FORMAT_TIERS is re-probed row by row, which catches a format that moved. An extension with no row is never probed, so a format Vale learned only shows up in upstream's source. The table's header asked for that check by hand on every bump. On 3.23.0 nobody did it, and code/kt.go moved Kotlin to the comment tier with the suite green (#428 adds the rows).

Now: the report lists the files upstream added under internal/lint/ between the pinned tag and the new one, using the compare API. It names readers, not extensions, and says to read internal/core/format.go and probe each one before adding a row. That matters most for the dangerous case: a reader that shells out to a converter turns a missing row into a crash that takes down the whole Vale run.

What #427's body would have said

Rendered from the real compare API and #427's actual test report:

### Contract suites
**3 of 204 failed.** The new Vale changed a recorded behaviour. ...
  vale-schema-contract.test.ts > ... agrees with every recorded verdict
    AssertionError: scope/doc-leaf-standalone (scope: doc(h1)): recorded ignored, Vale 3.23.0 says accepted ...
  (2 more)

### Format readers upstream added
- internal/lint/code/doc.go
- internal/lint/code/kt.go
- internal/lint/code/toml.go
- internal/lint/quote.go
- internal/lint/txtdoc.go

Silence never reads as a pass

A missing test report renders Did not run. A failed fetch renders Could not check with the error. A comparison at the API's 300-file cap is flagged as possibly incomplete. If the report step fails outright, the body says Not measured. Each case is covered in vale-upgrade-report.test.cjs (11 tests).

Not in this PR

  • ast-grep-upgrade.yml has the same action_required exposure, as the table above shows. The contract half of this would port directly; the format-reader half is Vale-specific.
  • Any repo setting that would let bot-triggered runs start without approval. That is a security trade-off for a maintainer to decide, not something to change from a PR.

pnpm lint and pnpm test:scripts (500 passed) are green locally. The workflow parses, and the new step order is contract, then report, then propose.

Refs #427

@theCodeDrift
theCodeDrift added this pull request to stack #430 September 30, 2026 16:03
@theCodeDrift
theCodeDrift removed this pull request from stack #430 September 30, 2026 17:16
theCodeDrift added a commit that referenced this pull request Sep 30, 2026
…rs on the upgrade PR

The upgrade workflow left the contract suites to Validate, "a red check
on a pull request that already carries upstream's notes". Validate does
not run there: a pull_request run triggered by github-actions[bot]
waits at action_required until a maintainer approves it. Every
bot-opened upgrade, Vale and ast-grep alike, measured the same way, and
#427 read as a pull request with no checks while three contract tests
failed.

The job now runs vale-schema-contract and vale-vendor-contract itself,
continue-on-error so a changed verdict still proposes the upgrade, and
vale-upgrade-report.cjs renders the verdict into the body.

The same report lists the files upstream added under internal/lint/.
That is the source check capabilities.ts asked for by hand on every
bump, and the one that would have caught kt.go on 3.23.0: an extension
with no VALE_FORMAT_TIERS row is never probed, so no test can.

Neither half gates. A missing test report or a failed compare fetch is
said in the body, so "not checked" never reads as a pass.
Three contract failures on #427 had one cause: a standalone
`doc(<leaf>)` scope now reads the leaf's text, where 3.21.0 and 3.22.0
matched nothing. The corpus row agrees with the schema now and drops
its divergence; the 3.21.0 test keeps only the chained half, which is
still true; a new 3.23.0 block pins the standalone form firing.

What no test caught, measured against both binaries on the same
fixtures:

- `.kt` and `.kts` moved from the plaintext fallback to the comment
  tier (upstream adds internal/lint/code/kt.go; format.go routes both
  to `code`). Two new rows in VALE_FORMAT_TIERS. `.toml` did not move.
- A comment addressed to a tool (`//nolint`, `# noqa`,
  `eslint-disable`) and a docstring's `:param:` field are no longer
  read. Pinned in the 3.23.0 block.
- `tests:` is a field of every check, accepted by 3.22.0 as well, and
  was missing from the generator's candidates, so `verify` rejected it
  as an E201 Vale never raised. Added as a candidate and regenerated;
  it is common and read literally.

create-vale-rule (topic v15) and update (topic v12) say all of it, and
the changeset carries the release note.
…rs on the upgrade PR

The upgrade workflow left the contract suites to Validate, "a red check
on a pull request that already carries upstream's notes". Validate does
not run there: a pull_request run triggered by github-actions[bot]
waits at action_required until a maintainer approves it. Every
bot-opened upgrade, Vale and ast-grep alike, measured the same way, and
#427 read as a pull request with no checks while three contract tests
failed.

The job now runs vale-schema-contract and vale-vendor-contract itself,
continue-on-error so a changed verdict still proposes the upgrade, and
vale-upgrade-report.cjs renders the verdict into the body.

The same report lists the files upstream added under internal/lint/.
That is the source check capabilities.ts asked for by hand on every
bump, and the one that would have caught kt.go on 3.23.0: an extension
with no VALE_FORMAT_TIERS row is never probed, so no test can.

Neither half gates. A missing test report or a failed compare fetch is
said in the body, so "not checked" never reads as a pass.
@theCodeDrift
theCodeDrift merged commit 837ef1e into main Sep 30, 2026
4 checks passed
theCodeDrift added a commit that referenced this pull request Sep 30, 2026
Three contract failures on #427 had one cause: a standalone
`doc(<leaf>)` scope now reads the leaf's text, where 3.21.0 and 3.22.0
matched nothing. The corpus row agrees with the schema now and drops
its divergence; the 3.21.0 test keeps only the chained half, which is
still true; a new 3.23.0 block pins the standalone form firing.

What no test caught, measured against both binaries on the same
fixtures:

- `.kt` and `.kts` moved from the plaintext fallback to the comment
  tier (upstream adds internal/lint/code/kt.go; format.go routes both
  to `code`). Two new rows in VALE_FORMAT_TIERS. `.toml` did not move.
- A comment addressed to a tool (`//nolint`, `# noqa`,
  `eslint-disable`) and a docstring's `:param:` field are no longer
  read. Pinned in the 3.23.0 block.
- `tests:` is a field of every check, accepted by 3.22.0 as well, and
  was missing from the generator's candidates, so `verify` rejected it
  as an E201 Vale never raised. Added as a candidate and regenerated;
  it is common and read literally.

create-vale-rule (topic v15) and update (topic v12) say all of it, and
the changeset carries the release note.
@theCodeDrift
theCodeDrift deleted the vendor/vale/upgrade branch September 30, 2026 17:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant