Skip to content

Check rules

manni strategy check reads the records and the pages that name them, and reports what does not line up. It reads metadata, never prose. Each rule id is manni:strategy/<rule>. Each rule’s level can be moved or turned off under strategy.severity, as the configuration shows.

RuleDefaultFires when
undefined-referenceerrorA page’s audiences:, personas: or journeys:, or a record’s audience: or personas:, names no record of that type.
duplicate-iderrorTwo records share an id, ignoring case.
missing-parentwarningA page names a journey but none of its personas in personas:, or a persona but not its audience in audiences:.
orphan-recordwarningA persona names no audience, or a journey names no persona, while the set holds records of the parent type.
unserved-journeywarningNo page names the journey in journeys:. Records do not count, and a draft or archived journey is exempt.
unused-recordnoticeNo page and no record names an audience or a persona.
broken-steperrorA step’s doc names no page, or a step says exists: true or partial and has no doc. A step with exists: false is exempt.
stale-stepwarningA step says exists: false, and its doc names a page.
missing-journeywarningA step’s page does not name the journey in journeys:.
unwritten-stepnoticeA step says exists: false, or has no doc.

A record is a page that declares type: audience, type: persona or type: journey, in any format manni meta reads. Its id is its id:, or the file name without its extension when it has none. A persona names its audience in audience:. A journey names the personas it serves in personas:, as one string or a list.

A reference is a value of a page’s audiences:, personas: or journeys:, as one string or a list. The pages are read the way manni meta validate reads them. So a value that a {page}.meta.yaml sidecar holds counts, and a finding about it sits at the sidecar’s line.

Every id compares ignoring case. A field is checked only when the set holds a record of that field’s type. A docset with persona records and no audience records keeps a free-form audiences:, and no rule reads it.

A record or page read from outside the run’s directory is borrowed. The run’s directory is the config file’s, or the working directory when no config file governs the run. A file is outside it when its path from there starts with .., or sits on another drive.

A path typed on the command line resolves from the working directory. So a run over typed paths also owns the working directory, wherever -c puts the config file.

A borrowed file resolves references like any other, and no rule files a finding in it. A local page’s typo still fails against borrowed records, with its did-you-mean. A borrowed journey no page names draws no unserved-journey, and a borrowed persona no page names draws no unused-record. A borrowed journey’s steps are each unchecked, so no step rule fires. Share one strategy across repositories sets it up.

A journey can carry steps:, its stages in order, each with the page that carries it. Each item is a mapping of stage, doc, exists and note. A field of another type reads as absent, and manni meta validate checks the shape. Steps count from 1, in the order written.

A step’s doc names a page in one of two forms:

  • A repo path, such as docs/guide.md. It resolves from the config file’s directory, or from the working directory when no config file governs the run. It names the file exactly.
  • A site route, a path that starts with /, such as /manni/meta/ci/. It resolves through graph.routes, as a link does for manni graph. manni strategy reads graph: only when a step names a route.

Anything after # or ? is dropped first, so an anchor is not checked. A URL with a scheme, such as https://, is not checked. Neither is a route that no graph.routes entry covers. A record is never a step’s page.

Each step gets one state. The tool detects what it can, and reads exists only for what it cannot detect:

docexistsState
names a page that names the journey in journeys:absent, true or falseserved
names a page that does notabsent, true or falseunclaimed
names a pagepartialpartial
names no pagefalseunwritten
absentabsent or falseunwritten
names no pageabsent, true or partialbroken
absenttrue or partialbroken
a scheme URL or an unmapped routeabsent, true or partialunchecked
a scheme URL or an unmapped routefalseunwritten

A step that names a page claims it exists, unless it says exists: false. That tells a planned page from a typo. Coverage is the served steps over all steps, so partial, unclaimed and unchecked steps do not count. A step of a draft or archived journey draws no finding, and get and list still show its state.

This set breaks each rule once. A finding sits on the line of the value that caused it. A finding about a whole record sits on its id: line, or on its first line when it declares no id:

