From 538272c222e9af80a5beaddfe45618c138190f16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Sat, 5 Sep 2026 00:35:00 +0200 Subject: [PATCH] :memo: Restore unique maintainer content as CONTRIBUTING.md After dropping project-specs.md last commit, three categories of content were lost that have no other home in the repo: 1. The script prefix convention with its 'pick the right prefix, don't invent one' rule 2. The feedback-tier system (table + 'why these splits?') 3. The publishing workflow (tagged-release flow) These are maintainer/contributor-facing material, not user-facing. The standard OSS location for this kind of doc is CONTRIBUTING.md, which keeps it separate from README.md (user docs) so the two don't drift. README.md gains a pointer at the bottom. The content is condensed: no config dumps, no restatements of package.json, no historical commit-message examples. Only the rationale that isn't already in the actual config files. --- CONTRIBUTING.md | 54 +++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 4 +++- 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..cb85989 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,54 @@ +# Contributing + +This document is for maintainers and contributors working on the project itself. End-user documentation is in [README.md](./README.md). + +## Script prefix convention + +Script names in `package.json` use a prefix that signals _when_ the script is intended to run. A `:` script is implicitly aggregated by a `` script (if one exists) and run by the corresponding lefthook hook or CI step. Picking the right prefix documents the script's intended lifecycle: + +- `check:*` — read-only verification. Aggregated by `npm run check`. Used in pre-commit hooks (on staged files) and CI's build job (on the whole project). Read-only; never modifies files. +- `fix:*` — mutating counterpart of a `check:*` script. Aggregated by `npm run fix`. Use after `npm run check` to auto-resolve issues; the diff is the review surface. +- `test:*` — test scripts. `npm run test` is the canonical entry point (`check:tsc` + unit tests); `test:unit` skips the typecheck for fast local iteration; `test:ci` adds c8 coverage and is the CI variant. +- `publish:*` — runs only at publish time, in the CI `publish` job (immediately before `npm publish`). There is **no** local `npm run publish` script — publishing is CI-only by policy. The `publish:` prefix still documents intent: this script validates the _publishable artifact_ (e.g., `dist/`) rather than the source. + +A new script should pick the prefix that matches its lifecycle, not invent a new one. If no existing prefix fits, that's a signal the script doesn't belong in the standard pipeline. + +## 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 | +| ----------------- | ---------- | ----------------------------------------------------------- | ----- | +| 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 | Full project audit: all `check:*` scripts + knip + outdated | ~10s | +| `npm run fix` | manual | Auto-resolve fixable issues (lint, format) | ~3s | +| CI build (auto) | on push/PR | `npm run check` + `npm run test:ci` | ~30s+ | +| CI publish (auto) | on tag | `publish:publint` + `publish:attw`, then `npm publish` | ~10s | + +### Why these splits? + +- **`check:tsc`, `check:oxlint`, `check:oxfmt`, `check:cspell`** are in pre-commit because they are fast (~0.2–0.5s each), fully offline, and naturally scope to staged files via the `LEFTHOOK_FILES` env var convention. They give instant feedback on what you typed. +- **`test` (and the `tsc` it includes) is in pre-push** because it runs the whole test suite across the whole project. The pre-commit `LEFTHOOK_FILES` convention doesn't apply to the test runner, so pre-commit isn't the right home. Pre-push runs after all commits are made but before the push leaves the machine, catching regressions that span multiple commits. +- **`check:knip` and `check:outdated` are NOT in pre-commit** — knip scans the whole project (~4s, would noticeably slow the hook), and `check-outdated` queries the npm registry (~6.5s, network-dependent, advisory not correctness). Both run in `npm run check` and CI; pre-commit stays fast and offline. +- **`publish:publint` and `publish:attw` are NOT in `check`** — they belong to the `publish:` prefix because they validate the _publishable artifact_ (`dist/`) and require a fresh build. Running them on every commit would be wasteful. They run in the CI `publish` job immediately before `npm publish`. + +### Before pushing + +Run `npm run check` locally. If you only trust pre-commit + CI, know that anything `check:knip` or `check:outdated` would catch will be caught by CI on the PR before merge. + +## Publishing workflow + +Publishing is CI-only by policy. Local `npm publish` is not supported. + +1. Develop and merge PRs to `main`. +2. CI runs `npm run check` + `npm run test:ci` on every push and PR — this is the authoritative gate. +3. After all intended changes are on `main`, bump the version locally: + ```sh + npm version + ``` +4. Push the tag to GitHub: + ```sh + git push --follow-tags origin main + ``` +5. The `publish` CI job runs on the tag: `build` → `publish:publint` → `publish:attw` → `npm publish --access public`. The publish-tier checks must pass before the artifact is published. diff --git a/README.md b/README.md index d94e3da..51c5456 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,9 @@ Script names follow a prefix convention that signals _when_ they run: - `test:*` — test scripts. `npm run test` runs the full suite; `test:unit` / `test:ci` are scope-specific variants. - `publish:*` — runs only at publish time, in the CI `publish` job (immediately before `npm publish`). There is no local `npm run publish` script — publishing is CI-only by policy. -## Contribution guidelines +## Contributing + +For maintainer and contributor docs — the script prefix convention, the feedback-tier system, and the publishing workflow — see [CONTRIBUTING.md](./CONTRIBUTING.md). - Commit signing (GPG). - Set up commit message template: `npm run use:git-commit-message`.