# Documentation Site Generator

`forge docsite` hosts **two architecturally separate families** under one noun. They share no state, no manifest, no config key, and no output directory.

- **`install` / `dev` / `build`** — scaffold and run a generator toolchain **inside `.forge/` itself**, so you can browse forge's own live state (specs, changes, decisions, branding) as a real site while you work.
- **`publish`** — renders forge's `.forge/` state into a **public** documentation site you already own — VitePress, Docusaurus, Starlight, Mintlify, or MkDocs Material — detecting the generator already in use and never destroying a hand-written page.

::: info Not the same thing as `forge docs`
[`forge docs build\|serve`](/features/dashboard) renders forge's own internal dashboard — spec inventory, breaker/budget chips, the session timeline. `docsite publish` renders a **public** documentation site for people outside the team. `docsite install`/`dev`/`build` is a third thing again — forge's own live-content site, browsed locally while you work. All three read the same `.forge/` state but produce different artifacts for different audiences.
:::

## Browse forge's own state live

The fastest way to see what is in `.forge/` without leaving your terminal:

```sh
forge docsite install --target vitepress   # once: scaffolds .forge/.vitepress + package.json
forge docsite dev                          # foreground dev server, Ctrl-C to stop
```

`install` scaffolds a VitePress project into `.forge/` itself and installs its dependencies. `dev` execs that project's dev server in the foreground, serving live `.forge/` content — specs, changes, decisions, branding tokens with color swatches, mermaid diagrams — regenerated automatically as files change on disk.

```sh
forge docsite build --out .forge/.vitepress/dist
```

`build` runs the same generator's static build — no separate derive stage: it produces exactly what `dev` already serves, over the same generated config, so there is never more than one coherent generator project living in `.forge/`.

Both `dev` and `build` require `install` to have run first: they read the installed target from `.forge/.docsite-install.json`, never from `.forge/config.yaml`, and error naming `install` if nothing is installed yet. `dev --print` / `build --print` show the exact command instead of running it.

## Publish a specification site from `.forge/`

This is the fastest path to a shippable public site — one command, no staging tree to hand-write:

```sh
forge docsite publish --target vitepress --out site
```

`publish` reads `.forge/specs/`, the active `.forge/changes/`, and the immutable `.forge/changes/archive/` directly, and produces three sections:

| Section | Source | Content |
| --- | --- | --- |
| `capabilities/` | `.forge/specs/<domain>/spec.md` | Requirements and their Given/When/Then scenarios — what the system canonically does |
| `active/` | `.forge/changes/<name>/` | Proposal intent/scope, design decisions, and the task list with status |
| `releases/` | `.forge/changes/archive/<date>-<name>/` | The same shape as `active/`, minus tasks — an archived change has none open by definition |

plus a generated `index.md` landing page with counts for each section. When present, data model and contract definitions are included alongside the design decisions.

::: warning Deliberately not a dashboard
Operational fields a change carries for the team running forge — session events, spend, breaker/secscan counters — never reach these pages. This is a client-facing artifact: proposal, design, and task substance only. For the operational view, see the [spec-viewer dashboard](/features/dashboard).
:::

`publish` always derives fresh from `.forge/` before rendering — there is no separate stage-then-render step to run by hand. `--source-only` stops after staging, leaving `.forge/docsite/staging/` in place for manual review without ever rendering.

```sh
forge docsite publish --source-only                    # stage only; review .forge/docsite/staging/
forge docsite publish --target vitepress --out site    # derive + render
```

## The full pipeline

```mermaid
flowchart LR
    subgraph OwnSite["forge's own site"]
      I[forge docsite install] --> DV[forge docsite dev]
      I --> B[forge docsite build]
    end
    subgraph YourSite["your site"]
      D[.forge/specs + changes] --> P[forge docsite publish]
      P --> E[VitePress / Docusaurus / Starlight / Mintlify / MkDocs]
    end
```

```sh
forge docsite install [--target <t>] [--force] [--no-gitignore]
forge docsite dev     [--print]
forge docsite build   [--out <dir>] [--print]
forge docsite publish [--target <t>] [--out <dir>] [--source-only]
```

- **`install`** scaffolds the chosen generator's template set into `.forge/` and installs its dependencies. Refuses a conflicting already-installed target unless `--force` (which removes the old target's owned files, preserving anything hand-edited).
- **`dev`** execs the installed generator's dev server in the foreground. Errors naming `install` if nothing is installed; errors by name — never silently substituting VitePress — if the installed target has no dev backend.
- **`build`** runs the installed generator's static build.
- **`publish`** derives fresh from `.forge/`, resolves the adapter, applies the ownership rules, writes the site, and emits `/llms.txt` when `docsite.llms_txt` is true (the default).

::: tip `detect`, `plan`, `render`, and `derive` were removed
No aliases, no deprecation window. Running one names its replacement:
`detect` → `install --dry-run`, `render`/`derive` → `publish`, `plan` → no
direct replacement (its bounded staleness work list had no other caller once
`publish` always derives the whole site fresh every run).
:::

## Ownership: forge owns files, not directories

`publish` tracks what it wrote in `.forge/docsite/owned.json` — a completely separate manifest from `install`'s own `.forge/.docsite-install.json`; the two families never touch each other's files. A page you hand-edit after `publish` wrote it is detected and skipped on the next run rather than overwritten; a page that exists on disk but forge never wrote is invisible to it and stays untouched. `publish` always derives the whole site, so pruning — deleting a page forge owns that is no longer produced — is unconditional; there is no partial or incremental mode to opt out of it.

## Configuration

`target`, `out`, `pattern`, `sections`, `scaffold`, `llms_txt`, `auto_on_archive`, and `exclude` are all set under `docsite:` in `.forge/config.yaml` and apply to **`publish` only** — see [Configuration](/guide/configuration#docsite). `install`/`dev`/`build` read no configuration at all: which target is installed lives entirely in `.forge/.docsite-install.json`. With no `docsite:` configuration, `publish` still needs zero setup for an existing public site: it resolves the generator and output directory from files already on disk.

## Next steps

- [Spec-Viewer Dashboard](/features/dashboard) — the internal, operational view `docsite publish` deliberately does not replace.
- [Configuration](/guide/configuration#docsite) — every `docsite:` key (`publish`-only).
- [Command Reference](/guide/commands) — the full `docsite` command table.
- [The forge CLI](/reference/cli) — the full `docsite` synopsis.
