diff --git a/AGENTS.md b/AGENTS.md index 6ddcd8d..f8d1fc4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -17,11 +17,10 @@ make workflow-check # workflow-lint + workflow-dryrun ``` `make setup` automatically installs all development tools: -- **Python deps** via `devx.tools.setup` (pip install -e .[dev], ansible-galaxy, pre-commit hooks) -- **devx package** via `make install-devx` (installs the devx package from git, providing all CI/CD tools) +- **Python deps** via `pip install -e .[dev]` (includes devx from Gitea PyPI registry, configured by `make configure-gitea-pypi`) +- **Post-install setup** via `devx.tools.setup --skip-install` (ansible-galaxy, pre-commit hooks, tea CLI login) - **checkmake** via `devx.tools.install_checkmake` (Makefile linter) - **actionlint, git-cliff, act_runner, tea** via `devx.tools.install_tools` (CI/CD tools to ~/.local/bin) -- **tea CLI login** via `devx.tools.setup` (configures `tea login` from `.env` `REPO_TOKEN`) ## Workflow Verification (Before Push) @@ -69,7 +68,7 @@ The auto-merge workflow enforces the APPROVE review check programmatically as a defense-in-depth measure, but branch protection is the primary gate. ### 1. Create Vikunja Task -Create a task in Vikunja project 6 to get a `GRM-N` identifier. +Create a task in Vikunja project 6 via `make create-task -- --title "Task title" --description "

...

