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.
8.8 KiB
Tooling
Every tool below was chosen and configured deliberately. The commands a contributor runs are in CONTRIBUTING.md and the versions in 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.jsonkey sorting replacessort-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.jsonfor ESM publishing. - @arethetypeswrong/cli (
attw) — validates.d.tsagainst 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 viatsc --lsp --stdio.
When each runs is in 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/. inlineSourcesembeds the original TypeScript indist/*.js.map, so debuggers map intosrc/without it being shipped.declarationMapstays off: a.d.ts.mapcannot 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
tscdoes not prune orphaned emit output — droppingdeclarationMapleft stale*.d.ts.mapfiles — so reproducibility needs an emptydist/.prebuildremoves onlydist; the manualcleanresetsdist+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-typesresolves the.tsform at test time.rewriteRelativeImportExtensionsrewrites them to.jsin the emitted JavaScript.- The emitted
.d.tskeep the.tsspecifier, which TypeScript >= 5.0 resolves (see README § Requirements), so no post-processing step is needed.
Rejected
- "Pre-fixing" an import to
.js: it breaks the innernode --strip-typesloop.
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:oxlintscripts: 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.jsonfor 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).
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-disablemeaningful: it marks a lone exception.
Rejected
- A source-level
oxlint-disableper 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
tscat 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
.editorconfigcovers the files oxfmt does not format: shell scripts, dotfiles,LICENSE, the commit-message template, and git'sCOMMIT_EDITMSGbuffer.- 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
typesproduces 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'slatestdist-tag to LTS (22.x); current-line types ride other tags. Scan sees latest < installed — permanent "reverted", exit 1, zero signal.--ignore-pre-releasesno 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/nodegoes unreported; match it to.node-versionby 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
tslibis 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#scriptsthe single source of truth;lefthook.ymlonly 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 (oxc, cspell, TypeScript native-preview, EditorConfig, todo-tasks).
- TypeScript 7 runs via the
typescriptteam.native-previewextension. - The oxc extension provides oxlint squiggles and oxfmt format-on-save;
.vscode/settings.jsonpins 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 nolib/tsserver.js(true of thetypescript-go/tsgoport), and spawns the repo's owntsc --lsp --stdio— notypescript-language-serverdependency is needed. - Earlier releases (
<= 0.0.10) hard-wire totypescript-language-server --stdioand 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/verifyare. .pi/settings.jsonis the committed declaration;.pi/npm/is a gitignored install cache that pi recreates on a trusted startup (runningnpm installfor any missing project package), so it is deliberately not tracked.