Terminal window
$ manni strategy check
docs/guide.md:5
warning manni:strategy/missing-parent personas: does not name "maya", the persona of journey "m1".
Add it, or drop "m1" from journeys:.
manni:strategy missing-parent: A page names a journey but none of its personas in personas:, or a persona but not its audience in audiences:.
Guide: https://hawkeyexl.github.io/manni/strategy/reference/rules/#missing-parent
error manni:strategy/undefined-reference journeys: "m9" names no journey.
Did you mean "m1"?
Correct the value, or add a record with type: journey and id: m9.
manni:strategy undefined-reference: A page's audiences:, personas: or journeys:, or a record's audience: or personas:, names no record of that type.
Guide: https://hawkeyexl.github.io/manni/strategy/reference/rules/#undefined-reference
strategy/audiences/ci-engineers.md:1
notice manni:strategy/unused-record no page and no record names this audience.
manni:strategy unused-record: No page and no record names an audience or a persona.
Guide: https://hawkeyexl.github.io/manni/strategy/reference/rules/#unused-record
strategy/journeys/d1.md:3
warning manni:strategy/unserved-journey no page names this journey in journeys:.
Name it on the pages that serve it.
manni:strategy unserved-journey: No page names the journey in journeys:. Records do not count, and a draft or archived journey is exempt.
Guide: https://hawkeyexl.github.io/manni/strategy/reference/rules/#unserved-journey
strategy/journeys/m1.md:3
error manni:strategy/duplicate-id id: "m1" is also the id of strategy/journeys/m2.md:3.
Give each record its own id.
manni:strategy duplicate-id: Two records share an id, ignoring case.
Guide: https://hawkeyexl.github.io/manni/strategy/reference/rules/#duplicate-id
strategy/journeys/m2.md:3
error manni:strategy/duplicate-id id: "M1" is also the id of strategy/journeys/m1.md:3.
Give each record its own id.
manni:strategy duplicate-id: Two records share an id, ignoring case.
Guide: https://hawkeyexl.github.io/manni/strategy/reference/rules/#duplicate-id
strategy/personas/devin.md:1
warning manni:strategy/orphan-record audience: missing, so this persona belongs to no audience.
Name one with audience:.
manni:strategy orphan-record: A persona names no audience, or a journey names no persona, while the set holds records of the parent type.
Guide: https://hawkeyexl.github.io/manni/strategy/reference/rules/#orphan-record
3 errors, 3 warnings, 1 notice in 7 records

The step rules need a journey with steps. This journey’s nine steps break each of them. A finding about a doc sits on the doc: line, one about exists on the exists: line, and an unwritten-step on the step’s first line:

Terminal window
$ manni strategy check
strategy/journeys/m1.md:16
warning manni:strategy/missing-journey step 4: docs/reference.md does not name "m1" in journeys:.
Add it to that page's journeys:, or point the step at a page that does.
manni:strategy missing-journey: A step's page does not name the journey in journeys:.
Guide: https://hawkeyexl.github.io/manni/strategy/reference/rules/#missing-journey
strategy/journeys/m1.md:18
error manni:strategy/broken-step step 5: doc "docs/gide.md" names no page.
Correct doc, or set exists: false if the page does not exist.
manni:strategy broken-step: A step's doc names no page, or a step says exists: true or partial and has no doc. A step with exists: false is exempt.
Guide: https://hawkeyexl.github.io/manni/strategy/reference/rules/#broken-step
strategy/journeys/m1.md:21
warning manni:strategy/stale-step step 6: exists: false, but doc "docs/ci/index.md" names a page.
Set exists: true or partial, or drop exists:.
manni:strategy stale-step: A step says exists: false, and its doc names a page.
Guide: https://hawkeyexl.github.io/manni/strategy/reference/rules/#stale-step
strategy/journeys/m1.md:22
notice manni:strategy/unwritten-step step 7: doc "/site/tutorial/" names a page that does not exist.
manni:strategy unwritten-step: A step says exists: false, or has no doc.
Guide: https://hawkeyexl.github.io/manni/strategy/reference/rules/#unwritten-step
strategy/journeys/m1.md:25
notice manni:strategy/unwritten-step step 8: no page carries this stage.
manni:strategy unwritten-step: A step says exists: false, or has no doc.
Guide: https://hawkeyexl.github.io/manni/strategy/reference/rules/#unwritten-step
1 error, 2 warnings, 2 notices in 4 records

A value names no record of the type its field holds. The finding sits on the value’s line. When an id of that type is a few edits away, the message suggests it:

