From 5925f6bb29ee0e16a794066f74ced3c258d5dfd0 Mon Sep 17 00:00:00 2001 From: Jakob Heuser Date: Mon, 21 Sep 2026 16:05:39 -0700 Subject: [PATCH 1/5] chore(vale): move VALE_VERSION to 3.22.0 and regenerate the vocabulary The pin bump in #368 left VALE_VERSION at 3.21.0, which is what engine-version-consistency fails on. The vocabulary re-derived against the 3.22.0 binary changes only in its version stamp: no operand, prefix or divergence moved. The v3.21.0...v3.22.0 tree adds no internal/lint/.go, so no tier row moves either; the docblock on the tier table records the check and points at what did change. --- .../cli/src/generated/vale-vocabulary-report.md | 12 ++++++------ packages/cli/src/generated/vale-vocabulary.ts | 14 +++++++------- packages/cli/src/rules/capabilities.ts | 15 ++++++++++++++- packages/cli/test/reconcile-marker.test.ts | 2 +- 4 files changed, 28 insertions(+), 15 deletions(-) diff --git a/packages/cli/src/generated/vale-vocabulary-report.md b/packages/cli/src/generated/vale-vocabulary-report.md index dc9bb6ff..f4ba997f 100644 --- a/packages/cli/src/generated/vale-vocabulary-report.md +++ b/packages/cli/src/generated/vale-vocabulary-report.md @@ -1,10 +1,10 @@ -# Vale 3.21.0 vocabulary: divergence report +# Vale 3.22.0 vocabulary: divergence report GENERATED FILE — DO NOT EDIT. Produced by `pnpm generate:vale-schema` alongside `vale-vocabulary.ts`. Every value in the vocabulary is the recorded answer of the vendored Vale -3.21.0 binary. This file is what the binary said that its own +3.22.0 binary. This file is what the binary said that its own documentation does not, in both directions. A generator that dropped these would be quietly deciding which of the two to believe. @@ -28,25 +28,25 @@ the worse failure. ### `scope: meta` -Vale 3.21.0 documents this operand and it never fired, on any fixture probed (.md). +Vale 3.22.0 documents this operand and it never fired, on any fixture probed (.md). **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. ### `scope: meta.class.title` -Vale 3.21.0 documents this operand and it never fired, on any fixture probed (.md). +Vale 3.22.0 documents this operand and it never fired, on any fixture probed (.md). **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. ### `scope: frontmatter` -This operand fired and Vale 3.21.0 documents it nowhere. +This operand fired and Vale 3.22.0 documents it nowhere. **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. ### `scope: frontmatter.title` -This operand fired and Vale 3.21.0 documents it nowhere. +This operand fired and Vale 3.22.0 documents it nowhere. **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. diff --git a/packages/cli/src/generated/vale-vocabulary.ts b/packages/cli/src/generated/vale-vocabulary.ts index d15dd834..e13aa712 100644 --- a/packages/cli/src/generated/vale-vocabulary.ts +++ b/packages/cli/src/generated/vale-vocabulary.ts @@ -6,7 +6,7 @@ * `scripts/generate-vale-schema.ts`, and its header explains what each value * below was measured with and what it is worth. * - * Derived against Vale 3.21.0. Every value here is the recorded answer + * Derived against Vale 3.22.0. Every value here is the recorded answer * of that binary to a rule the generator wrote and ran; nothing is transcribed * from documentation. Where the binary and the documentation disagree, the * disagreement is in `vale-vocabulary-report.md` rather than dropped. @@ -15,7 +15,7 @@ */ /** The binary this vocabulary was derived from. */ -export const VALE_VOCABULARY_VERSION = "3.21.0"; +export const VALE_VOCABULARY_VERSION = "3.22.0"; /** * Vale's check types, self-enumerated: an unknown `extends` makes the binary @@ -197,7 +197,7 @@ export const VALE_SCOPE_PREFIXES = [ ] as const; /** - * Where Vale 3.21.0 and its documentation disagree. + * Where Vale 3.22.0 and its documentation disagree. * * Carried in the artifact rather than only in the report, so that a consumer * can render them and a reviewer cannot miss them in a diff. @@ -206,26 +206,26 @@ export const VALE_DIVERGENCES = [ { subject: "scope: meta", finding: - "Vale 3.21.0 documents this operand and it never fired, on any fixture probed (.md).", + "Vale 3.22.0 documents this operand and it never fired, on any fixture probed (.md).", 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.", }, { subject: "scope: meta.class.title", finding: - "Vale 3.21.0 documents this operand and it never fired, on any fixture probed (.md).", + "Vale 3.22.0 documents this operand and it never fired, on any fixture probed (.md).", 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.", }, { subject: "scope: frontmatter", - finding: "This operand fired and Vale 3.21.0 documents it nowhere.", + finding: "This operand fired and Vale 3.22.0 documents it nowhere.", 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.", }, { subject: "scope: frontmatter.title", - finding: "This operand fired and Vale 3.21.0 documents it nowhere.", + finding: "This operand fired and Vale 3.22.0 documents it nowhere.", 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.", }, diff --git a/packages/cli/src/rules/capabilities.ts b/packages/cli/src/rules/capabilities.ts index 036e030a..40b08b3e 100644 --- a/packages/cli/src/rules/capabilities.ts +++ b/packages/cli/src/rules/capabilities.ts @@ -209,7 +209,7 @@ export const AST_GREP_TSX_SPLIT: Readonly< * Pinned against the binary by `test/vale-vendor-contract.test.ts` * ("engine capabilities" → "reports the pinned version"). */ -export const VALE_VERSION = "3.21.0"; +export const VALE_VERSION = "3.22.0"; /** * Which tier Vale routes an extension to. @@ -271,6 +271,19 @@ const CONVERTER_TIER_PREFIX = "converter:"; * plain text today, but the moment Vale routes it to a converter the same * omission is a crash that takes down every Vale rule in the run. * + * 3.21.0 → 3.22.0 LEARNED NO FORMAT. Every row was re-probed against the + * 3.22.0 binary and none moved. The v3.21.0...v3.22.0 tree adds no + * `internal/lint/.go` (its additions there are two `_test.go` files); + * `md.go`, `mdx.go`, `html.go`, `dita.go`, `code.go` and `metadata.go` change + * in what they do with a file, not in which files reach them. What did move + * is beside this table: an empty `BasedOnStyles` in a rule's `.vale.ini` + * clears every earlier matcher's settings for the file (see + * `src/schemas/vale-config.ts`, which now refuses the key, and migration + * 0008), a negated inline scope blanks the element's text out of its block, + * a `[formats]` key may be a file name or glob, and `lint.go`'s directory walk + * now skips the `StylesPath` tree unless a path inside it is named. All of it + * is pinned in `test/vale-vendor-contract.test.ts`. + * * 3.20.0 → 3.21.0 LEARNED ONE FORMAT, AND THE SOURCE CHECK IS WHAT FOUND IT. * Every existing row was re-probed against the 3.21.0 binary and none moved. * The v3.20.0...v3.21.0 tree adds `internal/lint/notebook.go` and routes diff --git a/packages/cli/test/reconcile-marker.test.ts b/packages/cli/test/reconcile-marker.test.ts index 14c81425..db50fd48 100644 --- a/packages/cli/test/reconcile-marker.test.ts +++ b/packages/cli/test/reconcile-marker.test.ts @@ -86,7 +86,7 @@ describe("recording a rules reconciliation", () => { expect(rules?.reconciledTo).toBe(version); // Engine versions are the input a later differential needs. Recorded here // and nowhere else, so an upgrade cannot silently refresh them. - expect(rules?.engines).toEqual({ sg: "0.45.3", vale: "3.21.0" }); + expect(rules?.engines).toEqual({ sg: "0.45.3", vale: "3.22.0" }); }); it("reports the marker through info", async () => { From 76ce6d930242afad1810c350a114b9dc260332c3 Mon Sep 17 00:00:00 2001 From: Jakob Heuser Date: Mon, 21 Sep 2026 16:05:56 -0700 Subject: [PATCH 2/5] feat(vale): reject BasedOnStyles in a rule config and migrate installed rules Vale 3.22.0 gives an empty BasedOnStyles a meaning (upstream c2d62437): it clears every setting a file inherited from an earlier matcher. The assembled run config is every rule's matchers in id order, and the recipe wrote BasedOnStyles = into every one of them, so on 3.22.0 a rule under [docs/**] silences every alphabetically earlier rule under docs/, and [*.md] beside another rule's [*.{md,markdown}] silences the first on every .md file. Measured on both binaries against the exact layout assembleValeConfig writes; only byte-identical globs, which Vale merges, escape, which is why this repository's own five rules did not notice. The schema now refuses the key with any value under vale-config-no-based-on-styles (renamed from -based-on-styles-empty, never released), and migration 0008 deletes the line from every installed .taskless/rules/vale/*/.vale.ini, touching no other byte. check and verify already refuse a scaffold behind the current version and name init, which applies it. This repository's own .taskless/ is migrated here by pnpm cli init. The three isolating configs stop writing the line too. Their docblocks claimed it kept Vale.Spelling from firing on a fixture; measured on both binaries with a baited document, nothing fires without the key and the control naming Vale fires all of it. No bundled style loads unless a run-level BasedOnStyles names one. --- .../comments-record-not-forecast/.vale.ini | 1 - .taskless/rules/vale/docs-npx-cli/.vale.ini | 2 - .../rules/vale/no-blocklist-phrases/.vale.ini | 6 - .taskless/rules/vale/no-em-dashes/.vale.ini | 6 - .taskless/rules/vale/no-hedging/.vale.ini | 6 - .taskless/taskless.json | 4 +- .../.taskless/rules/vale/no-simply/.vale.ini | 1 - packages/cli/assets/demo-vale/.vale.ini | 1 - packages/cli/assets/reference.json | 12 +- packages/cli/scripts/generate-vale-schema.ts | 22 +- packages/cli/src/filesystem/migrate.ts | 2 + .../migrations/0008-drop-based-on-styles.ts | 72 +++++++ packages/cli/src/rules/assemble.ts | 10 + packages/cli/src/rules/constraints.ts | 10 +- packages/cli/src/rules/vale/verify.ts | 13 +- packages/cli/src/schemas/vale-config.ts | 24 ++- packages/cli/test/assemble.test.ts | 2 +- packages/cli/test/check.test.ts | 2 +- packages/cli/test/constraints.test.ts | 19 +- .../rules/vale/no-obviously/.vale.ini | 1 - .../.taskless/rules/vale/no-simply/.vale.ini | 1 - .../test/fixtures/vale-config/bad-value.ini | 1 - .../vale-config/based-on-styles-named.ini | 4 + .../fixtures/vale-config/based-on-styles.ini | 2 +- .../vale-config/blank-inside-matcher.ini | 1 - .../cli/test/fixtures/vale-config/dogfood.ini | 2 - .../test/fixtures/vale-config/foreign-key.ini | 1 - .../fixtures/vale-config/never-enabled.ini | 1 - .../fixtures/vale-config/no-before-yes.ini | 1 - .../fixtures/vale-config/no-breadcrumb.ini | 1 - .../repeat-across-sections-still-enabled.ini | 1 - .../vale-config/repeat-across-sections.ini | 1 - .../vale-config/repeat-key-still-enabled.ini | 1 - .../test/fixtures/vale-config/repeat-key.ini | 1 - .../test/fixtures/vale-config/root-keys.ini | 1 - .../cli/test/fixtures/vale-config/star.ini | 1 - .../test/fixtures/vale-config/stray-key.ini | 1 - .../fixtures/vale-config/taskless-tree.ini | 1 - .../cli/test/fixtures/vale-config/valid.ini | 1 - .../fixtures/vale-config/wrong-breadcrumb.ini | 1 - .../test/migrate-drop-based-on-styles.test.ts | 189 ++++++++++++++++++ .../test/migrate-ignore-scratch-files.test.ts | 8 +- packages/cli/test/mixed-engine-check.test.ts | 12 +- packages/cli/test/vale-config-schema.test.ts | 77 +++++-- packages/cli/test/vale-formats.test.ts | 2 +- packages/cli/test/vale-orchestration.test.ts | 6 +- .../cli/test/vale-schema-contract.test.ts | 9 +- packages/cli/test/vale-verify.test.ts | 12 +- .../cli/test/verify-test-commands.test.ts | 10 +- 49 files changed, 430 insertions(+), 136 deletions(-) create mode 100644 packages/cli/src/filesystem/migrations/0008-drop-based-on-styles.ts create mode 100644 packages/cli/test/fixtures/vale-config/based-on-styles-named.ini create mode 100644 packages/cli/test/migrate-drop-based-on-styles.test.ts diff --git a/.taskless/rules/vale/comments-record-not-forecast/.vale.ini b/.taskless/rules/vale/comments-record-not-forecast/.vale.ini index 48d29942..5d2d33b1 100644 --- a/.taskless/rules/vale/comments-record-not-forecast/.vale.ini +++ b/.taskless/rules/vale/comments-record-not-forecast/.vale.ini @@ -2,5 +2,4 @@ # code body is invisible, so this rule can never fire on an identifier. [packages/cli/src/**/*.ts] tskl) rule = comments-record-not-forecast -BasedOnStyles = comments-record-not-forecast.comments-record-not-forecast = YES diff --git a/.taskless/rules/vale/docs-npx-cli/.vale.ini b/.taskless/rules/vale/docs-npx-cli/.vale.ini index d1acb470..e1924e65 100644 --- a/.taskless/rules/vale/docs-npx-cli/.vale.ini +++ b/.taskless/rules/vale/docs-npx-cli/.vale.ini @@ -12,11 +12,9 @@ # invocation, and the rule keeps its full reach. [**/README.md] tskl) rule = docs-npx-cli -BasedOnStyles = docs-npx-cli.docs-npx-cli = YES # Test fixtures are inputs to the CLI's own suite, not documentation. [**/test/fixtures/**/README.md] tskl) rule = docs-npx-cli -BasedOnStyles = docs-npx-cli.docs-npx-cli = NO diff --git a/.taskless/rules/vale/no-blocklist-phrases/.vale.ini b/.taskless/rules/vale/no-blocklist-phrases/.vale.ini index 83d3b005..37b7bfce 100644 --- a/.taskless/rules/vale/no-blocklist-phrases/.vale.ini +++ b/.taskless/rules/vale/no-blocklist-phrases/.vale.ini @@ -2,19 +2,16 @@ # is a separate decision with a large remediation attached. [**/README.md] tskl) rule = no-blocklist-phrases -BasedOnStyles = no-blocklist-phrases.no-blocklist-phrases = YES # Agent-facing instructions and the house conventions. Read as often as the # READMEs are, by both people and agents, and small enough to keep conforming. [CLAUDE.md] tskl) rule = no-blocklist-phrases -BasedOnStyles = no-blocklist-phrases.no-blocklist-phrases = YES [.conventions/*.md] tskl) rule = no-blocklist-phrases -BasedOnStyles = no-blocklist-phrases.no-blocklist-phrases = YES # Test fixtures are inputs to the CLI's own suite, not documentation. @@ -30,12 +27,10 @@ no-blocklist-phrases.no-blocklist-phrases = YES # the size of the thing that earned one. [packages/cli/src/agent/*.md] tskl) rule = no-blocklist-phrases -BasedOnStyles = no-blocklist-phrases.no-blocklist-phrases = YES [**/test/fixtures/**/README.md] tskl) rule = no-blocklist-phrases -BasedOnStyles = no-blocklist-phrases.no-blocklist-phrases = NO # Root prose docs that are not READMEs. `dotagents.md` explains the agent @@ -43,5 +38,4 @@ no-blocklist-phrases.no-blocklist-phrases = NO # outside the team exactly as a README is, and held to the same standard. [dotagents.md] tskl) rule = no-blocklist-phrases -BasedOnStyles = no-blocklist-phrases.no-blocklist-phrases = YES diff --git a/.taskless/rules/vale/no-em-dashes/.vale.ini b/.taskless/rules/vale/no-em-dashes/.vale.ini index 3271b045..e970eeea 100644 --- a/.taskless/rules/vale/no-em-dashes/.vale.ini +++ b/.taskless/rules/vale/no-em-dashes/.vale.ini @@ -2,19 +2,16 @@ # to source comments and openspec is a separate, much larger decision. [**/README.md] tskl) rule = no-em-dashes -BasedOnStyles = no-em-dashes.no-em-dashes = YES # Agent-facing instructions and the house conventions. Read as often as the # READMEs are, by both people and agents, and small enough to keep conforming. [CLAUDE.md] tskl) rule = no-em-dashes -BasedOnStyles = no-em-dashes.no-em-dashes = YES [.conventions/*.md] tskl) rule = no-em-dashes -BasedOnStyles = no-em-dashes.no-em-dashes = YES # Test fixtures are inputs to the CLI's own suite, not documentation. @@ -30,12 +27,10 @@ no-em-dashes.no-em-dashes = YES # the size of the thing that earned one. [packages/cli/src/agent/*.md] tskl) rule = no-em-dashes -BasedOnStyles = no-em-dashes.no-em-dashes = YES [**/test/fixtures/**/README.md] tskl) rule = no-em-dashes -BasedOnStyles = no-em-dashes.no-em-dashes = NO # Root prose docs that are not READMEs. `dotagents.md` explains the agent @@ -43,5 +38,4 @@ no-em-dashes.no-em-dashes = NO # outside the team exactly as a README is, and held to the same standard. [dotagents.md] tskl) rule = no-em-dashes -BasedOnStyles = no-em-dashes.no-em-dashes = YES diff --git a/.taskless/rules/vale/no-hedging/.vale.ini b/.taskless/rules/vale/no-hedging/.vale.ini index 0d55d3b9..9b5a1ffa 100644 --- a/.taskless/rules/vale/no-hedging/.vale.ini +++ b/.taskless/rules/vale/no-hedging/.vale.ini @@ -2,21 +2,17 @@ # blocks are not prose, so this rule never sees a command or an identifier. [**/README.md] tskl) rule = no-hedging -BasedOnStyles = no-hedging.no-hedging = YES [CLAUDE.md] tskl) rule = no-hedging -BasedOnStyles = no-hedging.no-hedging = YES [.conventions/*.md] tskl) rule = no-hedging -BasedOnStyles = no-hedging.no-hedging = YES [packages/cli/src/agent/*.md] tskl) rule = no-hedging -BasedOnStyles = no-hedging.no-hedging = YES @@ -56,7 +52,6 @@ no-hedging.no-hedging = YES [**/test/fixtures/**/README.md] tskl) rule = no-hedging -BasedOnStyles = no-hedging.no-hedging = NO # Root prose docs that are not READMEs. `dotagents.md` explains the agent @@ -64,5 +59,4 @@ no-hedging.no-hedging = NO # outside the team exactly as a README is, and held to the same standard. [dotagents.md] tskl) rule = no-hedging -BasedOnStyles = no-hedging.no-hedging = YES diff --git a/.taskless/taskless.json b/.taskless/taskless.json index 72bd9028..b00f7c3b 100644 --- a/.taskless/taskless.json +++ b/.taskless/taskless.json @@ -1,5 +1,5 @@ { - "version": 7, + "version": 8, "install": { "targets": { ".taskless": { @@ -21,7 +21,7 @@ "mode": "reference" } }, - "cliVersion": "0.11.0", + "cliVersion": "0.11.2", "onboarded": true }, "rules": { diff --git a/example/.taskless/rules/vale/no-simply/.vale.ini b/example/.taskless/rules/vale/no-simply/.vale.ini index a123e1f2..a97301bd 100644 --- a/example/.taskless/rules/vale/no-simply/.vale.ini +++ b/example/.taskless/rules/vale/no-simply/.vale.ini @@ -2,5 +2,4 @@ # directory. That's the point of the layout. [*.{html,md}] tskl) rule = no-simply -BasedOnStyles = no-simply.no-simply = YES diff --git a/packages/cli/assets/demo-vale/.vale.ini b/packages/cli/assets/demo-vale/.vale.ini index eeeca4a3..2dd81677 100644 --- a/packages/cli/assets/demo-vale/.vale.ini +++ b/packages/cli/assets/demo-vale/.vale.ini @@ -2,5 +2,4 @@ # should never widen its own scope into a project's source. [**/*.md] tskl) rule = prefer-use-over-utilize -BasedOnStyles = prefer-use-over-utilize.prefer-use-over-utilize = YES diff --git a/packages/cli/assets/reference.json b/packages/cli/assets/reference.json index 3760d5a1..98ead6d6 100644 --- a/packages/cli/assets/reference.json +++ b/packages/cli/assets/reference.json @@ -100,7 +100,7 @@ "id": "vale-config-own-key-only", "engine": "vale", "enforcedBy": "verify", - "summary": "Inside a matcher, the only assignments are the breadcrumb, an empty `BasedOnStyles`, and `.`.", + "summary": "Inside a matcher, the only assignments are the breadcrumb and `.`.", "rationale": "A `