Setup JS Stack
The JS/TS instantiation of the preferred stack. Intent answers (project
type, database, existing code) normally arrive from
the setup-tooling skill; invoked standalone, ask them first —
never scaffold before the interview, and never re-scaffold over
existing code. One extra detail question is this module's own:
forms-heavy? → TanStack Form wired to Zod.
The stack matrix
| Concern | Full-stack app | SPA | Backend/API |
|---|---|---|---|
| Framework | TanStack Start | Vite + React + TanStack Router | Hono |
| Data fetching | TanStack Query | TanStack Query | — |
| Client state | Zustand (only if needed) | Zustand (only if needed) | — |
| Forms | TanStack Form + Zod | TanStack Form + Zod | — |
| Validation | Zod (single source of truth, shared client/server) | Zod | Zod (@hono/zod-validator) |
| Styling | Tailwind + shadcn | Tailwind + shadcn | — |
| Motion | GSAP (when the UI animates) | GSAP (when the UI animates) | — |
| ORM/DB | Drizzle + Docker/Supabase Postgres | (via its API) | Drizzle + Docker/Supabase Postgres |
| Design | design-tooling |
design-tooling |
— |
| Tests | Vitest | Vitest | Vitest |
| Lint | lint-guardrails |
lint-guardrails |
lint-guardrails |
| CI | GitHub Actions | GitHub Actions | GitHub Actions |
Scaffolders (verify current flags against the tool's docs before running):
pnpm create @tanstack/start@latest # full-stack app
pnpm create vite@latest . --template react-ts # SPA (add @tanstack/react-router)
pnpm create hono@latest # backend
pnpm dlx shadcn@latest init # after Tailwind is in
pnpm baseline (every project, no exceptions)
pnpm is the package manager — not npm, not yarn, in the repo and in CI.
packageManagerfield pinned to the latest pnpm (checknpm view pnpm version), run via corepack.pnpm-workspace.yamlwith the supply-chain guard — always, plus any native-build approvals the install flags:
minimumReleaseAge: 1440 # a version must be ≥1 day old to install
verifyDepsBeforeRun: false # a linked worktree's symlinked node_modules fails pnpm's pre-run check and triggers a reinstall
With the check off, pnpm install runs by hand after a lockfile change.
pnpm 11 reads this from pnpm-workspace.yaml, not .npmrc.
- Scripts:
dev,build,test(vitest run),test:watch,test:coverage(vitest run --coverage, via@vitest/coverage-v8),lint(eslint --max-warnings 0),typecheck(tsc --noEmit), andprepare(lefthook install).
Database (when the interview says yes)
docker-compose.dev.ymlrunningsupabase/postgres(same engine as hosted Supabase) on a non-default host port (e.g. 54322) to avoid colliding with other local Postgres instances; healthcheck viapg_isready. Scripts:db:up/db:down.- Drizzle:
drizzle-orm+drizzle-kit+pg;db:push(local dev),db:generate(committed migrations),db:migrate(deploy). The same-commit migration rule ships viaagent-rules. - Vitest
globalSetupthat creates throwaway databases in the dev container and pushes the schema — tests never touch a real DB. - Secrets never in the repo:
.env.exampledocuments variables only.
Commit gate via lefthook (every project)
lefthook wires the lint/typecheck/test scripts into git so they gate the
branch before code lands (lefthook dev dep; the prepare script above
installs its hooks on pnpm install). Committed lefthook.yml:
- pre-commit —
eslint --max-warnings 0on staged*.{ts,tsx,js,jsx}only: fast, keeps commits snappy. - pre-push —
pnpm typecheck(tsc --noEmit), project-wide and slower, so it runs once per push rather than per commit. Addpnpm testhere too to gate tests locally, not only in CI. - post-checkout —
bash scripts/worktree-node-modules.sh: a linked worktree (git worktree add, or an agent'sisolation: "worktree") borrows the main checkout'snode_modulesby symlink instead of a per-tree install that costs minutes each; git runspost-checkoutaftergit worktree add, so every creator gets it:
#!/usr/bin/env bash
set -euo pipefail
common="$(git rev-parse --path-format=absolute --git-common-dir)"
main="$(cd "$common/.." && pwd)"
here="$(git rev-parse --show-toplevel)"
[ "$here" = "$main" ] && exit 0
[ -e "$here/node_modules" ] && exit 0
[ -d "$main/node_modules" ] || exit 0
ln -s "$main/node_modules" "$here/node_modules"
A new dependency in a linked worktree gets a real tree — remove the
symlink, then install (pnpm add through the link refuses with
ERR_PNPM_UNEXPECTED_VIRTUAL_STORE and touches neither side). Agent
worktrees inside the repo (for example, .claude/worktrees/ or
.agents/worktrees/) go in .gitignore and
the linter's ignores, or the main checkout's lint walks into every one.
It's a tripwire, not a sandbox — git commit --no-verify bypasses it — so
the same checks run in CI, the backstop that can't be skipped: a local
bypass still fails the PR.
CI via GitHub Actions (every project)
.github/workflows/ci.yml: lint, typecheck, and test jobs on push to
the working branches and PRs to main — pnpm/action-setup + setup-node
with cache: pnpm, pnpm install --frozen-lockfile, then pnpm lint /
pnpm typecheck / pnpm test. If the project has a DB, give the test job a
supabase/postgres service on the same port the compose file uses, with a
pg_isready healthcheck.
If a CLI is missing (pnpm, docker), prompt the user with its install command from skill.deps.json.