# Authoring a documentation package

A documentation package is a normal npm package whose payload is Markdown rather than code.
`docspack init` scaffolds one, `docspack build` regenerates it, `docspack doctor` checks it
and `docspack preview` shows what an agent would get back.

## Starting from nothing

Run `init` inside the library you want to document:

```bash
npx docspack init
```

It reads the surrounding project first — the library name and version, a `docs/` directory,
an OpenAPI document, the git remote, the license — and proposes a package built from what it
found. On a terminal it asks six questions, each pre-filled, so the common path is six times
Enter. In CI or a script, `--yes` takes the same defaults without asking:

```bash
npx docspack init --name @acme/docspack --from ./docs --yes
```

`--dry-run` prints the file tree and writes nothing. Files that already exist are kept
unless you pass `--force`, so re-running `init` in a real project is safe.

## What init writes

```text
docspack/
├── package.json      name, version, build settings, publish scripts
├── README.md         written for whoever installs the package
├── .gitignore        the generated payload is a build output
└── .github/workflows/publish-docspack.yml
```

Two lines in the generated `package.json` matter more than the rest. `files` includes
`.llms`, because omitting it publishes a package that installs fine and indexes nothing.
And `prepublishOnly` runs `docspack build && docspack doctor --strict`, so a stale or broken
payload cannot reach the registry by accident.

When no documentation is detected, `init` seeds `docs/` with three templates shaped the way
retrieval works: one heading per question. They contain `TODO:` markers, and `doctor
--strict` fails while those remain — a half-filled template cannot be published as though it
were documentation.

## Build settings live in package.json

`init` writes a `docspack` key:

```json
"docspack": { "documents": "@acme/sdk", "from": "../docs", "maxChunkTokens": 800 }
```

`docspack build` with no arguments reads it, which keeps the build script, the release
workflow and `prepublishOnly` from drifting apart. Paths are relative to the package
directory. Command-line flags always win over the key.

Supported fields: `documents`, `from`, `openapi`, `source`, `feedback`, `maxChunkTokens` and
`pages`. `feedback` is how you opt in to receiving documentation problems — see the chunk on
trust and safety. `init` does not write it, because opting you in by default would defeat the
point.

## Checking that the docs still describe the code

`docspack verify` compares the identifiers your chunks name against what the library
actually declares. It needs `documents` to know which library that is:

```bash
npx docspack verify
```

```text
drift @acme/docspack@1.4.0 documents @acme/sdk · 31 names checked, 30 declared
  client.setKey() is not declared; did the API become setApiKey?
  @acme/docspack@1.4.0/api-auth · chunks/api-auth.md
```

It reads the library's `.d.ts` files and never imports the package, so nothing you depend on
executes. Exit code 1 means something was reported, which makes it usable in CI beside
`doctor`.

`verify` reports a name only when it is absent *and* something very like it is declared —
the signature of a rename. An absent name with no close relative is far more often an
example about a different library, so it is counted and ignored. Measured against 69
published packages, that rule reported nothing at all, while catching 99.9% of simulated
qualifier renames such as `setKey` becoming `setApiKey`.

Findings stay on your machine. docspack has no code that can send them anywhere.

## Building from Markdown

```bash
npx docspack build --from ./docs --name @acme/docspack --pkg-version 1.4.0
```

The generator splits each document at `##` headings. If a section is still larger than the
chunk budget it splits again at `###`, and then at paragraph boundaries, until every chunk
fits. Retrieval works best when one chunk answers one question, so headings are worth
writing with that in mind.

Each chunk records the heading words as tags, and the identifiers appearing in inline code
as entities. Both member accesses (`Client.setApiKey`) and bare identifiers written as two
or more words (`TwoColumn`, `useSlide`, `text-accent`) count, so a component library's API
is indexed and checkable by `verify` like any other. Both are indexed, which is why a chunk
can be found by an API name that appears only in a code sample.

### Aiming one section

