Skip to content

CLI reference

manni strategy exposes three verbs. list prints the records as a tree, get shows one record and what names it, and check holds the pages to the records. There is no default. A bare manni strategy prints usage and exits 2.

Terminal window
manni strategy [options] <command> [command options] [arguments]

Global options are accepted before the subcommand.

OptionDescription
-V, --versionPrint the manni version and exit.
-h, --helpPrint help for the program or a subcommand.

The strategy tool. Every command on this page lives under it. strategy accepts its own -V, --version, which reports the manni version, and -h, --help. Run with no subcommand, it prints its usage to stderr and exits 2.

Terminal window
manni strategy [options] <command> [command options] [arguments]

list, get and check read the set the same way, with the same flags. They are the flags manni term takes for the same jobs.

  • Paths are positional. Files, directories and globs, space-separated. Without them the run reads every configured collection.
  • Records and pages are read together. A run that names paths has to name the records too: manni strategy check docs/content-strategy/ docs/guides/.
  • - reads stdin, alongside any named paths. It needs --as <format>, which names one of the metadata tool’s extractors.
  • A record is a page that declares a type. That is type: audience, type: persona or type: journey, in any format the metadata tool reads. Its id is its id:, else its file name without the extension.
  • Every page contributes references. A page’s audiences:, personas: and journeys: are read with what its {page}.meta.yaml sidecar adds.
  • No records is an error. A run that reads files but finds no record exits 2, unless --allow-empty or strategy.allowEmpty says an empty set is fine.

List the records as a tree. Each audience has its personas under it, and each persona has its journeys under it.

Terminal window
manni strategy list [paths...] [options]
ArgumentDescription
[paths...]Files, directories, or globs to read. Use - to read from stdin, with --as. Optional. Without paths the run covers every configured collection. See the shared input model.
OptionArgumentDefaultDescription
--as<format>n/aInput format for stdin. Required with -.
--ext<list>supported extensionsComma-separated extensions kept when expanding directories and globs. Given once.
--exclude<glob>n/aGlob to exclude from directory and glob walks. Repeatable, one glob per occurrence.
--collection<name>every collectionRead one configured collection rather than every declared one. Repeatable, one name per occurrence. Cannot be combined with positional paths (exit 2).
--allow-emptyn/aoffTreat a missing path, no matched files or no records as success rather than an error (exit 2). Wins over config allowEmpty:.
--no-gitignoren/aonRead files .gitignore covers as well. Wins over config respectGitignore:.
-c, --config<path>discoveredPath to a config file. The file must exist (exit 2).
--no-colorn/aoffDisable colored output. Color applies on a TTY only, and never under NO_COLOR.
-f, --format<pretty|json>prettyOutput format. An unknown value is an error (exit 2).

pretty prints the tree, then a count of each type. The number is how many pages name the record directly. A journey that serves several personas sits under each of them, and the count holds it once. A record whose parents are not in the set sits at the top level, and a record with no title shows its id:

record pages title
docs-engineers 2 Docs-as-code teams
maya 2 Maya, Documentation Engineer
m1 2 Stand up metadata validation for my repo
m2 1 Tighten the standard without breaking the build
1 audience, 1 persona, 2 journeys

When some journey in the set has steps, a steps column sits between pages and title. It holds served steps over steps on each journey that has them, and is blank on every other row:

record pages steps title
docs-engineers 2 Docs-as-code teams
maya 2 Maya, Documentation Engineer
m1 2 3/9 Stand up metadata validation for my repo
m2 0 0/1 Tighten the standard without breaking the build
1 audience, 1 persona, 2 journeys

json prints { "records": [...] }, in the tree’s order, each record once. Each record is the object get -f json prints.

CodeMeaning
0The records were listed.
2Operational or usage error. See usage errors.

Show one record, the records it belongs to, the records under it, and the pages that name it.