journeys: "s-17" names no journey. Did you mean "s17"? Correct the value, or add a record with type: journey and id: s-17.

When the value is the id of a record of another type, the message says so. It suggests the record’s own persona or audience when it has one:

personas: "s17" is a journey, not a persona. Did you mean its persona, "sara"?

A persona named in journeys: has no journey above it, so the message names the field it belongs in instead: Move it to personas:.

A record’s audience: or personas: is checked the same way. Each value of a journey’s personas: gets its own finding, on that value’s line:

personas: "mya" names no persona. Did you mean "maya"?

Reported on every record that shares the id, each naming the others with their file and line. Ids compare ignoring case, so M1 and m1 collide. Two files with no id: and the same name in different folders collide too. Give each record its own id.

A borrowed record draws no finding. When every other record with the id is borrowed, the fix on the owned record says to remove the copy:

id: "maya" is also the id of ../manni/docs/content-strategy/personas/maya.md:3. That record is borrowed from outside this repository. Remove this copy, or give it its own id.

When another owned record shares the id too, each owned record keeps Give each record its own id.

A page that names a journey also names at least one of that journey’s personas. A page that names a persona also names its audience. The rule reads a page’s own values and not a record’s. It reports each missing parent once per page, on the line of the first value that implies it:

personas: does not name "maya", the persona of journey "m1". Add it, or drop "m1" from journeys:.
audiences: does not name "schema-authors", the audience of persona "sara". Add it, or drop "sara" from personas:.

A journey that serves several personas is satisfied by any one of them. When the page names none, the message lists them all:

personas: names none of "maya", "sara", the personas of journey "m5". Add one, or drop "m5" from journeys:.

A persona with no audience:, or a journey with no personas:. It fires only while the set holds a record of the parent type, so a set with no audience records has no orphan personas. Name the parent:

audience: missing, so this persona belongs to no audience. Name one with audience:.
personas: missing, so this journey belongs to no persona. Name one with personas:.

No page names the journey in journeys:. A record’s own journeys: serves no reader, so it does not count. A journey can carry lifecycle from the manni:lifecycle:1.0.0 vocabulary. A draft journey is not live yet, and an archived one no longer is, so the rule skips both.

There are two fixes. Name the journey on the pages that serve it. Or, while those pages are still to be written, mark the journey lifecycle: draft:

---
type: journey
id: m33
title: Hold a new page to its journey
personas: [maya]
lifecycle: draft
---

Raise the rule to error once every journey is served. A new journey then lands with a page that serves it, or as a draft.

No page names the audience or persona, and no record names it as its parent. It is a notice, so it never fails a run. A journey has unserved-journey instead.

A step’s doc names no page of the run, or a step claims a page with exists: true or partial and names none. Both leave a dangling path in the journey, so the rule is an error. The finding sits on the doc: line, or on the exists: line when there is no doc:

step 5: doc "docs/gide.md" names no page. Correct doc, or set exists: false if the page does not exist.
step 3: exists: true, but the step has no doc. Name its page in doc:, or set exists: false.

Correct the path or the route. When the page is still to be written, say so with exists: false, and the step becomes an unwritten-step notice. A run given paths reads only those pages, so a step whose page is outside them is broken there.

A step says exists: false, and its doc names a page that is there. The page was written after the step. The finding sits on the exists: line:

step 6: exists: false, but doc "docs/ci/index.md" names a page. Set exists: true or partial, or drop exists:.

A step’s page does not name the journey in its own journeys:. Other tools read the page’s journeys:, so the step alone does not make the claim. The finding names the page by its path from the working directory, and sits on the doc: line:

step 4: docs/reference.md does not name "m1" in journeys:. Add it to that page's journeys:, or point the step at a page that does.

Add the journey to the page’s journeys:, or point the step at the page that carries the stage. A journey whose steps’ pages all leave it out also draws unserved-journey, and each names its own fix.

A step says exists: false, or has no doc at all. It gives a planned page a file and a line. It is a notice, so it never fails a run. The finding sits on the step’s first line:

step 7: doc "/site/tutorial/" names a page that does not exist.
step 8: no page carries this stage.

Write the page and drop exists: false, or turn the rule off under strategy.severity.