ADR-0014: setup:* Namespace as First-Use Setup Standard
Replaces dev:setup and dev:install with a setup:* namespace that models the distinct lifecycle of first-use setup operations independently from the daily developer workflow.
Status: Proposed
Date: 2026-04-24
Deciders: Engineering Leadership
Supersedes Decision 4 of: ADR-0013: Taskfile Contract v2
Context
ADR-0013 Decision 4 prescribes dev:setup + dev:install as the clone-to-working entry points. The rationale was:
"
dev:installstays useful standalone for 'just update my deps after a pull.'"
This works for repos where pnpm install / uv sync is unconditionally safe to run. It fails for repos with private registry dependencies, where installing without a valid auth token produces an error. In those repos, dev:install is not independently useful — it requires a preceding authentication step.
More broadly, the placement of setup tasks in dev:* creates a semantic mismatch:
| Namespace intent | Actual use |
|---|---|
dev:* = daily development lifecycle | dev:setup and dev:install run once per clone, rarely after |
Daily: dev:run, dev:watch, dev:migrate | First-use: env copy, registry auth, dep installation |
Setup is a sequential, phased, rarely-run operation. Daily development commands are stateless and independently invocable. Grouping them in the same namespace obscures both.
transcript-service introduced setup:* organically to solve this. Its Taskfile shows the concrete problem:
setup:registry → must run before setup:install
setup:install → reads NPM_TOKEN from .env written by setup:registry
dev:install cannot model this dependency. dev:setup calling both internally hides the phases from developers who need to re-run just the registry auth step (e.g., after token expiry).
Why not keep dev:setup + dev:install?
The argument for retaining them is familiarity and backward compatibility. But:
- Familiarity —
task setupis equally discoverable viatask --list. No learning cost. - Backward compatibility — Simple repos not using private registries don't need to migrate. The pattern is additive for them; prescriptive for repos with private deps.
The argument against dev:* placement is architectural: dev:* should describe operations that happen during development, not operations that enable development to begin. The distinction is meaningful to both humans and AI agents reading the Taskfile.
Decision
setup:* replaces dev:setup and dev:install as the prescribed pattern for first-use setup.
Standard tasks
| Task | Level | Purpose |
|---|---|---|
setup | MUST | Full setup orchestrator: prepare → registry → install. Idempotent. The canonical entry point. |
setup:prepare | SHOULD | Copy .env.example → .env if missing. |
setup:registry | SHOULD | Authenticate to private registry and persist token to .env. |
setup:install | MUST | Install project dependencies. Reads credentials from .env. |
setup:clean | SHOULD | Remove installed dependencies (node_modules/, .venv/). Does not touch dist/ — that is build:clean's responsibility. |
The setup task (no suffix)
setup is the flat entry point developers run after cloning. It orchestrates the sub-tasks in sequence:
setup:
desc: "First-use setup: configure env, authenticate registry, install deps"
cmds:
- task: setup:prepare
- task: setup:registry
- task: setup:install
- echo ""
- echo "Setup complete. Next steps:"
- echo " 1. Review .env and set any required values"
- echo " 2. task dev:run — start the application"
- echo " 3. task --list — see all available operations"
dev:setup as optional alias
dev:setup MAY be kept as a convenience alias for repos that want to preserve discoverability under dev:*. If present, it MUST delegate to setup:
dev:setup:
desc: "Alias for: task setup"
cmds:
- task: setup
It is not the canonical entry point. task setup is.
Relationship to deps:login:npm:env
setup:registry and deps:login:npm:env solve the same problem. setup:registry SHOULD be implemented by delegating to deps:login:npm:env rather than duplicating the logic:
setup:registry:
desc: Obtain CodeArtifact token and persist to .env
cmds:
- task: deps:login:npm:env
deps:login:npm:env remains the canonical login recipe. setup:registry is its integration point in the setup flow.
setup:clean vs build:clean
These are distinct operations with different scopes:
| Task | Removes |
|---|---|
setup:clean | Installed dependencies (node_modules/, .venv/) |
build:clean | Build artifacts (dist/) |
They may coexist. Running both returns the repo to a post-clone state.
When to use setup:*
Use setup:* when:
- The repo has private registry dependencies that require authentication before installation
- Setup has discrete phases that developers need to invoke independently (e.g., token rotation without reinstalling deps)
For repos with no private deps and no phased setup requirements, dev:setup + dev:install per ADR-0013 remains acceptable. No forced migration.
Consequences
Positive
dev:*is focused on daily operations — no first-use setup tasks polluting the namespace- Phases are independently invocable — token rotation, env reset, and install can run separately
- Dependency between phases is explicit —
setup:registrybeforesetup:installis visible in the Taskfile, not hidden insidedev:setup task setupis the clear onboarding entry point — discoverable, memorable, unambiguous
Negative
- Two valid patterns exist — repos without private deps may use
dev:setup/dev:install(ADR-0013); repos with private deps usesetup:*(this ADR). Engineers working across repos will encounter both. - Migration required for repos with private deps — any repo currently using
dev:setupthat internally authenticates to a private registry should migrate tosetup:*to make the phases explicit.
Migration
- Repos with no private registry deps: no migration required.
dev:setup+dev:installcontinues to work. - Repos with private registry deps: migrate to
setup:*. Thedev:setupalias is optional but recommended during transition. - New repos with private deps MUST use
setup:*from the start.
Examples
Basic setup (no private registry)
setup:
desc: "First-use setup: configure env, install deps"
cmds:
- |
if [ ! -f .env ]; then
cp .env.example .env
echo "Created .env from .env.example — review and adjust values."
fi
- task: setup:install
- task: validate:structure
- echo ""
- echo "Setup complete. Run: task dev:run"
setup:install:
desc: Install project dependencies
cmds:
- pnpm install # or: uv sync
setup:clean:
desc: Remove installed dependencies
cmds:
- rm -rf node_modules/
Setup with CodeArtifact (private registry)
setup:
desc: "First-use setup: configure env, authenticate registry, install deps"
cmds:
- task: setup:prepare
- task: setup:registry
- task: setup:install
- echo ""
- echo "Setup complete. Next steps:"
- echo " 1. Review .env and set any required values"
- echo " 2. task dev:run — start the application"
- echo " 3. task --list — see all available operations"
setup:prepare:
desc: Copy .env.example to .env if not present
cmds:
- |
if [ ! -f .env ]; then
cp .env.example .env
echo "Created .env from .env.example — review and adjust values."
else
echo ".env already exists, skipping."
fi
setup:registry:
desc: Obtain CodeArtifact token and persist to .env
cmds:
- task: deps:login:npm:env
setup:install:
desc: Install project dependencies
dotenv: ['.env']
cmds:
- pnpm install
setup:clean:
desc: Remove installed dependencies
cmds:
- rm -rf node_modules/
References
- ADR-0013: Taskfile Contract v2 — Decision 4 superseded by this ADR
- Taskfile Contract Pattern — updated to reflect this decision
ontopix/transcript-service— origin of this pattern
ADR-0013: Taskfile Contract v2 — Prescriptive Naming and Recipes
Decisions to extend the Taskfile contract with prescriptive task naming, CI namespace, developer lifecycle, environment handling, and CodeArtifact recipes.
ADR-0015: Infrastructure Layer Model inside .infra/
Formalizes the bootstrap / shared / service layer model inside .infra/, each an isolated Terraform stack, and renames the CI-roles layer to bootstrap.