Front matter `title` and `tags` belong to a whole document. A single section takes its own
with a comment under the heading, which is what to reach for when one page holds eighty of
them:

```md
## Two-column layout

<!-- docspack: tags=grid,columns -->
<!-- docspack: entities=TwoColumn -->
```

The comments are removed from the generated chunk. Directive tags are added before the
heading words, and tags outweigh prose in the ranking, so this is the lever that makes one
chunk the answer to one question.

## Building from an OpenAPI document

```bash
npx docspack build --openapi ./openapi.json
```

Every operation becomes one chunk containing its method and path, summary, description,
parameters and responses. The operation's `tags` become search tags and its `operationId`
becomes an entity, so `createCharge` finds the endpoint that implements it. The document
must be JSON; convert YAML first with a tool such as `yq -o json`.

## Building from a published llms.txt

Vendors are only beginning to publish documentation packages. To try docspack against a
library that has not, build a package from the project's public
[llms.txt](https://llmstxt.org):

```bash
npx docspack sources
npx docspack init --mirror hono --name @docspack-community/hono --yes
```

This fetches over the network, unlike every other command. It is an authoring convenience,
and the result is a mirror: publish it under `@docspack-community`, not the vendor's scope.

## Checking the package

```bash
npx docspack doctor
```

`doctor` reads the package the way the indexer and a reviewer would, and reports what would
otherwise fail silently:

- errors: an invalid manifest, a missing or empty chunk, a chunk path escaping `.llms/`, a
  version that disagrees with `package.json`, or a `files` array that would publish an empty
  package
- warnings: chunks too large to fit a response budget or too small to answer anything,
  chunks with no tags or entities, template placeholders, and a payload older than its
  source

- notes: prose style — narration a model does not need, and sentences over forty words

`--strict` turns warnings into failures, which is what `prepublishOnly` and CI use. It does
not fail on prose style: `--strict` is the gate `init` scaffolds, and documentation written
by hand is not a reason to block a first publish. `--pedantic` is `--strict` plus the prose
notes, for authors who want the tool to hold that line. `--json` prints the findings for a
machine.

## Seeing what an agent would get

```bash
npx docspack preview "how do I authenticate"
```

`preview` indexes the package in memory and answers through the same ranking and token
budget an agent gets — without publishing, installing, or writing to the global store. Most
authoring mistakes are obvious the moment you see what comes back: a heading that should
have been two, or a chunk with no distinguishing words in it.

## The loop

```bash
npx docspack init                          # scaffold, build and check
$EDITOR docs/02-getting-started.md         # write
npx docspack build                         # regenerate
npx docspack doctor                        # what is still wrong
npx docspack verify                        # do the docs still match the code
npx docspack preview "how do I authenticate"
npm publish                                # prepublishOnly rebuilds and checks again
```

Keep the package version equal to the library version it documents, so
`@acme/docspack@1.4.0` describes `acme@1.4.0`. The generated workflow does this for you on
every GitHub release.

## Writing the manifest by hand

`build` is a convenience, not a requirement. Any package with a valid `.llms/manifest.json`
can be indexed:

```json
{
  "$schema": "https://docspack.dev/schema/v1.json",
  "name": "@acme/docspack",
  "version": "1.4.0",
  "chunks": [
    {
      "id": "api-auth",
      "file": "chunks/api-auth.md",
      "tokens": 150,
      "tags": ["authentication", "bearer", "api key"],
      "entities": ["Client.setApiKey"]
    }
  ]
}
```

`id` must be unique within the package. `file` is relative to `.llms/`. `tokens` may be
omitted and is estimated at index time. `tags` and `entities` are optional, and are the
cheapest way to make retrieval noticeably better.

The whole format — naming, layout, every field, and the rules a consumer enforces when
reading a manifest it did not write — is specified at
[docspack.dev/spec](https://docspack.dev/spec), and the JSON Schema is served at the URL
`$schema` points to.
