# Darkmatter TS Toolchain

> 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.

- Skill: `darkmatter/darkmatter-ts-toolchain` (Agent Skill)
- Install (CLI): `npx skillmds@latest add darkmatter/darkmatter-ts-toolchain`
- Raw SKILL.md: https://api.skillmd.com/api/skills/darkmatter/darkmatter-ts-toolchain/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: darkmatter (https://skillmd.com/u/darkmatter)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/darkmatter/darkmatter-ts-toolchain

---


# 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](../../docs/adr/0012-ops-scripts-in-typescript.md).

## 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

```bash
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](../../docs/adr/0014-named-config-files-over-flags-and-env.md)). 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.

