♻️ Single-home the actionable rules and the rationale

development/ had restated the actionables that CONTRIBUTING.md owns: the
branching step list, the script prefix list, the feedback-tier rule of thumb,
the commit convention and the type-driven test loop. Those now live only in
CONTRIBUTING.md; development/ keeps the decision blocks and links to the rule.
development/README.md, CONTRIBUTING.md and AGENTS.md state the 'write each fact
once' principle explicitly.
This commit is contained in:
tmu committed 2026-09-15 13:46:31 +00:00
1 parent fe02317fc8
commit 95d73d11b6
5 files changed
+89 -126

No files matched your search

+56 -15
View File
@@ -51,14 +51,27 @@ are where they are: [development/workflow.md § Feedback tiers](./development/wo
## 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).
For this library the types _are_ the feature, so development is **type-driven**:
the compile-time expectation is written before the runtime assertion, and both
before the implementation. The loop is **type → red → green → refactor**:
1. **Type** — write the compile-time expectation first
(`expectTypeOf(...).toEqualTypeOf<…>()`) and let `npm run check:tsc` fail on
the _type_. The type error is the spec you want to hit before the runtime
logic exists.
2. **Red** — add the matching runtime assertion (`assert.*`) so
`npm run test:unit` now fails on behavior.
3. **Green** — implement in `src/*.ts` until both the type check and the test
pass.
4. **Refactor** — with the type system and the tests as the safety net, then
`npm run verify` as the definition-of-done gate.
Every test pairs an `expectTypeOf(...)` with an `assert.*`; 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
@@ -83,13 +96,39 @@ Gitmoji subject, imperative mood, 50/72 wrapping. The template is
## 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).
Script names in `package.json` use a prefix that signals _when_ the script is
intended to run. A `<prefix>:<name>` script is implicitly aggregated by a
`<prefix>` script (if one exists) and run by the corresponding lefthook hook or
CI step. Pick the prefix that matches the script's lifecycle:
- `create:*` — front doors of the repo's own workflow; these mutate git state
rather than the source. `create:branch` opens a unit of work, `create:finish`
closes the branch half, `create:release` closes the release half
(maintainer-only). No bare `create` aggregator on purpose.
- `check:*` — read-only verification; never modifies files. Aggregated by
`npm run check`.
- `fix:*` — mutating counterpart of a `check:*` script. Aggregated by
`npm run fix`; the diff is the review surface.
- `test:*` — test scripts. `test` is the canonical entry point (`check:tsc` +
unit tests); `test:unit` skips the typecheck for fast local iteration;
`test:ci` adds c8 coverage.
- `watch:*` — long-running watchers for the manual inner dev loop. Aggregated by
`watch`.
- `maintain:*` — advisory repo-maintenance scans: read-only, but whole-project
and/or network-bound, so never a correctness gate. Aggregated by
`npm run maintain`.
- `publish:*` — validates the _publishable artifact_ (e.g. `dist/`) rather than
the source, so it needs a fresh build.
- `setup:*` — one-time configuration of a fresh clone; mutates the local
environment rather than the repo source, so it is never part of a hook or CI
step. Aggregated by `npm run setup`, run once after cloning.
A new script must reuse an existing prefix. 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 this list in the same commit as its
first member; an undocumented prefix becomes invisible and quietly accrues
members. Why `create:` exists, the rejected names, and the design of the bare
scripts: [development/workflow.md § Script prefix convention](./development/workflow.md#script-prefix-convention).
## Rules the tools don't enforce
@@ -150,6 +189,8 @@ request workflow on Gitea yet.
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).
- **Releases are NOT triggered by pushes.** Only the maintainer triggers a
release; see [Publishing](#publishing).
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: