Development environment
Development happens inside the Nix / direnv environment defined by sabas0ba/dotfiles, or inside the container environment built from it with identical contents.
Tools are not installed directly on the host. apt install / brew install /
npm install -g / pip install --user and the like undermine
reproducibility and are not used.
1. Prerequisites
- Nix (with flakes enabled)
- direnv (optional; with it,
cdalone enters the environment) - Docker (optional; only if you use the container environment)
Installation steps, the version-pinning policy, and checksum verification are
covered by the dotfiles README. Running installers unverified
(curl ... | sh) is not done.
2. Setting up
git clone https://github.com/sabas0ba/dotfiles.git ~/repos/dotfiles
cd ~/repos/dotfiles
nix develop
scripts/check-env.sh
direnv setup and applying the home-manager configuration (make hm-dry /
make hm-switch) also follow the dotfiles README.
Work is done inside the development shell. If the environment variable
DOTFILES_ENV is nix-develop, you are inside it.
3. The container environment
An environment identical to the host can be built inside a container. The
Dockerfile carries no tool list of its own — it evaluates the dotfiles
flake.nix — so the contents match the host.
make docker-build # build the image
make docker-shell # enter the development shell inside the container
make docker-check # smoke test inside the container
Running CI inside a --network none container is the policy that keeps CI
from becoming a third environment distinct from host and container.
For now this is not in effect. CI runs on GitHub Actions runners
(.github/workflows/verify.yml). The path
for evaluating the dotfiles flake from this repository’s CI is not set up,
and there is no present need to build it.
To keep the eventual migration cheap, what is checked is decoupled from
where it runs: local runs and CI both invoke the same entry point,
scripts/verify.sh, and the workflow does nothing but launch it. Moving the
execution environment later swaps the workflow’s internals only; the
definition of the checks does not move.
3.1 Verification
Verification has a single entry point.
make verify # run every stage, leaving results in .work/verify/
A failing stage does not stop the run; everything executes and the run fails at the end. Knowing “what else passed” in the same run — not just “where it failed” — makes the repair loop faster.
| Output | Contents |
|---|---|
.work/verify/summary.md |
per-stage results, pass counts, timing, startup measurements |
.work/verify/results.json |
the same, machine-readable |
.work/verify/logs/<stage>.log |
each stage’s raw output |
.work/verify/startup.json |
startup-time measurements |
CI stores .work/verify/ as an artifact and prints summary.md into the job
summary. Results of failing runs, especially, are preserved.
The stages: fmt / clippy / per-crate unit tests / parser robustness /
model integration and incrementality / e2e / scenarios / real-shaped fixtures /
diagnostics and coverage / examples / release build / startup measurement.
Startup measurement alone is informational and does not fail the run on
machine noise (a loose cap catches only clear regressions).
What each layer answers, and where a new test belongs, is in 51-testing.md.
To run a subset, stages can be skipped:
DOWEL_VERIFY_SKIP="e2e example" make verify
make check (formatting check + lints + tests) is for quick iteration and
leaves no records.
4. Adding tools
Tools the implementation needs (compilers, linkers, qemu, ninja, …) are added
to nix/packages.nix on the dotfiles side:
- Add the package name to
nix/packages.nix - If it is used as a command, also add it to
required_commandsinscripts/check-env.sh - Confirm
make checkpasses
Do not add tool names to the Dockerfile; duplicated definitions drift.
Adding a dependency at all is confirmed in advance.
5. Conventions
The dotfiles README is where the conventions live. Only project-specific items are listed here; anything unlisted follows dotfiles.
Inherited
- Commits follow Conventional Commits; one purpose per commit
- Feature work happens on a branch or worktree
- Temporary files go in the gitignored
.work/inside the repository, never outside it (/tmpetc.) - External artifacts are pinned uniquely; a reference by tag or branch name alone does not count as pinned
- No secrets in commits; machine-specific settings go in
.envrc.local(outside git) - Formatting is done by
make fmt, not by hand - Comments explain why a choice was made, not what the code does
Project-specific
- The implementation language is Rust (ADR-0007). The core depends on the standard library only; adding an external crate requires agreement each time
- The language of the program is English: identifiers (including test
names), string literals, diagnostics / logs / CLI output, generated files
(
build.ninjaetc.), and metadata (descriptioninCargo.toml, workflow step names). The only exception is test data that exercises non-ASCII handling itself. Documentation (docs/and the READMEs) is written in English. Code comments and doc comments are in Japanese — the convention that comments record the reason for a choice benefits from the density of the native language - Formatting is
make fmt(cargo fmt); lints aremake lint(cargo clippy -D warnings) - Run
make check(formatting check + lints + tests) before submitting - Design decisions are recorded as ADRs in
docs/adr/. To overturn one, mark it Superseded and add a new ADR
6. Handing work to Claude Code
State explicitly:
- Work inside the dotfiles environment (Nix development shell or container)
- Refer to the dotfiles
CLAUDE.mdand README - This repository’s
CLAUDE.mdcontains repository-specific instructions and takes precedence over the shared conventions
The repository-root CLAUDE.md records the same.