014 - Documentation strategy and a docs site for mdd
Status: Research notes. Not an implementation spec.
Problem brief. mdd has been developed with a single developer
and an audience of AI agents, so its written record is almost
entirely a design record: 38 specs, 13 research notes, a 130-line
README, and CONTRIBUTING.md/AGENTS.md. There is nothing for
somebody who wants to mirror a Confluence space and needs to know
what will happen to their data. The project is also a documentation
tool, so the absence of its own documentation is a credibility
problem as much as a usability one.
The intent is a site at https://schubergphilis.github.io/mdd/,
built with Starlight and themed to
match the org site at
https://github.com/schubergphilis/schubergphilis.github.io,
deployed from main on every merge, publishing hand-written user
documentation alongside the existing specs and research notes.
(The theme half of that brief was later dropped on licensing
grounds — see the superseded note under “Site generator”.)
This note frames the audiences, proposes a way to decide what to generate versus what to write, surveys the tooling against external sources as of July 2026, and records the counterpoints that came back. It stops short of a design: the sequencing at the end is a recommendation, and the open questions at the end are real.
Audience before Diátaxis
Section titled “Audience before Diátaxis”Diátaxis is the standard framing and the
right vocabulary: four documentation modes — tutorials
(learning-oriented), how-to guides (task-oriented), reference
(information-oriented), and explanation (understanding-oriented) —
positioned on two axes, acquisition versus application and practical
versus theoretical. /start-here/
and /compass/ are the two pages
worth reading before writing anything; /compass/ in particular is
a decision procedure for “what kind of page is this?” rather than a
taxonomy to memorise.
Two caveats came back from the survey and both apply here.
The framework is a diagnostic tool, not a literal site map. The most useful framing in the Hacker News discussion of Sequin’s adoption is that it is good for asking whether an area has tutorial, how-to, reference and explanation coverage, without implying those must live on separate pages. Daniele Procida has said the same in response to the “four rigid buckets” critique, summarised by Tom Johnson in What is Diátaxis and should you be using it?. For a site with maybe ten hand-written pages, four top-level sections would be structure for its own sake.
Adoption makes things look worse before better. Canonical’s account of adopting Diátaxis is candid that the first effect is that existing documentation stops being able to hide its problems. That is a feature here — there is almost nothing to disrupt.
The more load-bearing split for mdd is audience, because there are
four and they want nearly disjoint things:
| Audience | Wants | Has today |
|---|---|---|
Operator — runs mdd confluence sync-space | install, first sync, what can clobber production, config and secrets | ~40 lines of README |
Integrator — writes a MirrorBackend, wraps mdd | extension points, config schema, stability expectations | one README paragraph, a link to mdd-wrapper |
| Contributor | conventions, gates, specs | good already |
| AI agent | machine-readable command tree, skills, conventions | AGENTS.md, the skills bundle |
The operator is the entire gap. The integrator is second and has
exactly two real consumers today (mdd-wrapper, sbp-mdd). Diátaxis
then applies within the operator and integrator sections, not
across the site.
A corollary worth stating plainly: the spec corpus is not user documentation. Publishing it is cheap and worth doing, but 52 of roughly 60 pages being design record makes the site read as an engineering archive. It belongs in a clearly-labelled section with a standing banner saying it describes intent at time of writing and that the command reference is generated from code.
Freshness tiers
Section titled “Freshness tiers”The organising idea that seems most useful for a one-developer project is to rank every page by how it stays true:
- Generated from code — CLI reference, config schema, module graph. Always true, zero maintenance, low insight.
- Executed in CI — how-tos whose code blocks actually run. True, or the build is red.
- Checked structurally — links resolve, every
mdd …string in prose is a real command, generated reference has not drifted. - Prose — the why. Decays. Needs an owner and a date.
The rule that falls out: never hand-write what can be generated, and never let prose assert a fact a generator could state. Prose’s job is why, when, and don’t.
This matches the external consensus on where LLM-generated
documentation is safe. Fabrizio Ferri-Benedetti’s What’s wrong with
AI-generated docs
is not an anti-AI piece; it limits LLM generation to “carefully
delimited scenarios like API docs or code snippets, where
documentation is produced from clear, concise sources in almost
programmatic ways” — which is tier 1, done by a generator rather
than a model. It also names the failure this project should care
about most: documentation exists to say what you cannot or
should not do, and that is the part a model has no grounding for.
mdd confluence sync-space can clobber a production space; nothing
in the codebase tells a model how bad that is.
Two 2026 papers back the same split from the other direction. “An Endless Stream of AI Slop” codes 1,154 posts across 15 threads into Review Friction, Quality Degradation, and Forces and Consequences, and documents overconfident-hallucination “death loops”. Cherryleaf’s 2026 survey (n=109, mostly technical writers) found 62% use AI regularly or daily but flagged that weak AI drafts often take longer to fix than human-written ones. The mitigation both point at is the same: ground output in a source of truth, and validate mechanically.
Site generator
Section titled “Site generator”Recommendation: Starlight, as intended. But the survey turned up a genuine counterpoint that should be recorded, because the naive version of it points the other way.
The stock 2026 advice for a Python project is Material for MkDocs —
same virtualenv, no Node toolchain, mkdocstrings for API docs, and
it powers FastAPI, Pydantic, Polars and Ruff. That advice is now
substantially undercut:
- Material for MkDocs entered maintenance mode in early 2026. 9.7.0 was the last feature release, and it is scheduled to reach end of life on 5 November 2026.
- Its successor, Zensical, is a from-scratch Rust rewrite, still pre-1.0 and in early development, with third-party modules only opening up during 2026.
- MkDocs 2.0 is a ground-up rewrite that removes the plugin system entirely, putting MkDocs core and its most popular extension in direct conflict.
So the “safe, boring Python choice” is currently the one with a
migration ahead of it. Starlight, by contrast, is at 0.41.x on Astro
7, ships Pagefind search, dark mode and i18n in the box, and Astro
had the highest retention of any SSG in State of JS 2025. The real
cost is honest and unchanged: a package.json, bun, and a Node
build step in a pure-Python repo, which raises the bar for outside
contributors who want to preview a docs change.
That cost is already paid elsewhere — both candidate reference sites
are Starlight with a bun-based mise task set. Decision stands; the
Node dependency is the price.
Superseded: which theme. This note originally called for matching
the organisation site,
schubergphilis.github.io
— its SBP Blue token ramps, blue header block and Poppins. That was
dropped before S06 was written.
mdd is Apache-2.0 and the corporate brand is not; a fork cannot
redistribute it. The theme is instead LSD Warm, whose palette
lives in colors/lsd-colors.json in
lsimons/lsimons-dotfiles
under Apache-2.0, with Merriweather and Merriweather Sans under the
SIL OFL. A working Starlight mapping of that palette already exists in
lsimons/lsimons.github.io.
S06 carries the token table.
What survives from the original port list regardless of theme: the
mise task names (docs-install, docs-dev, docs-build,
docs-check), and deploy.yml with its pinned action SHAs and
concurrency: pages. Two deltas from either reference site: set
base: '/mdd/', and turn pagefind and pagination back on — both
are single splash pages and disable them. Their splash-specific CSS
(the h1#_top hiding rule and the collapsed first .content-panel)
must not be carried over.
Getting docs/ into the site
Section titled “Getting docs/ into the site”The specs and research notes must stay readable as plain Markdown on
GitHub — this is a tool about Markdown-in-git, and docs/spec/ is
the durable record. So the site reads them; they do not move into the
site.
Starlight’s docsLoader() is hardcoded to src/content/docs/.
Loading from elsewhere is a known, requested-but-unimplemented
feature (see
also Starlight discussion
#1257).
The documented workaround is to swap docsLoader() for Astro’s
glob() loader with base pointing outside the app, keeping
schema: docsSchema(). Two caveats: glob() does not apply
Starlight’s sluggification, so URLs differ unless a generateId() is
supplied, and the **/[^_]*.md pattern has to be reproduced by hand
to preserve the underscore-ignore behaviour.
That workaround handles path resolution but not the two problems that
actually matter here: 52 spec and research files have no Starlight
frontmatter (no title, no sidebar ordering), and they are full of
repo-relative links (../spec/S14-...md, ../../src/mdd/...) that
must be rewritten differently depending on whether the target is a
published page or a source file on GitHub.
It is also disqualifying on its own terms. Recent Starlight versions
apply their remark and rehype plugins only to content loaded via
docsLoader(). Content pulled in through a bare glob() loader
therefore loses Starlight’s markdown processing — including the
aside directive plugin, which is the one Starlight component that
works in plain .md and the mechanism the guide pages depend on
(see below). Copying into src/content/docs/ and leaving
docsLoader() in place keeps all of it.
Recommendation: a scripts/sync-docs.py prebuild step that
copies docs/**.md into the site’s content directory, derives
frontmatter from the # 0NN - Title heading, injects the “design
record” banner, and rewrites links. It is a dumber mechanism than a
custom Astro loader, but it is Python, it is debuggable, it is
testable with the existing pytest setup, and it is the natural place
to also emit the generated pages. Revisit if Starlight ships
first-class external sources.
CLI reference
Section titled “CLI reference”There are direct argparse-to-Markdown generators —
argdown,
parse2docs, and
argparse-to-md — plus
the Sphinx-side sphinx-argparse-cli,
which is the best-maintained of the family (it renders tox’s and
pypa-build’s docs) but is useless outside Sphinx. The standalone
Markdown generators are small single-maintainer projects, and all of
them work by reaching into argparse internals to walk the subcommand
tree.
Recommendation: add mdd help --json and generate from that.
cli.py is 193 lines and the command tree is registered explicitly
per module in src/mdd/commands/, so dumping it is small. Three
payoffs over a third-party scraper: the output is a supported public
surface rather than argparse private attributes; AI agents get a
machine-readable command tree, which matters for a tool whose
skills bundle exists to be driven by agents; and it feeds the
generated CLI pages and any llms.txt index from one source.
Then commit the generated Markdown and add a drift gate —
regenerate in CI, fail if the tree is dirty. This is the
complexipy-snapshot.json pattern already in the repo, and the same
shape as spec-check. CLI documentation then cannot rot.
Code reference
Section titled “Code reference”Do not use pydoc. The current stack is
Griffe as the extraction
layer — it walks the AST and produces a structured model of the
package including annotations — with a renderer on top.
mkdocstrings-python renders HTML for MkDocs, which is the wrong
stack here, but
griffe2md
emits Markdown using the same data and Jinja templates, and
Griffonner is a template-first generator over Griffe for
arbitrary output. Either feeds a Starlight page. Griffe is
well-established beyond docs: the OpenAI Agents SDK and PydanticAI
both use it to parse docstrings into tool schemas.
The stronger recommendation is about scope, not tooling. A full API
reference over 194 modules is high noise and low value, because
nobody imports mdd except wrapper authors, and they need roughly
five things: MirrorBackend, the converter registry, the config
schema, typed frontmatter, and the IR entry points. Generate a
curated Extension API page over that symbol list and nothing else.
Completeness here is a cost, not a benefit.
Architecture documentation
Section titled “Architecture documentation”This is the part that genuinely rots, and the one where the initial instinct — run a code-graph tool and commit the graph — is worth resisting. A committed AI-generated graph becomes a stale second source of truth the moment a module moves, and nothing fails when it is wrong.
Recommendation:
import-linter (2.7 at time
of writing, on grimp 3.14+). You write architecture contracts —
“mdd.ir must not import mdd.confluence”, “commands may import
converters, not the reverse” — and CI enforces them. The contracts
file is an architecture document that is structurally incapable of
lying, which is a stronger property than any prose or generated
diagram has. It builds a directed import graph via grimp, reports
violations as full import chains, caches the graph between runs, and
notably does not require defining the whole architecture up
front, so it can start with two or three contracts. The known
complaint, from Roman Imankulov’s write-up,
is that the five built-in contract types sometimes force several
contracts where one would be logical; custom contract classes are the
escape hatch.
grimp also exposes the graph, so a Mermaid layer diagram can be generated into the site from the same data that gates CI.
The heavyweight alternative is the arc42 + C4 + Structurizr/PlantUML
- Kroki docs-as-code pipeline, which is the European industry
standard and has good reference
implementations.
It is the right answer for a system with multiple deployables and
multiple teams.
mddis one CLI, one developer, and 194 modules; arc42’s twelve sections would be mostly empty headings, which is exactly the symmetry-padding failure described below. (Also noted in passing: RAD-AI argues arc42 and C4 cannot capture probabilistic AI components, which is tangentially relevant to themdd aisurface but not worth acting on at this size.)
Pair the contracts with exactly one hand-written explanation page —
how a sync flows end to end, and why the IR exists — carrying a
last-reviewed date.
Executable examples
Section titled “Executable examples”The idea of extracting how-tos from the test suite is good, but the direction matters. Having a model summarise 146 test files into prose produces precisely the hollow output described above. The inversion: hand-lift five to eight integration tests that are already narrative workflows into how-to pages, then wire those pages back into pytest so the doc is the test. One-time human curation, permanent automated verification.
Three candidate harnesses:
| Tool | Fit |
|---|---|
| Sybil (10.1) | Most capable. Parses examples from source and evaluates them in the normal pytest run via pytest_collect_file; supports Markdown/GFM/MyST, pytest fixtures via the fixtures argument, skip directives, and custom parsers. pytest’s own docs recommend it for anything beyond doctest blocks. |
| pytest-examples | Pydantic-team tool. Distinctive feature is linting/formatting examples with ruff and updating expected output in place via --update-examples. |
| mktestdocs | Five lines of setup, runs ```python blocks and checks they do not raise. No output matching. |
Recommendation: Sybil, and the deciding factor is custom
parsers. Most mdd examples are shell, not Python — mdd convert …, mdd new … — and none of the three handle shell blocks out of
the box. Sybil is the only one designed to be extended with a parser
for a block type it does not know.
Start with the offline commands (convert, new, search,
.mddignore), which already cover most of the tutorial surface, run
in a tmpdir against a fixture config. Confluence and SharePoint
examples either reuse the snapshot corpus under
tests/corpus/confluence/_snapshots or are explicitly labelled
illustrative — never silently untested.
llms.txt
Section titled “llms.txt”The evidence here is considerably worse than expected and it changes the recommendation.
- A monitoring platform analysing 500M+ LLM bot traffic events over
90 days found 408 direct fetches of
/llms.txt. Server-log analyses converge on roughly 97% of deployed files receiving no AI bot requests at all. - As of Q1 2026 no major AI vendor has committed to reading it in production. Google said no on the record — Gary Illyes in July 2025, with John Mueller comparing it to the keywords meta tag.
- SE Ranking found 10.13% adoption across 300,000 domains, but among the 50 most AI-cited domains, one had the file.
See The State of llms.txt in 2026 for the aggregation. Nothing here says don’t ship it; it says do not build anything bespoke for it.
Recommendation: use
starlight-llms-txt,
which is a plugin entry and a projectName option and emits
llms.txt, llms-full.txt and llms-small.txt at build time.
Two lines of config, done.
Then spend the actual effort on per-page Markdown twins, which
agents demonstrably do fetch, via
starlight-md-txt
or @wave-rf/starlight-llm-tools (which also adds copy-markdown and
open-with-AI buttons). For an agent-facing tool this is the higher
value half. One caveat from the same sources: emitting Markdown
copies of every page is duplicate content at scale, so it interacts
with crawl budget — probably irrelevant at this site’s size.
Prose gates
Section titled “Prose gates”Vale is the established prose linter: Go, no runtime dependencies, offline, custom rules in YAML, importable Microsoft and Google style guides. Used by Datadog, GitLab, Microsoft, Mozilla, and the Linux Foundation.
Two patterns worth copying, both from teams that adopted it onto
existing corpora. Elastic runs it as a PR action reporting on
modified lines only. Another project used filter_mode: added so
pre-existing prose debt does not block CI, and cleared roughly 580
alerts just by expanding the vocabulary accept-list with legitimate
domain terms — which for mdd means Confluence, SharePoint,
frontmatter, roundtrip, mddignore, and so on. Both advise
starting at warning level and scoping it away from generated
reference output.
mise run docs-check is the natural home for Vale, astro check
and the site build, driven by a path-filtered workflow. The
code-coupled checks — the CLI drift gate and the assertion that
every mdd … string appearing in prose exists in the help --json
tree — belong in mise run ci instead. See question 4 for why the
split runs along that line.
How AI-generated documentation fails
Section titled “How AI-generated documentation fails”Recorded explicitly, because most of this documentation will be AI-drafted and these are the failure modes to gate against. The first seven are observable in output; the last three are process failures.
- Restating the signature in English. “The
--forceflag forces the operation.” Test: delete the identifier from the sentence — if nothing is lost, delete the sentence. - Symmetry padding. Every page gets the same four sections whether or not there is anything to say. Uneven documentation is honest documentation.
- Confident invention. Flags, config keys and error strings that do not exist. The trust-killer, and the reason the factual layer must be generated rather than written.
- No opinion, and no “don’t”. Models write “you can do X or Y”,
never “use X; Y is legacy” — and will not tell someone not to run
sync-spaceagainst a production space. - Timeless present tense. Prose that implicitly claims to be current forever, with nothing to contradict it.
- Tone inflation. “seamlessly”, “powerful”, “robust”, “simply”.
In a documentation context
simplyis usually a lie. - Examples that were never run. Plausible, wrong, expensive to discover.
- Volume as a proxy for quality. A model will cheerfully produce 60 pages. 60 unmaintained pages are worse than 8 true ones.
- Duplication drift. The same fact in README, index and quickstart; two get updated.
- Organised by module rather than by task.
Countermeasures, in the order they are worth building:
- Generate tier 1, execute tier 2, gate tier 3 (see above). This removes categories 1, 3 and 7 mechanically.
- Vale with a banned-word list handles 6.
- A hard page budget for the hand-written layer — around ten pages for the first cut — handles 8.
- Starlight’s git-derived
lastUpdatedon every page, plus a build date in the footer, handles 5 without asking anybody to promise anything. - Categories 2, 4, 9 and 10 have no mechanical fix and need human editing. The working rule: the model produces drafts to be cut, not drafts to be approved.
Synthesised articles from the spec and research corpus
Section titled “Synthesised articles from the spec and research corpus”The specs and research notes are written for agents and human reviewers. They are precise, cross-referenced and self-contained, and they are not prose anybody would read for pleasure. Publishing them raw gives the site bulk without giving it a reason to exist.
The alternative is to treat them as primary sources and publish
secondary literature: article-shaped pages that tell a
higher-level story and cite the specs and notes underneath. A
.claude/skills/ skill drafts these into site/, and a human
tightens them. This changes the answer to whether research notes are
worth publishing — as citation targets their staleness stops
mattering, because a citation is allowed to be from a date.
It also clarifies what tier 4 is for. An essay explaining why the IR is first-party is a historical account of a decision, not a description of current behaviour, so it does not decay the way a reference page does. That is a much safer thing to ask a model to write.
The corpus gives the site two derived surfaces rather than one:
articles for humans, raw Markdown and llms.txt for agents. Neither
audience is served the other’s format.
The risk
Section titled “The risk”This is the only artifact in the plan with no mechanical backstop. The CLI reference has a drift gate, examples have tests, links have a checker, prose has Vale. A synthesised article has nothing — no check can detect that it misrepresented R12’s conclusion. It is defended entirely by human review, and the failure mode is that tightening prose is pleasant while verifying claims against 52 documents is not, so review degenerates into copyediting.
Two mechanisms make review tractable:
- Citation density as a hard requirement. Every non-obvious claim links to the spec or note it came from. The purpose is not reader navigation but reviewability: the check becomes “does this sentence match the cited paragraph” rather than “does this match my memory of the corpus”. Link resolution and cited-document existence are then mechanically gateable.
- Provenance frontmatter with a staleness gate.
derived-from: [S28, S33, R09, R12]plusderived-at: <sha>, and adocs-checkrule that flags when any listed source has changed since that sha. Prose correctness is not checkable; prose staleness is, and staleness is the failure that actually happens. This pulls tier 4 halfway into tier 3 cheaply. Deferred — see question 6. It is more defensible than the equivalent mechanism for guide pages, because an article can be silently wrong about a spec edited months ago and nothing surfaces it, but that argument should be made with four articles in hand rather than none.
What the skill must be told
Section titled “What the skill must be told”Preserve reversals. R12 made a recommendation and R13 overturned it. A model synthesising both will produce a tidy narrative in which the right answer was arrived at directly. The true story — the first comparison was wrong, a later spike changed the conclusion — is the most interesting content in the corpus, and smoothing is what synthesis does to it by default. Dead ends, rejected options and the reasons for rejection are the payload, not the framing. This is also the antidote to failure mode 4 above: a story containing a wrong turn necessarily has opinions in it.
Draft handling
Section titled “Draft handling”Drafts are written to site/src/content/docs/_drafts/. The
underscore prefix is already what docsLoader() ignores, so drafts
never build. Promotion is a git mv. Re-running the skill against a
promoted article must diff and propose, never overwrite — that is
what protects the human editing pass.
Candidate articles
Section titled “Candidate articles”Organised by story, not by spec. One-article-per-spec would be failure mode 10 above.
- Why
mddhas its own document IR — from R06, R09–R13 andS28. Four measured spikes; Pandoc and docling both lost to a first-party typed IR. - What “near-lossless” actually means — from
S31,S33, R07 and R08. - Publishing into Confluence without clobbering it — from
S07,S26andS27. - Spec-driven development with AI agents — from
S01,S34andS37. The meta-story, and probably the most-read page the site would ever have.
Recommended sequencing
Section titled “Recommended sequencing”- Phase 0 — site, deploy, and ~10 operator pages, nothing
generated. Home (
index.mdx, hand-authored insite/), Install, Quickstart (offline:convert,new,search), Concepts (mirror / IR / sync direction), Safety (what clobbers what, and when not to run a command), Configuration and secrets, Commands overview, “Your first Confluence sync” and “Your first SharePoint sync” as separate bring-your-own-tenant how-tos, and a pointer to the specs. This is the whole point; everything below amplifies it. - Phase 1 — auto-publish
docs/spec/anddocs/research/viasync-docs.py, plus Pagefind and thestarlight-llms-txtplugin. Cheap bulk, clearly demoted behind a design-record banner. - Phase 2 — the article skill. Deliberately early, before the generated reference work: articles are what make the site worth visiting rather than worth searching. A beautiful essay on IR design next to no install page would be the wrong site, which is why this is not Phase 0 — but it should not wait until the end either.
- Phase 3 —
mdd help --json, generated CLI reference pages, and the drift gate. - Phase 4 — executable examples with Sybil and a shell-block parser, applied to the Quickstart first.
- Phase 5 — import-linter contracts, a generated module graph, and one hand-written architecture explanation.
- Phase 6 — curated Extension API via griffe2md, for wrapper authors.
Explicitly not recommended: a full API reference over all 194
modules, an AI code-graph pipeline committed to the repo, pydoc,
and a bespoke llms.txt generator.
Cross-references
Section titled “Cross-references”README.md— source material for Phase 0’s Install, Commands overview and Configuration pages.docs/spec/000-specs.mdanddocs/research/000-research.md— the two index pages that become section landing pages when published.docs/spec/S02-mdd-cli-tool.mdandS35-argparse-cli-parsing.md— the CLI surface ahelp --jsondump has to cover.docs/spec/S07-data-protection.mdandS26-managed-elsewhere.md— source material for the Safety page.docs/spec/S34-code-quality-gates.md— where adocs-checkgate and an import-linter gate would be specified.docs/spec/S36-module-structure.md— the layering that import-linter contracts would encode.lsimons/lsimons-dotfiles—colors/lsd-colors.json, the Apache-2.0 source of the LSD Warm palette.lsimons/lsimons.github.io— an existing Starlight mapping of that palette, plus the mise task set anddeploy.ymlshape.schubergphilis/schubergphilis.github.io— the originally proposed theme, dropped on licensing grounds; still the reference fordeploy.yml’s pinned action SHAs.
Open questions
Section titled “Open questions”-
Where does the Astro app live?Resolved.site/at the repo root, sodocs/stays a pure Markdown corpus. This diverges from both the Starlight convention (the Starlight repo puts its own site indocs/, sibling topackages/) and the org repo, at a cost of one line per mise task plus thedeploy.ymlartifact path. The deciding argument is specific to this project:mddis a Markdown-mirror tool anddocs/is the tree it would plausibly be pointed at, sonode_modules/,dist/and.astro/do not belong in it. A separate docs repository and agh-pagesbranch were both considered and rejected — none of the usual reasons to split a docs repo apply at one developer, docs move with code, agents read the whole repository, and GitHub now recommends the artifact flow (upload-pages-artifact+deploy-pages) over committing build output to a branch. -
DoesResolved.docs/get subdirectories for the new prose?docs/guide/*.md, alongsidedocs/spec/anddocs/research/, as plain Markdown carrying a small amount of Starlight-specific syntax. This follows the project’s own pattern:mddalready tolerates Confluence-specific syntax ({=confluence}fenced blocks) inside otherwise-plain Markdown, and trusts a lossy mapping between two representations rather than restricting the source format.Concretely: write GitHub alert syntax (
> [!CAUTION]), which renders natively on GitHub, and add a small remark plugin mapping it onto Starlight asides. Starlight declined to support GitHub alerts natively because the variants do not map 1:1 — GitHub has five (NOTE/TIP/IMPORTANT/WARNING/CAUTION), Starlight four (note/tip/caution/danger) — so the plugin has to collapse IMPORTANT and WARNING ontocautionand CAUTION ontodanger. Starlight’s own:::cautiondirective syntax stays available as an escape hatch; it renders as literal text on GitHub.Tabs, Steps, Cards and CardGrid are MDX-only and are not available in
.md. Do without them initially; Markdoc and custom remark directives are the escape routes if a page later demands them. One hand-authoredindex.mdxlives insite/for the landing page, since a hero and card grid are presentation rather than documentation. -
Are research notes published at all?Resolved. Yes, but demoted, and not as the main event. They are published so that synthesised articles have citation targets on the same site — a citation pointing off to GitHub is worse than one that resolves in place. Demotion is on three axes, all available as Starlight frontmatter thatsync-docs.pyinjects:pagefind: falseso they never surface in search, exclusion fromllms.txt/llms-full.txtvia the plugin’s exclude patterns, and a single collapsed sidebar group at the bottom with abanneron every page. The published human-facing surface is the article layer described above, not the raw notes. -
DoesResolved. Split by what makes a check fail, not by what kind of check it is.docs-checkrun inmise run ci, or only in the docs workflow?Checks that fail because code changed belong in
mise run ci, because a docs-only workflow does not run on a Python PR: the CLI drift gate,mdd …strings resolving against the command tree, and the article provenance gate. All three are pure Python and fast.Checks that fail because prose changed go in a separate
mise run docs-checkdriven by a workflow path-filtered ondocs/**andsite/**: Vale,astro check, and the site build. Nothing can go silently red, because none of these can be broken by a code change.Two constraints fall out. Node stays out of
mise run ci— that task is the local gate run on every change, and requiringbun installwould tax every Python contributor. And the site build must run on docs PRs, sincedeploy.ymlonly builds on merge to main, so without it a broken Astro config surfaces at deploy time rather than at review time.External link checking runs on a schedule rather than per-PR; external links rot on their own timetable and a third-party outage should not fail a PR.
-
What is the versioning story?Resolved. Single version at the root, no versioning plugin.The retrofit worry was mostly unfounded. Versioning is not native to Starlight — it is
starlight-versions, a community plugin, self-described as opinionated and in early development — but its model keeps the current version at the root and gives archived versions a slug prefix. So/guide/install/stays put and adding versioning later snapshots the then-current content to/1.0/guide/install/. There is no URL stability decision to make now, which was the only expensive part.Against adopting it today: Starlight’s release notes warn that community plugins may need manual updates for Astro v7, and more decisively,
mddinstalls from git main, so there is no released version to document. A version selector offering choices nobody can install is worse than none.Instead the site shows the date it was built, injected at build time. Not the commit sha — a sha would imply the pages were verified against that commit, which they were not. A date makes the weaker, true claim, and matches the claim strength of everything else on the site.
Revisit when the install instructions point at a tag rather than main.
-
Who owns theResolved. Nobody, because the field is dropped.last-revieweddates?A manual
last-reviewedis a promise to re-read. At one developer that promise will not be kept, and a stalelast-reviewedon a page nobody has looked at since is an affirmative false claim of freshness — failure mode 5 above in a different costume, and worse than saying nothing.Use Starlight’s
lastUpdatedinstead (boolean, defaultfalse), which derives the date from the file’s git history and is overridable per page. One operational gotcha: it needs real git history, andactions/checkoutdefaults to depth 1, sodeploy.ymlmust setfetch-depth: 0or every page displays the deploy date.A richer mechanism was considered and rejected as premature: a
covers: [src/mdd/convert/, S03]frontmatter list with a CI warning when a covered path changed after the page’s last git modification. At roughly ten pages this is machinery for a scale that does not exist, and the two code-coupled checks from question 4 — the CLI drift gate andmdd …string resolution — already catch most of what it would have caught, because most of those pages are about commands. The same cut applies to thederived-from/derived-atprovenance gate proposed for synthesised articles: defer it and decide when there are actually articles to protect.Revisit when the page count outgrows what can be re-read in one sitting, or when somebody other than the current single developer starts writing pages.
-
Does the operator documentation need a working example Confluence space?Resolved. No, and the reason is structural rather than operational. A quickstart’s job is to have the reader run a command that does something; for Confluence that command issync-space, which writes. Strangers cannot be given write access to the corpus instance, so a shared demo space cannot serve a sync quickstart regardless of how the free tier is configured. The instance is also already load-bearing for the test corpus (R07,S32,mise run refresh-corpus); pointing public documentation at it makes it load-bearing twice.The quickstart is therefore offline —
mdd convert,mdd new,mdd search— which is the same set identified above as the CI-testable examples. Both constraints reduce to “no external service”, so the page a reader can follow without a tenant and the page that executes in CI are the same page.Confluence and SharePoint get separate how-tos that open by stating the reader needs a space they administer, framed safety-first: scratch space, read-only export first, sync only after. Where documentation needs to show real content, use the committed snapshots under
tests/corpus/confluence/_snapshots/.
Next document
Section titled “Next document”S06 promotes phases 0 through 2
of the sequencing above into a spec: the repository layout, the theme
port, sync-docs.py, the guide’s Markdown dialect, the deploy
workflow, the gate split, and the article-synthesis skill. Phases 3
through 6 are separable and each get their own spec, or ride along
with the feature they document.
Site built 2026-07-30.