Docs Framework — Agent-Native Docs-as-Code Stack
Date: 2026-04-21 Status: Locked (design approved 2026-04-21) Owner: acidrums7 Scope: Ideony repo; Jooice federation considered but out-of-scope for initial rollout.
What this spec is: the WHAT — framework pick, repo layout, auto-gen pipelines, quality gates, cofounder access, CI/hosting.
What this spec is NOT: the WHY (will spawn ADR once locked) and the HOW-TO (will live in docs/testing.md-style doc under a new docs/docs-framework.md after implementation).
Contents
Section titled “Contents”- Locked goals
- Framework pick
- Repo layout
- Auto-gen pipelines
- Quality gates
- Zero-drift gate audit
- Agent-native layer —
llms.txt+ search - Diagram-as-code standard
- Cofounder / dual-audience access
- Versioning strategy
- Federation hook — Jooice later
- CI + hosting pipeline
- Migration + rollout phases
- Risks + mitigations
- Out of scope
1. Locked goals
Section titled “1. Locked goals”Ten goals locked during brainstorm (2026-04-21).
| # | Goal | Source |
|---|---|---|
| 1 | Total coverage — every shipped surface documented | user |
| 2 | SSoT / zero drift — code change without matching doc fails CI | user + existing check-docs-updated.sh |
| 3 | Agent-optimized search (primary audience = Claude) | user “e” |
| 4 | Auto-generate from source — API / schema / CLI / env | user “b” |
| 5 | Versioned snapshots (future, internal now) | user “d” |
| 6 | Dual audience — devs + cofounders (like the Google Drive cofounder doc) | user “f” |
| 7 | Write-time quality gates — lint / link-check / spell / style | user “g” |
| 8 | Diagram-as-code — Mermaid / D2, no PNG screenshots | user “h” |
| 9 | Living examples — code snippets executed in CI | user “i” |
| 10 | Multi-repo federation — same framework for Ideony + Jooice | user “j” |
2. Framework pick
Section titled “2. Framework pick”Choice: Diátaxis (taxonomy) + Starlight (renderer) + llms.txt (agent index) — hybrid D
Section titled “Choice: Diátaxis (taxonomy) + Starlight (renderer) + llms.txt (agent index) — hybrid D”Taxonomy — Diátaxis (already adopted in docs/README.md). Four quadrants: tutorial, how-to, reference, explanation. No change.
Renderer — Starlight (Astro-based).
- On-stack (TS / Vite ecosystem matches Expo + Nest)
- Pagefind static search (no Algolia signup)
- Content collections load raw MD from
docs/— no copy, no drift - Plugins native for every goal (
starlight-openapi,starlight-llms-txt,starlight-versions, Mermaid viarehype-mermaid) - Cloudflare Pages free tier on existing
ideony.is-a.devzone
Agent index — llms.txt + llms-full.txt standard. Machine-readable doc index for LLMs. Auto-generated by starlight-llms-txt.
Alternatives rejected
Section titled “Alternatives rejected”| Reason not picked | |
|---|---|
| Markdown-only (A) | No search for cofounders; no API auto-render; no llms.txt out of box |
| Docusaurus (B) | React hydration heavier than Astro; versioning+i18n better but YAGNI for MVP 0; aesthetics dated OOB |
| Mintlify (C) | SaaS lock-in contradicts OSS-first lean; $150+/mo past free tier; limits on MDX + self-host |
| MkDocs Material | Python stack off-tree (you’re TS everywhere) |
| VitePress | Vue-based; thinner plugin coverage for OpenAPI + llms.txt |
| Nextra | Next.js-shaped; you’re Expo + Nest, no Next in tree |
Rationale will be moved to an ADR (docs/decisions/0027-docs-framework-starlight.md) on lock.
3. Repo layout
Section titled “3. Repo layout”Invariant: docs/ = content SSoT. apps/docs/ = renderer shell (no content, ~8 files). Deleting apps/docs/ any day → docs still work on GitHub as raw MD. Starlight is a view, not a store.
apps/ docs/ # Starlight app (renderer only) astro.config.mjs # plugins: llms-txt, openapi, mermaid, pagefind package.json # astro + @astrojs/starlight + plugins src/ content.config.ts # content collections → ../../docs loader styles/custom.css # brand tokens (Italian Sole palette) components/ # Header override, cofounder gate public/ # favicon, OG imagedocs/ # SSoT — raw MD, git-tracked, human+agent write here README.md # sitemap (Diátaxis index, unchanged) architecture.md # unchanged design-system.md # unchanged getting-started.md # unchanged glossary.md # unchanged infrastructure.md # unchanged roadmap.md # unchanged status.md # unchanged testing.md # unchanged decisions/ # ADRs, unchanged specs/ # feature designs, unchanged plans/ # impl plans, unchanged research/ # audits, unchanged archive/ # historical, unchanged _generated/ # NEW — gitignored, CI-written api/ openapi.md # from NestJS @nestjs/swagger scalar.html # OpenAPI viewer db/ schema.md # from Prisma schema erd.mmd # Mermaid ERD config/ env.md # from .env.example + annotations cli/ pnpm-scripts.md # extracted from package.json scriptsCHANGELOG.md # stays at repo rootCLAUDE.md # stays at repo rootWhy apps/docs/ and not docs-site/ or docs/.starlight/
Section titled “Why apps/docs/ and not docs-site/ or docs/.starlight/”- Matches monorepo
apps/*convention (already used byapps/api+apps/mobile) - Turborepo pipeline picks it up free (
docs:buildtask cached remote) pnpm -F docs dev/pnpm -F docs buildnatural- Isolates Astro deps from root
package.json(smaller root lockfile) - Jooice federation later — same
apps/docs/pattern reusable
What MUST NOT happen
Section titled “What MUST NOT happen”- No MD inside
apps/docs/src/content/docs/— loader reads from../../docs/** - No copying
docs/*.mdintoapps/docs/— destroys SSoT - No hand-editing
docs/_generated/**— CI-only
4. Auto-gen pipelines
Section titled “4. Auto-gen pipelines”Each pipeline runs as a pnpm script, writes into docs/_generated/**, committed to gitignore. CI + dev both run pnpm docs:gen before Starlight build.
4.1 OpenAPI → Markdown
Section titled “4.1 OpenAPI → Markdown”Source: NestJS @nestjs/swagger (already producing spec in apps/api/scripts/generate-openapi.js, 30 routes, pipes into packages/api-client/ SDK gen).
Pipeline:
pnpm -F api generate:openapi→ writesapps/api/openapi.jsonwiddershins apps/api/openapi.json -o docs/_generated/api/openapi.md(oropenapi-to-md)- Scalar viewer: copy spec →
docs/_generated/api/scalar.html(Scalar CDN script reads spec) starlight-openapiplugin renders spec into searchable route pages in Starlight
Reuses existing spec → zero new infra for API docs. Auto-updates on every API route change.
4.2 Prisma schema → Markdown + ERD
Section titled “4.2 Prisma schema → Markdown + ERD”Source: prisma/schema.prisma.
Pipeline:
prisma-markdown→docs/_generated/db/schema.md(tables, fields, relations, indexes)prisma-erd-generator→docs/_generated/db/erd.mmd(Mermaid)- Starlight page embeds both via
<Mermaid />+ frontmatter include
4.3 Env config → Markdown
Section titled “4.3 Env config → Markdown”Source: .env.example (already annotated with comments).
Pipeline:
- Small node script parses
.env.example, extracts# KEY — descriptioncomments + default values - Emits
docs/_generated/config/env.mdas table (var | purpose | required | default | example)
4.4 CLI / pnpm scripts → Markdown
Section titled “4.4 CLI / pnpm scripts → Markdown”Source: root package.json + per-workspace package.json scripts sections.
Pipeline:
- Node script aggregates all
scriptsfrom allpackage.jsonin workspace - Cross-refs a
docs/cli-descriptions.ymlfile (hand-maintained 1-line descriptions per script) - Emits
docs/_generated/cli/pnpm-scripts.md
4.5 TypeScript types → Markdown (optional, phase 2)
Section titled “4.5 TypeScript types → Markdown (optional, phase 2)”Source: packages/types/src/**.
Tool: TypeDoc with typedoc-plugin-markdown.
Pipeline: typedoc --plugin typedoc-plugin-markdown packages/types/src/index.ts --out docs/_generated/types/.
Deferred — value lower than 4.1–4.4 for MVP 0.
5. Quality gates
Section titled “5. Quality gates”All gates run in pre-commit (Husky + lint-staged) AND CI (.github/workflows/docs-quality.yml). Fail-fast.
| Gate | Tool | Scope | Reason |
|---|---|---|---|
| Markdown lint | markdownlint-cli2 | docs/**/*.md + CLAUDE.md + README.md | Broken tables, heading levels, inconsistent list markers |
| Prose style | vale + write-good + custom rules | same | Catches hedging, passive voice, banned words (from CLAUDE.md: critical, crucial, essential, robust, elegant) |
| Spell check | cspell w/ project dictionary (Ideony, Dokploy, Clerk, Expo, NestJS, Gluestack, Mapbox, etc.) | same | Typos in technical terms |
| Link check | lychee (Rust, fast) | all MD + ADRs + specs | Dead internal + external links; catches doc reorg breakage |
| Frontmatter schema | custom Ajv validator in scripts/validate-frontmatter.ts | specs + ADRs (they have Date: / Status: / Supersedes:) | Schema drift on structured docs |
| Diátaxis tag | custom check in scripts/check-diataxis-quadrant.ts | any doc in docs/ top-level | Each doc must declare quadrant in frontmatter |
| Mermaid syntax | @mermaid-js/mermaid-cli dry-render | all .mmd + fenced mermaid blocks | Catches unrenderable diagrams pre-merge |
| Code-block compile | remark-typescript | fenced ts / tsx blocks marked example | Living examples (goal #9) |
Living-examples strategy (goal #9)
Section titled “Living-examples strategy (goal #9)”Mark code blocks with ts example language tag. Pre-commit hook extracts them → writes to tmp/examples/<file>-<hash>.ts → runs tsc --noEmit w/ project tsconfig. Fail = broken example.
For full runtime examples (e.g., API snippet that must compile + return 200), write them as e2e/docs-examples/*.spec.ts Playwright/Jest tests referencing the doc file. Drift fails in normal test run.
6. Zero-drift gate audit
Section titled “6. Zero-drift gate audit”User flagged goal #2 (“review if accurate already”). Current state:
scripts/check-docs-updated.sh— pre-commit hook mapping source paths → required doc paths (policy inscripts/README-docs-gate.md)- Enforced in 3 layers: pre-commit, commit-msg, pre-push + CI workflow
- Server-side branch protection OFF during MVP 0 (honor system, Claude is sole committer)
Audit plan
Section titled “Audit plan”- Read current
check-docs-updated.shline-by-line - Cross-check against CLAUDE.md trigger matrix (14 rows)
- For each row:
- Does the gate cover it automatically? → OK
- Does CLAUDE.md say “manually walk matrix”? → flag
- Gaps list goes into a follow-up task. Enhancement patterns:
- Shipped-feature detection: if commit type is
feat:→ requireCHANGELOG.md+docs/status.md+docs/roadmap.mdALL staged (currently gate only requires one) - ADR trigger: if
docs/decisions/**unchanged + commit touches listed arch surfaces → prompt for ADR - Auto-gen freshness: if API routes changed but
docs/_generated/api/openapi.mdnot regenerated → fail
- Shipped-feature detection: if commit type is
- Upgrade
check-docs-updated.shto fill gaps; add tests incheck-docs-updated.test.sh
Output: docs/research/2026-04-2x-docs-gate-audit.md + patch to the gate.
7. Agent-native layer — llms.txt + search
Section titled “7. Agent-native layer — llms.txt + search”Why this is the top-priority goal
Section titled “Why this is the top-priority goal”User explicit: agents (Claude) are primary doc consumers, humans second.
llms.txt standard
Section titled “llms.txt standard”Two files at site root (served by Starlight):
/llms.txt— curated index: title, one-liner, link to each doc. Human-readable, 1–2KB./llms-full.txt— flat concatenation of all MD content, delimited by headings. 10s of KB, single fetch = full context.
Generated by starlight-llms-txt plugin. Zero hand-maintenance.
Claude-specific optimizations
Section titled “Claude-specific optimizations”- Canonical MD URLs — every Starlight page exposes
?format=md→ returns raw source MD (Starlight native). Claude fetcheshttps://docs.ideony.is-a.dev/architecture?format=md→ gets MD, not HTML. - Stable anchors — enforce slug format (
kebab-case, no auto-numbering) so LLM-generated links stay valid. - Frontmatter = agent schema — every doc declares
quadrant,status,last_reviewed,audience(dev / cofounder / agent). Agents filter onaudience: agentfor the curated subset. llms-ctx.mdper complex topic — short curated brief per module (auth flow, Mapbox integration, SOS dispatch) surfaced first inllms.txt.
Search
Section titled “Search”- Pagefind (static, default in Starlight) — full-text, client-side, no infra, no API key, works offline. Good enough for MVP 0.
- Phase 2 (optional): Kapa.ai / Inkeep for semantic search over doc corpus, $$/mo. YAGNI for now.
8. Diagram-as-code standard
Section titled “8. Diagram-as-code standard”Hard ban: no PNG / JPG / screenshot-of-draw.io diagrams. Repo stores source, renders at build time.
| Use case | Tool | Output |
|---|---|---|
| Flowcharts, sequence, state, class | Mermaid (default) | Rendered inline by rehype-mermaid at build; raw .mmd inspectable |
| Architecture / network / complex layouts | D2 (optional phase 2) | SVG via d2 CLI |
| Pixel UX mockups | Excalidraw scene files (.excalidraw) | Rendered via excalidraw-to-svg in CI |
Current docs/architecture.md already has C4 block diagrams — migrate to Mermaid C4Context / C4Container diagrams. One commit, mechanical.
9. Cofounder / dual-audience access
Section titled “9. Cofounder / dual-audience access”Goal #6: same docs serve devs + cofounders (replace the Google Drive Ideony doc over time).
Option picked: Cloudflare Access (Zero Trust) on docs.ideony.is-a.dev
Section titled “Option picked: Cloudflare Access (Zero Trust) on docs.ideony.is-a.dev”- Public perimeter: entire site behind Cloudflare Access
- Access policy: email allowlist (cofounders + team Google Workspace) via CF Zero Trust
- Free tier: 50 users (plenty for Ideony pre-launch)
- Auth: one-time email PIN or Google OAuth — no passwords
- Future: public subset via separate
docs-public.ideony.is-a.devdomain pulling onlyaudience: publictagged docs
Audience tagging
Section titled “Audience tagging”Every doc frontmatter gets audience: enum: agent | dev | cofounder | public. Starlight build filters:
- Main site: all
/cofounder/subpath:audience: cofounderoraudience: publiconly (hides internal tech detail)/public/(future phase 2):audience: publiconly
Replacing the Google Drive doc
Section titled “Replacing the Google Drive doc”A migration pass moves shared decisions / roadmap / brand narrative from the cofounder Google Doc into docs/specs/ + docs/roadmap.md + a new docs/cofounder-overview.md (tagged audience: cofounder). Drive doc becomes historical / archive.
10. Versioning strategy
Section titled “10. Versioning strategy”User: “yeah cool, for now only internal but maybe in future.”
MVP 0 → post-launch: single version (main)
Section titled “MVP 0 → post-launch: single version (main)”- No versioning pre-launch. Docs reflect current
main. starlight-versionsplugin installed but inactive.
Post-launch trigger
Section titled “Post-launch trigger”When a public API surface or app ships v1.0:
- Tag
v1.0triggers docs snapshot:starlight-versionscreates/v1.0/subpath - Subsequent versions:
/v1.1/,/v2.0/, etc. /latest/alias → current stable
Versioning boundary
Section titled “Versioning boundary”Only docs/decisions/, docs/specs/, and the reference quadrant (architecture.md, design-system.md, API docs, schema docs) get versioned. Plans + research + archive stay unversioned (they’re historical by nature).
11. Federation hook — Jooice later
Section titled “11. Federation hook — Jooice later”Goal #10. Design now, implement when Jooice ready.
- Ideony
apps/docs/+ Jooiceapps/docs/= identical Starlight shells - Shared
packages/docs-shell/(new workspace) contains astro.config defaults, shared styles, shared plugins config, shared cspell dict, shared vale styles - Third “hub” repo OR Ideony itself as hub —
docs.ideony.is-a.dev/ideony/**+docs.ideony.is-a.dev/jooice/**, content pulled via Astro remote content collections
Decision deferred
Section titled “Decision deferred”Pick hub model when Jooice’s docs framework lands. For now, structure Ideony apps/docs/ so packages/docs-shell/ extraction is a one-day refactor.
12. CI + hosting pipeline
Section titled “12. CI + hosting pipeline”Pipeline
Section titled “Pipeline”[ commit ] → [ pre-commit gates (quality, living-examples, zero-drift) ] → [ push ] → [ CI: docs-ci.yml - pnpm install - pnpm docs:gen # regenerate _generated/** - pnpm docs:quality # rerun all gates - pnpm -F docs build # Starlight static build - upload dist/ artifact ] → [ CI: docs-deploy.yml (main-branch only) - download dist/ - cloudflare-pages deploy - smoke check: curl /llms.txt, /architecture, /api, / ]Hosting
Section titled “Hosting”- Target: Cloudflare Pages, project name
ideony-docs, custom domaindocs.ideony.is-a.dev - Zone: existing
ideony.is-a.dev(same asapp.+api.subdomains) - Access: Cloudflare Zero Trust policy (email allowlist)
- Cache: default CF edge cache;
llms.txt/llms-full.txtw/ short TTL (1 hr) so agents get fresh context fast - Cost: €0 (Pages free tier: 500 builds/mo, unlimited bandwidth)
Turbo integration
Section titled “Turbo integration”turbo.json adds:
{ "pipeline": { "docs:gen": { "outputs": ["docs/_generated/**"] }, "docs:build": { "dependsOn": ["docs:gen"], "outputs": ["apps/docs/dist/**"], "inputs": ["docs/**", "apps/docs/**", "packages/docs-shell/**"] } }}Remote-cached → CI rebuilds cheap.
13. Migration + rollout phases
Section titled “13. Migration + rollout phases”Ship incrementally. Each phase independently valuable, reversible.
Parallelism: Phases 1 → 2 → 3 are sequential (4 depends on 3’s _generated/ outputs). Phases 5 + 6 run parallel (Q5 resolution 2026-04-21) — disjoint file sets. Phase 7 + 8 async / non-blocking. Phase 4.5 gated on Phase 4 clean-for-1-week.
Phase 1 — Renderer shell (day 1)
Section titled “Phase 1 — Renderer shell (day 1)”- Scaffold
apps/docs/with Starlight starter - Content loader pointing at
../../docs docs/README.md→ Starlight landing- Build passes locally, no plugins yet
- Deploy to CF Pages behind Access (cofounder-visible)
- Done when:
docs.ideony.is-a.devrenders every existingdocs/**/*.md
Phase 2 — Agent layer (day 1–2)
Section titled “Phase 2 — Agent layer (day 1–2)”starlight-llms-txt→/llms.txt+/llms-full.txtlive?format=mdraw source URLs- Frontmatter schema enforced on all
docs/*.md(migration PR addsquadrant:,audience:to each existing file) - Done when:
curl docs.ideony.is-a.dev/llms-full.txtreturns full corpus
Phase 3 — Auto-gen pipelines (day 2–3)
Section titled “Phase 3 — Auto-gen pipelines (day 2–3)”- OpenAPI → MD (reuses existing Nest Swagger)
- Prisma schema + ERD → MD
- env → MD
- pnpm scripts → MD
docs/_generated/gitignored, CI-regen-before-build- Done when:
docs/_generated/api/openapi.mdrenders 30 routes, stays in sync on every API commit
Phase 4 — Quality gates (day 3)
Section titled “Phase 4 — Quality gates (day 3)”- markdownlint, vale, cspell, lychee, frontmatter validator
- Pre-commit + CI hooks
- Husky lint-staged integration
- Living-examples:
ts exampletag + extraction script wired, local dev-loop only (no CI block yet — Q4 resolution 2026-04-21) - Done when: CI blocks a commit introducing a dead link or banned word
Phase 4.5 — Living-examples CI block (day 5+, post-validation)
Section titled “Phase 4.5 — Living-examples CI block (day 5+, post-validation)”- Promote local extraction → CI gate once Phase 4 has run clean ≥ 1 week w/ zero false positives
- Add
tsc --noEmitstep todocs-quality.yml - Done when: CI fails a commit that breaks a
ts exampleblock
Phase 5 — Zero-drift audit + upgrade (day 3–4)
Section titled “Phase 5 — Zero-drift audit + upgrade (day 3–4)”- Read
check-docs-updated.sh, cross-check CLAUDE.md trigger matrix - Report gaps in
docs/research/ - Patch gate, add tests
- Done when: every row in CLAUDE.md trigger matrix is gate-enforced, not honor-system
Phase 6 — Diagram migration (day 4)
Section titled “Phase 6 — Diagram migration (day 4)”- Convert C4 blocks in
architecture.mdto MermaidC4Context - Any other PNG →
.mmd/.d2 - Done when: zero image files in
docs/**outside logos
Phase 7 — Cofounder migration (day 5+, async, non-blocking)
Section titled “Phase 7 — Cofounder migration (day 5+, async, non-blocking)”- Extract Google Drive cofounder doc content →
docs/cofounder-overview.md+ relevant specs - Tag
audience: cofounder - Share
docs.ideony.is-a.devlink with cofounders - Drive doc stays live in parallel (Q3 resolution 2026-04-21). Sunset only when cofounders confirm they’re reading the site; no hard cutoff.
Phase 8 — Federation prep (post-Jooice docs init)
Section titled “Phase 8 — Federation prep (post-Jooice docs init)”- Extract shared config →
packages/docs-shell/ - Document federation recipe in
docs/docs-framework.md
14. Risks + mitigations
Section titled “14. Risks + mitigations”| Risk | Mitigation |
|---|---|
| Starlight abandonware | Content is raw MD — Starlight removable in one PR, switch to Docusaurus/MkDocs |
llms.txt standard doesn’t take off | llms-full.txt is just concatenated MD — still useful even if spec stalls |
| Auto-gen pipelines break on schema change | CI gates: docs:gen failure blocks merge |
| Cloudflare Access misconfig exposes internal docs | Start closed (default deny); add emails one-by-one; audit log on Zero Trust |
| Devs write MD, bypass frontmatter schema | Pre-commit validator + PR template checklist |
Lockfile churn on pnpm-lock.yaml during multi-agent phases | Impl in git worktree; merge via format-patch when parallel agents idle |
| Living-examples TypeScript drift | Extracted examples hit same tsc as source → if source compiles, example compiles |
15. Out of scope
Section titled “15. Out of scope”- Kapa.ai / Inkeep semantic search (phase 2+)
- Public docs subdomain (phase 2+)
- i18n translations (IT/EN auto-sync) — future, not MVP 0
- Jooice docs stand-up (Jooice owns that)
- Storybook integration for component docs (
apps/mobiledesign system has its own path) - Video embedding / Loom integration
- Algolia DocSearch application (Pagefind covers MVP 0)
Resolved questions (2026-04-21)
Section titled “Resolved questions (2026-04-21)”| # | Question | Answer |
|---|---|---|
| 1 | apps/docs/ naming | Confirmed — apps/docs/ |
| 2 | Subdomain vs path | Subdomain docs.ideony.is-a.dev — SOTA industry standard (Vercel, Stripe, Clerk, Supabase, Mapbox, Cloudflare all use docs.*). Better Cloudflare Access isolation, separate CDN caching, cleaner llms.txt origin. |
| 3 | Cofounder access | Both during migration — docs.ideony.is-a.dev w/ CF Access + Drive doc stays in parallel. Drive sunset trigger deferred: once docs/cofounder-overview.md ships in Phase 7 AND cofounders confirm they’re reading the site. No hard cutoff. |
| 4 | Living examples | Implement syntax + extraction now, CI enforcement later. Phase 4 scaffolds ts example tag + extraction script (local-only). CI block on failed compile added in a Phase 4.5 after dev-loop validates no false positives. |
| 5 | Phase 5 vs Phase 6 ordering | Parallel. Phase 5 (gate audit) = scripts + research doc, independent file set. Phase 6 (diagram migration) = MD edits in docs/architecture.md. Zero overlap → parallelize to compress timeline. |
All five resolved — spec cleared for Phase 1 kick-off.