Terminal window
manni strategy get <id> [paths...] [options]
ArgumentDescription
<id>The record’s id, in any case.
[paths...]Files, directories, or globs to read, exactly as on list.
OptionArgumentDefaultDescription
--as<format>n/aInput format for stdin. Required with -.
--ext<list>supported extensionsComma-separated extensions kept when expanding directories and globs.
--exclude<glob>n/aGlob to exclude. Repeatable.
--collection<name>every collectionRead one configured collection. Repeatable.
--allow-emptyn/aoffTreat a missing path, no matched files or no records as success.
--no-gitignoren/aonRead files .gitignore covers as well.
-c, --config<path>discoveredPath to a config file.
--no-colorn/aoffDisable colored output.
-f, --format<pretty|json>prettyOutput format.

pretty prints the title and where the record was read, then a row per field. A journey shows its personas, one per line. A persona shows its audience and its journeys, and an audience its personas. The pages row prints none when no page names the record:

Maya, Documentation Engineer strategy/personas/maya.md:1
type persona
id maya
description Owns a docs-as-code repo and wants its frontmatter correct.
audience docs-engineers Docs-as-code teams
journeys m1 Stand up metadata validation for my repo
m2 Tighten the standard without breaking the build
pages docs/ci.md
docs/guide.md

A record that sets lifecycle shows it in a row after description. This journey is a draft, so no page names it yet:

Gate a new page on its journey strategy/journeys/m3.md:1
type journey
id m3
description A journey whose pages are still to be written.
lifecycle draft
personas maya Maya, Documentation Engineer
pages none

A journey with steps shows a steps row after personas and before pages. Its first line is the coverage, served steps over steps. Each step follows: its number, its state, and its stage. Under it, dim, is the page its doc names, else the doc as written, then its note. A step with neither has no second line. The states are on the rules reference:

steps 3 of 9 served
1 served Read the guide
docs/guide.md
2 served Add the CI step
docs/ci/index.md
3 partial Go deeper in the guide
docs/guide.md
Covers the config half only.
4 unclaimed Look up a flag
docs/reference.md
5 broken Follow the walkthrough
docs/gide.md
6 served Keep the gate green
docs/ci/index.md
7 unwritten Work through the tutorial
/site/tutorial/
8 unwritten Write a tutorial
9 unchecked Read how a team adopted it
https://example.com/blog/adopting

json prints the record. title is always there: a record with no title gets its id, so title equal to id means none was written. description and lifecycle are there only when the record has them. A persona carries audience, as written. A journey carries personas, always a list. namedBy holds personas on an audience, journeys on a persona, and pages always. Paths are relative to the working directory:

{
"id": "m1",
"type": "journey",
"title": "Stand up metadata validation for my repo",
"file": "strategy/journeys/m1.md",
"line": 1,
"personas": ["maya"],
"namedBy": {
"pages": ["docs/ci.md", "docs/guide.md"]
}
}

A journey with at least one step also carries steps and coverage, after personas and before namedBy. A step carries stage, and doc, exists and note as written when written. line is the step’s first line, and state is one of served, unclaimed, partial, unwritten, broken or unchecked. page is the page doc names, when it names one:

{
"steps": [
{
"stage": "Read the guide",
"doc": "docs/guide.md",
"line": 7,
"state": "served",
"page": "docs/guide.md"
},
{
"stage": "Work through the tutorial",
"doc": "/site/tutorial/",
"exists": false,
"line": 22,
"state": "unwritten"
}
],
"coverage": { "served": 3, "steps": 9 }
}

A borrowed journey’s steps name its owner’s pages, which the run does not hold. Each step’s state is unchecked, and the journey has no coverage. The pretty steps row has no coverage line, and list leaves its steps cell blank.

CodeMeaning
0The record was found and printed.
2No record matched, or an operational or usage error. The message names the set’s size and the nearest id.

Check the pages against the records, the records against each other, and each journey’s steps against the pages they name. The ten rules, their default severities and their messages are on the rules reference.

