Skip to content

manni lint

Check that every page has the shape its doctype promises. Deterministic, template-driven, built for CI.

manni meta checks what a page says it is. manni lint checks whether it is shaped like one. A page that declares type: how-to is supposed to carry an overview, the prerequisites, the steps, and somewhere to go next. Nothing enforces that but a reviewer’s eye, and a reviewer’s eye is the thing that runs out first.

Terminal window
npx @hawkeyexl/manni lint check docs/
Checked: structure.
✗ docs/rotate-key.md
5:1 manni:lint/structure/missing-section Rotate an API key: Missing section "See also"
1 file checked, 0 passed, 1 failed, 0 skipped

Exit 1, with a line and a rule id. The command you run locally is the one that gates the pull request.

manni lint has one verb per kind of check, and one that runs them all:

CommandRuns
manni lint check [paths...]Every configured job
manni lint structure [paths...]Document structure against doctype templates
manni lint templatesThe templates that could route a page, and the doctypes each serves
manni lint templates infer <page>A first template, written from a page that already has the shape you want
manni lint toolsEach job, the tool that performs it, what it reads, and which content kinds each format reports

A job is what is being checked. The tool that answers it is an implementation you can swap. structure is the job, and this package’s own engine, named manni, answers it. Naming the verbs after the jobs means swapping the tool underneath does not change the command anyone types. It also means running exactly one kind of check is one word rather than a flag.

There is no default subcommand. manni lint docs/ is a usage error that lists the verbs, because a domain with one verb still spells the verb.

A template describes the shape a doctype promises. That is which sections a page of that kind has, in what order, and what has to be inside each one. Inside can mean paragraphs, code blocks, lists, tables, admonitions, images, block quotes, definition lists, or a named component.

Seven ship, derived from The Good Docs Project at v1.6.0, covering how-to, tutorial, reference, concept and explanation, troubleshooting, release-notes and readme. A page routes to one by its own type: frontmatter, the same key manni meta already validates. A repository that declares doctypes gets structure checking with no per-page change at all.

Your own templates are one config entry away, and they outrank the built-ins for the doctypes they claim. manni lint templates infer writes the first one from a page you already have, and Write a template takes it from there.

There is no language model here, and no network call except fetching a template you pointed at yourself. Two runs over the same bytes give the same findings, so a red check is reproducible and a green one means something.

That line is load-bearing rather than decorative. The template format used to carry an instructions: key a model evaluated; it is refused now, with a message pointing at where that question belongs. Judgement about prose is a different tool’s job.

One config file, manni.config.yaml, shared with every other manni tool. The document set is the family’s collections: list, so manni meta validate and manni lint check run over exactly the same pages without being told twice. Everything else is under lint:.

manni.config.yaml
collections:
- name: guides
paths: ["docs/**/*.md"]
exclude: ["**/drafts/**"]
lint:
templates:
- ./templates.yaml

With that, both gates are a bare command:

Terminal window
manni meta validate
manni lint check

This is that package, folded into the manni bin and given a family surface. The command is manni lint check, the config moves under lint: in manni.config.yaml, and paths: / exclude: become a collection. The template format is a grammar now rather than a map of section rules. A template written against the old package is refused, with the new spelling of each key. The migration table has every change.