Files
tiny-pattern-ts/project-specs.md
T
tmu 74e4538094 📝 Document the three-tier check system (pre-commit / check / CI)
Adds a 'CHECK TIERS' subsection under the existing HOOKS section
in project-specs.md that explicitly documents:

- Where each check runs (pre-commit, npm run check, CI build, CI publish)
- A summary table mapping scripts to execution contexts
- The rationale for each split (speed, scope, side effects)
  - Why check:knip is not in pre-commit (~4s, whole project)
  - Why check:outdated is not in pre-commit (network dep, advisory)
  - Why publish:* is not in check (validates dist/, needs build)
- Cross-reference from the CI/CD section back to the tiers table

The split is a deliberate design choice: pre-commit is the fast
safety net for what you just changed, npm run check is the full
local audit, CI is authoritative. Documenting it makes the
rationale explicit and stops anyone from re-adding the slower
checks to the hook.
2026-09-04 23:36:32 +02:00

27 KiB
Raw Blame History

Project Specifications for TypeScript NPM Module

  • name of package: tiny-pattern-ts

0. References

typescript-lib-starter-tiny => https://github.com/tmueller/typescript-lib-starter-tiny/

1. Development Environment

  • TypeScript: Use strictest practical rules. Inline in tsconfig.json (not via @tsconfig/strictest). The rule set is: strict, noImplicitAny, noImplicitThis, alwaysStrict, strictNullChecks, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, noImplicitReturns, noFallthroughCasesInSwitch, noUncheckedIndexedAccess, noImplicitOverride, noUnusedLocals, noUnusedParameters, forceConsistentCasingInFileNames, isolatedModules, verbatimModuleSyntax.

  • EditorConfig: Use .editorconfig from typescript-lib-starter-tiny.

  • oxfmt: Rust-based formatter, Prettier-compatible. Replaces Prettier. Config in .oxfmtrc.json (same shape as .prettierrc).

  • oxlint: Rust-based linter. Replaces ESLint. Config in .oxlintrc.json with typescript, unicorn, oxc, import plugins. Categories enabled as errors: correctness, suspicious, restriction. As warnings: perf, style. nursery is off. Type-aware rules are enabled via options.typeAware: true in .oxlintrc.json (no CLI flag needed); the linter then uses oxlint-tsgolint for rules that require type information.

  • oxlint-tsgolint: TypeScript-Go-backed type-aware linter for oxlint. Activated by options.typeAware: true in .oxlintrc.json. Native bindings installed as optionalDependencies per platform (same pattern as oxlint).

  • oxlint rules disabled by design (in .oxlintrc.json):

    • eslint/no-undefined — we use undefined as the no-match sentinel.
    • eslint/sort-keys — handler/case order is semantic, not alphabetical.
    • eslint/id-length — T, R, U, V are standard TS generics.
    • import/no-named-export — false positive on library entry re-exports.
    • Stylistic rules superseded by oxfmt (oxfmt is the canonical formatter; these rules either conflict with its output or duplicate features oxfmt already provides):
      • eslint/one-var — oxfmt uses comma-joined const declarations; oxlint wanted to split them.
      • import/group-exports — oxfmt keeps separate export statements as-is.
      • import/exports-last — statement ordering is up to oxfmt.
      • eslint/sort-imports — replaced by oxfmt's built-in sortImports (enabled in .oxfmtrc.json).
      • import/consistent-type-specifier-style — inline import { type X, Y } is intentional for grouping.
      • unicorn/prefer-export-from — conflicts with how the 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 expectTypeOf(...) calls), no-empty-file (we have a single import per test file in some cases), import/no-nodejs-modules (we use node:test/node:assert/node:fs intentionally), eslint/no-magic-numbers (literals in tests are fine).
  • Import sorting: built into oxfmt (no separate plugin).

  • cspell: Basic spelling configuration. Words dictionary in cspell.json covers tooling names (oxlint, oxfmt, oxc, nodenext, oxfmtrc, oxlintrc, EDITMSG, typescriptteam, gitmoji, dbaeumer, msvc) and a few library names (tslib, typefest, lefthook).

  • Lefthook: Pre-commit checks (run in parallel, lefthook v2 schema). Single source of truth for the underlying commands is in package.json#scripts; the lefthook config only describes what to run on which files. Hooks that should run on staged files (oxlint, oxfmt, cspell) set the LEFTHOOK_FILES env var to the staged-files list via a sh -c wrapper, and the npm script uses ${LEFTHOOK_FILES:-<default>} to default to the full project when invoked manually:

    • pre-commit.commands.oxlint (glob *.{ts,tsx,js,jsx,mjs,cjs}): sh -c 'LEFTHOOK_FILES="$0" npm run check:oxlint' {staged_files}
    • pre-commit.commands.oxfmt (glob *.{ts,tsx,js,jsx,mjs,cjs}): sh -c 'LEFTHOOK_FILES="$0" npm run check:oxfmt' {staged_files}
    • pre-commit.commands.cspell: sh -c 'LEFTHOOK_FILES="$0" npm run check:cspell' {staged_files}
    • pre-commit.commands.typecheck: npm run check:tsc (no file args needed) outdated is intentionally NOT a pre-commit check (it can flag upstream patch releases that aren't actionable locally); it runs in CI and via the explicit lefthook run outdated command.
  • Additional Dev Dependencies:

    • lefthook
    • check-outdated
    • c8 (coverage for node --test)
    • expect-type (type-level assertions in tests)
    • oxfmt, oxlint (with their native bindings as optionalDependencies so the correct binding is selected per platform automatically)
    • typescript (TS 7)
    • @types/node
    • tslib (available for future runtime helper imports)
    • type-fest (utility types)

2. Build & Test

  • Build Tool: TypeScript 7 (tsc) — no bundler, no Vite. The build is a plain tsc -p tsconfig.build.json invocation that emits ESM JavaScript and .d.ts declarations to dist/. ESM only, no CJS.
  • Testing: Node's built-in node --test with --strip-types (Node 22.6+, unflagged on Node 24/26). Test files are co-located with source as *.test.ts. No Vitest.
  • TypeScript Build Output: dist directory (set in tsconfig.build.json#outDir; tsconfig.json keeps outDir for editor tooling but excludes tests from emission via tsconfig.build.json#exclude).
  • Import extensions: Source uses .ts extensions in imports (e.g. from "./match.ts") so node --strip-types resolves them at test time. TypeScript's rewriteRelativeImportExtensions (in tsconfig.build.json) rewrites these to .js in the emitted dist/* output, so consumers see conventional ESM imports.
  • TypeScript Compiler Options Notes:
    • allowImportingTsExtensions: true is set in the root tsconfig.json (works because the root config has noEmit: true).
    • rewriteRelativeImportExtensions: true is set in tsconfig.build.json to rewrite .ts to .js on emit.
    • module: nodenext, moduleResolution: nodenext for ESM-first Node packages.
    • verbatimModuleSyntax: true enforces explicit import type.
    • target: es2024, lib: ["es2024"] (Node 26 supports all ES2024 features natively).
  • 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.
  • Node Version: >=26 (engines field). Pinned via .node-version for fnm/nvm/volta/mise auto-switching and CI (actions/setup-node@v4 with node-version-file: .node-version).

3. Project Structure & Files

  • .gitignore: Ignore dist, node_modules, coverage, and other common files.
  • .node-version: Single line containing the Node major version (currently 26). Used by version managers and CI.
  • .npmignore: Ignore node_modules/, coverage/, *.log, *.tsbuildinfo, src/, .vscode/, .editorconfig, .oxfmtrc.json, .oxlintrc.json, .node-version, cspell.json, lefthook.yml, commit-message-template. (Note: dist/ is included via package.json#files, not by absence from .npmignore.)
  • .oxfmtrc.json: oxfmt configuration (Prettier-shaped).
  • .oxlintrc.json: oxlint configuration.
  • .oxlintrc.json + .oxfmtrc.json replace the old .eslintrc.cjs and .prettierrc.
  • LICENSE: MIT.
  • README.md: Scaffolded.
  • commit-message-template: From typescript-lib-starter-tiny.
  • Target Environments: Node 26 LTS only (no browser target; this is a pure Node library, no DOM, no DOM lib in tsconfig).
  • No React, No CJS, ESM only.

4. Automation & Quality

  • Version Automation: Use standard npm version for versioning.
  • Unused Dependency Check: Use check-outdated (devDep, runs in CI and via the explicit lefthook run outdated command).
  • No commitlint, no conventional commits. Commits use gitmoji prefixes (e.g. :sparkles:, :wrench:, :bug:, :fire:, :white_check_mark:, :tada:) for at-a-glance categorization.

5. Scripts

PREFIX CONVENTION

Script names 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. Picking the right prefix documents the script's intended lifecycle:

  • check:* — read-only verification. Aggregated by npm run check (which runs all check:* scripts in order). Called from the lefthook pre-commit hook on staged files, and from the CI build job on the full project. Read-only; never modifies files.
  • fix:* — mutating counterpart of a check:* script. There is no npm run fix aggregator by design: fixes should be intentional, not batched. Run individually.
  • 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 (see §7). 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 is a signal the script does not belong in the standard pipeline.

SETUP

  • use:git-commit-message: Set up commit message template (if needed).

TEST

  • test: Run check:tsc then node --test --strip-types "src/**/*.test.ts". The glob is required because Node 26 does not auto-discover test files in a bare directory argument (node --test src/ is interpreted as a module path on Node 26+).
  • test:unit: Run unit tests with node --test --strip-types "src/**/*.test.ts" (no preceding typecheck).
  • test:ci: Run tests in CI mode with c8 coverage (text + lcov + html reporters), uploading coverage/ as an artifact.

BUILD

  • build: Build the project using tsc -p tsconfig.build.json (emits dist/*.js + dist/*.d.ts + sourcemaps, with .ts imports rewritten to .js).

CLEAN

  • clean: Remove dist/ via node -e "fs.rmSync('dist', {recursive:true, force:true})". (Replaces clean:build from the original spec — same effect, no rimraf dep needed.)

CHECK

  • check: Run all checks in order — check:oxlint, check:oxfmt, check:tsc, check:cspell, check:knip, check:outdated.
  • check:oxlint: oxlint ${LEFTHOOK_FILES:-src} — lints src/ by default; when invoked from the lefthook pre-commit hook with LEFTHOOK_FILES set to the staged-files list, lints only those files. This is the single source of truth for the oxlint command and is shared between the manual npm run check and the pre-commit hook. Type-aware rules are activated declaratively via options.typeAware: true in .oxlintrc.json (not via a CLI flag), so the script command stays clean. oxlint only understands JS/TS- family languages, so config files (JSON/YAML/Markdown) are intentionally outside its scope; they are checked only by oxfmt. Source-level oxlint-disable directives are used to silence type-aware false positives in the generic type machinery: 4× for typescript/no-unsafe-type-assertion and 1× for typescript/no-unnecessary-type-parameters in src/pattern.ts / src/match.ts (legitimate type machinery that needs restructuring, documented in the source); 1× file-level for typescript/no-floating-promises in src/index.test.ts (expectTypeOf(...) is a sync type-assertion library that oxlint-tsgolint misidentifies).
  • check:oxfmt: oxfmt --check ${LEFTHOOK_FILES:-.} — formats the whole project (.) by default, including JS/TS, JSON/JSONC, YAML, Markdown, MDX and other supported file types. From lefthook pre-commit, the LEFTHOOK_FILES env var scopes to the staged files matching *.{ts,tsx,js,jsx,mjs,cjs,json,jsonc,yaml,yml,md,mdx}. Built-in sortPackageJson: true keeps package.json keys alphabetized (replaces the former sort-package-json tool). Built-in import sorting (enabled via sortImports: true) replaces any external import-sort plugin.
  • check:tsc: tsc (noEmit is set in tsconfig.json).
  • check:cspell: cspell lint ${LEFTHOOK_FILES:-.} — walks the 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:knip: knip --include dependencies,exports,files — finds unused dependencies, value exports, and source files. The scoped --include list intentionally omits the types category, which produces systematic false positives for libraries whose exported types (e.g. Matcher, Pattern) are part of the public API and not consumed internally. The targeted scope keeps the signal high (real unused-dep detection) without config-file boilerplate.

FIX

  • fix:oxlint: oxlint --fix src.
  • fix:oxfmt: oxfmt ${LEFTHOOK_FILES:-.} — same scoping as check:oxfmt (whole project by default, staged files from lefthook). Writes changes in place. (There is no fix aggregator in the scripts; run the fix:* scripts individually.)

PUBLISH

  • publish:publint: publint — runs the pack-and-lint flow against the current project (uses npm pack to validate the actual publishable artifact against package.json's files, exports, main, etc.). Requires a fresh build to have populated dist/. Called by the CI publish job immediately before npm publish (see §6). Not part of npm run check and not run on commit: it validates publishing correctness, not source correctness.
  • publish:attw: attw . --pack --profile esm-only — validates the emitted .d.ts declarations against multiple TypeScript module-resolution scenarios. Uses --profile esm-only because this package is intentionally ESM-only (no CommonJS shim); CJS resolution scenarios are explicitly out of scope by design, not a bug. Called by the CI publish job alongside publish:publint and npm publish (see §6). Not part of npm run check and not run on commit: same rationale as publish:publint.

HOOKS

  • Lefthook runs the relevant check:* scripts on staged files in parallel for pre-commit. See lefthook.yml.

CHECK TIERS — what runs where and why

The check:* scripts are split across three execution contexts based on speed, scope (staged files vs whole project), and side effects (network, registry queries). The split is a deliberate design choice; the rule of thumb is:

  • pre-commit hook: fast, file-scoped, offline, deterministic. Catches what you just changed in under a second or two.
  • npm run check: full project audit. Slower, may query the network, runs everything that's not in pre-commit.
  • CI build job: authoritative. Runs npm run check and npm run test:ci on every push and PR. The ground truth — if CI passes, the codebase is clean.
Script pre-commit npm run check CI build CI publish
check:tsc ✅ ✅ ✅ —
check:oxlint ✅ ✅ ✅ —
check:oxfmt ✅ ✅ ✅ —
check:cspell ✅ ✅ ✅ —
check:knip — ✅ ✅ —
check:outdated — ✅ ✅ —
publish:publint — — — ✅
publish:attw — — — ✅

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.

  • check:knip is NOT in pre-commit because it scans the whole project (not staged files) and takes ~4s. Adding it would roughly triple pre-commit time. It's still fast enough to run locally before pushing, and CI catches it regardless.

  • check:outdated is NOT in pre-commit because (a) it queries the npm registry (~6.5s) which means network dependency in a hook, (b) the result is advisory, not a pass/fail correctness check, and (c) the same network call in CI or locally on demand gives the same answer. It belongs in scheduled or on-demand runs, not in a blocking hook.

  • publish:publint and publish:attw are NOT in check — they belong to the publish: prefix (see above) because they validate the publishable artifact (dist/) and require a fresh build. Running them on every commit would be wasteful (and slow, npm pack is involved). They run in the CI publish job immediately before npm publish.

What to do 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 (assuming branch protection is configured to require CI to pass).


6. Repository & CI/CD

  • Repository: Hosted on GitHub.
  • Build Pipeline: GitHub Actions (.github/workflows/ci.yml).
    • build job on push and pull_request to main and on workflow_dispatch. Steps: actions/checkout@v4, actions/setup-node@v4 (with node-version-file: .node-version, cache: npm), npm ci, npm run build, npm run check, npm run test:ci, then upload coverage/ as an artifact. Runs the full check chain (all 6 check:* scripts) and the test suite with coverage. This is the authoritative gate — if it passes, the codebase is clean. See §5 ("CHECK TIERS") for which checks run here vs. locally vs. pre-commit.
    • publish job: only on refs/tags/*, depends on build. Steps: actions/checkout@v4, actions/setup-node@v4 (with node-version-file: .node-version and registry-url), npm ci, npm run build, npm run publish:publint (see §5 for why this is publish: and not check:), npm run publish:attw, and npm publish --access public with NODE_AUTH_TOKEN from secrets. Runs the publish-tier checks that validate the publishable artifact before publishing to npm.

7. Versioning & Publishing

  • Version Update: Use npm version to bump version after merging to main and before publishing.
  • Publishing to npm: Only publish from CI on tagged commits.
  • Recommended Workflow:
    1. Develop and merge PRs to main
    2. Run all checks via CI
    3. Bump version with npm version <patch|minor|major>
    4. Push tag to GitHub
    5. CI builds and publishes to npm on tag

8. NPM Keywords

  • pattern-matching
  • pattern
  • match
  • algebraic-data-types
  • adt
  • typescript

The library is for pattern matching (not regex), similar to F#'s pattern matching, for TypeScript/ESM environments.

9. Code Coverage

  • Tool: c8 (V8-native coverage, no instrumentation step).
  • Configuration: c8 has no project config; the report shape is pinned in the test:ci script:
"test:ci": "c8 --reporter=text --reporter=lcov --reporter=html node --test --strip-types src/"
  • Reporters: text summary, HTML, and lcov (matching the original spec).
  • CI: coverage/ is uploaded as a workflow artifact via actions/upload-artifact@v4 (see .github/workflows/ci.yml).
  • Optional: Coverage thresholds can be added in c8 config when the library surface stabilizes.

10. Source Structure & Tree Shaking

  • Source Directory: All source code resides in src/ and is exported via src/index.ts.
  • Configuration:
    • sideEffects: false in package.json (set).
    • ESM-only exports; package.json#exports field maps . to {"types": "./dist/index.d.ts", "import": "./dist/index.js"}.
    • Avoid top-level side effects in modules.
    • Explicit re-exports in index.ts for best results.
  • Source structure (current):
    • src/index.ts — public barrel.
    • src/match.ts — match(value).with(...).exhaustive() / .otherwise(...) builder.
    • src/pattern.ts — P.literal, P.type, P.when, P.any, P.shape constructors and the Matcher<T> interface.
    • src/index.test.ts — runtime + type-level tests using node --test + expect-type.
  • The human will implement the source code.

11. Included Templates from typescript-lib-starter-tiny

.editorconfig

# Editor configuration, see http://editorconfig.org
root = true

[*]
charset = utf-8
indent_style = space
indent_size = 4
insert_final_newline = true
max_line_length = 80
trim_trailing_whitespace = true
quote_type = double

[*.md]
max_line_length = 0
trim_trailing_whitespace = false

[COMMIT_EDITMSG]
max_line_length = 0

commit-message-template

# If applied, this commit will... (Max 50 char)


# Explain why this change is being made (Max 72 Char) [WHAT and WHY vs HOW]


# Provide links or keys to any relevant tickets, articles or other resources
Resolves #...

# --- COMMIT END ---
# Remember to
#    Use the imperative mood in the subject line
#    Capitalize the subject line
#    Do not end the subject line with a period
#    Separate subject from body with a blank line
#    Use the body to explain what and why vs. how
#    Can use multiple lines with "-" for bullet points in body

README.md Structure (scaffolded)

  • Project title and description
  • Development
    • Build: npm run build
    • Test: npm run test, npm run test:ci
    • Checks: npm run check, npm run fix:oxfmt, npm run fix:oxlint
  • Tooling section listing TypeScript 7, node --test, c8, oxlint, oxfmt, cspell, lefthook
  • Requirements: Node.js >= 26
  • VSCode integration
    • Debugging
    • Running tests
    • oxc.oxc-vscode provides oxlint and oxfmt in-editor
  • Workflows
    • Version updates via npm version
    • Publishing via GitHub Actions on tagged commits
  • Contribution guidelines
    • Commit signing (GPG)
    • How to set up commit message template (npm run use:git-commit-message)
    • Reference to commit-message-template
    • Type-level tests use expect-type's expectTypeOf(...) inside node --test cases

12. Project Initialization & Commit Strategy

  • Start by initializing git with a main branch.
  • Initial commit: empty README + LICENSE.
  • Create a feature branch: feature/setup.
  • For each technology or tool added (and its configuration), create a separate commit:
    • Prepend each commit message with a matching gitmoji (e.g. :sparkles: for new features, :wrench: for config, :bug: for fixes, :fire: for removals, :white_check_mark: for tests, :tada: for initial commit).
    • Example commit messages used in this project:
      • :tada: Initial commit with empty README
      • :wrench: Track .vscode/settings.json for workspace settings
      • :construction_worker: Added GitHub Actions workflow for CI/CD
      • :test_tube: Added Vitest configuration with coverage (later removed)
      • :sparkles: Scaffolded src/index.ts entry point for library code
      • :wrench: Replace Vite/Vitest with TypeScript 7 and node --test
      • :wrench: Replace ESLint and Prettier with oxlint and oxfmt
      • :fire: Remove Vite scaffold leftovers
      • :sparkles: Add initial pattern-matching API
      • :white_check_mark: Add expect-type for type-level tests
      • :wrench: Declare Node 26 as the supported runtime
      • :bug: Use .ts extensions in imports for node --strip-types
      • :wrench: Remove Prettier from editor formatter config
  • Each commit should include only the relevant files and configuration for that technology/tool. This approach ensures a clean, understandable project history and makes it easy to review or revert specific setup steps.

13. Changelog Automation

  • Not currently configured. The intended workflow, when adopted, is a changesets-driven release process: feature PRs include a changeset, which gets consumed by a release workflow, producing a CHANGELOG.md and a version bump on merge to main.
  • The changelog should be updated as part of the release process.

14. Publishing Public

  • npm publishing is configured to be public by default.
  • publishConfig: { "access": "public" } is set in package.json.
  • The CI/CD pipeline publishes with --access public on tagged commits.

15. VSCode Integration

  • .vscode/settings.json uses oxc.oxc-vscode as the default formatter for [typescript], [javascript], [json], [jsonc], [markdown], [mdx], and [yaml] (oxfmt under the hood).
{
    "[typescript]": {
        "editor.defaultFormatter": "oxc.oxc-vscode",
        "editor.formatOnSave": true
    },
    "[javascript]": {
        "editor.defaultFormatter": "oxc.oxc-vscode",
        "editor.formatOnSave": true
    },
    "[json]": {
        "editor.defaultFormatter": "oxc.oxc-vscode",
        "editor.formatOnSave": true
    },
    "[jsonc]": {
        "editor.defaultFormatter": "oxc.oxc-vscode",
        "editor.formatOnSave": true
    },
    "[markdown]": {
        "editor.defaultFormatter": "oxc.oxc-vscode",
        "editor.formatOnSave": true
    },
    "[mdx]": {
        "editor.defaultFormatter": "oxc.oxc-vscode",
        "editor.formatOnSave": true
    },
    "[yaml]": {
        "editor.defaultFormatter": "oxc.oxc-vscode",
        "editor.formatOnSave": true
    },
    "editor.defaultFormatter": "oxc.oxc-vscode",
    "editor.formatOnSave": true
}
  • .vscode/extensions.json recommends:
    • oxc.oxc-vscode (oxlint + oxfmt, replaces eslint/prettier/vitest)
    • streetsidesoftware.code-spell-checker (cspell)
    • typescriptteam.native-preview (TypeScript 7 nightly support; replaces the older ms-vscode.vscode-typescript-next)
{
    "recommendations": [
        "oxc.oxc-vscode",
        "streetsidesoftware.code-spell-checker",
        "typescriptteam.native-preview"
    ]
}
  • .vscode/tasks.json is not currently provided; common tasks (build, test, lint, typecheck, format, spell, check:outdated) are run via the npm scripts in package.json from the integrated terminal.
  • VSCode uses TypeScript 7 via the typescriptteam.native-preview extension, with oxc.oxc-vscode for formatting and linting.