Skip to content

create-vale-rule: a rule about a command passes its fixtures and catches nothing #167

Description

@thecodedrift

Summary

The create-vale-rule recipe leads an author straight into a rule that is green on verify, green on test, and catches zero real violations — because in markdown Vale skips code spans and fenced blocks by default, and a rule about a shell command is a rule about text that is almost always inside one.

What happened

Authoring docs-npx-cli (flag pnpm dlx @taskless/cli, prefer npx @taskless/cli) by following the recipe:

  1. Wrote the substitution rule and fixtures per the worked examples. verify ok, test ok.
  2. check against the real repository: zero findings, despite two READMEs containing the exact string — both inside ```bash blocks.
  3. Probed with a fixture holding the same command in three forms. Measured, with no scope: inline code not flagged, fenced block not flagged, plain prose flagged.
  4. scope: raw, scope: code, and scope: text each alone still missed at least one form. Only scope: [raw, code, text] caught all three — and then the two real violations surfaced.

Why the recipe does not prevent this

It does say "URLs and code spans are not prose" under the markup notes, which is accurate. But:

  • It is framed as a false-positive protection ("your rule won't fire on `Github/docs`"), not as the false-negative it becomes when the rule's whole subject is a command.
  • None of the nine worked rules is about a command, a flag, or a package name, so there is no example to copy for the case.
  • The step-1 table routes "preferring one term over another" to substitution with no scope guidance, and scope is introduced later as a way to narrow, never to widen into code.
  • The fixture advice ("the pass/ fixture must contain the same phrase outside that place") catches over-firing. Nothing prompts the author to put the phrase in a code span in fail/, which is what would have caught this.

The result is a rule that passes every local gate and is inert.

Suggested change

Add a worked rule for the command/identifier case — the one where a multi-scope value is required:

extends: substitution
message: "Use '%s' instead of '%s'"
level: error
scope: [raw, code, text]
swap:
  'pnpm dlx @taskless/cli': npx @taskless/cli

With the "goes wrong" line the other nine have: omitting the scope. Measured, the rule then passes its fixtures and reports nothing on a real repository, because every command in a README is in a code span or a fenced block.

Worth pairing with a fixture instruction: when a rule is about something that normally appears in code, the fail/ document must contain it inline, fenced, and in prose. That is the check that would have caught this before it shipped.

🤖 Generated with Claude Code

https://claude.ai/code/session_017cEN93Acyp4zBwP3oDnyy1

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions