Task · APH-0001

Migrate the repo task surface to just and retire Makefiles and ad-hoc scripts

Description

Migrate autopi-ha task surface to just

1. Outcome

autopi-ha has one top-level justfile implementing the fleet-mandatory recipe vocabulary (default, setup, fmt, fmt-check, lint, test, check) plus repo-specific optional recipes (docgen, run, clean, package). Makefile is deleted. scripts/setup and scripts/lint are deleted (absorbed). scripts/develop and scripts/generate_docs.py remain as files, each reachable through a recipe. .github/workflows/tests.yml’s lint-and-scan job calls just fmt-check and just lint; its pytest job calls just test. backlog/config.yml’s definition_of_done names just recipes. AGENTS.md documents the just task interface. Nobody runs make or ./scripts/setup/./scripts/lint again.

2. The complete justfile

Toolchain facts used below (verified from pyproject.toml, .pre-commit-config.yaml, backlog/config.yml, .github/workflows/tests.yml):

set shell := ["bash", "-euo", "pipefail", "-c"]

# show the task surface
default:
    @just --list

# install toolchain + deps into the repo-local environment
[group('check')]
setup:
    uv sync --all-extras
    uv run pre-commit install
    uv run pre-commit install --hook-type commit-msg

# format code in place
[group('check')]
fmt:
    uv run ruff format custom_components tests
    uv run ruff check --fix custom_components tests
    just --fmt

# verify formatting without mutating
[group('check')]
[no-exit-message]
fmt-check:
    uv run ruff format --check custom_components tests
    just --fmt --check

# run static analysis (ruff + bandit)
[group('check')]
[no-exit-message]
lint:
    uv run ruff check custom_components tests
    uv run bandit -r custom_components

# run mypy type checking
[group('check')]
[no-exit-message]
typecheck:
    uv run mypy custom_components

# run the test suite (optional pytest -k filter)
[group('check')]
[no-exit-message]
test filter="":
    #!/usr/bin/env bash
    set -euo pipefail
    if [ -n "{{ filter }}" ]; then
        uv run pytest -k "{{ filter }}" -vv
    else
        uv run pytest -vv
    fi

# full local gate — everything CI enforces
[group('check')]
check: fmt-check lint typecheck test

# regenerate entity documentation from code
[group('gen')]
docgen:
    uv run python scripts/generate_docs.py

# start a local Home Assistant instance for manual testing (long-running)
[group('dev')]
run:
    ./scripts/develop

# build a distributable integration zip
[group('build')]
build:
    rm -rf dist/
    mkdir -p dist/
    cd custom_components && zip -r ../dist/autopi.zip autopi/ -x "*.pyc" "*/__pycache__/*" "*/.DS_Store"

# remove build artifacts and caches
[group('dev')]
[confirm('remove build/dist/coverage/cache artifacts?')]
clean:
    rm -rf build/ dist/ *.egg-info .coverage htmlcov/ .pytest_cache/ .mypy_cache/ .ruff_cache/ coverage.xml bandit-report.json
    find . -type d -name "__pycache__" -exec rm -rf {} +

Notes on this file:

3. Makefile disposition

Makefile (repo root) — every target:

Make target Replacement Notes
help default (@just --list)
install setup drops the echo "Development environment setup complete!" — noise, just --list output already tells you it ran
test test drops the coverage flags on the command line; they live in pyproject.toml addopts already (Makefile was duplicating them)
test-file dropped just test <path via filter? no> — pytest path args aren’t the same as -k filter. Not carried forward; uv run pytest tests/test_x.py remains a fine escape hatch, not worth a dedicated recipe. Record as a deliberate drop, not an oversight.
test-match test filter=... just test filter="test_pattern"
test-watch dropped required installing watchdog on demand — non-trivial control flow (command -v check + conditional install) and a niche workflow. Not fleet vocabulary. Drop; note in traps.
test-debug dropped uv run pytest -vv -s --log-cli-level=DEBUG remains a fine one-off; not common enough to be a recipe
lint (+ lint-ruff, lint-mypy, lint-bandit) lint + typecheck mypy split out to its own recipe per §2 above; lint now covers ruff+bandit only
format fmt
type-check typecheck
clean clean [confirm] added
coverage dropped opening htmlcov/index.html in a browser is a human-interactive convenience with OS-specific branching (open/xdg-open) — non-trivial control flow, not fleet-recipe-shaped. just test already produces htmlcov/ via pyproject.toml’s --cov-report=html. Drop the browser-open wrapper.
pre-commit dropped from check not part of CI’s actual gate (see §2 toolchain notes) — do NOT wire into check. May be re-added later as an explicit non-gate convenience recipe if Rob wants it; not required by this task
pre-commit-update dropped one-off maintenance command, not routine task surface
validate dropped was lint pre-commit plus echo statements about hassfest/HACS running in CI — no unique logic, superseded by check
docs dropped did nothing but print two lines pointing at README/CONTRIBUTING
docgen docgen
check-all dropped was lint test validate — superseded by check
dev-server dropped printed 3 lines of manual instructions, did not start anything. run (wrapping scripts/develop) actually starts HA — that’s the real recipe
release dropped interactive manual version-bump/tag flow; obsolete now that release-please (.github/workflows/release-please.yml) owns releases. Do not port.
update-deps dropped uv lock --upgrade — one-off maintenance, not fleet vocabulary (no lockfile is even committed per pyproject.toml inspection: no uv.lock referenced in gate). If a uv.lock exists and is committed, this could become deps-update; verify with ls uv.lock before dropping — if present, add deps-update: uv lock --upgrade in the check group instead of dropping.
security dropped uv run bandit -r custom_components is now just lint; the safety check half was already conditional/best-effort and not installed by default
stubs dropped one-off stubgen invocation, not routine
docker-build / docker-test dropped no Dockerfile confirmed present in this repo (this is a HACS custom_component, not a containerized service) — verify with ls Dockerfile; if absent, these targets are dead already and dropping is correct
new-platform dropped interactive scaffolding helper (read -p), non-trivial control flow, not a fleet recipe pattern
package build renamed to match fleet optional vocabulary (build, not package)
setup (make target, distinct from install) setup (just) already covers this — merge, the make setup target just called install plus printed a banner
check-python dropped version-compatibility guard with conditional exit; requires-python = ">=3.14.2" in pyproject.toml already enforces this via uv sync itself
install-hooks folded into setup pre-commit install + pre-commit install --hook-type commit-msg, now two lines inside just setup

After the justfile is proven locally and CI is switched (§8 order of work), run:

git rm Makefile

4. Script disposition

Script Classification Replacement Reason
scripts/setup ABSORB folded into just setup thin wrapper: pip3 install uv (unneeded — CI/dev already has uv via astral-sh/setup-uv or a local install) + make install. No control flow worth keeping. just setup (§2) already does the real work (uv sync --all-extras, pre-commit install ×2). Delete the file.
scripts/lint ABSORB folded into just fmt + just lint (run separately, or just fmt lint when both are wanted) thin wrapper that just called make format then make lint with banner echoes. No unique logic. Delete the file.
scripts/develop KEEP just run calls ./scripts/develop has real conditional control flow (creates config/ dir and config/secrets.yaml template if missing, checks uv is on PATH with a fallback error message) and is long-running/interactive (starts a live Home Assistant server on port 8123). §6 KEEP criteria: non-trivial control flow. Update its final error message (Please run 'make install'...) to say just setup — this is a one-line edit inside the KEPT script, done as part of this task, not a recipe-authoring decision.
scripts/generate_docs.py KEEP just docgen calls uv run python scripts/generate_docs.py 37KB AST-based doc generator — a real program per §6, not a task.
scripts/fetch_all_events.py OUT OF SCOPE — do not touch none Not invoked by Makefile, CI, docs, or AGENTS.md. Contains a hardcoded live API token and device ID (scripts/fetch_all_events.py:9-10) — this is a pre-existing exposed-secret concern, unrelated to the just migration. Do not wrap it in a recipe (that would imply it’s part of the routine task surface, which it isn’t) and do not delete it (not authorized — this task is additive/replacement only for the Makefile-adjacent surface). Flag it to Rob separately.

5. CI changes

.github/workflows/tests.yml

Add the setup-just step to both jobs that currently run raw tool commands (lint-and-scan and pytest), immediately after the existing Set up uv step in each, before Install dependencies:

      - name: Set up just
        uses: extractions/setup-just@<pin-exact-sha> # v4
        with:
          just-version: '1.58.0'

(Resolve <pin-exact-sha> to the current SHA for extractions/setup-just tag v4 at implementation time — match the fleet’s existing SHA-pin + # vN comment convention visible on every other uses: line in this same file. Do not hand-guess a SHA.)

lint-and-scan job — replace:

      - name: Install dependencies
        run: uv sync --all-extras

      - name: Run Ruff linting
        run: uv run ruff check --fix custom_components tests

      - name: Run Ruff formatting
        run: uv run ruff format custom_components tests

with:

      - name: Install dependencies
        run: just setup

      - name: Check formatting
        run: just fmt-check

      - name: Run linting
        run: just lint

      - name: Run type checking
        run: just typecheck

This is a behavior tightening, not a mechanical rename: the old steps ran ruff check --fix (auto-fixing and silently committing nothing — a no-op fixer in CI that can mask drift) and ruff format (also mutating, not checking). just lint and just fmt-check are non-mutating and correctly fail the job on any finding, matching what check enforces locally. This job did not run mypy at all before — just typecheck is now added so CI matches just check exactly (the §1 contract: “If CI runs a check that check does not, the contract is broken”; the converse gap — check covering something CI didn’t — is being closed here, and this is required, not optional, because agents will otherwise run just check locally, see it fail on typecheck errors that CI was never catching, and lose trust in the gate).

pytest job — replace:

      - name: Install dependencies
        run: |
          # Install dependencies with all extras
          uv sync --all-extras
          # Verify installation
          uv pip list

      - name: Verify Python environment
        run: |
          uv run python --version
          uv run python -c "import sys; print('Python executable:', sys.executable)"
          uv run python -c "from homeassistant.const import __version__; print('Home Assistant version:', __version__)"

      - name: Create Home Assistant config directory
        run: mkdir -p /tmp/homeassistant

      - name: Run tests with coverage
        env:
          PYTHONPATH: ${{ github.workspace }}
          PYTHONIOENCODING: utf-8
          TZ: UTC
          HA_DISABLE_ANALYTICS: true
          HOMEASSISTANT_CONFIG_DIR: /tmp/homeassistant
          LC_ALL: C.UTF-8
          LANG: C.UTF-8
        run: |
          uv run python -m pytest tests/ \
            --cov=custom_components.autopi \
            --cov-report=term-missing \
            --cov-report=xml \
            --cov-report=html \
            --cov-fail-under=10 \
            --tb=short \
            -v

with:

      - name: Install dependencies
        run: just setup

      - name: Create Home Assistant config directory
        run: mkdir -p /tmp/homeassistant

      - name: Run tests with coverage
        env:
          PYTHONPATH: ${{ github.workspace }}
          PYTHONIOENCODING: utf-8
          TZ: UTC
          HA_DISABLE_ANALYTICS: true
          HOMEASSISTANT_CONFIG_DIR: /tmp/homeassistant
          LC_ALL: C.UTF-8
          LANG: C.UTF-8
        run: just test

Keep the env: block on the just test step exactly as-is — those are runtime environment variables pytest/Home Assistant read, unrelated to just itself, and just recipes inherit the step’s environment (§8: “Secrets and env pass through normally”). Drop the “Verify Python environment” step — it was diagnostic noise (printing the interpreter path and HA version), not a gate; if Rob wants that back it’s a just-external decision, not part of this migration. --tb=short and -v from the old CI invocation are dropped since they’re CI-only cosmetic flags not present in pyproject.toml’s addoptsjust test uses the pyproject.toml-defined addopts (-vv -s, more verbose than CI’s old -v) uniformly for both local and CI runs, which is the point of the gate contract (§1: check/test must be exactly what CI enforces).

Do NOT touch:

Other workflow files — no just changes

actionlint.yml, codeql.yml, dependency-review.yml, release-please.yml, scorecard.yml, stale.yml, trigger-docs-sync.yml, zizmor.yml, cleanup-draft-releases.yml, arm-automerge.yml — none contain build/test/lint run: shell logic that maps to a just recipe. Confirmed by reading actionlint.yml (a single uses: rknightion/.github/.github/workflows/actionlint.yml@... reusable call, no shell) and bandit.yml (a single uses: shundor/python-bandit-scan@... third-party action, no shell — separate from the bandit CLI invocation folded into just lint above; this workflow’s own bandit run stays as-is, it’s a different execution path uploading SARIF to the Security tab). Do not add just calls to any of these files.

6. Docs and agent-contract changes

No file in this repo currently references make <target> or ./scripts/foo.sh in prose (verified: grep -n "make " AGENTS.md CLAUDE.md README.md returns nothing — README and AGENTS.md never told anyone to run make, they used raw uv run commands directly). This means:

Replace:

## Development Environment

This project uses `uv` for Python dependency management. Always use `uv` to run Python commands:
- `uv run ruff check .` - Run linting
- `uv run ruff check . --fix` - Run linting with auto-fix
- `uv run mypy .` - Run type checking
- `uv run pytest` - Run tests
- `uv run python <script>` - Run any Python script
- `uv sync` - Sync dependencies
- `uv pip list` - List installed packages

with:

## Development Environment

This project uses `uv` for Python dependency management, orchestrated through `just`.

## Task interface

This repo's task surface is a `justfile`. Discover it, don't guess it:

    just --list                        # human-readable
    just --dump --dump-format json     # machine-readable
    just --show <recipe>               # what a recipe actually runs

- `just check` is the full gate and is exactly what CI enforces. It must pass before you commit.
- Prefer `just <recipe>` over the underlying tool. If you are typing `pytest`, you want `just test`.
- Run `just` with stdin from /dev/null. Recipes marked `[confirm]` are destructive — stop and ask
  before running one; never pass `--yes` or `JUST_YES=1`.
- If a task you need does not exist, add a recipe with a `#` doc comment and a `[group(...)]`
  rather than running a bare command.

7. backlog/config.yml

Current line (backlog/config.yml:4):

definition_of_done: ["uv run ruff check custom_components tests", "uv run mypy .", "uv run pytest"]

Replace with:

definition_of_done: ["just check"]

Drive this through the backlog CLI, never hand-edit the YAML (per house rule) — e.g. backlog config set definition_of_done '["just check"]" or the equivalent supported subcommand; confirm the exact CLI invocation against backlog --help / backlog config --help at implementation time since this task file cannot verify the installed CLI’s exact flag surface.

8. Order of work

  1. Create justfile at repo root (content in §2). Do not touch Makefile/scripts yet.
  2. Run just --fmt --check — fix formatting. Run just --list — confirm all 7 mandatory + optional recipes show with correct groups and doc comments.
  3. Run just setup && just check locally end to end. Fix any command-path mismatches (this repo’s real uv/ruff/mypy/bandit/pytest invocations were sourced from pyproject.toml and the existing Makefile — they should work as-is, but verify: bandit needs -r custom_components exactly, no -f json -o bandit-report.json — that flag combo was for producing a report file for nothing downstream, drop it, plain text findings to stdout are what CI needs to fail on).
  4. Edit scripts/develop’s trailing error message (make installjust setup), per §4. Leave the rest of the script untouched.
  5. Edit .github/workflows/tests.yml per §5. Push to a branch, confirm both jobs go green with the new just-based steps before merging — do not switch CI and delete the Makefile in the same step.
  6. Update AGENTS.md per §6.
  7. Update backlog/config.yml’s definition_of_done per §7, via the backlog CLI.
  8. Only once steps 1–7 are verified green (CI passing on the branch, just check green locally): git rm Makefile scripts/setup scripts/lint.
  9. Final check: git grep -n "make " and git grep -rn "scripts/setup\|scripts/lint" across the repo return nothing (confirms no stray reference survives).

9. Traps specific to this repo

10. Out of scope

Do not touch, in any way, as part of this task:

Acceptance Criteria

Definition of Done

Implementation Plan

  1. Inventory the current task surface, CI, hooks, and repository conventions.
  2. Replace the Makefile task surface with a formatted justfile and update only the mapped workflow, docs, script text, and tracker configuration.
  3. Validate local recipes and hooks; remove obsolete paths and search for surviving references.
  4. Commit named paths, push main, verify the exact-SHA CI run, then finalize the task atomically with evidence.

Implementation Notes

Implemented the justfile migration and retired the Makefile plus thin setup/lint wrappers. Repointed the devcontainer, docs, labeler, and editor configuration; preserved real scripts through just run and just docgen. Added the missing pre-commit development dependency and repaired the stale mypy hook to call just typecheck. Local evidence: just setup, just --fmt --check, just --dump --dump-format json, and just check passed (150 passed, 1 skipped); actionlint and zizmor passed; the installed pre-commit hook, pre-commit-update, and the mypy hook pass. The required CodeRabbit review is temporarily rate-limited and must succeed before commit/push.

CodeRabbit reviewed the final staged diff after the fixes and reported 0 findings. The earlier review findings were addressed: the local-versus-CI gate wording was corrected, just setup now uses uv sync --all-extras --locked, and this task’s Definition of Done now names just check.

Final CI evidence: GitHub Actions Tests run 33256773825 completed successfully at commit 63d2c4f940f3672389a86a70052acc5e98620017; its ci-success job passed. The list-valued Backlog configuration could not be set by the installed CLI, so the repository-approved config-file exception was used after the CLI reported that limitation.

Comments

author: campaign-ordering created: 2026-08-29 09:18

Fleet ordering — WAVE 2. Starts after the Wave 0 pilot (sf2loki / SFL-0073) and the Wave 1 hubs land.

Within Wave 2 the order is free — these repos do not depend on each other. Batching by language is worthwhile so one lane reuses its Makefile-to-recipe mapping across similar repos.

Do not start before the pilot reports. The standard may be amended off the back of it, and picking this up early risks coding against a superseded seam.

Provisioning just in CI. Which mechanism depends on the runner, and the two must not be mixed:

Runner Mechanism
arc-arm64 (m7kni self-hosted) just is baked into the runner image by m7kni/ci-tools (runner-image/Dockerfile, ARG JUST_VERSION). Do not add extractions/setup-just, and delete the step if this repo already has one — it installs a second just earlier on PATH and turns the image pin into a lie.
GitHub-hosted (all rknightion repos) extractions/setup-just, SHA-pinned, with an explicit just-version:.

Both sides currently sit on 1.58.0 and are Renovate-managed. ci-toolsTool version drift workflow fails if the Dockerfile ARG and the published image ever disagree, and lists any repo still carrying a second pin.

While you are in the workflow files, check the hub pin. On 2026-08-29 Renovate was unfrozen for rknightion/.github in m7kni/renovate-config — it had been enabled: false on the mistaken belief that callers tracked @main, which froze the fleet across 19 different hub SHAs (v1.3.1 June → v1.9.7 August) so that no hub fix ever propagated. Bumps now arrive as one grouped, CI-gated, automerged PR per repo. A uses: whose comment is not a real # vX.Y.Z still cannot be bumped (it resolves to a digest-only update, which the fleet rules disable) — if you find one, repair the comment as part of this task.

author: campaign-ordering created: 2026-08-29 10:42

Standard amendment — ci is the sanctioned superset of check (RATIFIED)

This supersedes the frozen wording check is the complete local gate and reproduces every CI job that can run off a GitHub runner”, which several lanes could not honour without making the pre-commit gate depend on a Docker daemon.

The definitions now are:

Every leg you put in ci must carry a comment naming which of those three it needs. That comment is the guard: without it ci becomes the bin for anything slow or awkward, check quietly stops meaning much, and the fleet is back to a per-repo gate.

Eleven of the 42 lanes arrived at this shape independently before it was ratified, which is why it won.

If this repo has no such legs, it has no ci recipe at all and check is the whole gate. Do not add an empty one.

Final Summary

Replaced the Makefile task surface with a formatted justfile, migrated CI and documented entry points, and removed the obsolete setup/lint wrappers. Verified with just setup, just –fmt –check, just –dump –dump-format json, just check (150 passed, 1 skipped), actionlint, zizmor, installed hooks, and a clean final CodeRabbit review. Tests run 33256773825 is green at commit 63d2c4f940f3672389a86a70052acc5e98620017.

View the source file on GitHub