Adds Setup, Development commands, Code style and formatting (with the VSCode extension hint) and Submitting changes. The decision prose moves to development/; this file keeps the actionable rules (feedback-tier table, script prefix list, standards) and links to the category files for the why.
173 lines
10 KiB
Markdown
173 lines
10 KiB
Markdown
# Contributing
|
|
|
|
This document is for maintainers and contributors working on the project
|
|
itself. End-user documentation is in [README.md](./README.md). The reasons
|
|
behind the rules here — the decisions, rejected alternatives, and known issues —
|
|
live in [development/](./development/README.md). The machine entry point for AI
|
|
coding agents is [AGENTS.md](./AGENTS.md); keep this file as the prose home for
|
|
the rules so agents and humans don't diverge.
|
|
|
|
## Setup
|
|
|
|
1. Clone the repository.
|
|
2. Install Node.js >= 26 — see [.node-version](./.node-version); the exact pinned
|
|
version is what CI and the runner image use.
|
|
3. `npm install`.
|
|
4. `npm run setup` — the one-time clone configuration (currently registers the
|
|
commit-message template).
|
|
|
|
## Development commands
|
|
|
|
- **Build:** `npm run build`
|
|
- **Test:** `npm run test`, `npm run test:ci`
|
|
- **Watch:** `npm run watch`
|
|
- **Checks:** `npm run check`, `npm run fix`
|
|
- **Verify:** `npm run verify` — the definition of done
|
|
- **Maintenance:** `npm run maintain` — advisory only
|
|
- **Individual fixes:** `npm run fix:oxfmt`, `npm run fix:oxlint`
|
|
|
|
## Feedback tiers
|
|
|
|
The tools are organized into a feedback ladder. Each tier catches different
|
|
things at different costs; the rule of thumb is "earlier tiers fire more often,
|
|
faster tiers catch less, slower tiers are more thorough":
|
|
|
|
| Tier | When | What it runs | Time |
|
|
| -------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- |
|
|
| `npm run watch` | manual | `watch:test` — re-runs tests on file save | ~0.1s |
|
|
| Pre-commit (auto) | on stage | tsc + oxlint + oxfmt + cspell (staged files only) | ~1.3s |
|
|
| Pre-push (auto) | on push | `npm test` (full tsc + unit tests) | ~3.5s |
|
|
| `npm run check` | manual | Correctness gates: tsc + oxlint + oxfmt + cspell (whole project) | ~3s |
|
|
| `npm run verify` | manual | Definition of done: `npm run check` + unit tests, one shot | ~6s |
|
|
| `npm run fix` | manual | Auto-resolve fixable issues (lint, format) | ~3s |
|
|
| `npm run maintain` | manual / CI (advisory) | `maintain:knip` + `maintain:outdated` (whole-project + network scans) | ~10s |
|
|
| CI build (auto) | on push to `main` / tag | `build` job (build + correctness + packaging) — see [.gitea/workflows/ci.yml](./.gitea/workflows/ci.yml) | ~30s+ |
|
|
| CI maintain (auto, non-blocking) | on push to `main` | `npm run maintain` — reports, never fails the build | ~10s |
|
|
| CI publish (auto) | on tag | packaging checks + `publish:publint` / `publish:attw`, then the Gitea release page and `npm publish` (skipped, and the job failed, without `NPM_TOKEN`) | ~15s |
|
|
|
|
Before pushing, run `npm run verify` — the one-shot correctness gate. Run
|
|
`npm run maintain` only on a maintenance / update-deps branch. Why the splits
|
|
are where they are: [development/workflow.md § Feedback tiers](./development/workflow.md#feedback-tiers).
|
|
|
|
## Testing discipline (type-driven)
|
|
|
|
For this library the types _are_ the feature, so the loop is **type → red →
|
|
green → refactor**: write the `expectTypeOf(...)` assertion first, then the
|
|
runtime `assert.*`, then the implementation. Every test pairs the two; keep
|
|
them together. Type-first is enforced structurally: `npm test` runs
|
|
`check:tsc` before the test runner, so a wrong type can never be papered over by
|
|
a passing assertion. Per [AGENTS.md § Never do](./AGENTS.md#never-do), reach
|
|
green honestly — fix the types, never suppress the checks you can't make pass.
|
|
Full rationale: [development/testing.md](./development/testing.md).
|
|
|
|
## Code style and formatting
|
|
|
|
`oxfmt` is the formatter and `oxlint` is the linter (with type-aware rules).
|
|
`npm run fix` resolves the fixable issues; `npm run check` verifies without
|
|
writing. Suppressions must be fixed at the root — do not add `oxlint-disable`
|
|
directives or `as` casts to force a green run (see
|
|
[AGENTS.md § Never do](./AGENTS.md#never-do)).
|
|
|
|
Suggested VSCode extensions are in
|
|
[.vscode/extensions.json](./.vscode/extensions.json); the project's formatter
|
|
and linter are wired up there. Toolchain decisions:
|
|
[development/tooling.md](./development/tooling.md).
|
|
|
|
## Commit messages
|
|
|
|
Gitmoji subject, imperative mood, 50/72 wrapping. The template is
|
|
[commit-message-template](./commit-message-template); `npm run setup`
|
|
(or `npm run setup:git-commit-message`) registers it as git's
|
|
`commit.template`. Examples and rationale:
|
|
[development/workflow.md § Commit messages](./development/workflow.md#commit-messages).
|
|
|
|
## Script prefix convention
|
|
|
|
A new `npm run` script must reuse an existing prefix: `create:` / `check:` /
|
|
`fix:` / `test:` / `watch:` / `maintain:` / `publish:` / `setup:`. If none fits,
|
|
that's a signal the script doesn't belong in the pipeline — not a reason to
|
|
invent a new prefix. If it genuinely does belong, add the prefix to the list
|
|
here in the same commit as its first member; an undocumented prefix becomes
|
|
invisible and quietly accrues members. Full convention and why `create:` exists:
|
|
[development/workflow.md § Script prefix convention](./development/workflow.md#script-prefix-convention).
|
|
|
|
## Rules the tools don't enforce
|
|
|
|
CI and review will bounce these even though `npm run check` and the linters
|
|
don't catch them. They're the high-frequency things a contributor (or an agent)
|
|
reaches for by default:
|
|
|
|
- **Source imports use `.ts` extensions, never `.js`.** `node --strip-types`
|
|
only resolves the `.ts` form at test time; "pre-fixing" an import to `.js`
|
|
breaks the inner loop. (why:
|
|
[development/tooling.md](./development/tooling.md#source-imports-use-ts-extensions))
|
|
- **A new `npm run` script must reuse an existing prefix.** See
|
|
[Script prefix convention](#script-prefix-convention).
|
|
- **`oxlint-disable` directives live in source, not `.oxlintrc.json`.** The
|
|
trade-off must sit next to the code it silences. This is a _human_ last-resort
|
|
convention; agents must not add these — see
|
|
[AGENTS.md § Never do](./AGENTS.md#never-do). (why:
|
|
[development/tooling.md](./development/tooling.md#oxlint-disable-directives-live-next-to-the-code))
|
|
- **Don't put slow / network / whole-project scans in `check` or pre-commit.**
|
|
Advisory scans are not correctness gates; they belong under `maintain:`. (why:
|
|
[development/workflow.md](./development/workflow.md#feedback-tiers))
|
|
- **New work starts with `npm run create:branch`, never a hand-written
|
|
`git switch -c` / `git checkout -b`.** The command carries the branch
|
|
precondition; branching around it skips the clean-tree, current-`main` and
|
|
green-baseline checks, and the skip is invisible until a failure can no longer
|
|
be attributed. (why:
|
|
[development/workflow.md](./development/workflow.md#branching-model))
|
|
- **Work is merged back with `npm run create:finish`, never a hand-written
|
|
`git merge`.** The command carries the merge-side preconditions (clean tree,
|
|
current `main`, a `feature/`/`fix/`/`chore/` branch) and runs `npm run verify`
|
|
after the merge, so a merge cannot land unverified. (why:
|
|
[development/workflow.md](./development/workflow.md#branching-model))
|
|
- **There is no local `npm run publish`, and `publish:publint` / `publish:attw`
|
|
don't go in `check`.** (why:
|
|
[development/publishing.md](./development/publishing.md#ci-only-publishing))
|
|
|
|
## Branching model
|
|
|
|
**GitHub Flow (single-developer).** Every change — feature, fix, refactor —
|
|
branches off `main` and is merged back via a local commit. There is no pull
|
|
request workflow on Gitea yet.
|
|
|
|
- **Base branch:** `main`
|
|
- **Branch naming:** `feature/<desc>` / `fix/<desc>` / `chore/<desc>`
|
|
- **Starting work:** `npm run create:branch -- <prefix>/<desc>`. It refuses,
|
|
without changing anything, unless the working tree is clean, no
|
|
merge/rebase/cherry-pick is in progress, `main` matches its upstream, and
|
|
`npm run test` is green on `main`. The prefix is _your_ call, inferred from the
|
|
task; the script validates it rather than guessing it.
|
|
- **Merging:** `npm run create:finish` (on the branch). It re-asserts the same
|
|
preconditions, merges `--no-ff`, runs `npm run verify`, and deletes the branch
|
|
only after the merge is green. The push is left to `create:release`, so the
|
|
merge stays local and reviewable.
|
|
- CI runs on every push to `main` — see [Feedback tiers](#feedback-tiers) and
|
|
[.gitea/workflows/ci.yml](./.gitea/workflows/ci.yml).
|
|
|
|
Full rationale, including the front-door decisions and a known issue about
|
|
`main` being ahead of its upstream between a merge and the next push:
|
|
[development/workflow.md § Branching model](./development/workflow.md#branching-model).
|
|
|
|
## Submitting changes
|
|
|
|
There is no pull request workflow on Gitea yet, so a contribution is submitted
|
|
as a branch that is merged locally:
|
|
|
|
1. `npm run create:branch -- <prefix>/<desc>`.
|
|
2. Commit your work (one or more commits, per the tests and style rules above).
|
|
3. `npm run verify` — the definition of done.
|
|
4. `npm run create:finish` to merge the branch into `main` and verify the
|
|
result.
|
|
5. Present a handover for review. Once there are no further objections, the
|
|
maintainer pushes.
|
|
|
|
When the project is promoted to GitHub, this step becomes a normal pull request
|
|
against `main`.
|
|
|
|
## Publishing
|
|
|
|
Publishing is maintainer-only and CI-only. See
|
|
[development/publishing.md](./development/publishing.md).
|