A docset’s content strategy says whom it serves. manni strategy keeps that
strategy as records, and checks every page that names one against them.
A record is a page that declares one of three types. It is one record per
file, in any format manni meta reads.
An audience declares type: audience. It is a group of readers.
A persona declares type: persona, and names its audience in
audience:.
A journey declares type: journey, and names the personas it serves
in personas:. A journey can serve more than one persona.
A record’s id is its id:, or else its file name without the extension.
list and get print its title and description. This is the journey
this section serves, as this repository keeps it:
docs/content-strategy/journeys/s17.md
---
type: journey
id: s17
title: Hold the pages to the strategy they were written for
description: The audiences, personas and journeys Sara's docs serve are records, and every page that names one is checked against them.
personas: [sara]
---
The prose below the frontmatter is the journey itself. No rule reads it.
A journey can also carry lifecycle, from the
manni:lifecycle:1.0.0
vocabulary. A draft journey is not live yet, and an archived one no
longer is. Neither is expected to have a page, so check skips both when it
looks for an unserved journey. A journey can land as a draft before its pages
exist.
Records also carry the fields
manni test tasks reads, where a synthetic
user attempts a journey as one of its personas. No strategy rule reads them.
A journey’s trigger, entry-point and success-criteria tell the
synthetic user what started the task, where to begin, and when it is done.
A journey’s workspace names the directory each run starts from.
A journey’s evals: are eval entries that say what done looks like.
Journeys that declare evals: are the golden set, which a bare
manni test tasks runs.
A persona’s goals, pains and constraints tell the synthetic user who
it plays. Each constraint is judged after the run, as a warning.
manni meta validate checks a record’s fields against the schemas a repo
applies through meta.overrides. This repository keeps one schema per record
type, for an
audience,
a
persona
and a
journey.
Three overrides apply them. The journeys override also lists the lifecycle
vocabulary, so a value such as lifecycle: drafty fails meta validate:
A page says whom it serves in three fields of the
manni:audience:1.0.0
vocabulary. Each value is a reference, and the check resolves it against the
records.
Field
Names
Where it lives
audiences:
audience records
the page
personas:
persona records
the page or its {page}.meta.yaml sidecar
journeys:
journey records
the page or its {page}.meta.yaml sidecar
A value in a sidecar counts as one in the page does. A finding about it sits
at the sidecar’s line.
A field is checked only when the set holds a record of its type. A docset
that keeps persona records and no audience records keeps a free-form
audiences:, and no rule reads it. So the records can arrive one type at a
time.
The records and the pages come from the family’s
collections. Give the
records a collection of their own. This is this repository’s config, cut to
the lines the domain reads:
The overrides above then check each record’s fields.
A record can also rest on evidence. Each goal or pain is pinned to the
interview line it came from, and manni cite check says when one stops
holding. Rest personas on evidence sets that up.
The strategy: section is optional, because every key has a default. The
configuration reference
lists the keys. This repository raises one rule, so a journey cannot land
without a page that serves it, unless it lands as a draft:
Prints the records as a tree, with how many pages name each one.
manni strategy get <id> [paths...]
Shows one record, its parents, the records under it, and the pages that name it.
manni strategy check [paths...]
Checks every reference against the records, and the records against each other.
Run with no paths, each command reads every collection. Here they run over
a set of one audience, one persona and two journeys, which two pages name.
list prints the tree:
Terminal window
$ manni strategy list
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
get matches the id in any case. Its pages row answers which pages serve
the record:
That set breaks every rule once, so the run goes on to five findings about
the records. It ends 3 errors, 3 warnings, 1 notice in 7 records, and it
exits 1 because of the errors. Warnings and notices never fail a run.
A journey’s steps: lists its stages in order, each with the page that
carries it. A step’s doc is a repo path, or a site route that
graph.routes maps to a file. A page still to be written says
exists: false:
strategy/journeys/m1.md
---
type: journey
id: m1
title: Stand up metadata validation for my repo
personas: [maya]
steps:
- stage: Read the guide
doc: docs/guide.md
- stage: Add the CI step
doc: /site/ci/
- stage: Look up a flag
doc: docs/reference.md
- stage: Work through the tutorial
doc: /site/tutorial/
exists: false
---
get then shows the path in order, with each step’s state and page:
Terminal window
$ manni strategy get m1
Stand up metadata validation for my repo strategy/journeys/m1.md:1
type journey
id m1
personas maya Maya, Documentation Engineer
steps 2 of 4 served
1 served Read the guide
docs/guide.md
2 served Add the CI step
docs/ci/index.md
3 unclaimed Look up a flag
docs/reference.md
4 unwritten Work through the tutorial
/site/tutorial/
pages docs/ci/index.md
docs/guide.md
The reference page carries stage 3, but its own journeys: leaves out m1.
check names that as a missing-journey warning on the step’s doc: line.
It names the unwritten tutorial as an unwritten-step notice. A doc that
names no page at all is a broken-step error. list adds a steps column
with each journey’s served steps over its steps. The
steps section of the rules
reference has every state.
Several docsets can serve the same readers from one set of records. Keep the
records in one repository, and check it out beside each repository that
reads them:
work/
manni/ owns docs/content-strategy/
manni-plugin/ borrows it
manni.config.yaml
docs/
strategy/journeys/ the journeys only the plugin serves
In the borrowing repository, the strategy collection names the owner’s
folders as directories. A glob that matches nothing is not an error, so a
missing checkout would quietly narrow the run. A missing directory stops it
with exit 2:
manni-plugin/manni.config.yaml
collections:
- name: site
paths: ["docs/**/*.md"]
- name: strategy
paths:
- ../manni/docs/content-strategy/audiences/
- ../manni/docs/content-strategy/personas/
- ../manni/docs/content-strategy/journeys/
- strategy/journeys/
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 checkout inside that directory reads as the
repository’s own, so check out side by side.
A borrowed record resolves every page’s references, as an owned one does.
check files no finding in a borrowed file. The owner’s own check reports
those. The summary counts the borrowed records:
Terminal window
$ manni strategy check
✓ 84 records (83 borrowed), 90 references, no findings
A kept copy of a borrowed record draws duplicate-id, and its fix says to
remove the copy:
Terminal window
$ manni strategy check
strategy/personas/maya.md:3
error manni:strategy/duplicate-id 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.
manni:strategy duplicate-id: Two records share an id, ignoring case.
The journeys the borrowing repository owns are checked as before. One that
no page names still draws unserved-journey. A borrowed journey’s steps name
its owner’s pages, so get shows each as unchecked, with no coverage line.
In CI, check out both repositories side by side. The second checkout’s ref:
pins the records to a tag of the owner’s:
A typo on a plugin page annotates that page, and nothing annotates
../manni/.
Coverage across products is data. Each repository’s strategy list -f json
prints every record with the pages that name it. Joined by id, the files
give pages per journey per repository:
Each repository is its input file’s base name. Append
| select([.pages[]] | add == 0) to list the journeys no product serves.
The ids match on both sides because there is one set of records. A journey
one product owns can share an id with another product’s, so give owned
journeys a product prefix.
The check says the pages agree with the records. It cannot say whether
readers take the journeys.
Measure the strategy against reader traffic
joins a page-views export to the records, for traffic per journey and per
persona.
Each finding names its rule, and its Guide: line links to that rule’s
section of the rules reference. That page
gives each of the ten rules its default severity, its exact message, and the
edit that clears it. It also has the full run of the set above.
Rest personas on evidencePin each goal and pain to the evidence it came from, require the pins, and repair them as records and evidence change.
Check rulesEach rule, its default severity, the message it prints, and what fixes it.
CLI referenceEvery flag, every output format, the strategy: keys, the exit codes and every usage error.
That every audiences:, personas: and journeys: value names a record
of its type. That a record’s audience: or personas: names one too.
That no two records share an id, ignoring case. That a page naming a
journey also names one of its personas, and a persona its audience. That
some page names every journey that is not a draft or archived. That each
journey step names a page, and that the page names the journey.
It doesn't
Read the prose of a record or a page. It doesn’t require a page to carry
journeys: either. That is a question of presence, so it belongs to
manni meta, as a schema override with required: [journeys].
manni graph build types every record in its build. An
audience becomes a schema:Audience, a persona a schema:PeopleAudience,
and a journey a graph:Journey. Each node sits at
{baseIri}strategy/{type}/{id}. A page’s journeys: become
dcterms:isPartOf edges, and its audiences: and personas: become
schema:audience edges. So the graph answers which pages serve a journey:
An edge needs the record it names in the build. A value no record declares
derives nothing, and check reports it here as undefined-reference.
Records are author-facing, so graph build --no-authoring leaves them out.
This repository’s site publishes only its site collection, which holds no
record. How manni graph works
lists every triple a record derives.
The domain is in play once a collection holds a record. manni status then
shows the strategy row, with a count of each type. manni check runs
manni strategy check beside the other domains’ checks.
Gate every check covers the family verbs, and
the family CLI reference has the in-play
rule for each domain.
manni cite check reads the same records. A citation finding on a page that
names a persona or journey record says whom the page serves. Its pretty
report ends with a roll-up of those records, worst first, and its json
carries them as impact. A value with no record is left out, since
manni strategy check already reports it.
Who a finding affects shows the
output.
manni check prints the same roll-up over every check’s findings, so a run
that fails several domains is ranked by the readers it reaches.
Fix first what your readers hit
walks through it.
manni meta get names the records a page names, one
record line each, with the record’s type, id
and file. The Claude Code plugin hands an agent the same references when it
reads the page, as Keep an agent’s docs green
shows.
A judged eval with readers: personas reads the descriptive fields of each
persona its page names. See
Judge a page as its readers.