"` (requires `VIKUNJA_TOKEN` in `.env`). This prints the `GRM-N` identifier and next-step instructions. ### 2. Create Branch ```bash @@ -91,7 +90,9 @@ docs: update README ``` ### 5. Push and Create PR -- **PR title format**: `GRM-N: ` (must match the Vikunja task title exactly) +- Push: `git push -u origin HEAD` (pre-push hook validates Vikunja task existence via `devx.tools.pre_push_check`) +- Create PR: `make create-pr` (creates a PR with title `GRM-N: `, auto-derived from the branch name and Vikunja task) +- Or both in one step: `make push-with-pr` - PR body: summary of changes, `Closes GRM-N` - Add `ready-to-merge` label **only after review is complete** diff --git a/Makefile b/Makefile index 30e7cc5..86b5211 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,6 @@ -.PHONY: all setup setup-ci setup-quality setup-molecule setup-release install-devx install update lint ansible-lint makefile-lint lint-all test test-unit pytest-cov molecule molecule-all test-all clean workflow-lint workflow-dryrun workflow-check install-tools +.PHONY: all setup setup-ci setup-quality setup-molecule setup-release install update lint ansible-lint makefile-lint lint-all test test-unit pytest-cov molecule molecule-all test-all clean workflow-lint workflow-dryrun workflow-check install-tools +.PHONY: configure-gitea-pypi +.PHONY: create-task create-pr push-with-pr git-push PYTHON := python3 VENV := .venv @@ -7,45 +9,57 @@ CHECKMAKE := $(shell command -v checkmake 2>/dev/null || echo $(HOME)/go/bin/che all: setup -# Pinned devx version — update this when upgrading devx. -# All workflow files (.gitea/workflows/*.yml) must be updated to match. -DEVX_VERSION := v0.14.1 +# Helper: run pip install with Gitea registry configured +# Usage: $(PIP_INSTALL) install -e '.[ci,lint]' +PIP_INSTALL := if [ -z "$$REPO_TOKEN" ]; then . ./.env 2>/dev/null; fi; \ + REPO_TOKEN="$${REPO_TOKEN:-$$GITEA_REGISTRY_TOKEN}"; \ + if [ -n "$$REPO_TOKEN" ]; then export PIP_EXTRA_INDEX_URL="https://emil:$$REPO_TOKEN@git.oblachno.oblachno.fyi/api/packages/oblachno-oss/pypi/simple/"; fi; \ + $(BIN)/pip -install-devx: $(VENV)/bin/activate - @# REPO_TOKEN may come from .env (local) or environment (CI secrets) +# Configure Gitea private PyPI registry so pip can find devx and other +# private packages. In CI, REPO_TOKEN is set as a secret. Locally, it's in .env. +configure-gitea-pypi: @if [ -z "$$REPO_TOKEN" ]; then . ./.env 2>/dev/null; fi; \ - if [ -z "$$REPO_TOKEN" ]; then echo "REPO_TOKEN not set (check .env or environment)"; exit 1; fi; \ - $(BIN)/pip install "devx==$(shell echo $(DEVX_VERSION) | sed 's/^v//')" \ - --extra-index-url "https://emil:$$REPO_TOKEN@git.oblachno.oblachno.fyi/api/packages/oblachno-oss/pypi/simple/" + REPO_TOKEN="$${REPO_TOKEN:-$$GITEA_REGISTRY_TOKEN}"; \ + if [ -z "$$REPO_TOKEN" ]; then echo "[configure-gitea-pypi] REPO_TOKEN not set — skipping (devx must be on public PyPI)"; exit 0; fi; \ + echo "[configure-gitea-pypi] Gitea PyPI registry configured (REPO_TOKEN present)." # Full setup for local development (all deps, tools, collections, hooks) -# install-devx must run before install-tools (which uses devx modules) -setup: $(VENV)/bin/activate .env activate-scripts install-devx checkmake install-tools +# devx is installed via pip install -e .[dev] (devx is in dev extra) +setup: $(VENV)/bin/activate .env activate-scripts configure-gitea-pypi + @$(PIP_INSTALL) install -e '.[dev]' + @$(BIN)/python -m devx.tools.install_checkmake + @$(BIN)/python -m devx.tools.install_tools @export PATH="$(HOME)/.local/bin:$$PATH"; \ - $(BIN)/python -m devx.tools.setup --bin "$(BIN)" + $(BIN)/python -m devx.tools.setup --bin "$(BIN)" --skip-install # Lean setup for CI jobs that need pytest + lint tools + runtime deps # (detect-changes, discover-runners, pr-review, sync-wiki, badges) # badges job runs generate_badges.py which needs ruff, pyright, bandit -setup-ci: $(VENV)/bin/activate .env install-devx - @$(BIN)/python -m devx.tools.setup --bin "$(BIN)" --extras "ci,lint" --no-ansible-collections --no-pre-commit --no-tea-login +setup-ci: $(VENV)/bin/activate .env configure-gitea-pypi + @$(PIP_INSTALL) install -e '.[ci,lint]' + @$(BIN)/python -m devx.tools.setup --bin "$(BIN)" --skip-install --no-ansible-collections --no-pre-commit --no-tea-login # Setup for the quality job (lint + test deps, actionlint tool) -# install-devx must run before install-tools (which uses devx modules) -setup-quality: $(VENV)/bin/activate .env install-devx install-tools +setup-quality: $(VENV)/bin/activate .env configure-gitea-pypi + @$(PIP_INSTALL) install -e '.[ci,lint]' + @$(BIN)/python -m devx.tools.install_tools @export PATH="$(HOME)/.local/bin:$$PATH"; \ - $(BIN)/python -m devx.tools.setup --bin "$(BIN)" --extras "ci,lint" --no-ansible-collections --no-pre-commit --no-tea-login + $(BIN)/python -m devx.tools.setup --bin "$(BIN)" --skip-install --no-ansible-collections --no-pre-commit --no-tea-login # Full setup for molecule testing (needs ansible, molecule, collections) -setup-molecule: $(VENV)/bin/activate .env install-devx install-tools +setup-molecule: $(VENV)/bin/activate .env configure-gitea-pypi + @$(PIP_INSTALL) install -e '.[ci,molecule]' + @$(BIN)/python -m devx.tools.install_tools @export PATH="$(HOME)/.local/bin:$$PATH"; \ - $(BIN)/python -m devx.tools.setup --bin "$(BIN)" --extras "ci,molecule" --no-pre-commit --no-tea-login + $(BIN)/python -m devx.tools.setup --bin "$(BIN)" --skip-install --no-pre-commit --no-tea-login # Setup for release jobs (needs git-cliff, tea, and lint tools for release.py) -setup-release: $(VENV)/bin/activate .env install-devx +setup-release: $(VENV)/bin/activate .env configure-gitea-pypi + @$(PIP_INSTALL) install -e '.[ci,lint]' @$(BIN)/python -m devx.tools.install_tools --tool git-cliff --tool tea @export PATH="$(HOME)/.local/bin:$$PATH"; \ - $(BIN)/python -m devx.tools.setup --bin "$(BIN)" --extras "ci,lint" --no-ansible-collections --no-pre-commit + $(BIN)/python -m devx.tools.setup --bin "$(BIN)" --skip-install --no-ansible-collections --no-pre-commit .env: @if [ ! -f .env ]; then \ @@ -64,14 +78,14 @@ activate-scripts: $(VENV)/bin/activate @test -f activate.zsh || (echo '#!/usr/bin/env zsh' > activate.zsh && echo '0="$${ZERO:-$${0:#$$ZSH_ARGZERO}}"' >> activate.zsh && echo '0="$${$${(M)0:#/*}:-$$PWD/$$0}"' >> activate.zsh && echo 'source "$${0:A:h}/.venv/bin/activate"' >> activate.zsh && chmod +x activate.zsh) install-hooks: - @cp hooks/pre-commit .git/hooks/pre-commit && chmod +x .git/hooks/pre-commit - @cp hooks/pre-push .git/hooks/pre-push && chmod +x .git/hooks/pre-push - @echo "Git hooks installed." + @git config core.hooksPath hooks + @chmod +x hooks/pre-commit hooks/pre-push 2>/dev/null || true + @echo "core.hooksPath set to hooks/ — tracked hooks are now live." -checkmake: install-devx +checkmake: @$(BIN)/python -m devx.tools.install_checkmake -install-tools: install-devx +install-tools: @$(BIN)/python -m devx.tools.install_tools install: @@ -181,3 +195,21 @@ clean: find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true find . -type f -name "*.pyc" -delete 2>/dev/null || true rm -rf .coverage htmlcov/ .molecule/ + +# --- Vikunja task and PR management (via devx.mak fragment) ------------------- +# Project config (task prefix, Vikunja project ID, repo owner/name) is read +# from [tool.devx] in pyproject.toml by devx.config — no Makefile variables needed. +DEVX_PYTHON := $(BIN)/python + +# Include shared targets from devx package (create-task, create-pr, push-with-pr, check-config) +# Silent if devx not installed yet — run 'make setup' first. +DEVX_MAK := $(shell $(BIN)/python -c \ + "from pathlib import Path; import devx; print(Path(devx.__file__).parent / 'make' / 'devx.mak')" \ + 2>/dev/null) +-include $(DEVX_MAK) + +# Aliases for project-specific target names +create-task: devx-create-task +create-pr: devx-create-pr +push-with-pr: devx-push-with-pr +git-push: devx-push diff --git a/hooks/pre-push b/hooks/pre-push index 3565c2c..806cf7c 100755 --- a/hooks/pre-push +++ b/hooks/pre-push @@ -1,6 +1,61 @@ #!/usr/bin/env bash -# pre-push hook: fail if unit tests are too slow. -# Checks both total suite time (10s) and per-test time (0.5s). -# Aligned with CI (ci.yml uses same thresholds). -set -e -python3 -m devx.tools.check_test_speed --max-seconds 4 --max-single-seconds 0.5 +# pre-push hook: validate Vikunja task exists and tests are fast. +# +# This catches issues that would otherwise only surface in CI: +# - Branch name missing task ID (e.g., GRM-N) +# - Vikunja task does not exist for the task ID in the branch name +# - Unit tests too slow (total > 4s, per-test > 0.5s) +# +# Uses devx.tools.pre_push_check for reusable validation logic. +# Project config (task prefix, Vikunja project ID) is read from +# [tool.devx] in pyproject.toml by devx.config — no hardcoded values here. +# +# Bootstrap resilience: if devx is not importable (e.g., during devx +# upgrades), the hook prints a warning and allows the push. + +# Determine the branch being pushed +BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "") + +if [ -z "$BRANCH" ] || [ "$BRANCH" = "master" ] || [ "$BRANCH" = "main" ]; then + exit 0 +fi + +# Load .env if present (for VIKUNJA_TOKEN) +if [ -f .env ]; then + set -a + # shellcheck disable=SC1091 + . .env + set +a +fi + +# Find the Python interpreter with devx installed +if [ -f .venv/bin/python ]; then + PY=.venv/bin/python +elif [ -x "${HOME}/.pyenv/bin/pyenv" ]; then + export PYENV_ROOT="${HOME}/.pyenv" + export PATH="${PYENV_ROOT}/bin:${PYENV_ROOT}/shims:${PATH}" + eval "$("${PYENV_ROOT}/bin/pyenv" init -)" 2>/dev/null || true + eval "$("${PYENV_ROOT}/bin/pyenv" virtualenv-init -)" 2>/dev/null || true + pyenv activate gitea-runner-manager 2>/dev/null || true + PY=python3 +else + PY=python3 +fi + +# Bootstrap resilience: if devx is not importable, warn but allow the push +if ! $PY -c "import devx.tools.pre_push_check" 2>/dev/null; then + echo "WARNING: devx not installed — pre-push check skipped." + echo "Run 'make setup' to install devx." + exit 0 +fi + +# Run pre-push validation via devx +$PY -m devx.tools.pre_push_check --branch "$BRANCH" || { + echo "" + echo "Pre-push validation failed. Fix the issues above before pushing." + echo "To bypass (NOT recommended): git push --no-verify" + exit 1 +} + +# Check test speed (aligned with CI thresholds) +$PY -m devx.tools.check_test_speed --max-seconds 4 --max-single-seconds 0.5 diff --git a/pyproject.toml b/pyproject.toml index 19382d7..0f8cec9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,8 @@ ci = [ "pytest-cov>=7.1.0", "build>=1.5.0", "twine>=6.2.0", + # Reusable CI/CD and dev tools (auto-merge, pr-review, pre-push checks, etc.) + "devx>=0.15.0", ] # Lint and type-checking tools (quality job) lint = [ @@ -52,6 +54,8 @@ molecule = [ # Full dev environment (local development, includes everything) dev = [ "gitea-runner-manager[ci,lint,molecule]", + # Reusable CI/CD and dev tools (pre-push hooks, create-task, create-pr) + "devx>=0.15.0", # Non-Python dev dependency: checkmake (Makefile linter) # Install via: go install github.com/checkmake/checkmake/cmd/checkmake@latest ] @@ -91,6 +95,13 @@ strict = ["src/gitea_runner_manager"] # Change classification — determines which changes trigger a release # --------------------------------------------------------------------------- # The framework provides DEFAULT_INFRASTRUCTURE (CI workflows, tests, docs, +# Project-specific devx configuration (read by devx.config) +[tool.devx] +task_prefix = "GRM" +vikunja_project_id = 6 +repo_owner = "oblachno-oss" +repo_name = "gitea-runner-manager" + # lint config, etc.) that applies to any Python project. We only specify # what's different about GRM. [tool.devx.classify]