Terminal window
manni strategy check [paths...] [options]
ArgumentDescription
[paths...]Files, directories, or globs to read, exactly as on list.
OptionArgumentDefaultDescription
--as<format>n/aInput format for stdin. Required with -.
--ext<list>supported extensionsComma-separated extensions kept when expanding directories and globs.
--exclude<glob>n/aGlob to exclude. Repeatable.
--collection<name>every collectionRead one configured collection. Repeatable.
--allow-emptyn/aoffTreat a missing path, no matched files or no records as success.
--no-gitignoren/aonRead files .gitignore covers as well.
-c, --config<path>discoveredPath to a config file.
--no-colorn/aoffDisable colored output.
-f, --format<pretty|json|github|sarif|junit>prettyOutput format. The shapes are below.
--baselinen/aoffCompare against .manni-strategy-baseline.json, or the file config baseline: names. When the file does not exist yet, record this run’s findings in it and exit 0. After that, fail only on findings the file does not hold. Takes no value.

pretty groups findings by file and line. Each row is the severity, the rule id and the message, and the rule’s guidance follows it, dim. The rules reference has a run that breaks every rule. The summary counts each severity:

3 errors, 3 warnings, 1 notice in 7 records

A clean run prints one line. An --allow-empty run that found no records prints the same line with zero counts:

✓ 4 records, 10 references, no findings

When the set’s journeys carry steps, the line also counts the steps it held to their pages:

✓ 4 records, 9 references, 10 steps, no findings

When the run read a borrowed record, the record count says how many, on the clean line and on the summary line. The steps count holds the owned journeys’ steps only:

✓ 84 records (83 borrowed), 90 references, no findings
1 error in 85 records (83 borrowed)

json prints { findings, summary }. Each finding carries rule, ruleId, severity, message, guidance, file and line. It adds the record’s id when the finding is about one, and the field that breaks the rule when one does. A step rule’s field is steps. summary holds records, references, errors, warnings and notices, and baseline when one was read or written. It holds steps, after references, when the set’s journeys carry any. It holds borrowed, after records, when the run read a borrowed record. records and references count borrowed ones too, and no finding’s file is borrowed. references counts every value of audiences:, personas:, journeys: and audience: the run read. A journey’s own personas: counts once:

{
"findings": [
{
"rule": "unserved-journey",
"ruleId": "manni:strategy/unserved-journey",
"severity": "warning",
"message": "no page names this journey in journeys:. Name it on the pages that serve it.",
"guidance": {
"id": "manni:strategy",
"rule": "unserved-journey",
"description": "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"
},
"file": "strategy/journeys/m2.md",
"line": 3,
"id": "m2"
}
],
"summary": {
"records": 4,
"references": 9,
"errors": 0,
"warnings": 1,
"notices": 0
}
}

github prints one workflow command per finding. The colon in the rule id is escaped, because a workflow command reads : as a separator. A clean run prints nothing:

::warning file=strategy/journeys/m2.md,line=3,title=manni%3Astrategy/unserved-journey::no page names this journey in journeys:. Name it on the pages that serve it.%0Amanni:strategy unserved-journey: No page names the journey in journeys:. Records do not count, and a draft or archived journey is exempt.%0AGuide: https://hawkeyexl.github.io/manni/strategy/reference/rules/#unserved-journey

sarif and junit carry the same findings under the rule id manni:strategy/<rule>, in the envelope, paths and fingerprints of the metadata tool’s renderers. A SARIF rule’s shortDescription is the rule’s line from the rules reference, and its helpUri is the rule’s section of that page. A SARIF result’s message carries the guidance lines too. JUnit testcases carry the classname manni.strategy, one per owned file that holds a record or a reference. No annotation, result or testcase names a borrowed file.

--baseline is one flag for both halves of the ratchet.

Terminal window
$ manni strategy check --baseline
✓ 7 findings recorded in .manni-strategy-baseline.json
$ manni strategy check --baseline
✓ 7 records, 8 references, no findings, 7 baselined

