anima-dao 0.2.0
Confucian-spirited dependency health checker for Python projects using pyproject + uv
Requires Python: >=3.11

# AnimaDao

[![ci](https://github.com/Absolentia/AnimaDao/actions/workflows/ci.yml/badge.svg)](https://github.com/Absolentia/AnimaDao/actions/workflows/ci.yml)
[![tagger](https://github.com/Absolentia/AnimaDao/actions/workflows/tag.yml/badge.svg)](https://github.com/Absolentia/AnimaDao/actions/workflows/tag.yml)
[![PyPI version](https://img.shields.io/pypi/v/anima-dao.svg)](https://pypi.org/project/anima-dao/)
![Python](https://img.shields.io/badge/python-3.13%2B-blue.svg)
[![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)

**AnimaDao** is a pragmatic dependency health checker for Python projects.  
It compares **declared** dependencies with **actual imports**, flags **unused** ones, and checks **pinned** specs (`==`)
against the latest PyPI releases.  
Works with **uv** and supports three declaration styles.

---

## One-liner GitHub Action

Use the official composite Action to run AnimaDao in one step.  
It writes a Markdown report to the **Job Summary**, optionally uploads artifacts, and can **fail** the job on policy
violations.

**Repo:** <https://github.com/Absolentia/animadao-action>  
**Recommended pin:** `uses: Absolentia/animadao-action@v1` (moving major tag).  
For full reproducibility you can pin a concrete version, e.g. `@v1.0.3`.

### Minimal usage (declared mode)

```yaml
- name: AnimaDao (declared)
  uses: Absolentia/animadao-action@v1
  with:
    mode: declared
    # One root is enough for most repos; you can also pass multiple roots (e.g. "anima_dao tests")
    src: "."
    ignore: pip,setuptools,wheel,ruff
    fail-if-outdated: "true"
    max-unused: "0"
    format: md
    artifact-name: anima-report-${{ matrix.python-version }}
    python-version: ${{ matrix.python-version }}
```

**Installed mode** (checks currently installed packages; no “unused” check):

```yaml
- name: AnimaDao (installed)
  uses: Absolentia/animadao-action@v1
  with:
    mode: installed
    fail-if-outdated: "true"
    ignore: pip,setuptools,wheel
    python-version: ${{ matrix.python-version }}
```

### Full example job (with cache + matrix)

```yaml
jobs:
  anima:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        python-version: [ "3.10","3.11","3.12","3.13" ]
    steps:
      - uses: actions/checkout@v4

      # Speed up uv by caching
      - uses: actions/cache@v4
        with:
          path: ~/.cache/uv
          key: uv-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('uv.lock') }}

      - name: AnimaDao (declared)
        uses: Absolentia/animadao-action@v1
        with:
          mode: declared
          src: "."                                  # or: "anima_dao tests"
          ignore: pip,setuptools,wheel,ruff
          fail-if-outdated: "true"
          max-unused: "0"
          format: md
          artifact-name: anima-report-${{ matrix.python-version }}
          python-version: ${{ matrix.python-version }}
```

### Notes & tips

- **Artifacts in matrix:** artifact names must be **unique** per shard (use
  `anima-report-${{ matrix.python-version }}`), or merge them later to avoid 409 conflicts.
- **Report visibility:** the Action appends the Markdown report to the **Job Summary**; HTML/JSON are attached as
  artifacts when `upload-artifact: "true"`.
- **Policies:** tune to your process:
    - allow a small number of unused deps: `max-unused: "1"`
    - ignore dev tools (e.g., `ruff`, `black`, `mypy`, `build`, `twine`) via `ignore`.
- **Multiple source roots:** pass them space-separated in `src` (e.g., `src: "anima_dao tests"`). The Action expands
  this into repeated `--src` flags for the report.  
  The gate runs against the project root and policy checks (works out of the box).
- **Pre-release Python:** if you run on RC versions (e.g., 3.14-rc), set `allow-prereleases: true` on your own
  `actions/setup-python` step (outside of this Action).

## Features

- 🗂 **Multiple sources** of declared deps:
    1. `pyproject.toml` — **PEP 621** `[project].dependencies`
    2. `pyproject.toml` — **Poetry** `[tool.poetry.dependencies]`
    3. **`requirements.txt`** (incl. nested `-r` includes)
- 🔍 **Import scan**: walks your source tree and extracts top-level imports (AST).
- 🧹 **Unused deps**: declared but not imported (heuristic, import-name ≈ normalized dist name).
- ⏫ **Outdated pins**: checks only `==`-pinned requirements against PyPI **latest**.
- ⚙️ **Two modes**: `--mode declared` (default) and `--mode installed`.
- 🚫 **Ignore lists**: skip tool packages (e.g. `pip`, `setuptools`, `wheel`) or any custom names.
- 🧩 **Config file**: project-level `.animadao.toml` with sane defaults and CLI overrides.
- 🗃️ **PyPI cache**: TTL + ETag with configurable concurrency for faster checks.
- 📄 **Reports**: `--format json | md | html`.

---

## Requirements

- Python 3.10+
- **uv** (modern Python package/dependency manager)

---

## Installation

### With `uv`

```bash
pip install uv
uv pip install anima-dao
```

### With `pip`

```bash
pip install anima-dao
```

### From sources

1) Clone:

```bash
git clone https://github.com/Absolentia/AnimaDao.git
cd AnimaDao
```

2) Install `uv`:

```bash
pip install --upgrade pip
pip install uv
```

3) Create a virtual env:

```bash
uv venv
```

4) Install project deps:

```bash
uv sync
```

---

## Configuration

Create `.animadao.toml` in your project root (optional):

```toml
[core]
mode = "declared"          # declared | installed
src = ["src", "app"]       # optional, source roots for import scan

# PyPI cache / concurrency
pypi_ttl_seconds = 86400   # default: 24h
pypi_concurrency = 8       # default: 8 parallel requests

[ignore]
distributions = ["pip", "setuptools", "wheel"]
imports = []
```

CLI flags always override config values.

---

## Usage (CLI)

All commands accept a project root (where either `pyproject.toml` or `requirements.txt` resides) and an optional source
root to scan imports.

### Scan: declared vs imports

```bash
uv run animadao scan --project . --src src
```

### Check against PyPI

**Declared dependencies (default mode):**

```bash
uv run animadao check --project . --mode declared --ignore pip --ignore setuptools
```

**Installed packages in the current venv:**

```bash
uv run animadao check --project . --mode installed --pypi-ttl 43200 --pypi-concurrency 16
```

### Find unused deps (declared but not imported)

```bash
uv run animadao unused --project . --src src --ignore pip --ignore wheel
```

### Generate report

**JSON:**

```bash
uv run animadao report --project . --src src --out report.json --format json
```

**Markdown:**

```bash
uv run animadao report --project . --src src --out report.md --format md
```

**HTML:**

```bash
uv run animadao report --project . --src src --out report.html --format html
```

Example `report.json`:

```json
{
  "summary": {
    "declared": 12,
    "imports_found": 34,
    "outdated": 2,
    "unpinned": 7,
    "unused": 3
  },
  "outdated": [
    {
      "name": "requests",
      "current": "2.31.0",
      "latest": "2.32.3"
    }
  ],
  "unpinned": [
    {
      "name": "numpy",
      "spec": ">=1.26"
    }
  ],
  "unused": [
    "rich",
    "typer"
  ],
  "imports": [
    "requests",
    "json",
    "..."
  ],
  "mode": "declared"
}
```

---

## Supported dependency sources & priority

When resolving declared dependencies, AnimaDao uses the first matching source:

1. `pyproject.toml` — **PEP 621** `[project].dependencies` (+ `[project.optional-dependencies]` merged)
2. `pyproject.toml` — **Poetry** `[tool.poetry.dependencies]`
    - `python = "^3.10"` is ignored
    - Exact versions become `name==X.Y.Z`
    - Caret `^` ranges are converted to PEP 440 intervals (best-effort)
    - Poetry **dev/group** deps are **not** included
3. `requirements.txt` — plain lines + nested includes via `-r` / `--requirement`

> Only the **first** detected source is used to avoid mixing ecosystems.

---

## How it works (quick notes)

- **Import mapping:** compares normalized distribution names (`-` → `_`) with top-level imports. Known alias:
  `beautifulsoup4` ↔ `bs4`.
- **Modes:**
    - `declared`: checks only `==` pins from declarations; non-pinned specs appear under `unpinned`.
    - `installed`: checks versions of packages currently installed in the environment.
- **Networking:** PyPI queries via `httpx` with timeouts, using a TTL/ETag cache; failures fall back to cached data.

---

## CI & Releases

- **CI (`ci.yml`)**: runs `pytest` with `uv` on every push/PR to `main` (matrix: Python 3.10–3.13).
- **Tagger (`tag.yml`)**: manual tagging `vX.Y.Z`.
- **Publish**: recommended trigger by tag `v*` or via `workflow_run` after tagger.

Create a release:

```bash
# bump version in pyproject.toml
git commit -am "chore: bump version to 0.1.1"
git tag -a v0.1.1 -m "v0.1.1"
git push origin v0.1.1
```

---

## Support matrix

- Python: 3.10 / 3.11 / 3.12 / 3.13
- OS: Linux, macOS, Windows
- Packaging: PEP 621 (project), Poetry, requirements.txt
- Manager: `uv` (install & run)

---

## Troubleshooting

- **`No module named build`**  
  `uv pip install build twine` or `uv sync --extra dev`.

- **Hatch: “Unable to determine which files to ship”**  
  Ensure package directory exists and add to `pyproject.toml`:
  ```toml
  [tool.hatch.build.targets.wheel]
  packages = ["anima_dao"]
  ```

- **Publish doesn’t start after tagger**  
  Events from `GITHUB_TOKEN` don’t trigger other workflows. Push tag with a PAT or use `workflow_run` trigger.

- **403 after moving repo to org**  
  Update remote: `git remote set-url origin git@github.com:<ORG>/AnimaDao.git` and check org permissions/SSO.

---

## Contributing

```bash
pip install uv
uv sync --extra dev
uv run pytest -vvvv
```

Please keep type hints (3.10+), docstrings and comments in English, and add tests for new loaders/edge cases.

## pre-commit

1) Add AnimaDao hooks to `.pre-commit-config.yaml`:

```yaml
repos:
  - repo: https://github.com/Absolentia/AnimaDao
    rev: v0.1.2        # <-- use the latest tag
    hooks:
      - id: animadao-check
        args: [ --ignore, pip, --ignore, setuptools ]
        files: '^(pyproject.toml|requirements.*txt|.*\.py)$'
      - id: animadao-report
        stages: [ manual ]  # optional: run on-demand via `pre-commit run -a animadao-report`
```

2) Install and run:

```bash
pip install pre-commit
pre-commit install
pre-commit run -a
```

**Policy examples:**

- Fail on outdated pins only:
  ```yaml
  - id: animadao-check
    args: [--mode, declared, --fail-if-outdated]
  ```
- Strict mode (declared): no outdated, no unpinned, no unused:
  ```yaml
  - id: animadao-check
    args: [--mode, declared, --fail-if-outdated, --fail-if-unpinned, --max-unused, "0"]
  ```
- Installed mode (CI on lockstep env):
  ```yaml
  - id: animadao-check-installed
    args: [--fail-if-outdated]
  ```

```