# ESI.ts Documentation Guide

**Implements:** `DOC-02` · `DOC-03` · `DOC-04` · `DOC-05` · `DOC-06` — see [CHARTER.md](CHARTER.md) Part 7. `DOC-01` is Gap today and lands with the 11.0 docs rewrite (ROADMAP Phase 7).

Where the documentation lives, how the site and the API reference are built and published, how examples in the guides are kept compiling, and what a pull request that adds a public export owes the documentation. The code is the fact: where a page and the code disagree, fix the page.

## Where documentation lives

| What                                | Where                              | Canonical for                                                                                   |
| ----------------------------------- | ---------------------------------- | ----------------------------------------------------------------------------------------------- |
| Orientation, install, first calls   | `README.md`                        | Nothing it can link to instead; it points at a guide for depth                                  |
| Engineering requirements and status | [CHARTER.md](CHARTER.md)           | Every `ARCH`, `DES`, `TEST`, `GATE`, `SEC`, `DOC`, `REL` and `PROC` requirement                 |
| The 11.0 plan                       | [ROADMAP.md](ROADMAP.md)           | Phase schedule, definitions of done, the release gate                                           |
| One topic per guide                 | `guides/*.md`                      | The topic in its title; each opens with an `Implements:` line naming the requirements it serves |
| API reference                       | TypeDoc from the TSDoc in `src/`   | Signatures, parameters, JSDoc                                                                   |
| Documentation site                  | `docs-site/` (VitePress)           | Nothing: its guide and example pages are generated from the files above                         |
| Runnable programs                   | `examples/*.ts`                    | Whole programs; type-checked by `npm run typecheck:examples` in `ci-fast.yml`                   |
| Static Data Export                  | `guides/SDE.md`, `guides/sde/*.md` | The `./sde` and `./sde/memory` sub-paths; `src/sde/README.md` is a pointer to them              |
| Test specification                  | `tests/bdd/features/**/*.feature`  | Behaviour, as EARS requirements with Gherkin scenarios                                          |
| Public API surface                  | `etc/esi.ts.api.md`                | Every exported shape, regenerated by `npm run api-report`                                       |
| Agent instructions                  | `CLAUDE.md`, `AGENTS.md`           | How an agent works in the repository; the SemVer section is mirrored and checked                |
| Changelog                           | `CHANGELOG.md`                     | Written by release-please from conventional commits                                             |

## The documentation site

https://lgriffin.github.io/ESI.ts/ is built from the repository, so it cannot say anything the guides and examples do not (`DOC-03`). Only `docs-site/index.md` (the home page), `docs-site/.vitepress/config.ts` and the theme are written by hand; everything else is generated at build time and git-ignored.

