Skip to content

Get started

Ten minutes, one command, and a question your docset could not answer before. Nothing here reaches a model or the network.

This is the first half of Maya’s M18. The second half is Explore the graph.

Node.js 24 or newer, and the package you may already have:

Terminal window
npm install -D @hawkeyexl/manni

Git is useful but not required. History is detected, not declared: where git can run over a repository, commit dates and authors become triples. Where it cannot, the run says so once and builds the rest.

Point it at your docs:

Terminal window
$ npx @hawkeyexl/manni graph build docs/
Wrote graph/graph.ttl (3 docs, 157 triples, 3 concepts)

That is the whole first step. You now have a Turtle file: one node per document, one per heading, one per concept, plus the agents and activities that produced them.

Two runs over unchanged inputs produce a byte-identical file. That is a product contract rather than a happy accident, and it is why the graph is safe to commit, diff, and cache.

Terminal window
$ npx @hawkeyexl/manni graph stats
Triples: 157
Documents: 3
Sections: 8
Concepts: 3
References: 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.md
Broken section refs (1):
docs/getting-started.md -> #renamed-heading

The orphan is a page nothing links to. The broken section ref is a link to an anchor that no longer exists, which is what a renamed heading leaves behind. Neither is visible from any single file.

stats on its own always exits 0. --check makes it a gate.

Terminal window
$ npx @hawkeyexl/manni graph query --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)

Omit a term and it is a wildcard, so that reads “everything the corpus says about subject”. --subject and --object narrow the other two positions.

Terminal window
$ npx @hawkeyexl/manni graph check
warning: docs/configuration.md, docs/getting-started.md: Pages spell this concept two ways: "Installation" and "installation". Use one spelling on every page that names it.
at https://example.com/dd/concept/installation skos:prefLabel
manni:graph-shapes:1.3.0 Concept-prefLabel-collision: One name per language, so search and the glossary agree on a spelling.
Guide: https://hawkeyexl.github.io/manni/graph/fix/
0 errors, 1 warning, 0 notices

Two pages named one concept with different spellings, so they collided on a single node. That finding does not exist in either file: it exists only in the graph. This is the class of failure check is for, and the reason a graph gate is not a second linter.

A warning reports and exits 0. An error exits 1.

You can keep typing paths, but the point of a config is that CI and your laptop agree:

Terminal window
$ npx @hawkeyexl/manni graph init
Created manni.config.yaml

It writes a commented starter graph: section into manni.config.yaml, appending to the file if it already exists and leaving every sibling tool’s keys alone. A file that already has a graph: key is refused rather than overwritten.

Then declare the documents once, at the top level, where every tool reads them:

collections:
- name: site
paths:
- "docs/**/*.{md,mdx}"
graph:
baseIri: https://docs.example.com/graph/
out: graph/graph.ttl

With that in place, manni graph build with no paths reads the collection. The same collection feeds manni meta validate and manni cite check, which is the point of declaring it once.

Set baseIri to a namespace you control. Without it, node IRIs fall back to urn:manni:graph:, which identifies without promising to resolve. That is fine for a graph that stays in CI. It is unhelpful for one you hand to someone else.

  • A deterministic graph of the docset, rebuildable in one command.
  • A list of orphans, broken links and broken anchors that no per-file check produces.
  • A corpus-level contract, enforced by check.
GoalPage
Ask the graph more questionsExplore the graph
Know what a change to one page affectsSee what a change affects
Understand what becomes a triple, and what never doesHow manni graph works
Categorize a corpus nobody annotatedFill the vocabulary
Make this a gate on every pull requestRun it in CI