A configured baseline: is compared on every run, without the flag. When that file does not exist and the flag is absent, the run stops with exit 2 and names the command that records one.

CodeMeaning
0No error-severity finding the baseline does not already hold. Warnings and notices never move the exit code. A run that records a baseline exits 0.
1At least one unbaselined error-severity finding.
2Operational or usage error. See usage errors.

The strategy: section of manni.config.yaml is optional. Every key has a default, and the records and pages come from the family collections:.

KeyTypeDefaultWhat it does
strategy.severity.<rule>notice, warning, error or offthe rules tableMoves one rule’s level. off drops its findings.
strategy.baselinestring, a pathnoneCompares against this baseline on every run. It resolves from the config file’s directory.
strategy.allowEmptybooleanfalseTreats no records as success.
strategy.respectGitignorebooleantrueSkips files .gitignore covers.
strategy:
severity:
unserved-journey: error
missing-journey: error

strategy.severity takes the ten rule names: undefined-reference, duplicate-id, missing-parent, orphan-record, unserved-journey, unused-record, broken-step, stale-step, missing-journey and unwritten-step.

A step whose doc is a / route resolves through the family’s graph.routes, read by manni graph’s own loader. With no entry, every route is unchecked. A run whose steps name no route never reads graph:.

Every usage error goes to stderr, prefixed manni:, and exits 2. Nothing is written.

Invocationstderr
strategythe full help
strategy geterror: missing required argument 'id'
strategy get myamanni: no record "mya". 82 records; did you mean "maya"?
strategy get zzzzmanni: no record "zzzz". 82 records.
strategy check -f xmlmanni: unknown format "xml". Expected pretty | json | github | sarif | junit.
A run that finds no recordsmanni: no strategy records found. A record is a page declaring type: audience, type: persona or type: journey.
strategy check - with no --asmanni: Reading from stdin (`-`) requires --as <format> to choose an extractor.
strategy check --collection site docs/manni: --collection selects a configured collection; it cannot be combined with paths.
strategy check --collection site, with no configmanni: --collection needs a config file to select from.
strategy check --collection nopemanni: no collection named "nope" in manni.config.yaml. Configured: site, strategy.
strategy check nowhere/manni: File not found: "nowhere/". Check the path. Named paths resolve from the working directory.
No paths and no collectionsmanni: No files to read. Pass paths/globs, or declare a collection under `collections:` in manni.config.yaml.
strategy: { paths: [x] } in configmanni: manni.config.yaml: strategy does not carry "paths". Name a collection under collections:.
strategy: { exclude: [x] } in configmanni: manni.config.yaml: strategy does not carry "exclude". Name a collection under collections:.
strategy: [1] in configmanni: strategy: in manni.config.yaml must be a mapping.
An unknown key under strategy:manni: Unknown key "rules" under strategy: in manni.config.yaml. Supported keys: severity, baseline, allowEmpty, respectGitignore.
An unknown rule under strategy.severitymanni: Unknown key "unserved" under strategy.severity: in manni.config.yaml. Supported keys: undefined-reference, duplicate-id, missing-parent, orphan-record, unserved-journey, unused-record, broken-step, stale-step, missing-journey, unwritten-step.
A step names a / route, and a graph.routes entry has no rootmanni: Invalid config in <config path>: then manni: /graph/routes/0: must have required property 'root', graph’s own message
strategy: { allowEmpty: "yes" }manni: strategy.allowEmpty in manni.config.yaml must be a boolean.
A severity that is not a levelmanni: manni.config.yaml: strategy.severity.unserved-journey "fatal" is not a level. Expected notice | warning | error | off.
The configured baseline file is missingmanni: Baseline ".manni-strategy-baseline.json" not found. Record one with `manni strategy check --baseline`.

A type error never quotes the value back, because a value can be a secret pasted under the wrong key. A severity level is quoted, because it is not one.