--- title: Working Tree Hygiene — Dependency Groups and Gitignore date: 2026-04-24 category: docs/solutions/best-practices module: development_workflow problem_type: best_practice component: development_workflow severity: low applies_when: - Reviewing uncommitted changes before committing - Adding new dependencies to pyproject.toml - Updating .gitignore with new ignore patterns tags: [dependencies, pyproject, gitignore, hygiene, code-review, dev-tools] --- # Working Tree Hygiene — Dependency Groups and Gitignore ## Context A code review of uncommitted changes on `main` caught three preventable hygiene issues: 1. `pyright` (a static type checker) was added to `[project] dependencies` in `pyproject.toml` instead of `[dependency-groups] dev` 2. `.gitignore` contained a duplicate `.worktrees` entry 3. A blog post included a hardcoded correlation coefficient with no reproducible source (documented separately in `blog-numbers-from-pipeline-outputs`) All three were caught before commit, but they illustrate a pattern: small working tree cleanups accumulate friction when not reviewed systematically. ## Guidance ### Dependency classification When adding a package to `pyproject.toml`, ask: **does this run in production?** | If... | Put it in... | |-------|-------------| | The app imports it at runtime | `[project] dependencies` | | It is a type checker, test runner, linter, or dev server | `[dependency-groups] dev` | | It is only used in build scripts or CI | `[dependency-groups] dev` | **Concrete check:** search the codebase for `import ` or `from `. If it only appears in `tests/`, `scripts/`, or type stubs, it belongs in `dev`. ### Gitignore hygiene Before committing a `.gitignore` change, run: ```bash sort .gitignore | uniq -d ``` If anything prints, you have duplicates. Remove them. Also check that your new entry does not overlap with an existing pattern: - `.worktrees/` and `.worktrees` are redundant — keep the slash form for directories - `data/*.json` already covers `data/motions.json` — do not add the specific file ### Pre-commit audit checklist For every set of uncommitted changes: 1. **Dependencies**: Any new packages in the right group? 2. **Gitignore**: Any duplicates or redundant patterns? 3. **Dev tools in venv**: Is the linter/formatter also listed in `[dependency-groups] dev` so `uv run ` works? (A tool in pre-commit but not in `pyproject.toml` will fail for developers who run it manually.) 4. **Blog/docs**: Any hardcoded numbers without canonical sources? (see `blog-numbers-from-pipeline-outputs`) 5. **Config**: Any secrets or local paths committed by accident? ## Why This Matters These issues are individually trivial, but together they create a "broken windows" effect. A `pyproject.toml` with dev tools in runtime dependencies signals that the project does not distinguish between production and development concerns. Duplicate `.gitignore` entries suggest the file is append-only and never reviewed. Small hygiene lapses compound into larger maintainability debt. The fix is cheap: a 30-second scan of the diff before committing prevents all of them. ## When to Apply - Before every commit that touches `pyproject.toml`, `.gitignore`, or `uv.lock` - When onboarding a new dependency - During code review of any PR that adds build tools, test frameworks, or local config ## Examples **Dependency misclassification:** ```toml # ❌ Before [project] dependencies = [ "duckdb>=1.3.2", "pyright>=1.1.408", # dev tool in runtime deps ] # ✅ After [project] dependencies = [ "duckdb>=1.3.2", ] [dependency-groups] dev = [ "pytest>=9.0.2", "pyright>=1.1.408", ] ``` **Gitignore duplicate:** ```diff # Worktrees .worktrees/ # Generated analysis files thoughts/explorer/*.json - - # Stray temp files - .worktrees # ← duplicate, remove **Dev tool missing from venv:** A linter was configured in `.pre-commit-config.yaml` and `pyproject.toml`'s `[tool.ruff]` section, but it was not listed in `[dependency-groups] dev`. Developers running `uv run ruff check .` got a "command not found" error even though pre-commit worked. ```toml # ❌ Before — tool configured but not installable [tool.ruff.lint] select = ["T20", "BLE"] [dependency-groups] dev = ["pytest>=9.0.2"] # ✅ After — tool is both configured and installable [tool.ruff.lint] select = ["T20", "BLE"] [dependency-groups] dev = [ "pytest>=9.0.2", "ruff>=0.11.0", ] ``` ## Related - `docs/solutions/best-practices/blog-numbers-from-pipeline-outputs-2026-04-16.md` — companion guidance on keeping quantitative claims reproducible - `docs/solutions/workflow-issues/verify-session-artifacts-against-canonical-sources-2026-04-24.md` — same verification principle applied to session artifacts