Shared TypeScript, oxlint, and Rust configuration for stella public packages.
This repo intentionally contains only portable tooling policy:
@stll/typescript-config: strict TypeScript config presets.@stll/oxlint-config: general upstream oxlint rules and the sharedstella-lowercaseandno-raw-colorsJS plugins.@stll/oxlint-plugin: portable static-safety rules for unsafe type assertions, incomplete union-keyed records, raw DOM HTML sinks, and route-query waterfalls.rust/: source-of-truth Rust formatting, lint, and Cargo profile templates.rust-lints/: Dylint libraries for stella-specific Rust rules.
Repo-specific stella rules stay in the consuming repo: domain authorization, i18n, generated native artifacts, benchmark exceptions, and package-specific ignores. Route-query conventions are shared here because they are the common TanStack Router + React Query contract.
@stll/oxlint-config/toolchain.json is the versioned source for shared tool
versions. Install @stll/oxlint-config as a dev dependency, inherit its pins,
and run bunx --no-install stll-toolchain-check from the repository root in CI.
The CLI inspects tracked configuration files, prints path:line: [rule] message,
and exits with status 1 on a mismatch. Tools absent from a repository do not
introduce requirements.
Schema version 1 contains these fields:
| Fields | Contract |
|---|---|
schemaVersion |
Supported policy schema version (1). |
bun |
Exact packageManager, bun-types, image and manager pin. |
oxlint, oxlint-tsgolint, @oxlint/plugins, @stll/oxlint-plugin, oxfmt, lefthook |
Exact dependency and catalog pins. Declared local workspace packages must resolve to the same version. |
typescript, typescriptInstallLayouts, typescript6Compatibility |
Exact compiler pins and complete direct or split install layouts; the optional compatibility alias is exact too. |
node, python, rust |
Runtime selectors and version-manager pins. Node selects the policy major (26.x); static minor and patch selectors within that major are allowed. A Python minor pin preserves existing patch selectors within that release series; a patch policy requires the exact patch. Rust selects one exact stable release across toolchain files, commands, workflow selectors and images. |
actions |
Approved action names mapped to full sha and version comment. |
dependabot |
Shared schedule, cooldown and groups. Package and action ignores derive from the owned pins. |
Configure oven-sh/setup-bun with bun-version-file: package.json and a
matching packageManager. Configure setup-node/setup-python with
node-version-file/python-version-file referencing tracked .node-version
or .nvmrc / .python-version files; direct version inputs are forbidden.
Node and Python image tags and mise/asdf entries inherit the same pins.
The Node policy is 26.x: selectors must select major 26. Bare major,
major wildcard, minor and stable patch selectors within that major are allowed;
open ranges, other majors, prereleases and floating aliases fail.
Release and publish workflows use exact patch version-file selectors for
reproducible builds; this repository pins .node-version to 26.10.0.
The series policy accepts that patch without weakening release validation.
Runtime images may retain a full SHA256 digest alongside an exact stable patch
tag: Node must be inside its policy series and match tracked root .node-version
when present; Python must be a patch in its policy series; Bun must equal its
policy version. Supported image variants remain allowed. Static image families
outside Node, Python, and Bun are outside this runtime check; unresolved image
names still fail.
Digest validation is declaration-only: the guard enforces the exact policy tag
and a full SHA256 digest, but does not verify that the digest belongs to the tag.
Dependabot refreshes digests per repository; a shared tag-to-digest map would
require a tooling release for every base-image digest bump across repositories,
which costs more than it buys.
GitHub JavaScript action host runtimes (runs.using) are platform-managed
and do not select the project toolchain Node version.
A published package can test the minimum Node major in its engines.node
support range in one declared workflow job. Add a scoped engineFloors entry
to tracked root stll-toolchain.json:
{
"engineFloors": [
{
"package": "packages/library",
"workflow": ".github/workflows/ci.yml",
"job": "node-floor"
}
]
}That job may use a literal exact stable patch in setup-node's node-version
whose major matches the range's minimum major and whose release satisfies
the range (for example, 20.10.0 for >=20.10.0). The approved action SHA, version comment and input validation
also apply. Other jobs and runtime files follow the shared Node series.
Missing packages or jobs, private packages, absent setup-node selectors and
floor mismatches fail configuration validation. optOuts is optional when
only engineFloors is declared.
The Rust stable pin is 1.99.0. Floating channels and off-pin Rust references
fail the shared check. Cargo rust-version declarations remain minimum support
versions and may be lower than the shared pin. Rust setup actions
(dtolnay/rust-toolchain and actions-rust-lang/setup-rust-toolchain) require
explicit exact toolchain inputs; every release in a comma-separated setup list
must satisfy the pin or its declared exception.
Rust exceptions use one closed declaration in root stll-toolchain.json:
{
"rustExceptions": [
{
"pin": "1.97.1",
"reason": "Awaiting upstream support for the shared compiler",
"tracking": "https://github.com/owner/repository/issues/123",
"expires": "2026-11-11"
}
]
}Replace the example with a verified compatibility constraint and its tracking
issue. Each exception permits only that exact release and must expire in the
future. Floating channels, duplicate pins, extra fields and rust-version
opt-outs fail validation. The same exception applies to toolchain files,
workflow Rust inputs and environments, Cargo/rustup commands and Rust images.
Hawk 0.1.11 is the current temporary Rust exception: its driver fails to build with 1.99.0 due to compiler-private API changes (E0061 and E0308). It uses 1.97.1 until tracking issue #99 is resolved, with the exception expiring on 2026-11-11. The stable source checker and all ordinary Rust work use the shared 1.99.0 pin. Consumer repositories that run Hawk need the same exact, tracked exception declaration.
engines.node and requires-python are support ranges: they must include the
shared runtime. The Node support range must include every stable release in
the policy major, so >=26, ^26 and ^24 || ^26 pass; ^24, <26,
>=27 and ranges that omit part of major 26 fail. A minor Python pin requires support for every patch in that
series. Python comparisons, compatible releases, wildcard exclusions,
and comma intersections are supported for final releases; unsupported syntax
produces a diagnostic.
Direct compiler invocations in scripts.typecheck must use the selected
layout command. Classification decodes shell tokens to find compiler calls;
acceptance requires the declared command text after leading environment
prefixes are removed, including its option spelling and quoting. Delegated wrappers and workspace runners are checked by the
consumer typecheck parity contract rather than inferred from manifests.
Listed shared actions require their approved SHA and matching # vX comment.
CI also checks the shared release policy's runtime selector action set against
toolchain.json, deriving its immutable checkout ref from the existing
release-policy workflow reference.
Other remote actions and reusable workflows require a full SHA. Local actions
are repository-owned. The checker resolves YAML aliases and TOML tool tables.
The package guide is the canonical contract for
checkout provenance, runtime version files and scoped dynamicSelectors.
It documents current-source and delegated refs, reusable-workflow caller context,
sparse-checkout requirements, declaration locators and fail-closed validation.
Use scoped declarations for reviewed unresolved selectors rather than broader
rule opt-outs; literal pins and local-action provenance remain enforced.
The named rules are bun-pins, package-pins, typescript-layout,
node-engine, node-version, python-version, rust-version,
runtime-manager, runtime-docker, runtime-workflow, action-pins,
dependabot-policy, and cloud-setup-drift. Non-Rust rule exceptions require a
tracked root stll-toolchain.json listing the rule and its reason:
{
"optOuts": [{ "rule": "bun-pins", "reason": "This repository uses npm." }]
}Workspace TypeScript toolchains combine the root and declared members' devDependencies. An included member may declare the exact TypeScript 6 compatibility API across dependencies, devDependencies, and peerDependencies when that shared install uses the complete split layout. Ambiguous or partial layouts do not grant this allowance. Peer ranges declare support: they must include the policy compiler release and the compatibility release when the selected layout uses it. Ranges do not contribute to the installed compiler inventory.
JavaScript update roots come from the repository root, declared workspace membership,
and directories with their own bun.lock, package-lock.json, yarn.lock, or
pnpm-lock.yaml. Other package manifests do not create update roots; directory names
have no special meaning. Workspace members share their owner's update entry.
rust-version, node-engine and cloud-setup-drift are mandatory: opt-outs cannot bypass the
shared Node support range or generated cloud setup requirements.
Unknown rules, empty reasons, duplicate rules, and malformed configurations fail. Opt-outs apply repository-wide to the named rule, so keep them narrow.
.github/dependabot.yml is the shared reference. It declares each detected
package ecosystem and update root (Bun roots use bun, npm/pnpm/Yarn use
npm, and uv projects use uv), uses the policy schedule/groups and a
five-day cooldown, and ignores tooling-owned npm and action pins. Workspace
members share their update root; independent manifests need their own entry.
pnpm-workspace.yaml owns pnpm catalogs and membership. Omitted packages
includes only the root, matching the current pnpm contract; declare member
patterns explicitly to keep membership stable across pnpm versions.
The generator derives the reference from the same policy and ecosystem
registry as the guard: run bun scripts/write-dependabot-policy.ts after
policy changes. Consumers can copy its applicable entries and directory paths;
the CLI rejects missing coverage and policy drift. Owned package pins follow
release metadata. The changeset:version command first includes the policy
package in the release plan whenever an owned workspace pin changes, then
synchronizes pins through bun scripts/check-lockfile-workspace-versions.ts --write, which also refreshes cached workspace versions without regenerating
the dependency graph.
Install the shared TypeScript and oxlint packages:
bun add -d @stll/typescript-config @stll/oxlint-config @stll/oxlint-plugin @oxlint/plugins oxlint oxlint-tsgolint typescriptThe shared defaults require TypeScript 7.0.2 or newer, oxlint 1.87.0 or
newer, and oxlint-tsgolint 7.0.2003 or newer. Pin the current versions from
@stll/oxlint-config/toolchain.json; do not use the deprecated
oxlint-tsgolint 0.x line.
toolchain.json defines two supported TypeScript install layouts:
directinstalls TypeScript 7 astypescriptfor compiler-API consumers and declaration generation. Prefer this when every framework supports TypeScript 7.split-compatibilitykeeps TypeScript 6 astypescriptfor incompatible compiler-API consumers, then installs TypeScript 7 as@typescript/nativefor tools that explicitly require that compiler.
TypeScript 6 is compatibility-only. Use the split layout only for a command
that loads one of the peer blockers listed in toolchain.json, or for code
that imports the TypeScript compiler API. Remove the compatibility install when
the blocker accepts TypeScript 7. The shared config's peer range accepts both
layouts. Both layouts use bun check with Bun 1.4.3 for typechecking; retain
TypeScript wherever a tool needs its compiler API or declaration generation.
stll-typecheck-parity runs from a consumer repository root. It uses that repo's
TypeScript compiler (including the declared split layout), compares repository
diagnostics by file, line, and code, then checks 31 shipped fixture classes under
the consumer tsconfig flags. TypeScript diagnostics determine each class's
activation; inactive classes are identified in the table, and zero active classes
fail the check. The command fails on diagnostic differences or lost seeded coverage and reports wall
time and peak RSS for TypeScript and Bun. It requires Bun and /usr/bin/time
on Linux or macOS, and runs with Node or Bun. Tooling invokes the same bin through
bun run check:typecheck-parity. The bin invokes
bun check with its dedicated project/build flags. Consumer repositories must
not define a check script: it shadows Bun's checker, so the parity bin fails
with a clear error until that script is renamed. Standalone bun --check
checks the current project but does not forward these checker options.
Without flags, the command checks the root tsconfig.json and uses build mode
for project references. To check selected configs, repeat --project, for example
stll-typecheck-parity --project packages/api/tsconfig.json --project packages/web/tsconfig.json.
Selected configs run independently without discovering or building their references;
reference metadata is retained for ordinary project checks. No root tsconfig is
required in this mode, and the compiler always comes from the repository-root
installation. Missing, unreadable, and zero-input selected configs fail the check.
--help prints usage and exits successfully. In default mode, referenced
configs are discovered recursively and grouped by effective compiler options.
Each group copies its representative config's effective options, removes emit-only
and build settings, and sets noEmit for the seeded projects. Type-checking options
and consumer type/module resolution are preserved. Each group must
activate at least one seeded class. The installed compiler and Bun runtime must match their selected toolchain policy versions.
Both compilers check the same temporary project graph. TypeScript declarations
and build metadata stay in that temporary tree; consumer files remain untouched.
The parity bin does not validate the consumer project graph; the repository's own typecheck does.
Source errors normalize TypeScript's diagnostic exit 2 (including declaration-only inputs) to Bun's
diagnostic exit 1; the report retains both raw exit codes. Other exit statuses
compare exactly. Comparisons retain source diagnostic locations, normalize bundled standard-library
paths, and distinguish locationless messages. Fixtures preserve the consumer package
module context and reject configuration errors. Tagged agent diagnostics are normalized
into the same comparison set.
Use the library TypeScript preset:
{
"extends": "@stll/typescript-config/library.json",
"include": ["src"]
}Use the oxlint preset with local exceptions:
import { library } from "@stll/oxlint-config";
export default library({
ignorePatterns: ["dist/", "npm/", "*.node"],
overrides: [
{
files: ["scripts/**"],
rules: {
"no-console": "off",
},
},
],
});Add the portable safety rules to an existing Oxlint config:
import { defineConfig } from "oxlint";
import {
portableSafetyPluginSpecifiers,
portableSafetyRules,
} from "@stll/oxlint-plugin";
export default defineConfig({
jsPlugins: [...portableSafetyPluginSpecifiers],
rules: { ...portableSafetyRules },
});For TanStack Router + React Query applications, add the route-query guards to prevent cache misses and render-fetch waterfalls during navigation:
import { defineConfig } from "oxlint";
import {
routeQueryPluginSpecifiers,
routeQueryRules,
} from "@stll/oxlint-plugin";
export default defineConfig({
jsPlugins: routeQueryPluginSpecifiers,
rules: routeQueryRules,
});require-loader-prefetch requires every statically attributable
useSuspenseQuery in a route to be referenced by that route's loader.
no-raw-route-query-client requires route freshness helpers instead of raw
TanStack Query client calls and keeps pending components synchronous.
When using no-unsafe-inner-html, disable the blanket react/no-danger rule;
the portable rule permits static and provably sanitized HTML while rejecting
untrusted values.
Oxlint 1.80 replaced react/react-compiler with category-specific React
Compiler rules. Enable the actionable categories explicitly with
library({ react: { files: ["src/**/*.{ts,tsx}"] } }); select only React-owned
paths, including .ts hooks. The base library() preset is framework-neutral;
react/invariant and react/todo remain off because they report compiler
internals rather than source defects. Consumers can suppress one precise
category without hiding unrelated compiler diagnostics. A repo adopting these
rules against an existing findings backlog should carve out temporary
overrides entries per React-owned legacy path and fix forward. Each consumer
React override must include the React plugin: oxlint resolves its rules against
its own plugin list or the framework-neutral base list, not an earlier override.
import { library, libraryPlugins } from "@stll/oxlint-config";
export default library({
react: { files: ["src/**/*.{ts,tsx}"] },
overrides: [
{
files: ["src/legacy/**/*.{ts,tsx}"],
plugins: [...libraryPlugins, "react"],
rules: { "react/hooks": "off" },
},
],
});Include any additional consumer plugins in that override's plugins list too.
Use the helper directly as the root config when possible. That keeps root-level options, shared JS plugins, shared ignores, and local exceptions in one merged object.
If a repo needs the extends style used by other oxlint config packages, keep
repo-specific ignores in the root config:
import { defineConfig } from "oxlint";
import stella from "@stll/oxlint-config";
export default defineConfig({
extends: [stella],
ignorePatterns: ["dist/", "npm/", "*.node"],
});CommonJS repos can use require in oxlint.config.ts:
const { library } = require("@stll/oxlint-config");
module.exports = library();Recommended scripts:
{
"scripts": {
"typecheck": "bun check",
"lint": "bun --bun oxlint -c oxlint.config.ts --report-unused-disable-directives-severity=error --deny-warnings --type-aware .",
"lint:fix": "bun --bun oxlint -c oxlint.config.ts --type-aware --fix ."
}
}Use the Rust templates by copying them into a Rust repository:
cp rust/rustfmt.toml /path/to/repo/rustfmt.toml
cp rust/clippy.toml /path/to/repo/clippy.toml
cp rust/cargo-config.toml /path/to/repo/.cargo/config.tomlThen copy either rust/cargo-root.toml or rust/cargo-workspace.toml into the
repository's root Cargo.toml. Cargo does not support extending these settings
from another package, so the templates are kept here as the canonical source and
synced into consumers.
Install the stable source lint using an exact tooling commit, then run it after
Clippy. Replace <sha> with the commit containing the adopted checker:
cargo install --git https://github.com/stella/tooling --rev <sha> --locked stella_source_lint
cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
cargo ci-stella-lintAdd a Changeset for every pull request that changes a published package. Choose
the affected package and semantic bump with bun run changeset; use an empty
Changeset when a release is intentionally unnecessary.
After the change lands on main, the shared release workflow maintains one
Version Packages pull request. Merging that pull request updates package
versions and changelogs, then .github/workflows/publish.yml builds the
tarballs and delegates the hardened npm and GitHub release transaction to the
versioned stella/.github contract. Package tags use the immutable
<name>@<version> form.
Declare cloud in tracked root stll-toolchain.json, then run
bunx --no-install stll-cloud-setup to generate .agents/cloud-setup.sh.
The declaration requires install exactly bun install --frozen-lockfile.
services is any subset of postgres and valkey; omit it or use [] for
runtimes and dependencies only. A canonical repository-relative envFile, such
as .env.cloud, is required with services and rejected without services.
Runtime-only scripts emit no environment file code. A tracked root
.node-version must select an exact stable patch within the shared Node series.
The mandatory cloud-setup-drift rule compares the committed script with the
same generator and rejects missing, changed, or undeclared scripts.
The script has explicit install and start commands. It targets Ubuntu 24.04
with root access or passwordless sudo; these are capability requirements,
not assumptions about every cloud image. See the
package guide for lifecycle
wiring, service isolation, environment-file ownership, and host documentation.
Run stll-publish-contract in PR CI. Every published root or declared workspace
package commits publish-contract.json, recording engines, peer support ranges,
resolved JavaScript targets, and entry points. stll-publish-contract --write
records an explicit contract decision as a reviewable diff; it also validates
consumer support and cannot accept development-only requirements.
The consumer policy pins Node 22.23.3, npm 12.2.0, pnpm 12.9.1, and TypeScript
6.0.3 independently of the development toolchain. Published engines and TypeScript
peers must support the consumer versions; ranges may include newer versions.
An absent engines.node is unrestricted, matching package-manager semantics; a
declared range must contain the consumer Node version. Both guards share this rule.
Bun runtime/compiler requirements are rejected. JSON and declaration-only packages
record {"type":"types-only"} explicitly.
Build adapters use the installed configuration loaders and bind their behavior to
tsdown 0.22.9, Vite 8.1.5, or @nuxt/module-builder 1.0.3 (unbuild 3.6.1 and
mkdist 2.4.1). They record final transform targets, including separate Nuxt module
and runtime entries. No syntax lowering records esnext.
Only exact single build invocations (tsdown, vite build, or
nuxt-module-build build) are supported. Shell composition, environment prefixes,
launchers, directory changes, workspace filters, CLI overrides, and pre/post build
lifecycle scripts fail.
Vite accepts its version-bound default plugin pipeline and the default
@vitejs/plugin-vue 6.0.8 factory; custom Vue compiler, template, script, and feature
options are unsupported. Nuxt accepts the version-bound builder and owned target
hook. Unapproved plugins, output transforms, and execution-order changes fail. The static guard validates the supported configuration, without parsing the
emitted JavaScript syntax. Add a reviewed adapter when adopting another build tool.
Nuxt modules can set both emitted targets without replacing the builder's entries:
import { nuxtModuleTarget } from "@stll/oxlint-config/build-target";
export default { hooks: { "build:before": nuxtModuleTarget("es2022") } };The Nuxt adapter accepts this owned hook from the same package version as the CLI; it captures the normalized configuration before cleanup or output writes. The built publish-contract CLI is also checked against a TypeScript configuration and relative TypeScript import on the package's minimum supported Node release.
The static guard follows the packer declared in the tracked release workflow.
With pnpm, it models publishConfig overrides for exports, main, module,
types, typings, bin, and typesVersions. npm does not apply these overrides,
so differing values fail; move the published entry into the source manifest.
access and registry are allowed publication metadata. Other overrides,
including engines, are rejected. Real npm and pnpm pack tests compare the
modeled contract with each tarball manifest.
Packed-artifact checks run nightly, while the static contract check runs per PR.
The reusable consumer job declares its exact scope in stll-toolchain.json:
{
"consumerChecks": [
{
"workflow": ".github/workflows/consumer-compat.yml",
"job": "consumer",
"packages": ["packages/library"],
"toolingVersion": "<installed tooling version>",
"fixturePath": "tests/consumer"
}
]
}The declared workflow requires valid five-field cron with bounded numeric values,
month/day names, lists, ranges, and positive steps; manual dispatch is optional.
Day of week is 0–6 (SUN–SAT), matching the GitHub Actions schedule contract; numeric 7 is unsupported. The combined schedules must cover every weekday with unrestricted month and date fields, so weekly or seasonal schedules cannot satisfy the daily contract. Public packages require a semver-valid version. Published workspace packages under a vendor directory are unsupported; the contract CLI reports them rather than omitting them. bundleDependencies and bundledDependencies are unsupported because artifact staging does not install a bundled dependency tree; remove these fields before declaring consumer checks.
The reusable consumer job must be stand-alone and unconditional (no needs, if, or strategy), so a job condition
cannot skip its scheduled execution.
That job calls package-consumer-compat.yml at the approved shared policy SHA with
packages as the same JSON array and consumer-node equal to the consumer policy
pin. Required tooling-version and fixture-path inputs equal the declared exact
toolingVersion and repository-relative fixturePath; its consumer-compat.json must
be tracked, parse with the runner’s fixture schema, and select exactly the declared package set. Replace <installed tooling version> with the exact installed
@stll/oxlint-config release that provides the consumer runner; the guard binds the
declaration to that package version. Every named package and its transitive workspace closure must be tracked,
published, and have valid manifests. Each selected package must support that Node version.
Unknown jobs, stale entries, undeclared consumer calls, and inconsistent inputs fail.
This declaration applies to consumer checks; engineFloors retains its separate
minimum-supported-major contract.
stll-consumer-compat --packages '["packages/library"]' --consumer-node 22.23.3 --fixture-path tests/consumer tests final tarballs in isolated projects with both
npm and pnpm. The repository builds once with its development toolchain first.
The runner reads the exact packer version and pack flags from a single tracked
release workflow and packs directly with that manager. Missing, ambiguous, or
unsupported pack configuration fails. Both consumers install the same release
artifacts; the runner does not rewrite workspace or catalog protocols through a
different manager. Fixture installs disable scripts, reject reserved runtime/manager bins throughout nested lifecycle paths, then approve only directly declared dependencies at their resolved lock identities. npm rebuilds those identities; pnpm runs its built-in pending rebuild. Both use the pinned consumer runtime before typecheck, build and smoke. Undeclared transitive build scripts receive no approval. Catalog entries that resolve to a local workspace
package are unsupported: use workspace: for that dependency. Registry catalog declarations are staged intact; the release packer must resolve
publication protocols. pnpm workspace owners require an adjacent tracked
package.json; manifestless owners are unsupported. Public package names must be
canonical lowercase, URL-safe npm names (at most 214 characters), with no traversal components. Workspace alias
specifiers are unsupported: use the package name as the dependency key. The runner verifies
the official Node archive checksum and exact runtime/package-manager versions,
uses the oldest published React satisfying the package peer range and, when ReactDOM is required, its compatible renderer peer range, typechecks with
the consumer TypeScript, and runs each fixture's build and usage smoke. Both
publication packers and fixture commands use the provisioned consumer Node with
isolated package-manager settings. Consumer command PATH contains only pinned wrappers, fixture binaries, and /usr/bin:/bin; it never inherits development tool directories. Installed binaries named node, npm, npx, or
pnpm are rejected before fixture build and smoke commands.
The fixture directory contains consumer-compat.json:
{
"packages": [
{
"package": "packages/library",
"fixture": "library",
"kind": "node",
"build": ["npm", "run", "build"],
"smoke": ["npm", "run", "smoke"]
}
]
}The selected package set must exactly match the fixture declaration set.
Each fixture is a standalone project with its own source, package manifest, and
TypeScript configuration. The package peers determine its fixture kind: a React
peer requires react and a render smoke; other packages require node and an
import with a representative call. A mismatching declaration fails. Dependency bindings, runtime tools,
and caches belong to the runner. A nightly failure must open or update one issue
in the consuming repository and use its existing failure notification.
stll-toolchain-changed --since <git-ref> compares committed snapshots at the
reference and HEAD. Fetch the reference and its history before running it.
It prints one JSON object with changed, tools, and status; the detector
exits successfully even when history is unreadable, reporting changed: true
and status: "unreadable". Use changed to force the full CI depth on both
pull requests and merge-queue runs. An unreadable snapshot never skips checks.
The comparison includes Bun declarations, Node selectors and support ranges, resolved TypeScript compiler versions (including workspace and compatibility aliases), oxlint, oxlint-tsgolint, oxfmt, and shared tooling pins. Lockfile resolutions determine installed package versions; manifest ranges alone do not. Resolution locations and importer bindings are retained: swapping compiler versions between workspaces cannot look unchanged. Compiler specifier changes run parity even when the resolved release version is unchanged. Unknown lock shapes and ambiguous resolutions report an unreadable snapshot. Supported text locks include Bun JSONC (schema 0/1), pnpm YAML (5.4/6/9), npm package-lock/ shrinkwrap (1/2/3), and bounded Yarn Classic/Berry shapes; a binary-only Bun lock cannot be compared without executing Bun. Workflow/action and Docker definition edits conservatively force full CI. Unrelated application dependency changes do not force compiler parity. Setup-action runtime selectors and their tracked version-file contents must identify an exact stable release. Floating selectors, ranges, expressions, empty values, or omitted runtime versions report an unreadable snapshot and run parity even when their committed text is unchanged.
On pull requests, run the repository's ordinary Bun typecheck and the probe, then select parity using the same detector:
stll-typecheck-probe --project packages/api/tsconfig.json --seed-dir packages/api/src -- bun run typecheck
stll-typecheck-parity --changed-since origin/main --project packages/api/tsconfig.jsonThe probe writes an exclusive temporary TypeScript file in the declared
project, requires the command to fail with TS2322 attributed to that file, removes
it, and requires the clean command to pass. Use --seed-dir to select a repository-relative directory inside the project;
the default is the config directory. The config must include the seed directory;
excluded seeds fail instead of producing a vacuous pass. Commands are argv,
not shell expressions. Cleanup also runs on failure and handled termination.
Repeat the probe for independently checked projects as needed.
Conditional parity runs when Bun, TypeScript, or shared declarations change;
otherwise it prints parity skipped: toolchain unchanged since .... An unreadable
reference runs full parity. Nightly jobs omit --changed-since to run full parity.
Node and lint-tool changes still force full CI through the shared detector.
Workflow, composite-action, Docker, mise, and tool-version declarations are conservatively fingerprinted for compiler parity, including referenced tracked setup-action version files (Node, Bun, Python, Go, Java and other runtimes). Rust toolchain-file selectors and conventional runtime files are included; cache-dependency-path is not a runtime selector. Compose and Kubernetes image definitions are conservatively fingerprinted alongside Dockerfiles. Catalog and unresolved declaration changes also force parity; an unclassifiable reference runs full parity. This may run parity for unrelated edits to these declaration files. Active installed compiler comparisons still use the owning lockfile resolutions.
