📝 Reconcile project-specs.md and cspell.json with the feature/setup branch

This commit is contained in:
tmu committed 2026-09-05 00:13:10 +02:00
1 parent 1fe3dbe28b
commit 1a749b13d6
2 files changed
+77 -44

No files matched your search

-2
View File
@@ -2,8 +2,6 @@
"version": "0.2", "version": "0.2",
"language": "en", "language": "en",
"words": [ "words": [
"tslib",
"typefest",
"lefthook", "lefthook",
"oxlint", "oxlint",
"oxfmt", "oxfmt",
+77 -42
View File
@@ -8,13 +8,28 @@ typescript-lib-starter-tiny => https://github.com/tmueller/typescript-lib-starte
## 1. Development Environment ## 1. Development Environment
- **TypeScript**: Use strictest practical rules. Inline in `tsconfig.json` (not - **TypeScript**: Use strictest practical rules via `@tsconfig/strictest`
via `@tsconfig/strictest`). The rule set is: `strict`, `noImplicitAny`, (extended from `tsconfig.json`). The project also extends
`noImplicitThis`, `alwaysStrict`, `strictNullChecks`, `strictFunctionTypes`, `@tsconfig/node26` for Node 26's `target`/`module`/`lib` defaults;
`strictBindCallApply`, `strictPropertyInitialization`, `noImplicitReturns`, `tsconfig.json` is the type-check base (`noEmit: true`, includes
`noFallthroughCasesInSwitch`, `noUncheckedIndexedAccess`, `noImplicitOverride`, `src/`), and `tsconfig.build.json` extends it to add the emit-only
`noUnusedLocals`, `noUnusedParameters`, `forceConsistentCasingInFileNames`, options (`declaration`, `sourceMap`, `outDir`, `target: es2024`,
`isolatedModules`, `verbatimModuleSyntax`. `rewriteRelativeImportExtensions: true`) and to exclude test files
from emission. The `verbatimModuleSyntax: true` and
`allowImportingTsExtensions: true` overrides live in
`tsconfig.json` (the latter only works because the base has
`noEmit: true`). The strictest flags the project relies on (inherited
from `@tsconfig/strictest`) are: `strict`, `noImplicitAny`,
`noImplicitThis`, `alwaysStrict`, `strictNullChecks`,
`strictFunctionTypes`, `strictBindCallApply`,
`strictPropertyInitialization`, `noImplicitReturns`,
`noFallthroughCasesInSwitch`, `noUncheckedIndexedAccess`,
`noImplicitOverride`, `noUnusedLocals`, `noUnusedParameters`,
`forceConsistentCasingInFileNames`, `isolatedModules`,
`verbatimModuleSyntax`, plus the additional strictest flags
(`exactOptionalPropertyTypes`, `noPropertyAccessFromIndexSignature`,
`noUncheckedIndexedAccess` etc.) that `@tsconfig/strictest` enables
beyond a plain `strict: true`.
- **EditorConfig**: Use `.editorconfig` from typescript-lib-starter-tiny. - **EditorConfig**: Use `.editorconfig` from typescript-lib-starter-tiny.
- **oxfmt**: Rust-based formatter, Prettier-compatible. Replaces Prettier. - **oxfmt**: Rust-based formatter, Prettier-compatible. Replaces Prettier.
Config in `.oxfmtrc.json` (same shape as `.prettierrc`). Config in `.oxfmtrc.json` (same shape as `.prettierrc`).
@@ -34,6 +49,8 @@ typescript-lib-starter-tiny => https://github.com/tmueller/typescript-lib-starte
- `eslint/sort-keys` — handler/case order is semantic, not alphabetical. - `eslint/sort-keys` — handler/case order is semantic, not alphabetical.
- `eslint/id-length` — `T`, `R`, `U`, `V` are standard TS generics. - `eslint/id-length` — `T`, `R`, `U`, `V` are standard TS generics.
- `import/no-named-export` — false positive on library entry re-exports. - `import/no-named-export` — false positive on library entry re-exports.
- `typescript/method-signature-style` — method signatures in
`interface` declarations are conventional TS ergonomics.
- Stylistic rules superseded by oxfmt (oxfmt is the canonical - Stylistic rules superseded by oxfmt (oxfmt is the canonical
formatter; these rules either conflict with its output or formatter; these rules either conflict with its output or
duplicate features oxfmt already provides): duplicate features oxfmt already provides):
@@ -48,8 +65,6 @@ typescript-lib-starter-tiny => https://github.com/tmueller/typescript-lib-starte
`import { type X, Y }` is intentional for grouping. `import { type X, Y }` is intentional for grouping.
- `unicorn/prefer-export-from` — conflicts with how the - `unicorn/prefer-export-from` — conflicts with how the
barrel `src/index.ts` re-exports through `src/match.ts`. barrel `src/index.ts` re-exports through `src/match.ts`.
- `typescript/method-signature-style` — method signatures
in `interface` are conventional TS ergonomics.
For `*.test.ts` files additionally: `no-unused-expressions` (for For `*.test.ts` files additionally: `no-unused-expressions` (for
`expectTypeOf(...)` calls), `no-empty-file` (we have a single import `expectTypeOf(...)` calls), `no-empty-file` (we have a single import
per test file in some cases), `import/no-nodejs-modules` (we use per test file in some cases), `import/no-nodejs-modules` (we use
@@ -58,9 +73,9 @@ typescript-lib-starter-tiny => https://github.com/tmueller/typescript-lib-starte
- **Import sorting**: built into oxfmt (no separate plugin). - **Import sorting**: built into oxfmt (no separate plugin).
- **cspell**: Basic spelling configuration. Words dictionary in `cspell.json` - **cspell**: Basic spelling configuration. Words dictionary in `cspell.json`
covers tooling names (`oxlint`, `oxfmt`, `oxc`, `nodenext`, covers tooling names (`oxlint`, `oxfmt`, `oxc`, `nodenext`,
`oxfmtrc`, `oxlintrc`, `EDITMSG`, `typescriptteam`, `oxfmtrc`, `oxlintrc`, `tsgolint`, `EDITMSG`, `typescriptteam`,
`gitmoji`, `dbaeumer`, `msvc`) and a few library names (`tslib`, `gitmoji`, `dbaeumer`, `msvc`, `publint`, `attw`, `arethetypeswrong`,
`typefest`, `lefthook`). `knip`) and a few library names (`lefthook`).
- **Lefthook**: Pre-commit checks (run in parallel, lefthook v2 schema). - **Lefthook**: Pre-commit checks (run in parallel, lefthook v2 schema).
Single source of truth for the underlying commands is in Single source of truth for the underlying commands is in
`package.json#scripts`; the lefthook config only describes _what to `package.json#scripts`; the lefthook config only describes _what to
@@ -82,16 +97,27 @@ typescript-lib-starter-tiny => https://github.com/tmueller/typescript-lib-starte
CI and via the explicit `lefthook run outdated` command. CI and via the explicit `lefthook run outdated` command.
- **Additional Dev Dependencies**: - **Additional Dev Dependencies**:
- lefthook - lefthook (git hook manager)
- check-outdated - check-outdated (outdated dependency warnings)
- knip (unused dependency / export / file detection)
- publint (publish-time `package.json` validation)
- @arethetypeswrong/cli (attw, publish-time `.d.ts` validation)
- @tsconfig/strictest, @tsconfig/node26 (extended from in
`tsconfig.json`)
- c8 (coverage for `node --test`) - c8 (coverage for `node --test`)
- expect-type (type-level assertions in tests) - expect-type (type-level assertions in tests)
- oxfmt, oxlint (with their native bindings as `optionalDependencies` - oxfmt, oxlint (with their native bindings as `optionalDependencies`
so the correct binding is selected per platform automatically) so the correct binding is selected per platform automatically)
- oxlint-tsgolint (type-aware linter backend, also with native
bindings as `optionalDependencies`)
- cspell (spell checking)
- typescript (TS 7) - typescript (TS 7)
- @types/node - @types/node
- tslib (available for future runtime helper imports)
- type-fest (utility types) `tslib` and `type-fest` were removed (commit `ec98223`): knip
flagged them as unused, and they were not appropriate for an
ES2024-targeting ESM-only library (`tslib` is a runtime helper for
old ES3/ES5 targets; `type-fest` was never imported).
## 2. Build & Test ## 2. Build & Test
@@ -121,10 +147,6 @@ typescript-lib-starter-tiny => https://github.com/tmueller/typescript-lib-starte
- `target: es2024`, `lib: ["es2024"]` (Node 26 supports all ES2024 - `target: es2024`, `lib: ["es2024"]` (Node 26 supports all ES2024
features natively). features natively).
- **Additional Dev Dependencies** (for types and tests): - **Additional Dev Dependencies** (for types and tests):
- `tslib` — available for runtime helper imports; not currently
used by source (kept for future use per the original spec).
- `type-fest` — utility types; not currently used by source
(kept for future use per the original spec).
- `@types/node` — types for `node:test`, `node:assert`, `node:fs`. - `@types/node` — types for `node:test`, `node:assert`, `node:fs`.
- **Node Version**: `>=26` (engines field). Pinned via `.node-version` - **Node Version**: `>=26` (engines field). Pinned via `.node-version`
for fnm/nvm/volta/mise auto-switching and CI for fnm/nvm/volta/mise auto-switching and CI
@@ -207,7 +229,10 @@ script does not belong in the standard pipeline.
- `test:unit`: Run unit tests with `node --test --strip-types "src/**/*.test.ts"` - `test:unit`: Run unit tests with `node --test --strip-types "src/**/*.test.ts"`
(no preceding typecheck). (no preceding typecheck).
- `test:ci`: Run tests in CI mode with c8 coverage (text + lcov + html - `test:ci`: Run tests in CI mode with c8 coverage (text + lcov + html
reporters), uploading `coverage/` as an artifact. reporters), uploading `coverage/` as an artifact. The command is
`c8 --reporter=text --reporter=lcov --reporter=html node --test
--strip-types "src/**/*.test.ts"` (the glob is required on Node 26
for the same reason `test` uses one — see below).
### BUILD ### BUILD
@@ -223,8 +248,11 @@ script does not belong in the standard pipeline.
### CHECK ### CHECK
- `check`: Run all checks in order — `check:oxlint`, `check:oxfmt`, - `check`: Run all checks in order — `check:tsc`, `check:oxlint`,
`check:tsc`, `check:cspell`, `check:knip`, `check:outdated`. `check:oxfmt`, `check:cspell`, `check:knip`, `check:outdated`.
`check:tsc` is first so a type error short-circuits the rest
(faster feedback than letting oxlint/oxfmt run and then failing on
tsc at the end).
- `check:oxlint`: `oxlint ${LEFTHOOK_FILES:-src}` — lints `src/` by - `check:oxlint`: `oxlint ${LEFTHOOK_FILES:-src}` — lints `src/` by
default; when invoked from the lefthook pre-commit hook with default; when invoked from the lefthook pre-commit hook with
`LEFTHOOK_FILES` set to the staged-files list, lints only those `LEFTHOOK_FILES` set to the staged-files list, lints only those
@@ -256,7 +284,8 @@ script does not belong in the standard pipeline.
- `check:tsc`: `tsc` (noEmit is set in `tsconfig.json`). - `check:tsc`: `tsc` (noEmit is set in `tsconfig.json`).
- `check:cspell`: `cspell lint ${LEFTHOOK_FILES:-.}` — walks the - `check:cspell`: `cspell lint ${LEFTHOOK_FILES:-.}` — walks the
project root by default; from lefthook, only the staged files. project root by default; from lefthook, only the staged files.
- `check:outdated`: `check-outdated --ignore-pre-releases --ignore-packages @oxfmt/binding-*,@oxlint/binding-*`. The oxc native bindings are declared as `optionalDependencies` so the correct one is selected per platform automatically; the `*`-platform bindings show as "not installed" on the current platform and are explicitly ignored here. - `check:outdated`: `check-outdated --ignore-pre-releases
--ignore-packages @oxfmt/binding-darwin-arm64,@oxfmt/binding-darwin-x64,@oxfmt/binding-linux-arm64-gnu,@oxfmt/binding-linux-arm64-musl,@oxfmt/binding-linux-x64-gnu,@oxfmt/binding-linux-x64-musl,@oxfmt/binding-win32-x64-msvc,@oxlint/binding-darwin-arm64,@oxlint/binding-darwin-x64,@oxlint/binding-linux-arm64-gnu,@oxlint/binding-linux-arm64-musl,@oxlint/binding-linux-x64-gnu,@oxlint/binding-linux-x64-musl,@oxlint/binding-win32-x64-msvc,@oxlint-tsgolint/darwin-arm64,@oxlint-tsgolint/darwin-x64,@oxlint-tsgolint/linux-arm64,@oxlint-tsgolint/linux-x64,@oxlint-tsgolint/win32-arm64,@oxlint-tsgolint/win32-x64`. The oxfmt, oxlint, and oxlint-tsgolint native bindings are declared as `optionalDependencies` so the correct one is selected per platform automatically; bindings for non-current platforms show as "not installed" and are explicitly ignored here.
- `check:knip`: `knip --include dependencies,exports,files` — finds - `check:knip`: `knip --include dependencies,exports,files` — finds
unused dependencies, value exports, and source files. The unused dependencies, value exports, and source files. The
scoped `--include` list intentionally omits the `types` scoped `--include` list intentionally omits the `types`
@@ -311,7 +340,7 @@ deliberate design choice; the rule of thumb is:
- **pre-commit hook**: fast, file-scoped, offline, deterministic. - **pre-commit hook**: fast, file-scoped, offline, deterministic.
Catches what _you just changed_ in under a second or two. Catches what _you just changed_ in under a second or two.
- **pre-push hook**: runs the unit test suite (`npm test`, - **pre-push hook**: runs the unit test suite (`npm test`,
including a full `tsc` over the project). ~500ms. Catches including a full `tsc` over the project). ~3.5s. Catches
runtime/logic bugs across the whole codebase before the push runtime/logic bugs across the whole codebase before the push
leaves your machine. The `tsc` step is technically redundant leaves your machine. The `tsc` step is technically redundant
with pre-commit but validates against unstaged changes too. with pre-commit but validates against unstaged changes too.
@@ -321,17 +350,22 @@ deliberate design choice; the rule of thumb is:
`npm run test:ci` on every push and PR. The ground truth — `npm run test:ci` on every push and PR. The ground truth —
if CI passes, the codebase is clean. if CI passes, the codebase is clean.
| Script | pre-commit | pre-push | `npm run check` | CI build | CI publish | | Script | pre-commit | pre-push | `npm run check` | `npm run fix` | CI build | CI publish |
| ----------------- | :--------: | :------: | :-------------: | :------: | :--------: | | ----------------- | :--------: | :------: | :-------------: | :-----------: | :------: | :--------: |
| `check:tsc` | ✅ | ✅ | ✅ | ✅ | — | | `check:tsc` | ✅ | ✅ | ✅ | — | ✅ | — |
| `check:oxlint` | ✅ | — | ✅ | ✅ | — | | `check:oxlint` | ✅ | — | ✅ | — | ✅ | — |
| `check:oxfmt` | ✅ | — | ✅ | ✅ | — | | `check:oxfmt` | ✅ | — | ✅ | — | ✅ | — |
| `check:cspell` | ✅ | — | ✅ | ✅ | — | | `check:cspell` | ✅ | — | ✅ | — | ✅ | — |
| `test` | — | ✅ | — | ✅ | — | | `test` | — | ✅ | — | — | ✅ | — |
| `check:knip` | — | — | ✅ | ✅ | — | | `check:knip` | — | — | ✅ | — | ✅ | — |
| `check:outdated` | — | — | ✅ | ✅ | — | | `check:outdated` | — | — | ✅ | — | ✅ | — |
| `publish:publint` | — | — | — | — | ✅ | | `fix:oxlint` | — | — | — | ✅ | — | — |
| `publish:attw` | — | — | — | — | ✅ | | `fix:oxfmt` | — | — | — | ✅ | — | — |
| `publish:publint` | — | — | — | — | — | ✅ |
| `publish:attw` | — | — | — | — | — | ✅ |
(Each `test` row also runs `check:tsc` internally, so pre-push gets a
full `tsc` for free.)
#### Why these splits? #### Why these splits?
@@ -342,12 +376,13 @@ deliberate design choice; the rule of thumb is:
on what you typed. on what you typed.
- **`test` (and the `tsc` it includes) is in pre-push** because it - **`test` (and the `tsc` it includes) is in pre-push** because it
runs the whole test suite across the whole project (~500ms runs the whole test suite across the whole project (~3.5s
for the current 6 tests). The pre-commit `LEFTHOOK_FILES` for the current 6 tests, including a full `tsc`). The pre-commit
convention doesn't apply to the test runner, so pre-commit `LEFTHOOK_FILES` convention doesn't apply to the test runner,
isn't the right home. Pre-push is the natural place: it runs so pre-commit isn't the right home. Pre-push is the natural
after all commits are made but before the push leaves the place: it runs after all commits are made but before the push
machine, catching regressions that span multiple commits. leaves the machine, catching regressions that span multiple
commits.
- **`check:knip` is NOT in pre-commit or pre-push** because it - **`check:knip` is NOT in pre-commit or pre-push** because it
scans the whole project (not staged files) and takes ~4s. scans the whole project (not staged files) and takes ~4s.