CLI reference
Fifteen commands under one domain, and one subcommand under shapes. Every flag below is verified against
src/graph/cli.ts, and the exit codes are exercised by the inline tests at the
foot of this page.
manni graph <command> [command options] [arguments]There is no default subcommand. manni graph docs/ is a usage error, because a
domain owns verbs and build is the verb.
Common conventions
Section titled “Common conventions”Two verbs read documents: build and fill. They take positional paths,
which are files, directories, or globs relative to the working directory. With
none given they read every
collection the config
declares, or the ones --collection names. A collection’s paths resolve from
the config file’s directory, wherever the command runs. A document’s
graph:path and node IRI count from the working directory for a typed path, and
from the config file’s directory for a collection. Nothing matching is
exit 2, not an empty successful build, unless --allow-empty is given.
- reads one more document from stdin, alongside any named paths and never
instead of them. It needs --as, because stdin has no file name to choose a
parser by. Its display path is <stdin>, its node is <baseIri>stdin, and its
relative links resolve from the working directory. It has no git history.
It belongs to no collection, so no external-metadata manifest supplies its fields.
The other verbs read the built graph rather than the documents: check,
query, stats, search, traverse, lookup, retrieve, embed and export all load the
Turtle file at graph.out, or the one -g names. Build first, then ask.
shapes reads neither. It reads the shapes files check runs.
| Flag | Commands | Meaning |
|---|---|---|
-c, --config <path> | all except init | Path to manni.config.yaml. Without it, manni graph looks for that file from the working directory up to the repository root. It reads the file’s graph: key plus the top-level collections: and providers: keys. With no file at all, built-in defaults apply and no collections are declared. |
--no-config | same as above | Ignore any discovered config file and run on built-in defaults. It shares one option with -c, so the one written later wins. |
--collection <name> | build, fill | Read only this configured collection. Repeatable, one name per occurrence, never comma-separated. It cannot be combined with positional paths, and it needs a config file to select from. Each of those is exit 2. |
--exclude <glob> | same as above | Glob to exclude. Repeatable, one glob per occurrence. |
--as <format> | same as above | Parse every input as asciidoc, html, markdown, mdx, rst or xml, whatever its extension. Required with -. Another value is exit 2. |
--ext <list> | same as above | Extensions a directory walk or a glob keeps, comma-separated and given once. A file named outright is kept whatever this says, when a parser reads its extension. A named file none reads is skipped with a warning. |
--allow-empty | same as above | Treat zero matched files as success. |
-g, --graph <path> | the graph readers | The built Turtle file. Defaults to the resolved graph.out. |
-f, --format <format> | every verb that reports | pretty or json. check adds github and sarif, and shapes show adds markdown. An unknown value is refused while the options are parsed. A typo therefore costs a message rather than a graph load: exit 2, with the allowed set named. |
Flags override config; they do not bypass it. An unset flag falls through to
the resolved graph: value, so a config file and the command line reach the same
code.
One separator per list. A positional variadic is space-separated because argv
is. --ext, --fields and --predicates are written <list>, comma-separated
and given once. --collection, --exclude and --shapes are repeatable and never
split on commas, because a path or a name may hold one.
Each command’s table below lists every flag it accepts, the shared ones included. A table is the whole surface of its command.
Global options
Section titled “Global options”Global options are accepted before the subcommand.
| Option | Description |
|---|---|
-V, --version | Print the manni version and exit. |
-h, --help | Print help for the program or a subcommand. |
The knowledge-graph tool. Every command on this page lives under it. graph
accepts its own -V, --version, which reports the manni version, and
-h, --help.
manni graph <command> [command options] [arguments]Options
Section titled “Options”| Option | Argument | Default | Description |
|---|---|---|---|
--no-color | n/a | color on a TTY | Turn colored output off. NO_COLOR in the environment does the same, and output that is not a terminal is never colored either way. |
graph init
Section titled “graph init”Writes a starter graph: section into manni.config.yaml in the working
directory, and prints the path it wrote. Takes no arguments and no options.
manni graph initgraph build
Section titled “graph build”Reads the documents, derives the graph, and writes canonically sorted Turtle. Two runs over unchanged inputs are byte-identical. Dates come from frontmatter and then from git committer times, never from the clock. Every node gets a deterministic IRI rather than a blank node.
A page’s frontmatter is its metadata as manni meta validate reads it. A field
kept in a collection’s
external-metadata manifest derives
exactly as one written on the page.
manni graph build [paths...] [options]$ manni graph build -c test/graph/fixtures/dd/docs.config.yamlWrote .tmp/dd/graph.ttl (3 docs, 157 triples, 3 concepts)A page piped in with - joins the graph beside the named paths:
$ cat draft.md | manni graph build - docs/ --as markdownWrote graph/graph.ttl (4 docs, 152 triples, 3 concepts)When the terms derive source reads a termbase, the summary says how many
concepts it gave:
$ manni graph buildWrote graph.ttl (6 docs, 110 triples, 3 concepts (2 from the termbase))When the build holds strategy records, the summary counts
them last. One record reads 1 strategy record, and a build with none leaves
the clause out:
$ manni graph build -o .manni/graph/internal/graph.ttlWrote .manni/graph/internal/graph.ttl (344 docs, 23385 triples, 560 concepts (50 from the termbase), 82 strategy records)A build carries author-facing content by default. That is the strategy
records, typed, and every value a page’s schemas mark
x-manni-location: external, at any depth. --no-authoring leaves both out
for one run, and graph.authoring
sets the default. Publish the graph
says what each derive source loses.
Warnings go to stderr, so stdout stays the one-line summary. A degraded build is still a successful one. A repository git cannot read warns once and builds the rest. A near-miss frontmatter key warns and derives nothing. Neither changes the exit code.
Each page’s parsed body is cached in .manni/graph/analyze-cache/ under the
working directory, one file per page. A page is parsed again only when its
bytes, its path, its --as format or the manni version changed. Links resolve
against the current pages on every run, so the cache never changes the Turtle.
A damaged entry is parsed afresh, and deleting the directory is always safe.
The graph check in manni check reads the same cache.
Arguments
Section titled “Arguments”| Argument | Description |
|---|---|
[paths...] | Files, directories, or globs to build from, relative to the working directory. - reads stdin as well. Optional; without them, the configured collections. |
Options
Section titled “Options”| Option | Argument | Default | Description |
|---|---|---|---|
-c, --config | <path> | discovered | Path to manni.config.yaml. See Common conventions. |
--no-config | n/a | off | Ignore any discovered config file. |
--collection | <name> | every collection | Read only this configured collection. Repeatable. |
--exclude | <glob> | n/a | Glob to exclude. Repeatable. |
--as | <format> | by extension | Parse every input as asciidoc, html, markdown, mdx, rst or xml. Required with -. |
--ext | <list> | .md,.markdown,.mdx,.html,.htm,.flsnp,.adoc,.asciidoc,.rst,.dita,.ditamap,.fltoc,.flbrs | Extensions for directory walks and globs, comma-separated. |
--allow-empty | n/a | off | Zero matched files writes an empty graph and exits 0. |
-o, --out | <path> | config out, else graph/graph.ttl | Where to write the Turtle. The directory is created if it does not exist. |
--no-authoring | n/a | config authoring, else on | Leave out author-facing content: strategy records, and every value a page’s schemas mark x-manni-location: external. |
graph check
Section titled “graph check”Validates the built graph against SHACL shapes. This is where the emergent
failures live: they exist only after N documents merge into shared nodes, so no
per-file check can see them. Findings carry the family’s
notice | warning | error scale, and an error exits 1.
manni graph check [options]$ manni graph check -c test/graph/fixtures/dd/violations.config.yamlerror: violations/alpha.md, violations/beta.md, violations/gamma.md: "Alpha" and "Beta" form a broader cycle, so each is its own ancestor. Remove one `broader` or `narrower` value on the cycle. at https://example.com/dd/concept/alpha skos:broader manni:graph-shapes:1.3.0 Concept-broader: A more general concept. A concept is never its own ancestor. Guide: https://hawkeyexl.github.io/manni/graph/fix/error: violations/alpha.md, violations/beta.md, violations/gamma.md: A concept is related to a concept that is also broader than it. Remove one link: from `related-concepts` or `related-terms`, or from `broader` or `narrower`. at https://example.com/dd/concept/alpha skos:related manni:graph-shapes:1.3.0 Concept-related: A concept on the same level as this one. It is never also an ancestor or a descendant. Guide: https://hawkeyexl.github.io/manni/graph/fix/error: violations/alpha.md: The page lists a variant in both `applies-to` and `not-applicable-to`. Keep it only in the one that is true. at https://example.com/dd/doc/violations/alpha.md graph:notApplicableToVariant manni:graph-shapes:1.3.0 Document-notApplicableToVariant: The product variants the page does not apply to. A variant cannot be in both lists. Guide: https://hawkeyexl.github.io/manni/graph/fix/
3 errors, 0 warnings, 0 noticesEach finding opens with its severity and the pages it came from, which are
where an author edits. (no page) stands in when a finding names none. The
message names the frontmatter key to change. The lines under it are dim.
| Line | What it says |
|---|---|
at <focus> <path> | The graph node that failed and the property, with known prefixes compacted. A finding with no property shows the node alone. |
<id> <rule>: <description> | The shapes file’s id, the rule that judged the finding, and what the rule is for. |
Guide: <url> | The page that explains how to fix it. |
The last two print only when a shape judged the finding. A sample-question
finding has no shape, so only its at line follows it.
-f json prints the same findings. Each one ends with guidance, the last two
lines as an object, or null when no shape judged it.
{ "severity": "error", "shaclSeverity": "violation", "message": "The page lists a variant in both `applies-to` and `not-applicable-to`. Keep it only in the one that is true.", "focusNode": "https://example.com/dd/doc/violations/alpha.md", "path": "https://hawkeyexl.github.io/manni/graph/ns#notApplicableToVariant", "docs": ["violations/alpha.md"], "guidance": { "id": "manni:graph-shapes:1.3.0", "rule": "Document-notApplicableToVariant", "description": "The product variants the page does not apply to. A variant cannot be in both lists.", "guide": "https://hawkeyexl.github.io/manni/graph/fix/" }}-f github writes one workflow annotation per finding, on the first page it
names. The guidance lines follow the message, each after %0A.
-f sarif writes one SARIF 2.1.0 run whose driver is manni-graph. A result’s
ruleId is manni:graph/<rule>, or manni:graph/shacl when no shape judged
it. Its locations are the pages it names, each at startLine 1. Its
message.text is the message and the guidance lines, and
properties.guidance holds the object. A rule carries the shape’s description
and guide as fullDescription and helpUri. A finding that names no page is
left out of the results. invocations[0].toolExecutionNotifications counts
them instead, as 1 finding names no page. Run manni graph check to see them.
The exit code is the same in every format.
A shapes file named by --shapes speaks to authors the same way when it
declares the parts. Its owl:Ontology header gives the id through
dcterms:identifier and the guide through rdfs:seeAlso. A file with no header
is named by its path, as given. See Govern the graph
for writing one, and graph shapes for reading one.
The bundled shapes are closed, so a predicate the shapes have not learned fails here. That is deliberate: a change to what the graph contains is not done until the shapes say so.
graph check also asks each page’s sample-questions through the retrieval
graph search uses. A page outside the top check.questions.top results for
its own question is a finding at check.questions.severity, warning by
default. It names the question, the page’s rank, and the page found first.
$ manni graph checkwarning: docs/limits.md: "How do I retry a failed upload?" does not find this page in the top 1 (it ranks 2; first is docs/retry.md) at https://example.com/graph/doc/docs/limits.md graph:sampleQuestionA question that finds the page nowhere says it is not found in place of the
rank. The check measures whether a reader finds the page, not whether the page
answers. Configuration sets the
cutoff and the severity.
The questions are read from each page’s source, so a question kept out of the published graph is still asked. A page in the graph that is no longer on disk is skipped, and one warning names how many and says to rebuild.
A question can pin the body lines that answer it, as
manni:ai-context:1.2.0
describes. A pinned question must find the part of the page that holds those
lines. That is a section whose own text covers them, from its heading to the
next heading of any rank. Text before the first heading belongs to the page
itself. A hit on another section of the same page does not count.
$ manni graph checkwarning: docs/retry.md: "How many times does an upload retry by default?" does not find the section that answers it, § Retry limits, in the top 1 (it ranks 2; first is docs/retry.md § Retry a failed upload) at https://example.com/graph/doc/docs/retry.md graph:sampleQuestionA pin with an integrity is compared with the page as it reads now, under the
hashing rule citations use. A pin with none is checked where it points.
| Finding | Severity | What it means |
|---|---|---|
| The answer moved | notice | The pinned lines sit elsewhere now, and are asked there. Update the pin’s lines. |
| The answer moved to two places | warning | More than one place matches, and either counts as found. Pin one of them by hand. |
| The answer changed | check.questions.severity | No lines match the pin, so the question counts as not found. Re-pin it and review it. |
$ manni graph checknotice: docs/upload.md: the answer to "How large may an upload be?" moved from lines 4-5 to 7-8, so manni graph fill --fields sample-questions re-pins it. at https://example.com/graph/doc/docs/upload.md graph:sampleQuestionAfter a rewrite that drops the answer, the same pin reads:
warning: docs/upload.md: lines 4-5 no longer hold the answer to "How large may an upload be?" as pinned, so re-pair it with manni graph fill --fields sample-questions and review it. at https://example.com/graph/doc/docs/upload.md graph:sampleQuestionA question marked reviewed: false is a draft. Its findings report at
notice, whatever check.questions.severity says, and end
(drafted, not reviewed). A pin marked reviewed: false reports the same way
and ends (answer not reviewed). While every pin on a confirmed question is a
draft, the question must also find its page at full severity. off silences
every finding at check.questions.severity, drafts included. A moved answer
is still reported.
Golden-set files
Section titled “Golden-set files”A question that needs two pages, or that the docs don’t answer, lives in a
golden-set file. graph.questionFiles names them, relative to the config file.
Each item is a page item’s object form, with two more keys. answers[].file
names the page that holds each answer, and language is optional.
- question: After rotating the key, how do I check nothing broke? answers: - file: docs/rotate.md lines: 4 integrity: sha256-57f8… - file: docs/validate.md lines: 4 integrity: sha256-7e32…- question: Does manni run on Windows on ARM? answers: []Each answer must be found, as a page’s pin must. An empty answers list marks
a known gap. A gap is counted and never asked, because search has no score
floor to test “finds nothing” against. A finding on a file question points at
the file, and its at line names the line its item starts on.
$ manni graph checkwarning: questions.yaml: "After rotating the key, how do I check nothing broke?" does not find docs/rotate.md § Rotate in the top 1 (it ranks 4; first is docs/validate.md § Check the result) at questions.yaml:3Across pages and files, check also reports these.
| Finding | Severity | What it means |
|---|---|---|
| An answer’s page is gone | check.questions.severity | The file an answer names does not exist. The question counts as not found. |
| A question asked twice | check.questions.severity | The same question is asked elsewhere, ignoring case, punctuation and spacing. Each copy after the first, in path order, is reported. |
| A question copies its answer | notice | Over half its adjacent word pairs also appear in its answer’s lines. It tests matching words more than finding. |
| The found rate is low | error | Fewer reviewed questions are found than check.questions.foundThreshold asks for. check.questions.severity does not touch it. |
warning: questions.yaml: docs/old.md, which holds the answer to "How large may an upload be?", does not exist, so re-pin or remove the question. at questions.yaml:1warning: docs/retry.md: "how do I retry a failed upload" is also asked on docs/limits.md, so keep it where it is answered. at https://example.com/graph/doc/docs/retry.md graph:sampleQuestionnotice: docs/retry.md: "Do uploads retry three times by default?" repeats 5 of its 6 word pairs from its answer, so it tests matching words more than finding. (drafted, not reviewed) at https://example.com/graph/doc/docs/retry.md graph:sampleQuestionerror: (no page): 0 of 1 reviewed questions are found in the top 1 (0%), under the 98% that graph.check.questions.foundThreshold asks for. at questionsA golden-set file that cannot be read stops the run with exit 2.
| Case | stderr |
|---|---|
| A listed file is missing | manni: graph.questionFiles names questions.yaml, which does not exist. Fix the path, or remove it from graph.questionFiles. |
| A file fails its schema | manni: questions.yaml: /0/answers/0 must have required property 'lines'. |
The question set’s health
Section titled “The question set’s health”When any question exists, check prints a summary of the whole set between the
findings and the counts.
$ manni graph checkquestions 3: 2 reviewed, 1 drafted, on 1 page found 2 of 2 reviewed in the top 2 (100%), 1 first (50%) drafted 1 of 1 in the top 2, not counted until reviewed answers 3 pinned, 1 not reviewed: 3 current, 0 moved, 0 changed; 0 not pinned
0 errors, 0 warnings, 1 notice| Row | What it counts |
|---|---|
questions | Every question, reviewed and drafted, and where they are declared. |
found | Reviewed questions with a test, a page or at least one pin. A question is found when every answer is in the top results, and first when every answer ranks first. |
drafted | The same count for drafts. It prints only when a draft has a test, and the rate gate ignores it. |
answers | Every pin, how many are drafts, and how each compares with its page now. not pinned counts page questions with no pin. |
gaps | Golden-set questions with no answers. It prints only when there is one. |
A changed answer, or one whose page is gone, counts as not found. The
percentages round down. Under -f github the summary prints after the
annotations, as plain text. The JSON report gains a questions key beside
findings, with the same counts.
"questions": { "total": 2, "reviewed": 2, "drafted": 0, "pages": 1, "files": ["questions.yaml"], "top": 1, "found": { "reviewed": { "inTop": 1, "first": 1, "of": 1 }, "drafted": { "inTop": 0, "first": 0, "of": 0 } }, "answers": { "pinned": 0, "unreviewed": 0, "current": 0, "moved": 0, "changed": 0, "unpinned": 1 }, "gaps": 1}Stored per CI run, that object is the time series. manni keeps no history of its own.
Cited by graph retrieve
Section titled “Cited by graph retrieve”When a chunk artifact sits beside the graph, check also asks each reviewed
question through graph retrieve, in the question’s own
language. A question is cited when the bundle cites a node that answers it.
For a question with no pin, that is its page or any of its sections. For a
pinned one, it is a node whose own text holds each answer.
$ manni graph publish && manni graph checkquestions 4: 4 reviewed, 0 drafted, on 4 pages found 3 of 4 reviewed in the top 5 (75%), 3 first (75%) cited 2 of 4 reviewed by graph retrieve (50%), 1 withheld answers 0 pinned, 0 not reviewed: 0 current, 0 moved, 0 changed; 4 not pinnedThe JSON questions object gains cited and withheld, both counts of
reviewed questions. The row’s denominator is found.reviewed.of.
| Finding | Severity | Message |
|---|---|---|
| Not cited | questions.severity | "<question>" is not cited by graph retrieve, which cites <page> § <section> first. |
| Not cited, withheld | questions.severity | "<question>" is not cited by graph retrieve, because graph.retrieve.withhold left out § <section> as drifted. |
| Cited outside its language | error | "<question>" asks in de, and graph retrieve cites <page>, which is en. |
Under citedThreshold | error | 2 of 4 reviewed questions are cited by graph retrieve (50%), under the 75% that graph.check.questions.citedThreshold asks for. |
| No chunk artifact | notice | the cited row did not run: no chunk artifact; run manni graph publish. |
| An artifact from another graph | notice | the cited row did not run: graph/chunks.json was built from another graph; run manni graph publish. |
A withheld answer that is a whole page is named by its path. When the bundle
cites nothing, the first message ends which cites nothing for it. The row
never runs without an artifact, so citedThreshold gates only a published
graph.
Options
Section titled “Options”| Option | Argument | Default | Description |
|---|---|---|---|
-c, --config | <path> | discovered | Path to manni.config.yaml. |
--no-config | n/a | off | Ignore any discovered config file. |
-g, --graph | <path> | config out | The built Turtle file to validate. |
--shapes | <path> | config check.shapes, else bundled | A shapes .ttl file. Repeatable, one path per occurrence, never comma-separated. Given here or in config, these replace the bundled shapes rather than adding to them. Include the bundled file when you mean to extend it. |
-f, --format | <pretty|json|github|sarif> | pretty | Output format. github writes one workflow annotation per finding, and sarif writes one SARIF 2.1.0 log. |
Checking a graph: block
Section titled “Checking a graph: block”There is no graph validate. A graph: block is frontmatter, and frontmatter is
checked by manni meta validate against the page vocabulary. That is the
command every vocabulary in this family is checked with.
The page vocabulary is
manni:graph:1.0.0, a built-in
schema of manni meta. Name it for the pages it governs:
meta: overrides: - collection: site schemas: - manni:graph:1.0.0$ manni meta validate✗ docs/install.md /graph must NOT have additional property 'bogus' (line 3) [manni:graph:1.0.0] manni:graph:1.0.0 graph Guide: https://hawkeyexl.github.io/manni/meta/reference/schemas/manni-graph/
1 file checked, 0 passed, 1 failed, 1 errorA typo inside the block is a schema error with a line number, because the block
is closed. manni graph build reads x-manni-graph-output from the set
manni meta validate resolves for each page, to decide what a published graph
may carry.
graph shapes
Section titled “graph shapes”Lists the SHACL node shapes graph check runs, grouped by the file they come
from, with what each one targets. It reads the same shapes check does: the
--shapes files, else graph.check.shapes, else the bundled file. It reads no
documents and no graph, and it exits 0.
manni graph shapes [options]$ manni graph shapesmanni:graph-shapes:1.3.0 (bundled) Guide: https://hawkeyexl.github.io/manni/graph/fix/ ActivityShape prov:Activity A generation, fill or build run that manni records. manni writes it. AssociationShape prov:Association The role an agent played in an activity. manni writes it. AttributionShape prov:Attribution The role an author played on a page. manni writes it. AudienceShape schema:Audience An audience from the content strategy, one per audience record. BlockShape graph:Block A paragraph, list or element that an inline meta statement describes. ConceptShape skos:Concept A concept of the glossary, named by a term page, a graph label, a tag or a concepts entry. DocumentShape graph:Document A page of the docset, as its frontmatter, links, headings and history describe it. FilledFieldEntryShape objects of graph:filledFieldEntry One field a graph fill run filled, with the model's confidence. manni writes it from meta-provenance. JourneyShape graph:Journey A journey from the content strategy, one per journey record. OrganizationShape prov:Organization An organization credited on a page. PersonShape prov:Person A person credited as an author of a page. PersonaShape schema:PeopleAudience A persona from the content strategy, one per persona record. ProductVariantShape iirds:ProductVariant A product variant that pages name in applies-to or not-applicable-to. SchemeShape skos:ConceptScheme The glossary that holds every concept of the docset. manni writes it. SectionShape graph:Section A heading of a page, typed through graph.sections or a meta statement on the heading. SoftwareAgentShape prov:SoftwareAgent A model or tool that wrote or filled part of a page, or built the graph. VerificationShape graph:Verification One verified-by entry: a tool, and the check it runs.Each file opens with its id and where it came from: (bundled), or the path as
given. A file with no owl:Ontology header is named by that path. The
Guide: line prints when the header names one.
-f json prints { shapes }, one object per node shape:
{ "shapes": [ { "name": "DescribedShape", "id": "house:shapes:1.0.0", "source": "house-shapes.ttl", "target": "https://hawkeyexl.github.io/manni/graph/ns#Document", "closed": false, "description": "A page the house style describes.", "guide": "https://example.com/house/guide/" } ]}Options
Section titled “Options”| Option | Argument | Default | Description |
|---|---|---|---|
-c, --config | <path> | discovered | Path to manni.config.yaml. |
--no-config | n/a | off | Ignore any discovered config file. |
--shapes | <path> | config check.shapes, else bundled | A shapes .ttl file. Repeatable, one path per occurrence, never comma-separated. |
-f, --format | <pretty|json> | pretty | Output format. |
graph shapes show
Section titled “graph shapes show”Prints every property one node shape checks, and the frontmatter key that
feeds it. The shape is named by its local name, as graph shapes lists it.
manni graph shapes show <shape> [options]$ manni graph shapes show ConceptShapeConceptShape manni:graph-shapes:1.3.0 skos:Concept, closed A concept of the glossary, named by a term page, a graph label, a tag or a concepts entry. Guide: https://hawkeyexl.github.io/manni/graph/fix/
Concept-altLabel skos:altLabel graph.alt-labels · optional, repeats Other names readers use for the concept. Search matches them too. Concept-broadMatch skos:broadMatch optional, repeats A broader concept in another vocabulary, from the term page. It never points into this glossary. Concept-broader skos:broader graph.broader · optional, repeats A more general concept. A concept is never its own ancestor. Concept-closeMatch skos:closeMatch optional, repeats A near-equivalent concept in another vocabulary, from the term page. It never points into this glossary. Concept-definition skos:definition optional, repeats What the concept means, from its term page. Concept-exactMatch skos:exactMatch optional, repeats The same concept in another vocabulary, from the term page. It never points into this glossary. Concept-hiddenLabel skos:hiddenLabel optional, repeats Misspellings and old names that search matches but never shows, from the term page. Concept-inScheme skos:inScheme required, repeats The glossary the concept belongs to. manni writes it. Concept-narrowMatch skos:narrowMatch optional, repeats A narrower concept in another vocabulary, from the term page. It never points into this glossary. Concept-narrower skos:narrower graph.narrower · optional, repeats A more specific concept. manni also writes it as the inverse of broader. Concept-notation skos:notation optional, repeats A code that names the concept, from its term page. Concept-prefLabel skos:prefLabel graph.label · required, repeats The concept's name. Every concept has one. Concept-prefLabel-collision skos:prefLabel optional, repeats, warning One name per language, so search and the glossary agree on a spelling. Concept-related skos:related graph.related-concepts · optional, repeats A concept on the same level as this one. It is never also an ancestor or a descendant. Concept-relatedMatch skos:relatedMatch optional, repeats A related concept in another vocabulary, from the term page. It never points into this glossary. Concept-scopeNote skos:scopeNote optional, repeats When to use the concept, and when not to, from its term page. Concept-topConceptOf skos:topConceptOf optional, repeats Marks a concept with no broader concept. manni writes it.Each row is a rule, the property it checks, and the frontmatter key that feeds
it when an author writes one. A row with no key is a value manni writes. A
severity other than error closes the row. The indented line under it is the
rule’s description, from sh:description.
-f markdown prints a frontmatter skeleton of the keys an author writes. Each
key follows its description as a comment. An optional key is commented out,
and graph.* keys nest under graph:.
$ manni graph shapes show ConceptShape -f markdown---# ConceptShape, manni:graph-shapes:1.3.0# Guide: https://hawkeyexl.github.io/manni/graph/fix/graph: # Other names readers use for the concept. Search matches them too. # alt-labels: [] # A more general concept. A concept is never its own ancestor. # broader: [] # The concept's name. Every concept has one. label: [] # A more specific concept. manni also writes it as the inverse of broader. # narrower: [] # A concept on the same level as this one. It is never also an ancestor or a descendant. # related-concepts: []----f json prints { shapes } with the one shape, which gains properties. Each
property is { rule, path, key, min, max, severity, description }. key is
null for a value manni writes, and max is null when the property repeats.
Arguments
Section titled “Arguments”| Argument | Description |
|---|---|
<shape> | The node shape to show, by local name, such as DocumentShape. |
Options
Section titled “Options”| Option | Argument | Default | Description |
|---|---|---|---|
-c, --config | <path> | discovered | Path to manni.config.yaml. |
--no-config | n/a | off | Ignore any discovered config file. |
--shapes | <path> | config check.shapes, else bundled | A shapes .ttl file. Repeatable, one path per occurrence, never comma-separated. |
-f, --format | <pretty|json|markdown> | pretty | Output format. |
Usage errors
Section titled “Usage errors”Each of these exits 2.
| Case | stderr |
|---|---|
| No shape named | error: missing required argument 'shape' |
| A shape the files do not hold | manni: Unknown shape "Document". Available: ActivityShape, AssociationShape, AttributionShape, BlockShape, ConceptShape, DocumentShape, FilledFieldEntryShape, OrganizationShape, PersonShape, ProductVariantShape, SchemeShape, SectionShape, SoftwareAgentShape, VerificationShape. |
| A shape named when the files define no node shapes | manni: Unknown shape "Document". The shapes in effect define no node shapes. |
-f markdown on a shape with no frontmatter keys | manni: ActivityShape has no frontmatter keys. manni writes every value it checks. |
-f markdown on graph shapes | manni: Unknown --format "markdown". Use pretty | json. |
An unknown -f on graph shapes show | manni: Unknown --format "xml". Use pretty | json | markdown. |
A --shapes file that does not exist | manni: Shapes file not found: <resolved path>. Fix the path in --shapes or graph.check.shapes. Leave both unset to use the bundled shapes. |
graph fill
Section titled “graph fill”Proposes graph metadata fields and sample questions from each page’s content,
and writes the ones that clear a confidence gate. This is the one verb that
reaches a model.
manni graph fill [paths...] [options]Run --dry-run first. The proposals are cached before the gate, so
re-running at a different --confidence spends no turns. A turn is one
inference call, one page is one turn, and a cached page costs none. A page the
budget stopped at is reported skipped with the reason turn budget.
With -, the page from stdin is filled in memory and printed to stdout, as
meta fill - does. The report goes to stderr in either format.
A field already held in an external-metadata manifest counts as filled. Each
key is written where meta fill would write it. A key a manifest owns goes to
the page’s entry there, and every other key goes to the page. A page that
carries graph: while a manifest owns it is that page’s error, in the words
meta validate uses for the collision.
A key that goes to the page is written in the page’s own format, where
meta fill writes it. Markdown and MDX get YAML frontmatter. HTML gets one
<meta name="graph"> in <head>, the block as one line of YAML flow. A DITA
topic gets an <othermeta name="graph"> in its <prolog>. A fenced AsciiDoc or
reStructuredText page has its fence edited in place. meta-provenance goes the
same way. Two kinds of page cannot take a write: an AsciiDoc or
reStructuredText page with no fenced block, and an HTML page with no <head>.
Each is that page’s error, in meta fill’s words, and is left unchanged. The
run exits 1. A page whose manifest owns every key written is not refused. See
the formats reference.
The graph check vets each proposal against the whole configured corpus. A corpus page it cannot read is left out, and one warning lists it. A named page that cannot be read is that page’s error.
Every written graph value is recorded in the page’s meta-provenance, naming
the model and the fields. See
Fill the vocabulary.
Sample questions
Section titled “Sample questions”sample-questions is one of the fill fields, and it is in the default set. A
config that lists fill.fields without it leaves questions alone. Questions
run in a pass of their own beside the graph fields, in this order:
- Re-pin. An answer whose lines moved gets its new
lines. A pin withlinesand nointegritygets one. This needs no model and spends no turn. - Pair. For a question with no
answers, the model quotes the passage that answers it.fillfinds the quote in the body and pins the whole lines it touches. The quote itself is never written. - Re-pair. An answer whose lines changed gets a new pin the same way.
- Draft. A page with fewer than
fill.questions.perPagequestions gets new ones until it has that many.
A new pin is written reviewed: false, and a paired question keeps its own
standing. A drafted question is written reviewed: false, with generated-by
and confidence when fill.writeProvenance is on. Questions never touch
meta-provenance, and --force never rewrites or removes one. A page with
enough questions, each one paired and none moved or changed, is complete.
The gates run in this order. A draft that fails one is dropped, and an
existing question that fails one is not-paired and kept.
| Gate | Reason shown |
|---|---|
| The quote is not in the page. | answer not on page |
| A draft repeats a question asked on any page or golden-set file. | duplicate of <file> |
| A draft repeats over half its word pairs from its answer. | copies its answer (5 of 6 word pairs) |
| The model finds no passage for an existing question. | no passage answers it |
The confidence is under --confidence. | low confidence, not written |
A question request sends the whole body. A body over --chunk-chars is split
at line boundaries, one request per piece. Drafts from every piece are pooled,
and the most confident are kept. A golden-set file named in questionFiles
gets the re-pin step only. On HTML and DITA, the questions sit inside the body
that lines are counted from, so every pin is taken from the page as written.
Pretty output adds one line per question under its page:
filled docs/retry.md (sample-questions) re-pinned "How do I resume a paused upload?" lines 9 to 12 paired "How do I retry a failed upload?" lines 4 (0.77) drafted "How often does the client try again?" lines 8 (0.90) dropped "Open the upload list and choose retry?" copies its answer (6 of 6 word pairs)filled questions.yaml (sample-questions) re-pinned "After rotating the key, how do I check nothing broke?" docs/rotate.md lines 4 to 6Under -f json, each result gains a questions list. Each entry holds
action, question, and any of file, lines, from, confidence and
reason. The action is drafted, paired, re-paired, re-pinned,
dropped or not-paired.
Offline
Section titled “Offline”--offline, or fill.offline in config, sends nothing off this machine. It
means what it means for meta fill and term fill.
autoresolves only tollama-cpp, and only to a model already downloaded. Nothing is downloaded.- A configured hosted provider is set aside, and one notice says so:
manni: --offline: using llama-cpp instead of "anthropic" from graph.provider. - A hosted provider named with
--provideris a usage error, exit 2. - With a downloaded model, the run is the same as under
--local. - With none, only the re-pin step runs. Each page that needed a model is
skippedwith the reasonoffline, and the run exits 0 with one warning.
$ manni graph fill --fields sample-questions --offlinemanni: no model is available offline, so only answer pins were updated, and nothing was drafted or proposed; download one first with --local, or drop --offline.filled docs/retry.md (sample-questions) re-pinned "How do I resume a paused upload?" lines 9 to 12skipped docs/limits.md (offline)The warning is on stderr and the report on stdout.
Arguments
Section titled “Arguments”| Argument | Description |
|---|---|
[paths...] | Files, directories, or globs to fill, relative to the working directory. - reads stdin as well. Optional; without them, the configured collections. |
Options
Section titled “Options”| Option | Argument | Default | Description |
|---|---|---|---|
-c, --config | <path> | discovered | Path to manni.config.yaml. |
--no-config | n/a | off | Ignore any discovered config file. |
--collection | <name> | every collection | Read only this configured collection. Repeatable. |
--exclude | <glob> | n/a | Glob to exclude. Repeatable. |
--as | <format> | by extension | Parse every input as asciidoc, html, markdown, mdx, rst or xml. Required with -. |
--ext | <list> | .md,.markdown,.mdx,.html,.htm,.flsnp,.adoc,.asciidoc,.rst,.dita,.ditamap,.fltoc,.flbrs | Extensions for directory walks and globs, comma-separated. |
--allow-empty | n/a | off | Zero matched files is an empty report and exit 0. |
-f, --format | <pretty|json> | pretty | Output format. |
--dry-run | n/a | off | Report proposals without writing files. |
--force | n/a | off | Overwrite graph fields a human set. Without it, a field already present on the page is left alone. It never rewrites or removes a sample question. |
--no-cache | n/a | off | Bypass the proposal cache, so every page is asked again. |
--no-validate-graph | n/a | off | Skip the SHACL guardrail that vets each proposal against the derived graph before it is written. |
--sections | n/a | config fill.sections, else off | Also propose per-section metadata, not only page-level. |
--max-turns | <n> | config fill.maxTurns, else unbounded | Stop after this many inference calls. A whole number, at least 1. |
--confidence | <n> | config fill.confidenceThreshold, else 0.7 | Minimum model confidence, 0 to 1, for a field to be written. Outside that range is a usage error. |
--fields | <list> | config fill.fields, else every field | Which fields to propose, comma-separated and given once. sample-questions is one of them. An unknown name is a usage error naming the allowed set. |
--chunk-chars | <n> | config fill.chunkChars, else 12000 | The most characters of a page one question request sends. A whole number, at least 1. |
--provider | <name> | config graph.provider, then providers.provider, then auto | auto, anthropic, openai, claude-cli, or llama-cpp. An unknown name is exit 2. |
--model | <model> | config graph.model, else the provider’s default | The model within the provider. It needs a named provider, from here or from config, because a model name does not say which provider owns it. |
--local | n/a | off | Run inference on this machine with llama-cpp, over graph.provider and the family’s providers.provider. |
--offline | n/a | config fill.offline, else off | Send nothing off this machine. Only a local model already downloaded runs. See Offline. |
graph query
Section titled “graph query”Matches a triple pattern against the built graph. Omit a term and it is a wildcard, so all three omitted prints the whole graph.
manni graph query [options]$ manni graph query -c test/graph/fixtures/dd/docs.config.yaml --predicate dcterms:subject<https://example.com/dd/doc/docs/configuration.md> dcterms:subject <https://example.com/dd/concept/reference><https://example.com/dd/doc/docs/getting-started.md> dcterms:subject <https://example.com/dd/concept/onboarding>
2 match(es)The three pattern flags are long-only and named for the triple position each
one fills. -o is --out on every other verb, and -s is the metadata tool’s
schema flag. One letter meaning two things across one family costs more than
the letters it saves.
Options
Section titled “Options”| Option | Argument | Default | Description |
|---|---|---|---|
--subject | <term> | wildcard | The subject, as a full IRI or a prefix:local name. |
--predicate | <term> | wildcard | The predicate, as a full IRI or a prefix:local name. |
--object | <term> | wildcard | The object, as a full IRI, a prefix:local name, or a literal value. |
-c, --config | <path> | discovered | Path to manni.config.yaml. |
--no-config | n/a | off | Ignore any discovered config file. |
-g, --graph | <path> | config out | The built Turtle file to read. |
-f, --format | <pretty|json> | pretty | Output format. |
No match is exit 0 with No matches., not a finding. A query that asks a
question and gets no answer has not failed.
graph stats
Section titled “graph stats”Summarizes the graph. It reports node counts, the most connected documents, orphans, broken internal links and section references, and per-field metadata coverage.
manni graph stats [options]$ manni graph stats -c test/graph/fixtures/dd/docs.config.yamlTriples: 157Documents: 3Sections: 8Concepts: 3References: 2
Most connected: docs/configuration.md (2) docs/getting-started.md (2)
Orphan docs (1): docs/troubleshooting.md
Broken internal links (1): docs/troubleshooting.md -> missing.mdThe sample-questions coverage row counts the pages that ask a reader
question. It is gated only when the per-field coverageThreshold map names it,
never by a single number. The report ends with the concepts no question
reaches. A concept has a question when a page whose primary topic or subjects
name it asks one. The list is reported and never gated, like
orphans. A graph with no concepts prints no such list.
$ manni graph stats -c test/graph/fixtures/dd/docs.config.yaml…Concepts with no question (3): Installation onboarding referenceThe JSON report lists those concepts by IRI, under conceptsWithoutQuestions.
Options
Section titled “Options”| Option | Argument | Default | Description |
|---|---|---|---|
-c, --config | <path> | discovered | Path to manni.config.yaml. |
--no-config | n/a | off | Ignore any discovered config file. |
-g, --graph | <path> | config out | The built Turtle file to read. |
-f, --format | <pretty|json> | pretty | Output format. |
--check | n/a | off | Exit 1 when a broken internal link exists, or when coverage is below its threshold. This is the CI gate. |
--top | <n> | every hub | How many most-connected documents to list. A whole number, at least 1. |
--coverage-threshold | <pct> | config stats.coverageThreshold, else none | Minimum coverage percentage, 0 to 100, applied to every measured field but sample-questions for this run. Outside that range is a usage error. See Coverage. |
graph search
Section titled “graph search”Ranks graph nodes for a text query. It reads the search index that
manni graph export search writes, and adds a vector leg when
manni graph embed has written sidecars.
manni graph search <query> [options]$ manni graph search "installation" -c test/graph/fixtures/dd/docs.config.yaml0.0164 https://example.com/dd/concept/installation [skos:Concept] — Installation
1 result (lexical)Arguments
Section titled “Arguments”| Argument | Description |
|---|---|
<query> | The text to rank nodes against. Required. |
Options
Section titled “Options”| Option | Argument | Default | Description |
|---|---|---|---|
-c, --config | <path> | discovered | Path to manni.config.yaml. |
--no-config | n/a | off | Ignore any discovered config file. |
-g, --graph | <path> | config out | The built Turtle file the index sits beside. |
-i, --index | <dir> | beside the graph | Directory holding the indexes and the manifest. |
--lang | <tag> | the only localization | Which localization to search, matched in any case. Required when the corpus has more than one. |
--limit | <n> | 10 | Maximum results. A whole number, at least 1. |
--vectors | <path> | from the manifest | Vector sidecar path. |
--mode | <mode> | hybrid when vectors exist, else lexical | Which legs to run. An unknown mode is a usage error naming the allowed set. |
-f, --format | <pretty|json> | pretty | Output format. |
graph traverse
Section titled “graph traverse”Walks the graph outward from a node and reports what it reaches, with the full trace of why each hop was taken. This is the impact question: what depends on this page, through a hyperlink or through a shared concept.
manni graph traverse <node> [options]$ manni graph traverse -c test/graph/fixtures/dd/docs.config.yaml \ https://example.com/dd/doc/docs/getting-started.md --impact https://example.com/dd/doc/docs/configuration.md — Configuration https://example.com/dd/activity/build https://example.com/dd/graph
3 nodes, 5 hops, 0 excludedArguments
Section titled “Arguments”| Argument | Description |
|---|---|
<node> | Where to start, as a full IRI or a prefix:local CURIE. Required. |
Options
Section titled “Options”| Option | Argument | Default | Description |
|---|---|---|---|
-c, --config | <path> | discovered | Path to manni.config.yaml. |
--no-config | n/a | off | Ignore any discovered config file. |
-g, --graph | <path> | config out | The built Turtle file to read. |
-d, --depth | <n> | 1; 3 under --impact | Maximum hops from the node. A whole number, and 0 is allowed: it means the node itself, which is a real answer. |
--predicates | <list> | every predicate | Only follow these predicates, comma-separated and given once. A CURIE holds no comma, so one separator is enough. Naming rdf:type follows type edges. A predicate no edge uses is exit 2. |
--reverse | n/a | off | Follow inbound edges, to find who points at this node. |
--impact | n/a | off | Transitive inbound reach, to find what a change here affects. |
--variant | <variant> | no filter | Scope filter on a product variant IRI, title, or slug. |
--software-subject | <subject> | no filter | Scope filter on a software subject. An unknown one is exit 2. |
--lang | <tag> | no filter | Scope filter on a BCP-47 language tag, matched in any case. |
--limit | <n> | unbounded | Stop after this many nodes. A whole number, at least 1. |
--context | n/a | off | Return the passages the walk reaches, with their text and proof, as a bundle. It reads the chunk artifact beside the graph. |
--max-chars | <n> | config retrieve.maxChars | The character budget for the passages. Valid only with --context. |
--withhold | <list> | none | Also leave out passages that are drifted or failing, comma-separated and given once. Valid only with --context. |
--where | <field=value> | none | Serve only passages whose row metadata field holds the value, as graph retrieve does. Repeatable, and every condition must hold. Valid only with --context. |
-f, --format | <pretty|json> | pretty | Output format. |
Without --context, the output is the walk above, unchanged. With it, the
node the walk starts from has the role match. A node reached outward is
context, and one reached under --impact or --reverse is dependent.
$ manni graph traverse urn:manni:graph:doc/docs/en/rotate.md --contextrotate.md matchrotate.md § Rotate the encryption key context, depth 1setup.md context, depth 1
3 passages, in en. Pass -f json for the text and trace.--max-chars, --withhold or --where without --context is exit 2,
with manni: --max-chars needs --context., and likewise for the other two. A
malformed --where is exit 2, with the message graph retrieve prints.
graph lookup
Section titled “graph lookup”Answers a question from the chunk artifact. It takes only the question and
where the artifact is. Every retrieval setting comes from
graph.retrieve.
manni graph lookup <question> [options]It runs three operations and returns each one in full:
- Matches. Every ranked match, with its lexical, vector and fused scores.
The top
retrieve.seedspassages are the matches. - Traversal. Every passage reached outward from the matches, to
retrieve.depthhops, with every edge it followed. - Impact. Every passage that depends on the matches, three hops inward, with every edge.
It enters every localization and keeps the one whose best match scores
highest, so it never asks for a language. The lexical score decides, since
the words a question shares with a page say which language it is in. The
question is then embedded with that language’s model alone. Nothing is dropped from a section.
The budget, retrieve.maxChars, applies to each section’s text on its own. A
passage past it keeps its node, edges and record, and its text is null.
$ manni graph lookup "How do I rotate the encryption key?"Matches1 rotate.md lexical 134.9, vector 0.092 ci.md § Run in CI lexical 60.5, vector 0.143 rotate.md § Rotate the encryption key lexical 170.5, vector -0.49Traversal, depth 1 setup.md from #1 via dcterms:references rotate.md § Rotate the key with the CLI from #3 via dcterms:hasPart rotate.md § What rotation re-encrypts from #3 via dcterms:hasPartImpact, depth 3 ci.md depth 1, hasPart #2 release.md depth 2, references ci.md
3 matches, 3 reached outward, 2 depend on them, in en. Pass -f json for the text, edges and trace.Under each passage, the pretty output adds its proof when there is any. That
means a pins line, a dates line and an evals line.
Arguments
Section titled “Arguments”| Argument | Description |
|---|---|
<question> | The question, in quotes. Required. |
Options
Section titled “Options”| Option | Argument | Default | Description |
|---|---|---|---|
-c, --config | <path> | discovered | Path to manni.config.yaml. |
--no-config | n/a | off | Ignore any discovered config file. |
-g, --graph | <path> | config out | The built Turtle file. The artifact must have been built from it. |
-i, --index | <dir> | beside the graph | The directory holding chunks.json, its rows and the search index. |
-f, --format | <pretty|json> | pretty | Output format. json prints the lookup result. |
The lookup result
Section titled “The lookup result”| Key | Meaning |
|---|---|
question, language | The question, and the language chosen. |
matches.passages[] | Every match in rank order: iri, title, page, lines, rank, score, text, truncated and provenance. score is { lexical, vector, fused }, with null where a path did not run or did not rank the passage. |
traversal.passages[], impact.passages[] | Every passage reached outward or inward, with depth in place of rank. A passage reached by both walks appears in both. A match never repeats in either. |
traversal.edges[], impact.edges[] | Every edge followed, as { from, predicate, to } in the graph’s own direction. |
*.truncated | true when a passage in the section lost its text to the budget. |
pages | { "<page>": { metadata } }: the delivered fields of each page served, once per page. |
citations | { iri, title, sourceUrl } for each passage served with its text. |
trace | The trace, whose entry adds languages, each language’s best score, and chosen. |
refusal | Present only when nothing matched, or every match was withheld. Every section is then empty. |
A passage in passages is a passage the chunk artifact holds. A node with no
passage, such as a concept, appears only in edges.
matches plus traversal is what graph retrieve returns for the chosen
language without a budget. impact is everything graph traverse --impact
reaches from each match, less the matches.
graph retrieve
Section titled “graph retrieve”Retrieves passages for a question, with control over language, scope, depth and budget. It prints its bundle’s rows, without lookup’s section headings.
manni graph retrieve <question> [options]$ manni graph retrieve "How do I rotate the encryption key?" --lang en --where tags=keysrotate.md matchrotate.md § Rotate the encryption key matchrotate.md § Rotate the key with the CLI matchrotate.md § What rotation re-encrypts context, depth 1
4 passages, in en. Pass -f json for the text and trace.The top --seeds ranked passages are the matches. The walk goes outward from
them, --depth hops, and the bundle holds what fits in --max-chars, in that
order.
Arguments
Section titled “Arguments”| Argument | Description |
|---|---|
<question> | The question, in quotes. Required. |
Options
Section titled “Options”| Option | Argument | Default | Description |
|---|---|---|---|
-c, --config | <path> | discovered | Path to manni.config.yaml. |
--no-config | n/a | off | Ignore any discovered config file. |
-g, --graph | <path> | config out | The built Turtle file. The artifact must have been built from it. |
-i, --index | <dir> | beside the graph | The directory holding chunks.json, its rows and the search index. |
-f, --format | <pretty|json> | pretty | Output format. json prints the bundle. |
--lang | <tag> | the only localization | The localization, matched in any case. Required when the corpus has more than one. |
--variant | <variant> | no filter | Scope filter on a product variant IRI, title, or slug. An unknown one is exit 2. |
--software-subject | <subject> | no filter | Scope filter on a software subject. An unknown one is exit 2. |
--mode | <mode> | hybrid when the language’s rows carry vectors, else lexical | Which legs rank the question: lexical, hybrid or vector. |
--seeds | <n> | config retrieve.seeds | How many ranked passages the walk starts from. A whole number, at least 1. |
-d, --depth | <n> | config retrieve.depth | Hops out from each match. 0 serves the matches alone. |
--max-chars | <n> | config retrieve.maxChars | The character budget. A passage that does not fit is left out, and truncated says so. |
--withhold | <list> | none | Also leave out passages that are drifted or failing, comma-separated and given once. It only tightens the floor the export applied. |
--where | <field=value> | none | Serve only passages whose row metadata field holds the value. A list field matches when it contains it. Repeatable, one condition per occurrence, and every condition must hold. |
A candidate --where rejects gives its place to the next one. A withheld
match keeps its place and is left out, so every match withheld is a refusal.
The bundle
Section titled “The bundle”graph retrieve -f json and graph traverse --context -f json print a
bundle.
| Key | Meaning |
|---|---|
question, node | The question on retrieve, or the node on traverse. |
language | The language served, or null. |
context[] | Each passage: iri, title, text, sourceUrl, depth, page, provenance and role. role is match, context or dependent. |
citations[] | { iri, title, sourceUrl } for each passage in context. |
pages | { "<page>": { metadata } }: the delivered fields of each cited page, once per page. |
truncated | true when the budget left a passage out. |
refusal | { reason, detail }, present when nothing could be served. |
trace.entry | { candidates }, the ranked candidates that became matches. fallback is "lexical" when the vector path was wanted and could not run, or when the language has no model and another has one. |
trace.hops, trace.exclusions, trace.resolutions | Every edge walked, every node a scope filter excluded, and every row looked up. A candidate no row holds is a resolution with ok: false. |
trace.withheld | { node, because, at } for each passage the floor left out. at is export or query. Always present. |
trace.filtered | { node, field } for each passage --where left out, with the first field it failed. The vector store applies --where itself, so a row it never returns is not listed. Always present. |
A refusal is an answer, and it exits 0. Its reason is one of these:
| Reason | When |
|---|---|
no-route | Nothing matched, or nothing was reachable from the node. |
no-content | Every passage was over the budget. |
withheld | Every match was withheld. |
lang-required | The corpus has several localizations and none was named. The CLI makes this exit 2 instead. lookup never gives it. |
unknown-node | The start node is not in the graph. The CLI makes this exit 2 instead. |
unknown-scope | A variant, subject or language names nothing. The CLI makes this exit 2 instead. |
Usage errors
Section titled “Usage errors”Each is exit 2, on stderr.
| Case | Message |
|---|---|
| No artifact | manni: graph/chunks.json does not exist; run manni graph publish. |
| An artifact from another graph | manni: graph/chunks.json was built from another graph; run manni graph publish. |
| The query model differs from the language’s rows’ | manni: chunks.json was embedded with <id>, and this run loads <id>; run manni graph publish again. |
Several localizations, no --lang | manni: This corpus has more than one localization (de, en) — pass --lang to choose one. |
A bad --withhold value | manni: --withhold takes drifted or failing, and got stale. |
A malformed --where | manni: --where takes field=value, and got audience. |
graph serve
Section titled “graph serve”Serves the chunk artifact to an agent over the Model Context Protocol (MCP), as JSON-RPC on stdio. It serves until stdin closes, then exits 0. Serve the docs to an agent walks through registering it.
manni graph serve [options]$ manni graph servemanni: serving lookup, retrieve, traverse and impact over stdio from graph/chunks.json.That line goes to stderr, because stdout carries the protocol. The server
needs @modelcontextprotocol/sdk, an optional dependency npm installs with
manni. It opens the artifact once, at start, and answers every call from it.
With --publish, the server keeps the artifact current itself. It answers
the handshake at once and publishes behind it, so a client’s start-up timeout
never waits on a publish. tools/list answers at once too.
$ manni graph serve --publishmanni: publishing the graph before serving it: graph/chunks.json does not exist.manni: published the graph in 11.3s.manni: serving lookup, retrieve, traverse and impact over stdio from graph/chunks.json.It publishes at start when the first of these holds, and names it in the first line:
| Reason | When |
|---|---|
<dir>/chunks.json does not exist | No artifact. |
<dir>/chunks.json is not a chunk manifest | The manifest does not parse. |
<dir>/chunks.json was built from another graph | The manifest and the graph disagree. |
<dir>/graph.ttl does not exist | No graph. |
it records no commit | The manifest was built outside git. |
it was built at <sha> and HEAD is <sha> | HEAD moved since. Both are 7-character SHAs. |
the working tree has changes | git status lists a file outside <dir>. |
When none holds, it prints only the serving line.
Before each call, it compares the checkout with the one the last publish
began from. That means HEAD, plus the path, size and modification time of
each file git status lists. Untracked files count, and files under <dir> do not. When
they differ, it publishes again before it answers:
manni: republishing the graph: the checkout changed since the last publish.manni: published the graph in 9.5s.A call made during a publish waits for it, then answers from the new artifact. An edit made during a publish starts another one on the next call. Outside git there is nothing to compare, so the server publishes once, at start. Publish’s own report never reaches stdout. Its warnings go to stderr.
Options
Section titled “Options”| Option | Argument | Default | Description |
|---|---|---|---|
-c, --config | <path> | discovered | Path to manni.config.yaml. |
--no-config | n/a | off | Ignore any discovered config file. |
-g, --graph | <path> | config out | The built Turtle file. The artifact must have been built from it. |
-i, --index | <dir> | beside the graph | The directory holding chunks.json, its rows and the search index. |
--withhold | <list> | none | Also leave out passages that are drifted or failing on every call, comma-separated and given once. No tool argument can loosen it. |
--publish | n/a | off | Publish the graph when its artifact is missing or older than the checkout, and again before any call made after the checkout changes. It connects at once, and calls wait for a publish in progress. |
--no-authoring | n/a | config authoring, else on | Publish without author-facing content, as graph publish --no-authoring does. Valid only with --publish. |
With --publish, -i is the directory publish writes to, as its -o, and
the one the server reads from. The server reads the graph from there too,
under the file name of config out. -g is refused, because publish writes
the graph into that directory.
The tools
Section titled “The tools”tools/list gives four tools, in this order. Each is read-only and
closed-world, and each runs the same core as its CLI twin.
| Tool | Required | Optional | Returns | CLI twin |
|---|---|---|---|---|
lookup | question | none | the lookup result | graph lookup |
retrieve | question | lang, variant, softwareSubject, depth, maxChars, where | the bundle | graph retrieve |
traverse | node | depth, predicates, reverse, lang, variant, softwareSubject, limit, maxChars, where | the bundle | graph traverse --context |
impact | node | depth (default 3), lang, variant, softwareSubject, limit, maxChars, where | the bundle | graph traverse --impact --context |
where is an object of field to value, such as { "tags": "keys" }. A list
of values asks for each one, as a repeated --where does. Every input schema
sets additionalProperties: false, and none names withhold.
Each call returns its result as structuredContent, and again as one text
block holding the same JSON. Each tool declares an outputSchema for it.
What an answer carries
Section titled “What an answer carries”An answer over MCP is the CLI twin’s result, made to fit an agent’s context. A refusal goes out as it is. Every other answer is compacted first:
tracekeepsentry,withheldandfiltered. It leaves outhops,exclusionsandresolutions.- A passage whose
textisnullcarries noprovenance. pagesnames only the pages of passages that carry text.
The compact answer is then capped at graph.serve.maxChars characters of
JSON, 40000 by default. While it runs longer, passages become references, one
at a time, in this order:
| Tool | Reduced first to last |
|---|---|
lookup | impact passages, deepest first and later first at equal depth. Then traversal passages the same way. Then impact edges from the end, then traversal edges. Then matches, lowest-ranked first. |
retrieve, traverse, impact | context passages, last first. |
A reference keeps iri, page, and whichever of title, lines, rank,
score.fused, depth and role the passage has. Its text is null and
its truncated is true. A section that loses text or an edge sets its own
truncated to true. pages drops a page once no passage with text names
it. The cap stops as soon as the answer fits, and an answer that still does
not fit goes out as it is.
graph lookup -f json and the other CLI twins print the full result, and so
does the runtime’s lookup.
Errors over MCP
Section titled “Errors over MCP”| Case | What the agent gets |
|---|---|
| A refusal | The result with its refusal, and isError: false. |
A CLI usage error, such as an unknown node or no lang | A refusal with the CLI’s text in detail, and isError: false. |
| An argument the schema does not name, or a value of the wrong type | isError: true, with Invalid arguments for <tool>: <why>. |
With --publish, a publish that fails | isError: true, with graph publish stopped at <step>: <why>. Run manni graph publish to see the whole report. |
When a publish fails, stderr gets manni: graph publish stopped at <step>: <why>
too. A failure at start answers every call that way, and the server exits 2
when stdin closes. A later failure answers only the calls that waited on it.
The previous artifact stays loaded, and the next call after another change
tries again.
A usage error takes the reason that fits it. An unknown node is
unknown-node, and a missing lang is lang-required. An unknown variant,
subject, language or predicate is unknown-scope.
Usage errors
Section titled “Usage errors”Each is exit 2, on stderr, before anything is served.
| Case | Message |
|---|---|
| No SDK | manni: graph serve needs the optional @modelcontextprotocol/sdk package; run npm install @modelcontextprotocol/sdk. |
No artifact, without --publish | manni: graph/chunks.json does not exist; run manni graph publish. |
An artifact from another graph, without --publish | manni: graph/chunks.json was built from another graph; run manni graph publish. |
A bad --withhold value | manni: --withhold takes drifted or failing, and got stale. |
--publish with -g | manni: --publish writes graph.ttl into the index directory, so it cannot be combined with --graph. Use --index to choose the directory. |
--no-authoring without --publish | manni: --no-authoring decides what --publish builds, so it needs --publish. |
graph publish
Section titled “graph publish”Builds the graph and writes everything serving needs into one directory. It runs each step’s own command in-process, in the order each step’s input exists.
manni graph publish [options]$ manni graph publish --out docs/public/graphbuild graph.ttl: 256 pages, 20577 triplesexport search.en.json, localizations.json: 2920 entriesskipped embed: no graph.embed sectionexport graph.jsonld: 3003 nodesexport chunks.json, chunks.en.jsonl: 2359 chunks, 0 withheldexport llms.txt: 256 pagesskipped iirds: no graph.export.iirds sectionPublished 7 files to docs/public/graph/.| Step | Runs when | What it writes |
|---|---|---|
build | always | The graph, under the file name config out gives it. |
export:search | always | The search index and its manifest. |
embed | a graph.embed section exists | One vector sidecar per language. |
export:jsonld | always | graph.jsonld, which createRetriever reads. |
export:chunks | always | chunks.json and its rows. See Chunks. |
export:llms | a graph.export.llms section exists | llms.txt. See llms.txt. |
export:iirds | a graph.export.iirds section exists | The iiRDS package. |
A step that is not set up prints a skipped line and never fails the run.
Each step’s warnings and notices go to stderr as that step says them. The
first step that fails stops the run with exit 2:
manni: graph publish stopped at export:llms: graph export llms needs graph.export.llms.origin, the site's scheme and host.publish does not gate. Run graph check on what it published, so the cited
row reads the new chunks:
manni graph publish --out site/graph && manni graph check -g site/graph/graph.ttl.
With -f json it prints the steps:
{ "out": "docs/public/graph", "steps": [ { "step": "build", "status": "ran", "detail": "graph.ttl: 256 pages, 20577 triples", "files": ["graph.ttl"] }, { "step": "embed", "status": "skipped", "detail": "no graph.embed section", "files": [] } ] }files names each file relative to out.
When the build holds strategy records, the build line counts them, as
build graph.ttl: 344 pages, 23385 triples, 82 strategy records. The
build step’s detail carries the same clause.
--no-authoring publishes a graph without author-facing content, and every
step reads that build. A --no-authoring run refuses --evals with
exit 2, because eval verdicts are author-facing:
manni: --evals attaches eval verdicts, which are author-facing, and this run leaves author-facing content out.A configured graph.export.chunks.evals warns once instead, and the run
exits 0:
manni: graph.export.chunks.evals is set, but eval verdicts are author-facing, so this run leaves them out.Options
Section titled “Options”| Option | Argument | Default | Description |
|---|---|---|---|
-c, --config | <path> | discovered | Path to manni.config.yaml. |
--no-config | n/a | off | Ignore any discovered config file. |
-o, --out | <dir> | the directory of config out | The directory for every artifact. |
--evals | <path> | config export.chunks.evals | A docevals JSON report for the chunks step. |
--collection | <name> | every collection | A configured collection the build step reads. Repeatable, one name per occurrence. |
--no-authoring | n/a | config authoring, else on | Leave out author-facing content: strategy records, and every value a page’s schemas mark x-manni-location: external. |
-f, --format | <pretty|json> | pretty | Output format. |
graph embed
Section titled “graph embed”Computes local embeddings for the search index, one sidecar per language. It
needs the optional @huggingface/transformers peer; --embedding-model mock runs
offline and is what regenerates the committed fixtures.
manni graph embed [options]Options
Section titled “Options”| Option | Argument | Default | Description |
|---|---|---|---|
-c, --config | <path> | discovered | Path to manni.config.yaml. |
--no-config | n/a | off | Ignore any discovered config file. |
-g, --graph | <path> | config out | The built Turtle file the index sits beside. |
-i, --index | <dir> | beside the graph | Directory holding the indexes and the manifest. |
-o, --out | <dir> | config embed.out, else the index directory | Directory for the sidecars. |
--embedding-model | <id> | config embed.model | Embedding model id. Any id is accepted, because the documented table is the tested set rather than the permitted one. mock runs offline. |
--dtype | <dtype> | config embed.dtype, else q8 | Weight quantization. |
--no-cache | n/a | off | Ignore the vector cache. |
-f, --format | <pretty|json> | pretty | Output format. |
graph export
Section titled “graph export”Reserializes the built graph into a consumer format. The target is a
positional rather than a -f. Every other verb of the family means output
format by -f, and one flag cannot mean two things.
manni graph export <target> [options]$ manni graph export jsonld -c test/graph/fixtures/dd/docs.config.yamlWrote 32 nodes to .tmp/dd/graph.jsonld| Target | What it writes |
|---|---|
jsonld | JSON-LD, for a linked-data ingester. |
iirds | A conformant iiRDS package, for a content delivery portal. RDF/XML is written inside it. |
search | The browser search index and its manifest, read client-side by @hawkeyexl/manni/graph/runtime. |
questions | The question set as JSON Lines, for an eval tool. See The question set. |
chunks | The passages a retrieval host serves, each with its provenance record. See Chunks. |
llms | llms.txt, one link and description per page. See llms.txt. |
An unknown target is exit 2, with the allowed set named.
Arguments
Section titled “Arguments”| Argument | Description |
|---|---|
<target> | What to write: jsonld, iirds, search, questions, chunks, or llms. Required. |
Options
Section titled “Options”| Option | Argument | Default | Description |
|---|---|---|---|
-c, --config | <path> | discovered | Path to manni.config.yaml. |
--no-config | n/a | off | Ignore any discovered config file. |
-g, --graph | <path> | config out | The built Turtle file to read. |
-o, --out | <path> | the graph path with the target’s extension; questions.jsonl beside the graph for questions; llms.txt beside the graph for llms; the graph’s directory for search and chunks | Where to write. For search and chunks it names a directory, because each writes more than one file. |
--evals | <path> | config export.chunks.evals | A docevals JSON report, as manni docevals run -f json writes it. It fills each chunk record’s evals. Any target but chunks refuses it with exit 2. |
--no-authoring | n/a | config authoring, else on | Leave author-facing content out of what chunks and llms read from the pages. Any other target refuses it with exit 2. |
chunks and llms read the pages as well as the graph, so --no-authoring
shapes them. They skip strategy records and drop every value marked
x-manni-location: external. The chunks carry no pins and no eval verdicts.
jsonld, iirds, search and questions reserialize the graph as it was
built, so they refuse the flag. A reader-only graph of those comes from a
--no-authoring build or publish.
The question set
Section titled “The question set”export questions writes one JSON line per question. It reads each page’s
sample-questions and each file graph.questionFiles names. An answer’s text
is rendered from its pin when the export runs, so the source stays pinned
rather than copied.
$ manni graph export questionsWrote 431 questions to graph/questions.jsonl
$ manni graph export questions -o eval/golden.jsonl -g build/graph.ttl -c ci/manni.config.yamlWrote 431 questions to eval/golden.jsonl{"id": "docs/uploads/retry.md#how-many-times-does-an-upload-retry-by-default", "question": "How many times does an upload retry by default?", "language": "en", "reviewed": true, "answerable": true, "expected": [{"id": "https://docs.example.com/graph/doc/docs/uploads/retry.md#retry-limits", "file": "docs/uploads/retry.md", "lines": "41-43", "reviewed": true, "text": "Uploads retry three times, waiting 2, 4 and 8 seconds."}]}The file is shown wrapped here. On disk, each question is one line.
| Field | Meaning |
|---|---|
id | The page or golden-set file, then # and the question’s slug. |
language | The page’s language. A file question takes its own language, else its first answer’s page, else und. |
reviewed | false for a drafted question. Drafts are included for the consumer to filter. |
answerable | false for a gap, a file question with no answers. Its expected is empty. |
expected[].id | The node export search indexes the answer’s first line under, so ID-based recall compares like with like. |
expected[].file | The page that holds the answer. |
expected[].lines | The body lines the answer is on now. An answer that moved is written at its new lines. |
expected[].reviewed | false when the pin or its question is a draft. |
expected[].stale | true when the answer changed since it was pinned, or its page is gone. |
expected[].text | The answer’s lines, as the page reads at export time. |
A question with no pin has one expected entry for its page, with no lines
or text. A stale answer is written as its lines read now. A stale entry with
no text is an answer whose page is gone. A pin whose text now matches in more
than one place is written at its first match, and graph check reports the
ambiguity. One warning on stderr gives the stale count, and the exit code is
still 0:
manni: 5 answers changed since they were pinned, so they are exported as they read now, marked stale.No question anywhere writes an empty file and reports Wrote 0 questions.
The format is manni’s own, because each eval tool names its fields
differently. Each mapping below reads the reviewed questions from rows, the
parsed lines.
# Ragassamples = [{"user_input": r["question"], "reference_contexts": [a["text"] for a in r["expected"] if "text" in a]} for r in rows if r["reviewed"]]dataset = EvaluationDataset.from_list(samples)# DeepEvalgoldens = [Golden(input=r["question"], context=[a["text"] for a in r["expected"] if "text" in a]) for r in rows if r["reviewed"]]dataset = EvaluationDataset(goldens=goldens)# promptfoo: point `tests: file://tests.jsonl` at the resultjq -c 'select(.reviewed) | {vars: {question: .question}, metadata: {id: .id}}' graph/questions.jsonl > tests.jsonlChunks
Section titled “Chunks”export chunks writes the passages a retrieval host serves, and the proof
behind each one. A passage is a section’s text, from its heading to the next
heading of the same or higher rank. Each document’s whole body is a passage
too. A passage is keyed by its graph node IRI.
$ manni graph export chunks --evals .manni/evals.jsonWrote 214 chunks to graph/chunks.jsonIt writes into one directory: -o names it, and the graph’s directory is the
default.
| File | What it holds |
|---|---|
chunks.json | The manifest. It says what the rows were built from, and holds the retrieval defaults. |
chunks.<tag>.jsonl | One JSON line per passage, one file per language. |
Two exports of the same inputs are byte-identical. Keys are sorted, rows are
sorted by id, and nothing reads the wall clock.
The manifest
Section titled “The manifest”| Key | Meaning |
|---|---|
format | Always "manni-graph-chunks". |
version | Always 1. |
graph | The sha256- digest of the graph’s JSON-LD, the bytes graph export jsonld writes. |
graphFile | That JSON-LD file’s name. |
searchIndex | The search index’s localization manifest, as graph export search names it. |
commit | The docs commit, or null outside git. |
worktree | clean or modified, or null outside git. |
retrieve | The resolved graph.retrieve keys, the retrieval defaults every host reads. |
evals | { report, digest }: the report as the config or --evals names it, and its digest. null with no report. |
languages | { "<tag>": { file, rows, model } }: each language’s rows file, how many rows it holds, and its model. model is { id, dimensions } of the embedding model that language’s vectors come from, or null with no vector sidecar. graph.embed.byLanguage can give each language its own. |
pages | { "<page>": { metadata } }: each page’s delivered fields. |
withheld | [{ id, page, because }]: the passages graph.retrieve.withhold left out, and which reasons matched. |
| Field | Meaning |
|---|---|
id | The passage’s graph node IRI. |
graph | The manifest’s graph digest. |
page | The page the passage comes from. |
lines | The passage’s body lines, N-M. A MadCap Flare topic’s span is its own source lines, even where a snippet supplies the text. |
language | The page’s language tag. |
title | The heading, or the page’s title for a whole body. |
text | The passage, as the page reads at export time. A MadCap Flare topic’s passage is its text as its target builds it, with snippets expanded and variables resolved. |
vector | The node’s vector. Present only when a sidecar from manni graph embed holds one for that node. |
metadata | The page’s delivered fields. The delivered fields of every heading meta statement from the page down to this section overlay them, the nearest winning field by field. |
provenance | The passage’s provenance record. |
The provenance record
Section titled “The provenance record”A record is self-contained. Copied out of its row, it still says what it describes.
| Field | Meaning |
|---|---|
node, page, lines | What the record describes. |
commit, worktree | As in the manifest. Both are null outside git. |
pins | The cite pins whose claim overlaps the passage. |
dates | The page’s derived and verified dates. |
evals | The page’s docevals verdicts, or null when no report is configured or the report holds none for the page. |
statements | [{ lines, status, data }]: the meta statements anchored inside the passage, with their delivered fields. A heading statement sits at its heading’s lines. |
pins is { total, current, notice, warning, error, items }. Each pin is
counted once, at its worst finding. The findings use cite’s own rules at the
severity cite.severity sets, and they are computed outside git too. Each item
is { id, claim, source, commit, findings, lastVerified, verifiedBy }. A
finding is { rule, severity }, and an empty list means the pin is current.
dates holds created and lastUpdated, each { day, stamp }. day is the
UTC day manni meta derive would stamp. It is null outside git, or for a body
change not yet committed. stamp compares that day with the page’s own field:
current, stale or absent. When day is null, so is stamp, since
there is no day to compare. Beside them, lastVerified is the page’s
last-verified as written, or null. verifiedBy holds its verified-by
entries, or [].
evals is { report, current, pass, fail, needsReview, skipped, error, items }.
report is the report’s digest. current says whether the page still has the
bytes the report’s integrity names. Each item is
{ name, suite, grader, outcome }, plus confidence when the judge gave one.
What the metadata carries
Section titled “What the metadata carries”A field reaches the manifest’s pages[].metadata, a row’s metadata and a
statement’s data unless its schema marks it. The marks are
x-manni-graph-output: false and x-manni-location: external, read at every
depth. So a sample-questions item keeps its question and loses its answers
pins, which manni:ai-context:1.2.0 marks. JSON-LD, iiRDS and the search index
keep the graph build’s rule, which reads top-level fields only.
Withheld passages
Section titled “Withheld passages”graph.retrieve.withhold
leaves passages out of the rows and lists them under the manifest’s withheld.
drifted matches a passage with a pin at error severity. failing matches a
current eval verdict that failed or errored. A stale verdict never counts.
Messages
Section titled “Messages”Each goes to stderr, prefixed manni: . Only the four marked exit 2 stop
the export. The rest leave the exit code at 0.
| Message | Kind |
|---|---|
no vector sidecar, so chunks carry no vectors and retrieval is lexical only; configure graph.embed to add them. | warning |
<N> chunks withheld by graph.retrieve.withhold (drifted); chunks.json lists them. | notice |
no docevals report is configured, so records carry no eval verdicts. | warning |
graph.export.chunks.evals names .manni/evals.json, which does not exist. | exit 2 |
.manni/evals.json is not a docevals JSON report; write one with manni docevals run -f json. | exit 2 |
.manni/evals.json records no page integrity, so every verdict reads stale; write it again with manni docevals run -f json. | warning |
4 pages changed since .manni/evals.json was written, so their verdicts read stale. | warning |
3 pages in .manni/evals.json are not in the graph, so their verdicts are left out. | warning |
git cannot read this directory (<git's reason>), so records carry no commit and no derived dates. | notice, said once |
2 pages differ from 26dad159, so their records describe uncommitted text. | warning |
--evals applies only to graph export chunks. | exit 2 |
--no-authoring applies only to graph export chunks and llms; the others write the graph as built, so build it with --no-authoring. | exit 2 |
--evals attaches eval verdicts, which are author-facing, and this run leaves author-facing content out. | exit 2 |
graph.export.chunks.evals is set, but eval verdicts are author-facing, so this run leaves them out. | warning, said once |
Outside git the export still succeeds. Its records carry pins and evals, with no commit and no derived dates.
llms.txt
Section titled “llms.txt”export llms writes llms.txt beside the graph, or to the file -o names.
It needs graph.export.llms
with a title and an origin.
# manni
> One CLI for docs-as-code.
## Docs
- [manni cite](https://hawkeyexl.github.io/manni/cite/): Pin a sentence to the source lines it rests on.The H1 is title, and the blockquote is summary when it is set. A corpus in
one language gets one ## Docs section. A corpus in several gets one section
per language tag. Within a section, pages are in path order.
Each description is the page’s own description as delivered, so a field
marked x-manni-graph-output: false stays out. Each link is origin plus the
page’s route, from the nearest route
whose root holds it. An index file names its directory. The route takes the
URL style of the framework manni site detects.
| Framework | A page’s route |
|---|---|
| Starlight, MkDocs, Zensical, Hugo, or none detected | /guide/setup/ |
| Docusaurus, Nextra, Fumadocs, Mintlify, Fern | /guide/setup |
| VitePress, Rspress, Sphinx, Jekyll | /guide/setup.html |
| Flare | /guide/setup.htm |
| Message | Kind |
|---|---|
graph export llms needs graph.export.llms.title. | exit 2 |
graph export llms needs graph.export.llms.origin, the site's scheme and host. | exit 2 |
2 pages in the graph are not on disk, so llms.txt leaves them out; run manni graph build. | warning |
2 pages are under no route, so llms.txt leaves them out. | warning |
Exit codes
Section titled “Exit codes”| Code | Meaning |
|---|---|
0 | Everything passed. A warning or a notice is still 0. |
1 | Findings. A SHACL error, a stats --check failure, or a fill error. The author’s problem. |
2 | Operational or usage error. No input files matched, a bad config, an unknown flag value, a missing provider. Not the author’s problem. |