| Route            | Built from                                                            | How                                                                                                                                                                                                                                                                                                                                                 |
| ---------------- | --------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `/guide/`        | `README.md` and every `guides/*.md`                                   | `scripts/docs/sync-docs.ts` copies each file. A link to another guide becomes a link to its page, a link to any other repository file becomes a GitHub URL, and an image is copied beside the page. The sidebar sections are `GUIDE_SECTIONS` in `scripts/docs/sync-docs-core.ts`; a guide not listed there is still published, in the last section |
| `/examples/`     | every `examples/*.ts`                                                 | One page per example from its header comment (title, description, `Setup:`, `@nightly` tier) and its source, imports rewritten to the package sub-paths a consumer uses (`IMPORT_MAP` in `scripts/docs/examples-showcase-core.ts`), plus an index grouped by category                                                                               |
| `/api/`          | the TSDoc in `src/`                                                   | TypeDoc (next section), copied as static files                                                                                                                                                                                                                                                                                                      |
| `/requirements/` | every `tests/bdd/features/**/*.feature` and the `npm run ears` report | `npm run docs:ears` writes the EARS dashboard (`scripts/quality/ears-dashboard.ts`, see [TESTING.md](TESTING.md#the-standalone-ears-check)) to `docs-site/public/requirements/`, copied as a static file. `docs-site.yml` runs `npm run ears` first, so the published page carries that run's verdicts                                              |

The version in the navigation bar is read from `package.json` when the site builds. Edit the guide or the example, never a generated page; each generated page's "Edit this page" link opens its source. A new example needs a title line and an `@nightly` tag, which it already needs for the nightly run, and an import of a `../src` path `IMPORT_MAP` does not know fails the build until it is mapped.

```bash
npm ci --prefix docs-site   # once: the site's own dependencies (VitePress)
npm run docs:site           # TypeDoc, docs:sync, docs:ears, then vitepress build into docs-site/.vitepress/dist/
npm run docs:site:preview   # serve the built site at http://localhost:4173/ESI.ts/
npm run docs:site:dev       # docs:sync, then VitePress with hot reload (run npm run docs first for /api/)
npm run docs:sync           # regenerate the guide and example pages only
```

Where it is built:

| Where                                      | What happens                                                                                                                                               |
| ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ci.yml` `documentation` job               | Runs `npm run docs:site` and `validate:versions -- --site` on every pull request, and uploads the built site as the `documentation` artifact for 30 days   |
| `release.yml` `deploy-docs`                | Calls `docs-site.yml` after `validate-release` passes on the release's tag                                                                                 |
| `docs-site.yml` (also `workflow_dispatch`) | Builds the site, checks its version, and publishes `docs-site/.vitepress/dist` to the `gh-pages` branch. Dispatch it on master to publish between releases |

VitePress fails the build on a link to a page that does not exist. Links that become GitHub URLs are checked by `tests/tdd/scripts/sync-docs.test.ts`, which fails when a guide links to a repository file that is not there. GitHub Pages must serve the `gh-pages` branch (repository Settings, Pages, "Deploy from a branch"); that setting is the maintainer's.

## API reference (TypeDoc)

`typedoc.json` expands every module under `src/` and writes HTML to `docs-site/public/api/`, which `.gitignore` excludes. No committed file lives under that directory, and `npm run clean` and `npm run clean:docs` delete only it (`DOC-02`).

```bash
npm run docs         # Generate into docs-site/public/api/
npm run docs:watch   # Regenerate on change
npm run docs:serve   # Serve docs-site/public/api at http://localhost:8080
npm run clean:docs   # Delete docs-site/public/api/
```

Where it is built:

| Where                            | What happens                                                                                      |
| -------------------------------- | ------------------------------------------------------------------------------------------------- |
| `ci.yml` `documentation` job     | Runs it as part of `npm run docs:site` on every pull request                                      |
| `release.yml` `validate-release` | Runs `npm run docs`, so a TypeDoc failure blocks the release                                      |
| `docs-site.yml`                  | Publishes it under `/api/` with the site                                                          |
| `release.yml` `create-assets`    | Attaches `docs.tar.gz` to the GitHub release, listed in `checksums.txt` with the tarball and SBOM |

What TypeDoc shows is what the TSDoc says. The public classes it documents are the construction surfaces (`EsiClient`, `EsiClientBuilder`, `CustomEsiClient`, `EsiApiFactory`), the <!-- metric:clients -->39<!-- /metric --> domain clients, the auth classes (`EveSsoClient`, `EsiTokenManager`, the token stores), the middleware and its interfaces (`ICache`, `IRateLimiter`, `ICircuitBreaker`, `IRetryStrategy`, `IDeduplicator`, `ILogger`), the error classes, and the response types: <!-- metric:typeModules -->36<!-- /metric --> domain type files in `src/types/` plus `common.ts`, `branded.ts` and `api-responses.ts`. Because `entryPoints` is all of `src/`, TypeDoc also renders modules the package does not export, including `src/core/ports/`, `src/adapters/` and `src/generated/operations.generated.ts`. The `exports` map in `package.json` and `etc/esi.ts.api.md` decide what is public, not the reference.

## Guide conventions

Every guide follows the same shape, so a reader can move between them without relearning the layout (`DOC-05`).

- **First line after the title: `**Implements:**`** with the requirement identifiers the guide serves and a link to [CHARTER.md](CHARTER.md). Seven guides still lack one (CHARTER DOC-05 lists them); add it when you next edit one.
- **Reachable from the README** within one link.
- **Plain declarative sentences.** Say what the code does and name the file, script or CI job that proves it. A claim with no mechanism behind it is a requirement with status Gap, and belongs in the CHARTER as one.
- **State the gap.** Where the code falls short of the charter, say so in the guide and cite the requirement or issue. Do not soften it.
- **Counts are generated.** A count of something in the source (clients, routes, operations, requirements, scenarios, examples, test files) is written between markers, `<!-- metric:clients -->39<!-- /metric -->`, never typed as a bare number. `npm run docs:metrics` computes every metric into `etc/doc-metrics.json` and rewrites the marked numbers; `validate:versions` fails when one is stale. The metric names are `METRIC_NAMES` in `scripts/docs/doc-metrics-core.ts`, and a marker must not start a line, because Markdown reads a line that opens with a comment as HTML. A count only a test run can measure stays prose with the date it was measured (`DOC-04`).
- **No duplication.** When a topic has a canonical guide, link to it and summarise in one sentence (`DOC-01`).

## When a public export is added

`DOC-06` asks that a pull request adding a public export carries its documentation with it:

1. **JSDoc** on the export. TypeDoc and the API report both read it.
2. **A guide section** in the canonical guide for the topic, with a type-checked example.
3. **An example** under `examples/` when the export is user-facing.
4. **The API report**: `npm run build && npm run api-report`, and commit `etc/esi.ts.api.md`. The `api-surface` job fails when it is stale.

Only part of this is checked. `npm run test:export-coverage -- --ci` fails a pull request that adds an export no test references, and the API report diff makes the addition visible to the reviewer. There is no pull request template checklist yet, so items 1 to 3 are review items (`DOC-06` is Gap).

## Documentation examples are checked

A README example that does not compile costs more trust than a bug. `npm run test:docs-examples` finds every fenced `ts` or `typescript` block in `README.md`, `guides/*.md`, `guides/sde/*.md` and `src/sde/README.md`, and checks it against the packed package, the way a reader who copies it sees it:

1. Builds and packs the library as the consumer contract does, and installs the tarball into a scratch consumer outside the repository.
2. Writes each block to its own ES module next to a shared prelude, and type-checks all of them with `strict` under `moduleResolution: nodenext` and `bundler`. An import of a sub-path the package's `exports` map does not list fails here.
3. Runs each block marked `runnable` with node, with `fetch` replaced by a stub.

CI runs it in the `doc-examples` job, inside `ci-success`. `npm test` checks the annotations and the baseline without packing.

### Fragments and the prelude

Most blocks are fragments: they use a `client` built in an earlier block, or a `characterId` the reader supplies. [`tests/doc-examples/prelude.d.ts`](../tests/doc-examples/prelude.d.ts) declares those names as typed globals, so a fragment type-checks as written. A block that declares or imports a name itself shadows the global. When a new fragment needs a name the prelude lacks, add it there with the type a reader would have; do not type it `any`.

### Annotations

A block is type-checked unless it says otherwise. Two annotations exist, and anything else is an error, so a typo cannot switch a check off:

| Annotation                                | Where                                                                | Effect                                                                                                                                                                                                           |
| ----------------------------------------- | -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `runnable`                                | The fence (` ```ts runnable `) or a comment                          | Type-checked, then run against the stub transport in [`tests/doc-examples/stub-fetch.mjs`](../tests/doc-examples/stub-fetch.mjs). The block must build everything it uses: prelude names do not exist at runtime |
| `<!-- doc-example: no-check <reason> -->` | A comment on the line above the fence (blank lines between are fine) | Not checked. The reason is required                                                                                                                                                                              |

Use `no-check` for blocks that are not consumer code: method signature listings, fragments of a class body, and contributor examples that import from `src/`. Do not use it to silence an example that is wrong; fix the example.

### Known-broken examples

When an example exposes a real mismatch that cannot be fixed in the docs alone, mark it `no-check` with a reason that names the bead tracking it, for example `<!-- doc-example: no-check esi-abc.1 getFoo was removed; decide the replacement -->`, and list its key in [`scripts/docs/doc-examples-baseline.json`](../scripts/docs/doc-examples-baseline.json). The key is the file, the nearest heading, and the block's position under that heading: `README.md#Quick Start [1]`.

The baseline only shrinks. The run fails when a `no-check` names a bead but is not listed, when a listed block no longer exists or no longer names a bead (remove the entry once the example is fixed), and when an entry is not on `origin/master` already. Without `origin/master` to compare with, the run fails closed; CI fetches it first.

### Negative fixtures

`tests/tdd/doc-examples/fixtures/` holds a compliant file and four that must be rejected: a block that does not parse (tsc reports no type errors while any file has a syntax error, so the checker drops such blocks and checks the rest again), a block calling a method that does not exist, an import of `@lgriffin/esi.ts/dist/errors` (present in the tarball but not exported), and a `no-check` without a reason. `tests/tdd/doc-examples/doc-examples.test.ts` puts them through the checker against a small stub package, and `npm run test:docs-examples` puts them through again against the packed library. If a fixture stops being rejected, both fail.

## What changes in 11.0

ROADMAP Phase 7 rewrites the documentation against the new client. The site build (`DOC-03`), the generated counts (`DOC-04`) and the single testing guide (`DOC-01`: root `TESTING.md` and the former `guides/MUTATION-TESTING.md` folded into `guides/TESTING.md`, [#273](https://github.com/lgriffin/ESI.ts/issues/273)) have landed; nothing else in this list exists yet:

- The README becomes an orientation page written against `createEsi` and `esi.as(identity)`, every snippet checked by `test:docs-examples`. `guides/MULTI-CHARACTER.md` arrived with ROADMAP Phase 2 PR 11.
- The Beads blocks in `AGENTS.md` and `CLAUDE.md` become pointers ([#276](https://github.com/lgriffin/ESI.ts/issues/276)).
