# Development documentation Why this project works the way it does: the decisions, what was rejected, and the shortcomings and known issues we carry. Written for maintainers and contributors. The actionable rules — setup, running, testing, submitting — live in [CONTRIBUTING.md](../CONTRIBUTING.md). **Each fact is written once**: the rule there, the reason here; neither restates the other, and where a fact is useful in both they link. Read the relevant file before changing an area, and when a rule changes update its rationale here in the same commit. User-facing documentation is [README.md](../README.md). `docs/` is deliberately unused: that name is reserved for the future user documentation site, and deploying it is out of scope. These files are not part of that site. ## Layout One file per category: | File | Covers | | -------------------------------- | ----------------------------------------------------------------------- | | [library.md](./library.md) | Public API design, the type-level contract, and its limitations | | [workflow.md](./workflow.md) | Branching and merging, script prefixes, feedback tiers, commit messages | | [tooling.md](./tooling.md) | Toolchain choices and configuration, editor setup | | [testing.md](./testing.md) | Test strategy and type-driven development | | [docs.md](./docs.md) | Validating the Markdown code fences in the prose docs | | [ci.md](./ci.md) | CI pipeline, runner image, coverage serving | | [publishing.md](./publishing.md) | Release and npm publishing | We start with one file per category so each area stays small enough to hold in mind; a category that outgrows it becomes a folder with an index, and the links in CONTRIBUTING.md and README.md point at the category, not a single decision. ## Decision blocks Record every non-obvious choice as a block in the relevant category file: ```md ## Runner image #### Decision (2026-09) Bake Node into the CI job image at the setup-node tool-cache layout instead of downloading per job. #### Why - ... #### Rejected - Gitea Pages / per-job download - force-pull #### Known issue - a Dockerfile-only change re-pushed under an unchanged tag is invisible to the runner - recover with `docker rmi ` ``` - The date is the month the decision was made, not when the file was edited — the anchor for "current" versus "was current once". - `Rejected` stops the project re-litigating the same alternatives; an empty one usually means they were never written down. - `Known issue` is where shortcomings live. A caveat not tied to one decision goes under a `## Known issues` section at the end of the file. - Replace a superseded decision in place rather than archiving it; git history is the archive. - Terse is the point: humans skim and agents imitate the style already in the file, so verbosity compounds edit over edit. Grammar loses to density here on purpose. ## Adding to these docs 1. Pick the category: `library`, `workflow`, `tooling`, `testing`, `ci`, `publishing`. 2. Add or update a decision block; keep existing text unless the decision changed. 3. If an actionable rule changes, update [CONTRIBUTING.md](../CONTRIBUTING.md) in the same commit and cross-link. Never change a rule there without updating its rationale here.