---
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 <package>` or `from <package>`. 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 <tool>` 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
