-
Notifications
You must be signed in to change notification settings - Fork 0
fix: correct the Vale regex-engine claim, the fixture-bucket advice, and the .taskless/** advisory #384
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
fix: correct the Vale regex-engine claim, the fixture-bucket advice, and the .taskless/** advisory #384
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
3687028
docs(vale): correct the regex-engine and fixture-bucket claims (topic…
thecodedrift 86473b6
fix(vale): say what a .taskless/** matcher actually costs
thecodedrift bc16225
test(vale): reuse existenceOver for the raw regex cases
thecodedrift f4d82cb
fix(vale): measure tokens and swap, and correct the spec to match
thecodedrift File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| --- | ||
| "@taskless/cli": patch | ||
| --- | ||
|
|
||
| `agent create-vale-rule` (topic v13) corrects two claims that cost rule authors work. Vale patterns are not RE2-only: Vale compiles with Go's `regexp` and falls back to `regexp2`, so lookahead, lookbehind and backreferences work, and the recipe no longer tells you to split a rule that one pattern expresses. Two silent limits are measured and documented alongside it: a backreference does nothing as a `swap` key, and the implicit word boundary on `tokens`/`swap` lands after a trailing lookahead, so that lookahead has to peek at a non-word character. `check` on a rule's `.tests/fail` bucket is a supported way to read a rendered message, because `.taskless/` is excluded from the whole-project walk only; when that bucket comes back empty, the recipe now sends you to the rule's own config, specifically a `[.taskless/**]` matcher, before the pattern. | ||
|
|
||
| `verify` carried the same imprecision and now states both halves: a `[.taskless/**]` matcher is unnecessary on a whole-project check, AND it silences the rule on a path you name, such as the rule's own fixture bucket. It was previously described as acting only under a bare `vale` invocation, which read as harmless. `agent update` (topic v10) is corrected to match. |
2 changes: 2 additions & 0 deletions
2
openspec/changes/archive/2026-09-22-vale-taskless-matcher-spec/.openspec.yaml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| schema: spec-driven | ||
| created: 2026-09-22 |
55 changes: 55 additions & 0 deletions
55
openspec/changes/archive/2026-09-22-vale-taskless-matcher-spec/proposal.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| ## Why | ||
|
|
||
| taskless/cli#370 and #371 corrected the recipe and the `verify` advisory: a | ||
| `[.taskless/**]` matcher is not harmless. `check` excludes `.taskless/` from a | ||
| _whole-project walk_ only, so the matcher does nothing there — but on a path | ||
| named explicitly, such as the rule's own `.tests/fail` bucket, the matcher is | ||
| live and silences the rule. That is the shape that reproduces "`test` says the | ||
| fixture fired, `check` on the same fixture says nothing". | ||
|
|
||
| The recipe, the `update` ledger and the advisory string now all say both halves. | ||
| The standing spec does not. `cli-vale-rule-engine` still reads "`check` excludes | ||
| that tree before Vale runs, so the matcher acts only under a bare `vale` | ||
| invocation", and its scenario still requires `verify` to "report that `check` | ||
| already excludes that tree" — the exact imprecise phrasing the code no longer | ||
| ships. The spec is the source of truth for this capability, so leaving it | ||
| disagreeing with the advisory it describes is how the next author reproduces | ||
| #370 from the spec instead of the recipe. | ||
|
|
||
| This change carries no code. The implementation already landed in this PR; the | ||
| spec is what is behind. | ||
|
|
||
| ## What Changes | ||
|
|
||
| - **`cli-vale-rule-engine`** — the advisory bullet and the | ||
| `.taskless/**` scenario state both halves of the behaviour: unnecessary on a | ||
| whole-project check, AND silencing on a named path. The scenario also gains | ||
| the `check`-notices half that the other advisory scenarios already carry, so | ||
| the two advisory paths are specified alike. | ||
|
|
||
| No requirement is added or removed, and no behaviour changes: this is the spec | ||
| catching up to an advisory string and a recipe that already shipped. | ||
|
|
||
| ## Capabilities | ||
|
|
||
| ### New Capabilities | ||
|
|
||
| None. | ||
|
|
||
| ### Modified Capabilities | ||
|
|
||
| - `cli-vale-rule-engine`: "A rule's Vale config is validated against a schema | ||
| before it is assembled" — the `.taskless/**` advisory bullet and its scenario | ||
| are restated to match the shipped advisory. | ||
|
|
||
| ## Impact | ||
|
|
||
| Documentation only. No source file, test, or public surface changes. The bump | ||
| stays `patch` and rides the existing changeset for this PR; no second changeset | ||
| is added. | ||
|
|
||
| ## Delivery shape | ||
|
|
||
| **Single PR.** The spec correction is two edits inside one requirement and | ||
| belongs with the code change that made the standing text wrong, which is this | ||
| PR. It is the tip, so the change is archived here. |
84 changes: 84 additions & 0 deletions
84
...rchive/2026-09-22-vale-taskless-matcher-spec/specs/cli-vale-rule-engine/spec.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| ## MODIFIED Requirements | ||
|
|
||
| ### Requirement: A rule's Vale config is validated against a schema before it is assembled | ||
|
|
||
| The system SHALL parse each rule's `.vale.ini` into an ordered, lossless AST and validate that AST against a schema keyed by the rule's directory id, before the config is assembled into the run config and when the rule is verified. Validation SHALL be performed on the parsed structure, never by matching the file's text. | ||
|
|
||
| The schema SHALL reject a config that: | ||
|
|
||
| - assigns any property above its first matcher (`StylesPath`, `MinAlertLevel`, or anything else; Vale ignores such a line with a `W101` warning and the rule verifies clean while enabled nowhere) | ||
| - declares a matcher without a `tskl) rule = <id>` breadcrumb naming this rule | ||
| - assigns a key other than `<id>.<id>` (a `<style>.<check>` key naming any other rule is a cross-rule override) | ||
| - assigns a value other than `YES` or `NO` | ||
| - assigns `BasedOnStyles`, with any value, empty included (on Vale 3.22.0 an empty value clears every earlier matcher's settings for the file, which in the assembled config silences every other rule whose glob reaches it; a named style loads alongside every overlapping rule; and no rule config ever needed either, since no bundled style loads unless a run-level `BasedOnStyles` names one) | ||
| - declares no matcher | ||
| - never assigns `<id>.<id> = YES` in its final per-matcher verdicts (matchers with the same glob are folded, as Vale merges them, and the last assignment wins, so a `YES` that a later `NO` in the same matcher overrides does not count) | ||
| - declares a `NO`-verdict matcher before every `YES`-verdict matcher (no style is loaded, so a rule is off until a `YES`, and such a `NO` is either dead or overridden by the `YES` that follows; no config means it) | ||
|
|
||
| The schema SHALL report, without rejecting, a config that: | ||
|
|
||
| - assigns the same key twice inside one matcher (Vale 3.21.0 keeps the last assignment; 3.20.0 kept the first) | ||
| - declares a `[*]` matcher | ||
| - declares a matcher under `.taskless/**` (`check` excludes that tree from a whole-project walk, so the matcher is unnecessary there; it is not harmless, because it silences the rule on a path named explicitly, such as the rule's own fixture bucket under `.taskless/`) | ||
|
|
||
| A rejected config SHALL refuse the Vale engine for that `check` run: the engine reports a failure naming the rule and the offending line, that failure SHALL reach the exit code, and other engines SHALL still run. A rule with a rejected config SHALL NOT be silently omitted from the assembled config, because a rule that is present, verifies, and reports nothing is the silent-disable failure this engine's design exists to prevent. Advisories SHALL be surfaced as notices and SHALL NOT affect the exit code. | ||
|
|
||
| Assembly SHALL write each accepted config's source verbatim. The parsed structure is read for validation and for the list of matcher patterns; it is not re-serialized. | ||
|
|
||
| #### Scenario: A foreign assignment refuses the run | ||
|
|
||
| - **WHEN** `no-simply/.vale.ini` assigns `no-hedging.no-hedging = NO` and `check` runs | ||
| - **THEN** the Vale engine SHALL report a failure naming `no-simply` and that line | ||
| - **AND** the exit code SHALL be non-zero | ||
| - **AND** ast-grep results for the same run SHALL still be reported | ||
|
|
||
| #### Scenario: A run-level key in a rule config is rejected | ||
|
|
||
| - **WHEN** a rule's config places `StylesPath = .` above its first matcher | ||
| - **THEN** `verify` SHALL reject the rule, naming the key and the line | ||
| - **AND** `check` SHALL refuse the Vale engine rather than strip the line | ||
|
|
||
| #### Scenario: A matcher without a breadcrumb is rejected | ||
|
|
||
| - **WHEN** a rule's config declares `[*.md]` with `<id>.<id> = YES` and no `tskl) rule` key | ||
| - **THEN** `verify` SHALL reject the rule, naming the matcher | ||
|
|
||
| #### Scenario: A disable that precedes every enable is rejected | ||
|
|
||
| - **WHEN** a rule's config declares `[docs/legacy/**]` with `<id>.<id> = NO` and then `[docs/**]` with `<id>.<id> = YES` | ||
| - **THEN** `verify` SHALL reject the rule, naming the `NO` matcher and the `YES` that re-enables it | ||
|
|
||
| #### Scenario: A BasedOnStyles assignment is rejected | ||
|
|
||
| - **WHEN** a rule's config sets `BasedOnStyles =` inside a matcher | ||
| - **THEN** `verify` SHALL reject the rule under `vale-config-no-based-on-styles`, naming the line and saying that the line silences the other rules whose globs overlap | ||
| - **AND** a `BasedOnStyles` naming a style SHALL be rejected under the same constraint | ||
|
|
||
| #### Scenario: A [formats] section is rejected as a matcher it cannot be | ||
|
|
||
| - **WHEN** a rule's config declares a `[formats]` section | ||
| - **THEN** `verify` SHALL reject it for the breadcrumb it lacks and the foreign key it assigns, so no rule config can move a file between parser tiers | ||
|
|
||
| #### Scenario: A repeated key is reported, not rejected | ||
|
|
||
| - **WHEN** one matcher assigns `<id>.<id> = YES` and then `<id>.<id> = NO`, and an earlier matcher assigns `<id>.<id> = YES` | ||
| - **THEN** `verify` SHALL accept the rule and report the repeat as a notice | ||
| - **AND** `check` SHALL run the Vale engine and carry the same text in its notices | ||
|
|
||
| #### Scenario: A rule whose only enable is overridden is rejected | ||
|
|
||
| - **WHEN** the only matcher assigning `<id>.<id> = YES` later assigns `<id>.<id> = NO`, in the same section or in a second section with the same glob | ||
| - **THEN** `verify` SHALL reject the rule as present but off, under `vale-config-enabled-somewhere` | ||
| - **AND** the repeat SHALL still be reported as a notice | ||
|
|
||
| #### Scenario: A `.taskless/**` matcher is reported as unnecessary | ||
|
|
||
| - **WHEN** a rule's config declares a matcher under `.taskless/**` | ||
| - **THEN** `verify` SHALL accept the rule and report both halves: that the matcher is unnecessary on a whole-project check, which excludes `.taskless/` before Vale runs, AND that it silences the rule on a path named explicitly, such as the rule's own fixture bucket | ||
| - **AND** `check` SHALL carry the same text in its notices | ||
|
|
||
| #### Scenario: An accepted config is assembled byte-for-byte | ||
|
|
||
| - **WHEN** a config passes the schema | ||
| - **THEN** the assembled run config SHALL contain that file's bytes unchanged under the rule's breadcrumb comment | ||
| - **AND** the matcher patterns reported for the run SHALL equal the section names in the parsed structure |
12 changes: 12 additions & 0 deletions
12
openspec/changes/archive/2026-09-22-vale-taskless-matcher-spec/tasks.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| ## 1. Spec | ||
|
|
||
| - [x] 1.1 Restate the `.taskless/**` advisory bullet in | ||
| `cli-vale-rule-engine` to say it is unnecessary on a whole-project check | ||
| AND silencing on a named path. | ||
| - [x] 1.2 Restate the `A .taskless/** matcher is reported as unnecessary` | ||
| scenario to require both halves, plus the `check`-notices half the other | ||
| advisory scenarios carry. | ||
| - [x] 1.3 Confirm the MODIFIED block restates the requirement in full: same | ||
| title, all ten scenarios, and every bullet of both lists. | ||
| - [x] 1.4 Dry-run `openspec archive` and diff the scenario count before and | ||
| after to prove nothing is dropped. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.