They were the template's demo API, not the library's real surface. Drop them, their README walkthrough, and the tooling note that cited their disable directives, and point index.ts at the primitive matchers that remain.
282 lines
8.8 KiB
Markdown
282 lines
8.8 KiB
Markdown
# Tooling
|
|
|
|
Every tool below was chosen and configured deliberately. The commands a
|
|
contributor runs are in [CONTRIBUTING.md](../CONTRIBUTING.md) and the versions
|
|
in [package.json](../package.json).
|
|
|
|
## Tool inventory
|
|
|
|
- **TypeScript 7** — type checker and build (`tsc`).
|
|
- **node --test** + `--strip-types` — test runner.
|
|
- **c8** — coverage for `test:ci`.
|
|
- **oxlint** — Rust linter, type-aware via **oxlint-tsgolint** (typescript-go).
|
|
- **oxfmt** — Rust formatter (Prettier-compatible) for JS/TS, JSON/JSONC, YAML,
|
|
Markdown, MDX, and more; its `package.json` key sorting replaces
|
|
`sort-package-json`.
|
|
- **cspell** — spell checking.
|
|
- **knip** — unused dependencies, exports, and files.
|
|
- **check-outdated** — dependencies behind the registry; exits non-zero when any
|
|
is outdated.
|
|
- **publint** — validates `package.json` for ESM publishing.
|
|
- **@arethetypeswrong/cli** (`attw`) — validates `.d.ts` against module-resolution
|
|
scenarios.
|
|
- **lefthook** — git hooks.
|
|
- **@spences10/pi-lsp** — read-only LSP code intelligence for AI agents
|
|
(project-local `.pi/settings.json`); talks to this repo's TypeScript 7 via
|
|
`tsc --lsp --stdio`.
|
|
|
|
When each runs is in
|
|
[CONTRIBUTING.md § Feedback tiers](../CONTRIBUTING.md#feedback-tiers).
|
|
|
|
## TypeScript and build
|
|
|
|
### One type-check config, one emit config
|
|
|
|
#### Decision (2026-09)
|
|
|
|
`tsconfig.json` extends `@tsconfig/strictest` + `@tsconfig/node26`.
|
|
`tsconfig.build.json` adds the emit-only options (`declaration`, `sourceMap`,
|
|
`inlineSources`, `outDir`, `target: es2024`,
|
|
`rewriteRelativeImportExtensions: true`) and excludes test files.
|
|
|
|
#### Why
|
|
|
|
- The editor and CI type-check from one config while the build emits from the
|
|
other, so a test file cannot leak into `dist/`.
|
|
- `inlineSources` embeds the original TypeScript in `dist/*.js.map`, so
|
|
debuggers map into `src/` without it being shipped.
|
|
- `declarationMap` stays off: a `.d.ts.map` cannot embed source and would
|
|
dangle.
|
|
|
|
### The build starts from an empty `dist/`
|
|
|
|
#### Decision (2026-09)
|
|
|
|
`npm run build` runs a `prebuild` hook that empties `dist/`.
|
|
|
|
#### Why
|
|
|
|
- `tsc` does not prune orphaned emit output — dropping `declarationMap` left
|
|
stale `*.d.ts.map` files — so reproducibility needs an empty `dist/`.
|
|
- `prebuild` removes only `dist`; the manual `clean` resets `dist` + `coverage`,
|
|
so a local coverage report survives a build.
|
|
|
|
### Source imports use `.ts` extensions
|
|
|
|
#### Decision (2026-09)
|
|
|
|
Source imports use `.ts`, never `.js`.
|
|
|
|
#### Why
|
|
|
|
- `node --strip-types` resolves the `.ts` form at test time.
|
|
- `rewriteRelativeImportExtensions` rewrites them to `.js` in the emitted
|
|
JavaScript.
|
|
- The emitted `.d.ts` keep the `.ts` specifier, which TypeScript >= 5.0 resolves
|
|
(see [README § Requirements](../README.md#requirements)), so no
|
|
post-processing step is needed.
|
|
|
|
#### Rejected
|
|
|
|
- "Pre-fixing" an import to `.js`: it breaks the inner `node --strip-types`
|
|
loop.
|
|
|
|
## Linting and formatting
|
|
|
|
### Type-aware oxlint is a config property
|
|
|
|
#### Decision (2026-09)
|
|
|
|
Type-aware oxlint is enabled via `options.typeAware: true` in `.oxlintrc.json`
|
|
(powered by `oxlint-tsgolint`).
|
|
|
|
#### Why
|
|
|
|
- The script commands stay clean — no CLI flag.
|
|
- A config property cannot be forgotten on one call site.
|
|
|
|
#### Rejected
|
|
|
|
- A CLI flag in the `check:oxlint` / `fix:oxlint` scripts: it puts the mode in
|
|
two places and invites them to drift.
|
|
|
|
### `oxlint-disable` directives live next to the code
|
|
|
|
#### Decision (2026-09)
|
|
|
|
A type-aware rule that false-positives is silenced with a source-level
|
|
`oxlint-disable` directive (see `src/primitive.ts`,
|
|
`src/primitive.test.ts`), not by turning the rule off in `.oxlintrc.json`.
|
|
|
|
#### Why
|
|
|
|
- The disable sits next to the code it silences, visible to anyone reading the
|
|
source.
|
|
- The rule stays on everywhere else, so only the mis-firing line is exempted.
|
|
|
|
#### Rejected
|
|
|
|
- A project-wide disable in `.oxlintrc.json` for a false positive: it hides the
|
|
exemption from the reader of the affected code and switches the rule off
|
|
repo-wide for a one-site problem.
|
|
|
|
#### Known issue
|
|
|
|
- A source-level disable is a _human_ last resort. AI agents must not add one;
|
|
they fix the type at its root (see [AGENTS.md § Never do](../AGENTS.md#never-do)).
|
|
|
|
### Unwanted stylistic rules are turned off in the config
|
|
|
|
#### Decision (2026-09)
|
|
|
|
A stylistic rule the project rejects is `"off"` in the `.oxlintrc.json` `rules`
|
|
map, not silenced at a use site. Current entries: `eslint/capitalized-comments`
|
|
(comments may start lowercase) and `eslint/no-ternary` (ternaries are allowed),
|
|
joining the oxfmt-superseded rules already off.
|
|
|
|
#### Why
|
|
|
|
- The rule is wrong for the whole project, not mis-firing at one site, so there
|
|
is no line to annotate.
|
|
- Keeping the two mechanisms separate keeps a source-level `oxlint-disable`
|
|
meaningful: it marks a lone exception.
|
|
|
|
#### Rejected
|
|
|
|
- A source-level `oxlint-disable` per use: the same exemption repeated at every
|
|
site, and oxfmt can move the site.
|
|
|
|
### `check:tsc` runs first
|
|
|
|
#### Decision (2026-09)
|
|
|
|
`check:tsc` runs first in the `npm run check` chain.
|
|
|
|
#### Why
|
|
|
|
- A type error short-circuits the rest, which is faster than running
|
|
oxlint/oxfmt and failing on `tsc` at the end.
|
|
|
|
### `.editorconfig` is a fallback, not a gate
|
|
|
|
#### Decision (2026-09)
|
|
|
|
`.editorconfig` exists for editor compatibility; where both apply,
|
|
`.oxfmtrc.json` is authoritative.
|
|
|
|
#### Why
|
|
|
|
- `.editorconfig` covers the files oxfmt does not format: shell scripts,
|
|
dotfiles, `LICENSE`, the commit-message template, and git's `COMMIT_EDITMSG`
|
|
buffer.
|
|
- oxfmt is the formatter; the overlapping keys only keep non-oxfmt editors close
|
|
to the formatted result, so they cannot disagree with the checker.
|
|
|
|
## Static analysis and packaging
|
|
|
|
### `knip` omits the `types` category
|
|
|
|
#### Decision (2026-09)
|
|
|
|
`knip --include dependencies,exports,files` omits the `types` category.
|
|
|
|
#### Why
|
|
|
|
- `types` produces systematic false positives for libraries whose exported types
|
|
are part of the public API.
|
|
- The narrower scope keeps the signal high without config-file boilerplate.
|
|
|
|
### `maintain:outdated` ignores `@types/node`
|
|
|
|
#### Decision (2026-09)
|
|
|
|
Pass `--ignore-packages @types/node`.
|
|
|
|
#### Why
|
|
|
|
- DT pins `@types/node`'s `latest` dist-tag to LTS (22.x); current-line types
|
|
ride other tags. Scan sees latest < installed — permanent "reverted", exit
|
|
1, zero signal. `--ignore-pre-releases` no help: 22.20.3 is stable.
|
|
|
|
#### Rejected
|
|
|
|
- `--types major,minor,patch`: hides real reverted reports elsewhere.
|
|
- `@types/node@26.*`: tag stays wrong across majors; un-pin per bump = ritual.
|
|
|
|
#### Known issue
|
|
|
|
- A genuinely behind `@types/node` goes unreported; match it to
|
|
`.node-version` by hand.
|
|
|
|
### `attw` targets ESM-only
|
|
|
|
#### Decision (2026-09)
|
|
|
|
`attw --profile esm-only` is used.
|
|
|
|
#### Why
|
|
|
|
- The package is intentionally ESM-only (no CommonJS shim), so CJS resolution
|
|
scenarios are out of scope by design, not a bug.
|
|
|
|
### `tslib` is deliberately not used
|
|
|
|
#### Decision (2026-09)
|
|
|
|
`tslib` is not a dependency.
|
|
|
|
#### Why
|
|
|
|
- `tslib` is a runtime helper for old ES3/ES5 targets; this project targets
|
|
ES2024.
|
|
|
|
## Git hooks and script wiring
|
|
|
|
### `LEFTHOOK_FILES` scopes commands to staged files
|
|
|
|
#### Decision (2026-09)
|
|
|
|
The pre-commit hook sets `LEFTHOOK_FILES` to the staged-files list, and the
|
|
affected scripts use `${LEFTHOOK_FILES:-<default>}` to default to the whole
|
|
project.
|
|
|
|
#### Why
|
|
|
|
- It keeps `package.json#scripts` the single source of truth; `lefthook.yml`
|
|
only says what to run on which files.
|
|
- The same script works by hand (whole project) and staged (scoped), so there is
|
|
no second command to maintain.
|
|
|
|
## Editor and agent tooling
|
|
|
|
### VSCode integration
|
|
|
|
- Recommended extensions are in
|
|
[.vscode/extensions.json](../.vscode/extensions.json) (oxc, cspell, TypeScript
|
|
native-preview, EditorConfig, todo-tasks).
|
|
- TypeScript 7 runs via the `typescriptteam.native-preview` extension.
|
|
- The oxc extension provides oxlint squiggles and oxfmt format-on-save;
|
|
`.vscode/settings.json` pins it per language so a local `[language]` formatter
|
|
setting cannot override the project's choice.
|
|
|
|
### `@spences10/pi-lsp` is pinned and read-only
|
|
|
|
#### Decision (2026-09)
|
|
|
|
`@spences10/pi-lsp` is pinned to `0.0.46` and used read-only.
|
|
|
|
#### Why
|
|
|
|
- It inspects `node_modules/typescript`, sees major >= 7 with no
|
|
`lib/tsserver.js` (true of the `typescript-go` / `tsgo` port), and spawns the
|
|
repo's own `tsc --lsp --stdio` — no `typescript-language-server` dependency is
|
|
needed.
|
|
- Earlier releases (`<= 0.0.10`) hard-wire to `typescript-language-server
|
|
--stdio` and are TS6-only.
|
|
- It is _intermediate_ agent feedback (hover, references, definition, symbols,
|
|
diagnostics), with no rename / code-action / apply-edit surface, and is never a
|
|
gate — `npm run check` / `verify` are.
|
|
- `.pi/settings.json` is the committed declaration; `.pi/npm/` is a gitignored
|
|
install cache that pi recreates on a trusted startup (running `npm install`
|
|
for any missing project package), so it is deliberately not tracked.
|