> ## Documentation Index
> Fetch the complete documentation index at: https://exegia.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Multi-repo docs strategy

> How content from separate exegia repositories is aggregated into this Mintlify site.

This page is the authoritative reference for anyone setting up docs in a new exegia repo.

<Note>
  This supersedes the git-submodule approach from [issue #3](https://github.com/exegia/corpora-docs/issues/3) / [PR #5](https://github.com/exegia/corpora-docs/pull/5), which **does not work** — see [Why not submodules](#why-not-submodules). Direction history is recorded in [issue #9](https://github.com/exegia/corpora-docs/issues/9).
</Note>

## Chosen approach

**One Mintlify project. A CI job mirrors each source repo's `docs/` into this repository and merges its navigation.**

Each exegia repository that wants a section on the org docs site:

1. Maintains its own `docs/` directory with `.mdx` files and a `docs.json` containing a `navigation` block. This doubles as its standalone preview config.
2. Files a *section request* by opening a PR that adds `sections/<slug>.yml` here (see [Filing a section request](#filing-a-section-request)).
3. Adds a small workflow that pings this repo when its docs change (see [Wiring up the source repo](#wiring-up-the-source-repo)).

From then on it is automatic. The [Mirror sections](https://github.com/exegia/corpora-docs/blob/main/.github/workflows/mirror-sections.yml) workflow clones each active source, copies its docs into `content/<slug>/`, regenerates that section's navigation in `docs.json`, and commits. Mintlify's GitHub App sees the commit and rebuilds.

### Why this approach

| Option                                       | Verdict                                                                                                                               |
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| CI content mirroring (this approach)         | ✅ Works on the current plan. Source repos own their content and navigation. Sync state is visible in git rather than in a dashboard.  |
| Git submodules + hand-merged nav             | ❌ **Does not work.** Mintlify never checks out the submodule content — [verified](https://github.com/exegia/corpora-docs/pull/12).    |
| Mintlify native multi-repo sources           | ❌ Correct solution, but [Enterprise-gated](https://www.mintlify.com/docs/deploy/multi-repo#requirements). Revisit if we ever upgrade. |
| Separate Mintlify projects linked by anchors | ❌ Fragments the URL structure; deep links between sections are fragile.                                                               |

### Why not submodules

The original strategy added each source repo as a git submodule at `content/<slug>/`. Mintlify's builder clones this repository **without** `--recurse-submodules`, so those directories are empty at build time.

The failure is silent: `docs.json` is processed normally, so the navigation entries render and appear in the sidebar — every one of them leading to a 404. No build error, no dashboard warning. Confirmed empirically in [PR #12](https://github.com/exegia/corpora-docs/pull/12) against a real preview deployment.

Mirroring exists because Mintlify only ever sees one repository. It is also what [Mintlify's own documentation recommends](https://www.mintlify.com/docs/api-playground/sdk-reference-setup#sdk-in-a-separate-repository) for content that lives in another repo.

## Repository layout

```text theme={null}
corpora-docs/                  ← this repo (exegia/corpora-docs)
├── docs.json                  # site config + own nav + generated section nav
├── sections/                  # registry of section requests
│   └── <slug>.yml
├── content/                   # generated — never edit by hand
│   └── <slug>/                # mirrored from exegia/<repo> docs/
├── scripts/
│   └── mirror_sections.py     # the mirror itself
├── index.mdx
├── quickstart.mdx
├── project/                   # platform docs (owned by this repo)
├── backend/
└── contributing/
```

<Warning>
  Everything under `content/` is generated. Edits there are reverted on the next mirror run, and CI fails the PR that made them. Fix the source repo instead.
</Warning>

## Filing a section request

Open a PR against this repository adding `sections/<slug>.yml`:

```yaml theme={null}
# sections/corpora-auth.yml
slug: corpora-auth        # also the URL segment: /content/corpora-auth/...
repo: exegia/corpora-auth
branch: main              # branch to mirror from
docs_path: docs           # directory in the source repo holding docs.json
label: "Auth"             # top-level sidebar group name — must be unique
icon: lock
status: pending           # pending | active | archived
requested_by: "@author"
requested_at: "YYYY-MM-DD"
```

### Acceptance gate

A request is accepted once the source repo has, at `docs_path`:

* a valid `docs.json` with a `navigation` block (either `tabs` or `groups`), and
* at least one `.mdx` page.

There is no navigation review step — the source repo owns its own structure.

## Activating a section

<Steps>
  <Step title="Set status to active">
    Change `status: pending` to `status: active` in `sections/<slug>.yml` and merge.
  </Step>

  <Step title="Let the mirror run">
    Merging to `main` triggers **Mirror sections**. It clones the source, populates `content/<slug>/`, regenerates the navigation, and commits. Run it manually from the Actions tab if you don't want to wait.
  </Step>

  <Step title="Wire up the source repo">
    Add the dispatch workflow below so future changes propagate on their own.
  </Step>
</Steps>

No dashboard step, and nothing to grant Mintlify access to — it only ever reads this repository.

## Wiring up the source repo

Without this, a source repo's changes only reach the site on the nightly run.

```yaml theme={null}
# <source repo>/.github/workflows/notify-docs.yml
name: Notify docs site

on:
  push:
    branches: [main]
    paths:
      - "docs/**"
  workflow_dispatch:

jobs:
  dispatch:
    name: Trigger docs mirror
    runs-on: ubuntu-latest
    steps:
      - name: Check the dispatch token is configured
        id: token
        env:
          GH_TOKEN: ${{ secrets.CORPORA_DOCS_DISPATCH_TOKEN }}
        run: |
          if [ -z "${GH_TOKEN}" ]; then
            echo "::warning::CORPORA_DOCS_DISPATCH_TOKEN is not set — skipping. \
            The docs site will pick this change up on its nightly mirror instead."
            echo "present=false" >> "$GITHUB_OUTPUT"
          else
            echo "present=true" >> "$GITHUB_OUTPUT"
          fi

      - name: Dispatch to corpora-docs
        if: steps.token.outputs.present == 'true'
        env:
          GH_TOKEN: ${{ secrets.CORPORA_DOCS_DISPATCH_TOKEN }}
        run: |
          gh api repos/exegia/corpora-docs/dispatches \
            -f event_type=docs-updated \
            -F client_payload[repo]="${{ github.repository }}" \
            -F client_payload[sha]="${{ github.sha }}"
          echo "Dispatched — corpora-docs will re-mirror and redeploy."
```

`CORPORA_DOCS_DISPATCH_TOKEN` needs `contents: write` on `exegia/corpora-docs`. Set it as an org-level secret so each new source repo doesn't need its own.

<Note>
  The token check is not optional boilerplate. Without it, a repo that adds this workflow before the secret exists gets a red X on **every** docs push. Skipping with a warning means the workflow can be merged ahead of the secret and starts working the moment it appears — and the nightly mirror covers the gap.
</Note>

Live reference implementation: [`corpora-auth/.github/workflows/notify-docs.yml`](https://github.com/exegia/corpora-auth/blob/main/.github/workflows/notify-docs.yml).

## How links work

The mirror rewrites **root-relative links that point at pages within the same section**. A source page linking to `/quickstart` means *its* quickstart; left alone it would land on this site's `/quickstart`. The mirror rewrites it to `/content/<slug>/quickstart`.

Links to anything else — external URLs, or hub pages — are left untouched.

To link **into** a section from elsewhere, use the full mirrored path:

```mdx theme={null}
See the [auth quickstart](/content/corpora-auth/quickstart).
```

## Sections registry

| Status     | Meaning                                                                                                                                                              |
| ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pending`  | Request filed; not yet mirrored.                                                                                                                                     |
| `active`   | Mirrored into `content/<slug>/` and present in the navigation.                                                                                                       |
| `archived` | No longer mirrored. Its `content/<slug>/` directory must be deleted in the same PR that archives it — CI fails on a content directory with no matching section file. |

## What CI checks

[Reconcile sections](https://github.com/exegia/corpora-docs/blob/main/.github/workflows/reconcile-sections.yml) runs on any PR touching `sections/`, `content/`, `docs.json`, or the mirror script, and fails if:

* an `active` section has no `content/<slug>/` directory,
* a `content/<slug>/` directory has no matching `sections/<slug>.yml`,
* re-running the mirror produces anything different from what is committed — which covers hand-edited `content/`, a hand-edited `docs.json` navigation, and a source repo whose docs have moved on.

The mirror also emits a warning for any mirrored page the source's own navigation never references. Those pages deploy and are reachable by URL but appear in no sidebar, which is nearly always an oversight in the source repo.

## Known limits

* **Staleness window.** A source repo without the dispatch workflow is up to 24 hours stale. With it, propagation takes about a minute.
* **`label` must be unique** across sections — it is the top-level sidebar group name, and the mirror matches on it when regenerating.
* **Mirrored pages live under `/content/<slug>/`.** Prettier URLs would need Mintlify's native multi-repo feature (Enterprise) or a redirect layer.
* **Private source repos** need `DOCS_SOURCES_TOKEN` set on this repo with read access to them. Public sources need no token.
* **Only pages the source navigation references are mirrored.** A `docs/` directory frequently holds Markdown that isn't documentation — agent `SKILL.md` files, notes, templates — and that content routinely breaks MDX parsing. If a page is missing from the site, check that the source's `docs.json` lists it.
* **The drift check clones sources live**, so a PR touching only `docs.json` can fail because an unrelated source repo moved on since the last mirror run. Re-run the Mirror sections workflow and rebase.
