Choose dev entrypoints
Use this skill to assign development-environment responsibilities to the layer that naturally owns them. The goal is not to pick one universal command runner. The goal is to keep each layer small, local, cacheable where possible, and unsurprising for humans entering the repo.
Core model
Prefer declarations by the consumer of a capability over eager setup by an earlier layer.
nix develop
provides tools and native capabilities
package manager and package scripts
own package-local runtime commands
task graph runner
owns cross-package dependencies, inputs, outputs, and caching
just or similar command runner
owns human-friendly aliases and repo chores
scripts/
owns imperative glue (TypeScript in TS-only repos; see ADR-0012)
The useful question is "who consumes the result?" rather than "which tool is capable of running it?"
When to use
- The user asks whether
nix develop, shellHook, just, bun run, turbo run, or ./scripts/* should own a workflow.
- The user is designing a dev entrypoint such as
dev, build, test, codegen, install, fmt, or watch.
- A shell hook is doing too much work on environment entry.
- A repo has several ways to start a dev server and the user wants a principled split.
- The user wants lazy setup, cacheable generated outputs, or dependency-aware dev commands.
- The user is deciding where
bun install, code generation, local service startup, or package graph orchestration belongs.
When NOT to use
- The task is specifically reorganizing Nix flake outputs or module layout. Use
nix-flake-organization.
- The task is only writing or debugging an existing script, with no responsibility-boundary question. In a TypeScript-only repo, new ops scripts are TypeScript (ADR-0012), not bash.
- The task is only adding one package script in an already-established repo convention.
- The user asks for current docs or exact CLI syntax. Fetch the relevant docs first, then apply this skill if the ownership decision remains.
Decision workflow
- Inventory existing entrypoints before proposing a new one:
flake.nix, .envrc, justfile, package.json, turbo.json, scripts/, and app/package-local manifests.
- Identify the artifact or capability being produced: tools on
PATH, installed JS dependencies, generated source, build artifacts, a long-running dev server, local services, formatting, or validation.
- Assign ownership to the lowest layer that can express the dependency truthfully.
- Keep the outer entrypoint as a thin delegate when ergonomics matter.
- Validate the resulting path by asking: "Can someone enter the environment without paying for work they do not need?" and "Will the command that needs the thing declare that it needs it?"
Responsibility map
| Layer |
Owns |
Avoid |
nix develop |
System tools, native libraries, pinned CLIs, language runtimes, environment variables that are true for every workflow |
Running app-specific setup, installing JS dependencies, starting dev servers, guessing future command needs |
Nix shellHook |
Lightweight shell initialization, short messages, env normalization, optional checks that are cheap and idempotent |
Expensive installs, codegen, builds, long-running services, work that only some commands need |
.envrc / direnv |
Entering the Nix shell automatically, watching files that should reload the shell, local PATH additions |
Replacing the task graph or running heavy setup on every directory entry |
bun install |
Materializing JS dependency state from package.json and bun.lock |
Being hidden in shell entry when many shell users do not need JS deps |
bun run <script> |
Package-local commands and scripts that belong to one package or root JS workspace |
Cross-package orchestration that needs graph dependencies and caching |
turbo run <task> |
Monorepo task graph, task dependencies, cacheable inputs and outputs, workspace-wide build, test, lint, dev orchestration |
Arbitrary shell initialization, secrets loading, one-off imperative logic better expressed as a script |
just <recipe> |
Human-friendly command aliases, repo chores, composition across ecosystems, discoverable shortcuts |
Owning hidden build semantics that package scripts or Turbo need to cache |
scripts/* |
Complex imperative glue, traps, port cleanup, service readiness checks, multi-step procedures awkward in JSON. In a TypeScript-only repo this is TypeScript (scripts/*.ts), not bash (ADR-0012) |
Becoming a second untracked task graph; new .sh files in a TS-only repo |
Common patterns
Fast shell entry
Keep nix develop fast. It should provide bun, node, turbo, just, buf, watchexec, compilers, and other tools. It should not install dependencies or run codegen unless every shell entry genuinely needs that work.
If helpful, print a short hint:
echo "Run: turbo run dev"
echo "Run: turbo run deps"
Dependency materialization
For JS dependencies, prefer an explicit task over a shell hook:
{
"tasks": {
"deps": {
"inputs": ["bun.lock", "package.json", "apps/*/package.json", "packages/*/package.json"],
"outputs": ["node_modules/**", "apps/*/node_modules/**", "packages/*/node_modules/**"]
},
"dev": {
"dependsOn": ["deps"],
"cache": false,
"persistent": true
}
}
}
Use this when the repo accepts caching or restoring dependency directories. If that is too large or platform-sensitive, keep bun install --frozen-lockfile as an explicit package-manager step and have the dev command fail clearly when dependencies are missing.
Dev server entrypoints
Use package scripts for app-local dev servers:
{
"scripts": {
"dev": "next dev"
}
}
Use Turbo when the root command must coordinate packages:
{
"tasks": {
"dev": {
"dependsOn": ["^build"],
"cache": false,
"persistent": true
}
}
}
Use Just as the human-facing front door only when it delegates:
dev:
turbo run dev
Generated files
If generated files are pure build artifacts, model inputs and outputs in Nix, Turbo, or the package's build tool. If generation is a dev-time side effect, put it behind a task or watcher that the consumer depends on.
Avoid making shellHook decide that all future commands need generated files. Prefer:
dev -> codegen
test -> codegen
build -> codegen
over:
enter shell -> maybe codegen for everyone
Imperative glue
Use scripts/dev.ts when startup needs conditionals, traps, process cleanup, readiness checks, or multi-service orchestration that is hard to express in JSON. Call it from bun run, turbo, or just so the public entrypoint remains discoverable. In a TypeScript-only repo do not add scripts/dev.sh (ADR-0012).
{
"scripts": {
"dev": "bun scripts/dev.ts"
}
}
Review checklist
- Shell entry remains cheap and useful even when the user does not need JS modules.
- Commands that need generated files or dependencies declare that relationship.
- Cacheable tasks declare accurate inputs and outputs.
just recipes are thin aliases or repo chores, not hidden task semantics.
- Scripts contain real imperative complexity, not a parallel task graph.
- The public command a human should type is obvious from README, package scripts, or
just --list.
- There is one canonical path for each workflow, with compatibility aliases only when they reduce friction.
Tools
None. This is a pure prompt and review skill.
Reference
No separate reference files. Use the responsibility map, common patterns, and review checklist above.
1---2name: choose-dev-entrypoints3description: Choose the right dev-environment entrypoint and responsibility boundary across Nix, Just, Bun, Turborepo, package scripts, shellHook, and ./scripts. Triggers when the user asks which command should run dev/build/test/install/codegen, whether to put work in nix develop or shellHook, how to split responsibilities between just, bun, turbo, and scripts, or how to make dev environments lazy, cacheable, and understandable. Do NOT trigger for ordinary implementation work or Nix flake layout refactors. In a TypeScript-only repo, ops scripts are TypeScript (ADR-0012), not bash.4---56# Choose dev entrypoints78Use this skill to assign development-environment responsibilities to the layer that naturally owns them. The goal is not to pick one universal command runner. The goal is to keep each layer small, local, cacheable where possible, and unsurprising for humans entering the repo.910## Core model1112Prefer declarations by the consumer of a capability over eager setup by an earlier layer.1314```text15nix develop16 provides tools and native capabilities1718package manager and package scripts19 own package-local runtime commands2021task graph runner22 owns cross-package dependencies, inputs, outputs, and caching2324just or similar command runner25 owns human-friendly aliases and repo chores2627scripts/28 owns imperative glue (TypeScript in TS-only repos; see ADR-0012)29```3031The useful question is "who consumes the result?" rather than "which tool is capable of running it?"3233## When to use3435- The user asks whether `nix develop`, `shellHook`, `just`, `bun run`, `turbo run`, or `./scripts/*` should own a workflow.36- The user is designing a dev entrypoint such as `dev`, `build`, `test`, `codegen`, `install`, `fmt`, or `watch`.37- A shell hook is doing too much work on environment entry.38- A repo has several ways to start a dev server and the user wants a principled split.39- The user wants lazy setup, cacheable generated outputs, or dependency-aware dev commands.40- The user is deciding where `bun install`, code generation, local service startup, or package graph orchestration belongs.4142## When NOT to use4344- The task is specifically reorganizing Nix flake outputs or module layout. Use `nix-flake-organization`.45- The task is only writing or debugging an existing script, with no responsibility-boundary question. In a TypeScript-only repo, new ops scripts are TypeScript ([ADR-0012](../../docs/adr/0012-ops-scripts-in-typescript.md)), not bash.46- The task is only adding one package script in an already-established repo convention.47- The user asks for current docs or exact CLI syntax. Fetch the relevant docs first, then apply this skill if the ownership decision remains.4849## Decision workflow50511. Inventory existing entrypoints before proposing a new one: `flake.nix`, `.envrc`, `justfile`, `package.json`, `turbo.json`, `scripts/`, and app/package-local manifests.522. Identify the artifact or capability being produced: tools on `PATH`, installed JS dependencies, generated source, build artifacts, a long-running dev server, local services, formatting, or validation.533. Assign ownership to the lowest layer that can express the dependency truthfully.544. Keep the outer entrypoint as a thin delegate when ergonomics matter.555. Validate the resulting path by asking: "Can someone enter the environment without paying for work they do not need?" and "Will the command that needs the thing declare that it needs it?"5657## Responsibility map5859| Layer | Owns | Avoid |60| --- | --- | --- |61| `nix develop` | System tools, native libraries, pinned CLIs, language runtimes, environment variables that are true for every workflow | Running app-specific setup, installing JS dependencies, starting dev servers, guessing future command needs |62| Nix `shellHook` | Lightweight shell initialization, short messages, env normalization, optional checks that are cheap and idempotent | Expensive installs, codegen, builds, long-running services, work that only some commands need |63| `.envrc` / direnv | Entering the Nix shell automatically, watching files that should reload the shell, local PATH additions | Replacing the task graph or running heavy setup on every directory entry |64| `bun install` | Materializing JS dependency state from `package.json` and `bun.lock` | Being hidden in shell entry when many shell users do not need JS deps |65| `bun run <script>` | Package-local commands and scripts that belong to one package or root JS workspace | Cross-package orchestration that needs graph dependencies and caching |66| `turbo run <task>` | Monorepo task graph, task dependencies, cacheable inputs and outputs, workspace-wide `build`, `test`, `lint`, `dev` orchestration | Arbitrary shell initialization, secrets loading, one-off imperative logic better expressed as a script |67| `just <recipe>` | Human-friendly command aliases, repo chores, composition across ecosystems, discoverable shortcuts | Owning hidden build semantics that package scripts or Turbo need to cache |68| `scripts/*` | Complex imperative glue, traps, port cleanup, service readiness checks, multi-step procedures awkward in JSON. In a TypeScript-only repo this is TypeScript (`scripts/*.ts`), not bash ([ADR-0012](../../docs/adr/0012-ops-scripts-in-typescript.md)) | Becoming a second untracked task graph; new `.sh` files in a TS-only repo |6970## Common patterns7172### Fast shell entry7374Keep `nix develop` fast. It should provide `bun`, `node`, `turbo`, `just`, `buf`, `watchexec`, compilers, and other tools. It should not install dependencies or run codegen unless every shell entry genuinely needs that work.7576If helpful, print a short hint:7778```sh79echo "Run: turbo run dev"80echo "Run: turbo run deps"81```8283### Dependency materialization8485For JS dependencies, prefer an explicit task over a shell hook:8687```json88{89 "tasks": {90 "deps": {91 "inputs": ["bun.lock", "package.json", "apps/*/package.json", "packages/*/package.json"],92 "outputs": ["node_modules/**", "apps/*/node_modules/**", "packages/*/node_modules/**"]93 },94 "dev": {95 "dependsOn": ["deps"],96 "cache": false,97 "persistent": true98 }99 }100}101```102103Use this when the repo accepts caching or restoring dependency directories. If that is too large or platform-sensitive, keep `bun install --frozen-lockfile` as an explicit package-manager step and have the dev command fail clearly when dependencies are missing.104105### Dev server entrypoints106107Use package scripts for app-local dev servers:108109```json110{111 "scripts": {112 "dev": "next dev"113 }114}115```116117Use Turbo when the root command must coordinate packages:118119```json120{121 "tasks": {122 "dev": {123 "dependsOn": ["^build"],124 "cache": false,125 "persistent": true126 }127 }128}129```130131Use Just as the human-facing front door only when it delegates:132133```just134dev:135 turbo run dev136```137138### Generated files139140If generated files are pure build artifacts, model inputs and outputs in Nix, Turbo, or the package's build tool. If generation is a dev-time side effect, put it behind a task or watcher that the consumer depends on.141142Avoid making `shellHook` decide that all future commands need generated files. Prefer:143144```text145dev -> codegen146test -> codegen147build -> codegen148```149150over:151152```text153enter shell -> maybe codegen for everyone154```155156### Imperative glue157158Use `scripts/dev.ts` when startup needs conditionals, traps, process cleanup, readiness checks, or multi-service orchestration that is hard to express in JSON. Call it from `bun run`, `turbo`, or `just` so the public entrypoint remains discoverable. In a TypeScript-only repo do not add `scripts/dev.sh` ([ADR-0012](../../docs/adr/0012-ops-scripts-in-typescript.md)).159160```json161{162 "scripts": {163 "dev": "bun scripts/dev.ts"164 }165}166```167168## Review checklist169170- Shell entry remains cheap and useful even when the user does not need JS modules.171- Commands that need generated files or dependencies declare that relationship.172- Cacheable tasks declare accurate inputs and outputs.173- `just` recipes are thin aliases or repo chores, not hidden task semantics.174- Scripts contain real imperative complexity, not a parallel task graph.175- The public command a human should type is obvious from README, package scripts, or `just --list`.176- There is one canonical path for each workflow, with compatibility aliases only when they reduce friction.177178## Tools179180None. This is a pure prompt and review skill.181182## Reference183184No separate reference files. Use the responsibility map, common patterns, and review checklist above.