Get started
This page takes you from one Markdown file to a structure check you can put in CI. You will lint a how-to that is missing a section, read what the finding says, fix the page, and see the run go green. Then you will ask the tool why it picked the template it picked, and move the paths into config.
Every transcript below is real output from the built tool, over files committed in this repository.
Before you start
Section titled “Before you start”- Node.js 24 or newer. Check with
node --version. - A page that says what it is. Structure routing reads the
type:frontmatter key. A page without one is skipped rather than failed. You can point the tool at a whole tree and lint only what has opted in.
manni lint ships in the same package as manni meta. If you have one, you have the other.
npx @hawkeyexl/manni lint --helpnpm install -g @hawkeyexl/mannimanni lint --helpSee what can route a page
Section titled “See what can route a page”Before linting anything, ask what templates exist:
manni lint templatesTemplates: tgdp:how-to:1.6 — How-to guide (The Good Docs Project) types: how-to [builtin] tgdp:tutorial:1.6 — Tutorial (The Good Docs Project) types: tutorial [builtin] tgdp:reference:1.6 — Reference (The Good Docs Project) types: reference [builtin] tgdp:concept:1.6 — Concept (The Good Docs Project) types: concept, explanation [builtin] tgdp:troubleshooting:1.6 — Troubleshooting (The Good Docs Project) types: troubleshooting [builtin] tgdp:release-notes:1.6 — Release notes (The Good Docs Project) types: release-notes [builtin] tgdp:readme:1.6 — README (The Good Docs Project) types: readme [builtin]Seven built-in templates, derived from
The Good Docs Project at v1.6.0. The
types: column is the vocabulary a page’s type: frontmatter may use to route
itself. Nothing here needs configuring.
Lint a how-to
Section titled “Lint a how-to”Here is the page. It declares type: how-to, so tgdp:how-to:1.6 is what it
will be held to:
---type: how-to---
# Rotate an API key
## Overview
Rotating a key replaces the old secret without interrupting live traffic.
## Before you start
You need an account with the `admin` role.
## Rotate the key
Issue the replacement, then retire the old one.
```bashwidget keys rotate --id abc123```-
Run the check.
Terminal window manni lint structure docs/rotate-key.md✗ docs/rotate-key.md5:1 manni:lint/structure/missing-section Rotate an API key: Missing section "See also"1 file checked, 0 passed, 1 failed, 0 skippedExit code
1. -
Read the finding. Four parts, left to right:
Part What it tells you 5:1Line and column. Line 5 is the # Rotate an API keyheading, because the section that is incomplete is the one the heading opens.manni:lint/structure/missing-sectionThe rule id: manni:lint/ the job / the rule. Durable, and the same string the GitHub annotation, the SARIFruleIdand the JUnit failure type carry.Rotate an API key:The section the finding is about, named by its heading. Missing section "See also"What to change. The how-to template asks for a
See alsosection, and upstream does not mark it optional the way it marksBefore you start. So this page is a how-to that does not say where to go next. -
Fix the page. Add the section:
docs/rotate-key.md ## See alsoRead about key scopes and expiry. -
Re-run.
Terminal window manni lint structure docs/rotate-key.md✓ docs/rotate-key.md1 file checked, 1 passed, 0 failed, 0 skippedExit code
0. That is the whole loop.
Run every job at once
Section titled “Run every job at once”structure is one job. check runs every job the repository configures, and
names them on stderr so the report cannot be read against the wrong set:
manni lint check docs/rotate-key.mdChecked: structure.✓ docs/rotate-key.md
1 file checked, 1 passed, 0 failed, 0 skippedstructure is the only job, so the two commands report the same thing. check
is the command for CI. It runs every job the configuration declares, and it
carries no job’s own flags.
Ask why a page routed where it did
Section titled “Ask why a page routed where it did”A page can be held to a template you did not expect, or to none at all.
--explain prints the routing table and lints nothing:
manni lint structure docs/ --explain▸ docs/rotate-key.md · cli --template not given · frontmatter-template no $template in frontmatter · config-override no overrides configured → type tgdp:how-to:1.6 type: how-to -> builtin template alignment (page) ← "Rotate an API key" overview ← "Overview" before-you-start ← "Before you start" task ← "Rotate the key" see-also ← "See also"
1 file, 1 routed, 0 unroutedFive rungs are tried in order. · is a rung that did not decide and → is the
one that did. Here the page’s own type: how-to picked the built-in template,
which is the ordinary case.
Under the rungs is the alignment, which is the pairing the matcher chose. Each row is one template rule and the section it took. Read it when a finding surprises you.
A page with no type
Section titled “A page with no type”Point the tool at a tree and most files will not have opted in yet. Those are skipped, counted apart, and never mistaken for passes:
- docs/notes.md skipped: no type in frontmatter and no template resolved✓ docs/rotate-key.md
1 file checked, 1 passed, 0 failed, 1 skippedA page that declares a type: nothing serves is different. That is an
assertion the tool could not honour, so it is a finding. The message
suggests the nearest doctype it knows.
1:1 manni:lint/structure/unknown-type No template serves type "how-two". Did you mean "how-to"? Declare it on a template with "types:", then pass that file with --templates.Let the config supply the paths
Section titled “Let the config supply the paths”Declare the pages once, under the family-level collections: key that every
manni tool reads, and the command loses its arguments:
collections: - name: guides paths: ["docs/**/*.md"] exclude: ["**/drafts/**"]manni lint checkThe same command now runs locally and in CI, over exactly the pages
manni meta validate runs over. A collection’s exclude decides membership, so
the drafts are not in the set at all rather than filtered out afterwards.
Add your own doctype shapes beside it whenever the built-ins stop fitting:
lint: templates: - ./templates.yamlA template in that file declaring types: [how-to] replaces the built-in
how-to shape for the whole repository, and no page changes. You do not have to
write one from scratch. manni lint templates infer writes a first template
from a page that already has the shape you want:
manni lint templates infer docs/rotate-key.md --out ./templates.yamlWrote template "how-to" to ./templates.yamlWrite a template takes it from there, and the templates reference has every key.
Next steps
Section titled “Next steps”You have linted one page, read a finding, fixed it, and moved the paths into config. To make it a standing guarantee, run the check on every push.