Files
zernie__vigiles/docs/markdown-mode.md
T
zernie 7be092ce19 refactor(linters): LinterAdapter port + JVM/Go doc & test parity (#116)
LinterAdapter registry (Record<BuiltinLinter, LinterAdapter>) makes a missing linter a tsc error; generate-types iterates the registry; conformance test set-matches registry vs BUILTIN_LINTERS + docs + site chip list. CI installs detekt/ktlint/checkstyle/golangci-lint so their gated tests run (no silent skips). Docs/site catalogs->linters parity. add-a-linter contributor skill. Report-view teaser cards lightened + linter footnote de-staled. Stage 4 (dispatch-map collapse) tracked as a follow-up.
2026-07-23 16:08:55 +05:00

11 KiB

Markdown mode

The README has the pitch; this is the full guide to verifying rules in plain markdown — no TypeScript, no build step.

vigiles meets you in the markdown you already have. You don't need a TypeScript build step, and you don't need to port your CLAUDE.md into a .spec.ts to get verified rules. Declare enforce rules in the instruction file itself and vigiles lint verifies them against your real linter config — existence check, closest-match typo suggestions, disabled-rule detection, and GitHub Actions annotations, exactly like a typed spec.

Two on-ramps, one tool

There are two ways to author rules, and each adds value without requiring the other:

On-ramp Where rules live Build step When
Plain markdown <!-- vigiles:enforce ... --> in the prose none Try a rule on any CLAUDE.md, zero ceremony
Typed spec CLAUDE.md.spec.ts → compiled markdown vigiles compile You want compiler-grade guarantees + enforcement

Plain markdown is the on-ramp; the typed spec is the source of truth when you want full type checking, programmatic rule composition, and generate types. Your agent writes the spec, and vigiles eject hands it back to plain markdown anytime — so graduating to a spec is never a one-way door.


Inline enforce comments

The minimum-commitment path: add a single HTML comment per rule, anywhere in your existing markdown. It's the vigiles equivalent of // eslint-disable-next-line — maximum incrementalism, zero new files.

<!-- vigiles:enforce eslint/no-console "Route output through logger.ts" -->

Three pieces, all required:

  1. vigiles:enforce — only enforce is supported inline. The prose around the comment is the guidance, so a guidance comment would be a tautology.
  2. <linter>/<rule> — the same reference format as enforce() in spec mode. All eleven linters (ESLint, Stylelint, Ruff, Clippy, Pylint, RuboCop, Cedar, detekt, ktlint, Checkstyle, golangci-lint), scoped plugin names (eslint/@typescript-eslint/...), and the vigiles-internal namespace (vigiles/orphan-docs) work here.
  3. "<why>" — a double-quoted string shown to the agent as context. No newlines or embedded quotes; if you need either, move to a spec.

A fuller example:

# My Project

<!-- vigiles:enforce eslint/no-console "Route output through logger.ts" -->
<!-- vigiles:enforce eslint/@typescript-eslint/no-floating-promises "Await or explicitly void" -->
<!-- vigiles:enforce ruff/F401 "No unused imports" -->

## Logging

All application output must go through the shared logger module.

What vigiles lint catches

  • Verifies each rule reference against your real linter config.
  • Emits closest-match suggestions on typos: "no-consol" → did you mean "eslint/no-console"?
  • Flags rules that exist but are disabled in your linter config.
  • Emits ::error annotations under GitHub Actions; exits code 2 on any failed rule, so CI fails fast.

What it does not do (vs a typed spec)

  • No edit-time type safety. A .spec.ts gets editor squiggles because rules are a type union; inline strings surface typos only at vigiles lint time (still before CI).
  • No programmatic composition — each comment stands alone.
  • No NCD duplicate detection — that runs on spec-mode files.

That's all fine for the adoption on-ramp. When you outgrow it, graduate to a spec.

Mixing with a spec — don't

A file is checked for inline rules only when it isn't managed by a spec (no sibling <file>.spec.ts, no vigiles:sha256 … compiled from … header). If both exist, the compiler overwrites the markdown on the next compile and your inline comments vanish. Pick one per file.

Graduating to a spec

When a dozen inline rules start crowding the prose:

npx vigiles init --target=CLAUDE.md

That scaffolds a CLAUDE.md.spec.ts beside your CLAUDE.md. Copy the enforce rules into the rules: block, delete the inline comments, and run vigiles compile — the markdown is rebuilt with a sha256 header, and future edits flow through the spec. vigiles eject reverses it anytime.


require-instructions-spec

The built-in require-instructions-spec rule asks for a .spec.ts sibling for every CLAUDE.md / AGENTS.md. It is narrow: only a .spec.ts satisfies it. Inline <!-- vigiles:enforce --> comments and vigiles: frontmatter are still valid plain-markdown on-ramps, but they do not satisfy require-instructions-spec — so a markdown-mode user simply keeps the rule off (it is off by default; turning it on is a workflow-tier opt-in for teams that want to require a typed spec). No vigiles-disable require-instructions-spec comment needed unless you have enabled the rule.

A file is checked for inline rules only when it is not managed by a spec — i.e. it has no sibling <file>.spec.ts and no vigiles:sha256 … compiled from … header. The typed spec is the source of truth; it overwrites the markdown on the next compile, so don't mix a spec with hand-authored markers in the same file.

See also