Darkmatter TypeScript toolchain
Org-wide contract for TS repos (platform, nixmac-web, and friends). The stack
is deliberate; substituting familiar defaults (npm, jest, wrangler) creates a
second convention and breaks CI.
Ops scripts — TypeScript, not bash
If the repo is TypeScript-only, utility and CI scripts are TypeScript
(scripts/*.ts, bun scripts/ci.ts). Do not add .sh files. See
ADR-0012.
Package management — Bun only
- Install:
bun install; CI uses bun install --frozen-lockfile. If CI fails
with "lockfile had changes, but lockfile is frozen", the lockfile is out of
sync with a package.json — fix by running bun install locally and
committing bun.lock; never delete the lockfile to "fix" it.
- Run scripts with
bun run <script>; execute tools with bun x <tool>.
- Monorepos use Turbo + workspaces (
@repo/* packages). Add shared code to a
workspace package, not a relative ../../ import across apps.
Verify in this order
bun run typecheck # tsgo -p tsconfig.json (NOT tsc)
bun run test # vitest
bun run lint # oxlint / biome per repo
Run the narrowest target that covers your change first (single test file,
single package), the repo-wide gates before handing off. All three must pass
on main; merge queues enforce required checks — prefer a PR over a direct
push even when your credentials technically bypass protection.
Test files are <file>.test.ts beside the source they cover; vitest picks
them up anywhere (include: ["**/*.test.ts"]). Do not add a test/ or
tests/ directory per package. The repo-root tests/ exists only for
end-to-end tests that spawn the real server or span packages.
Effect for meaningful I/O
Code with real I/O (network, DB, queues, retries) uses Effect — services,
Layers, typed errors, Config for configuration (named config files, with
env vars and flags as overrides: ADR-0014). Load the effect-typescript skill
for patterns. Plain async/await is fine for trivial glue; don't wrap a single
fetch in ceremony.
Deploys — Alchemy, never wrangler
- Infrastructure is code in
alchemy.run.ts; wrangler.toml is prohibited.
- Deploy:
STAGE=prod bun run deploy (per-app; check the app's README/run
config for required env). Secrets come from himitsu read <path> at deploy
time and SOPS for config — never hardcoded.
- Alchemy's config schema requires
SOPS_AGE_KEY explicitly in env; it does
NOT fall back to ~/.config/sops/age/keys.txt:
SOPS_AGE_KEY=$(grep '^AGE-SECRET-KEY-' ~/.config/sops/age/keys.txt | head -1).
- Vite apps:
client build must precede ssr build (hydration manifest).
Releases
Changesets are the release source of truth: user-facing changes ship with a
.changeset/*.md; version bumps and changelogs are generated, not hand-edited.
Style rules that surprise newcomers
- No tiny functions: don't extract a function whose whole body is one
expression/return — inline it unless the name is a durable contract with
multiple call sites (enforced as
ts-no-tiny-functions).
- Conventional Commits, subject ≤50 chars; body only when the why isn't
obvious.
- Fix problems at the source; no leftover shims, aliases, or re-exports after
a refactor.
1---2name: darkmatter-ts-toolchain3description: The darkmatter TypeScript toolchain contract - Bun (never npm/pnpm), tsgo typecheck, vitest, oxlint/biome, changesets, Effect for I/O, Alchemy deploys (wrangler.toml is prohibited), merge-queue mains, and the no-tiny-functions rule. Use when writing, fixing, building, or shipping TypeScript in any darkmatter repo (platform, nixmac-web, omp-chat, genesis): "fix TS errors", "make CI green", "add a package", "deploy this worker", lockfile complaints, or choosing libraries for I/O-heavy code. Do NOT use for generic TS style (coding-standards) or deep Effect patterns (effect-typescript) — this skill is the org-specific toolchain glue around those.4---56# Darkmatter TypeScript toolchain78Org-wide contract for TS repos (platform, nixmac-web, and friends). The stack9is deliberate; substituting familiar defaults (npm, jest, wrangler) creates a10second convention and breaks CI.1112## Ops scripts — TypeScript, not bash1314If the repo is TypeScript-only, utility and CI scripts are TypeScript15(`scripts/*.ts`, `bun scripts/ci.ts`). Do not add `.sh` files. See16[ADR-0012](../../docs/adr/0012-ops-scripts-in-typescript.md).1718## Package management — Bun only1920- Install: `bun install`; CI uses `bun install --frozen-lockfile`. If CI fails21 with "lockfile had changes, but lockfile is frozen", the lockfile is out of22 sync with a `package.json` — fix by running `bun install` locally and23 committing `bun.lock`; never delete the lockfile to "fix" it.24- Run scripts with `bun run <script>`; execute tools with `bun x <tool>`.25- Monorepos use Turbo + workspaces (`@repo/*` packages). Add shared code to a26 workspace package, not a relative `../../` import across apps.2728## Verify in this order2930```bash31bun run typecheck # tsgo -p tsconfig.json (NOT tsc)32bun run test # vitest33bun run lint # oxlint / biome per repo34```3536Run the narrowest target that covers your change first (single test file,37single package), the repo-wide gates before handing off. All three must pass38on main; merge queues enforce required checks — prefer a PR over a direct39push even when your credentials technically bypass protection.4041Test files are `<file>.test.ts` beside the source they cover; vitest picks42them up anywhere (`include: ["**/*.test.ts"]`). Do not add a `test/` or43`tests/` directory per package. The repo-root `tests/` exists only for44end-to-end tests that spawn the real server or span packages.4546## Effect for meaningful I/O4748Code with real I/O (network, DB, queues, retries) uses Effect — services,49Layers, typed errors, `Config` for configuration (named config files, with50env vars and flags as overrides: [ADR-0014](../../docs/adr/0014-named-config-files-over-flags-and-env.md)). Load the `effect-typescript` skill51for patterns. Plain async/await is fine for trivial glue; don't wrap a single52fetch in ceremony.5354## Deploys — Alchemy, never wrangler5556- Infrastructure is code in `alchemy.run.ts`; `wrangler.toml` is prohibited.57- Deploy: `STAGE=prod bun run deploy` (per-app; check the app's README/run58 config for required env). Secrets come from `himitsu read <path>` at deploy59 time and SOPS for config — never hardcoded.60- Alchemy's config schema requires `SOPS_AGE_KEY` explicitly in env; it does61 NOT fall back to `~/.config/sops/age/keys.txt`:62 `SOPS_AGE_KEY=$(grep '^AGE-SECRET-KEY-' ~/.config/sops/age/keys.txt | head -1)`.63- Vite apps: `client` build must precede `ssr` build (hydration manifest).6465## Releases6667Changesets are the release source of truth: user-facing changes ship with a68`.changeset/*.md`; version bumps and changelogs are generated, not hand-edited.6970## Style rules that surprise newcomers7172- **No tiny functions**: don't extract a function whose whole body is one73 expression/return — inline it unless the name is a durable contract with74 multiple call sites (enforced as `ts-no-tiny-functions`).75- Conventional Commits, subject ≤50 chars; body only when the why isn't76 obvious.77- Fix problems at the source; no leftover shims, aliases, or re-exports after78 a refactor.