diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 4fdbd94..23ebc36 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -30,30 +30,16 @@ jobs: run: | . .venv/bin/activate 2>/dev/null || true make pytest-cov - - name: Documentation lint check - env: - PYTHONPATH: src - run: | - . .venv/bin/activate 2>/dev/null || true - python3 -m devx.ci.lint_docs --root . - - name: Documentation version reference check - env: - PYTHONPATH: src - run: | - . .venv/bin/activate 2>/dev/null || true - python3 -m devx.tools.check_doc_versions --root . --package grm - - name: Vale prose lint check + - name: Documentation gate (coverage + stale refs + lint + version refs + prose) env: PYTHONPATH: src + DEVX_DOC_COVERAGE_STRICT: "1" + DEVX_DOC_VERSIONS_PKG: grm + DEVX_VALE_LEVEL: warning run: | . .venv/bin/activate 2>/dev/null || true export PATH="$HOME/.local/bin:$PATH" - # Install vale if not present - if ! command -v vale >/dev/null 2>&1; then - python3 -m devx.tools.install_tools --tool vale - fi - vale sync - vale --minAlertLevel=error docs/ AGENTS.md README.md + make devx-docs-check - name: Translation completeness check run: | . .venv/bin/activate 2>/dev/null || true diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 99314f4..d1c7acf 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -81,11 +81,12 @@ repos: pass_filenames: false stages: [pre-commit] - - id: lint-docs - name: documentation lint check - entry: env PYTHONPATH=src .venv/bin/python -m devx.ci.lint_docs --root . + - id: docs-check + name: documentation gate (coverage + stale refs + lint + version refs + prose) + entry: bash -c 'PYTHONPATH=src DEVX_DOC_COVERAGE_STRICT=1 DEVX_DOC_VERSIONS_PKG=grm DEVX_VALE_LEVEL=warning make devx-docs-check' language: system pass_filenames: false + always_run: true stages: [pre-commit] - id: pytest-cov diff --git a/.vale.ini b/.vale.ini index c33dc28..40537d6 100644 --- a/.vale.ini +++ b/.vale.ini @@ -34,14 +34,17 @@ write-good.E-Prime = NO write-good.So = NO write-good.ThereIs = NO write-good.TooWordy = NO +write-good.Passive = NO # Vale defaults — spelling catches too many technical terms Vale.Terms = NO Vale.Repetition = NO Vale.Spelling = NO -# Readability — warnings only, technical docs are naturally complex +# Readability — technical docs are naturally complex, downgrade to suggestions Readability.FleschReadingEase = suggestion +Readability.FleschKincaid = suggestion +Readability.AutomatedReadability = suggestion Readability.ColemanLiau = suggestion Readability.LIX = suggestion Readability.GunningFog = suggestion diff --git a/.vale/styles/devx/CodeBlockLanguage.yml b/.vale/styles/devx/CodeBlockLanguage.yml index eb8a2e1..6361e05 100644 --- a/.vale/styles/devx/CodeBlockLanguage.yml +++ b/.vale/styles/devx/CodeBlockLanguage.yml @@ -3,4 +3,4 @@ message: "Unlabeled code block — add a language tag (```bash, ```yaml, etc.)" level: warning scope: raw raw: - - '(?s)```\n(?!.*```)' + - '(?ms)^\n```\n.*?^```\s*$' diff --git a/.vale/styles/write-good/README.md b/.vale/styles/write-good/README.md index 953c5a1..3edcc9b 100644 --- a/.vale/styles/write-good/README.md +++ b/.vale/styles/write-good/README.md @@ -2,7 +2,7 @@ Based on [write-good](https://github.com/btford/write-good). > Naive linter for English prose for developers who can't write good and wanna learn to do other stuff good too. -```text +``` The MIT License (MIT) Copyright (c) 2014 Brian Ford diff --git a/AGENTS.md b/AGENTS.md index eab6e35..30ddcc3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -166,7 +166,7 @@ it attests that the reviewer has gone through every checklist category. The `--checklist-categories` flag is also **required** — it must list at least 8 of the 13 category numbers, ensuring the reviewer actually checked each category rather than rubber-stamping. The review body must -be substantive (> 50 characters) — trivial approvals like "LGTM" are +be substantive (> 50 characters) — perfunctory approvals like "LGTM" are rejected. Then add the `ready-to-merge` label. The auto-merge workflow will: @@ -244,7 +244,7 @@ Vikunja task updates: - Loops are prevented by `has_unreleased_changes` — after a release commit is tagged, the next run finds no unreleased changes and exits 3. **sync-wiki** — Syncs documentation to the Gitea wiki. Runs for ALL - non-release commits (not just when release succeeds), so docs-only + non-release commits (not only when release succeeds), so docs-only changes still update the wiki. 4. **badges** — Generates and pushes quality badge SVGs to the `badges` branch. @@ -253,7 +253,7 @@ Vikunja task updates: any release commits. 5. **vikunja** — Marks the corresponding Vikunja task as done. Runs for ALL - non-release commits (not just when release succeeds), so infrastructure-only + non-release commits (not only when release succeeds), so infrastructure-only changes still update the task tracker. 6. **publish** — Runs after release succeeds (needs: release). Builds and @@ -486,7 +486,7 @@ main.yml → systemd_check → user_setup → rootless_docker → install_runner - `install_runner.yml` handles: download, config, validate, register, service - `main.yml` handles: prune, integration_test (NOT install_runner — avoids duplicates) - `systemctl --user` tasks must be guarded by `docker_rootless_setup` -- Template creation tasks are NOT guarded by `docker_rootless_setup` (they just create files) +- Template creation tasks are NOT guarded by `docker_rootless_setup` (they only create files) ## Molecule Scenarios @@ -590,7 +590,7 @@ the user should not need to specify which profile to use. 2. **Background by default, foreground when blocking.** 3. **Provide full context in the prompt** — subagents don't inherit conversation history. 4. **One subagent per concern.** Chain: investigate → fix in main session → review. -5. **Don't delegate trivial work** (<30s, <50 lines of context). +5. **Don't delegate minor work** (<30s, <50 lines of context). 6. **Compact after subagent returns.** 7. **Never skip delegation to save time** — it keeps main context small. diff --git a/Makefile b/Makefile index 138d4a9..524377a 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ .PHONY: all setup setup-ci setup-quality setup-molecule setup-release setup-image install update lint ansible-lint makefile-lint lint-all lint-ruff lint-format lint-bandit lint-deps typecheck checkmake install-hooks test test-unit pytest-cov molecule molecule-all test-all clean workflow-lint workflow-dryrun workflow-check install-tools check-api-identity-checks .PHONY: configure-gitea-pypi .PHONY: create-task create-pr push-with-pr git-push +.PHONY: check-docs docs-check PYTHON := python3 VENV := .venv @@ -202,3 +203,7 @@ create-task: devx-create-task create-pr: devx-create-pr push-with-pr: devx-push-with-pr git-push: devx-push + +# --- Documentation checks (via devx.mak fragment) ----------------------------- +check-docs: devx-check-docs +docs-check: devx-docs-check diff --git a/README.md b/README.md index 35afd55..734364c 100644 --- a/README.md +++ b/README.md @@ -147,7 +147,7 @@ GRM provides a single `grm` command with subcommands for the full runner lifecyc | `grm enable ` | Enable a runner to start on boot | | `grm disable ` | Disable and deregister a runner | | `grm status ` | Check the status of a registered runner | -| `grm remove ` | Remove a runner completely (with remote cleanup) | +| `grm remove ` | Remove a runner entirely (with remote cleanup) | | `grm remove --force` | Remove only the local registry entry (skip remote cleanup) | | `grm list` | List all registered runners with live status | | `grm list --no-status` | List registered runners without SSH status checks | @@ -187,7 +187,7 @@ GRM reads configuration from a `.env` file in the current directory (loaded auto ### Sudo Password Handling -GRM delegates remote operations to Ansible, which uses `sudo` (become) on the target host. There are several ways to provide the sudo password, in priority order: +GRM delegates remote operations to Ansible, which uses `sudo` (become) on the target host. There are multiple ways to provide the sudo password, in priority order: 1. **`--become-password-file `** (CLI flag, global) — Read sudo password from a file. Works for all commands including `grm list`. 2. **`GRM_BECOME_PASSWORD_FILE`** (env var) — Same as above, set in `.env` or environment. diff --git a/docs/tech/architecture.md b/docs/tech/architecture.md index 62dbfaa..701be21 100644 --- a/docs/tech/architecture.md +++ b/docs/tech/architecture.md @@ -44,7 +44,7 @@ main.yml → systemd_check → user_setup → rootless_docker → install_runner - `install_runner.yml` handles: download, config, validate, register, service - `main.yml` handles: prune, integration_test (NOT install_runner — avoids duplicates) - `systemctl --user` tasks must be guarded by `docker_rootless_setup` -- Template creation tasks are NOT guarded by `docker_rootless_setup` (they just create files) +- Template creation tasks are NOT guarded by `docker_rootless_setup` (they only create files) ### Ansible task files diff --git a/docs/tech/contributing.md b/docs/tech/contributing.md index bb041e0..faf258f 100644 --- a/docs/tech/contributing.md +++ b/docs/tech/contributing.md @@ -202,7 +202,7 @@ main.yml → systemd_check → user_setup → rootless_docker → install_runner - `install_runner.yml` handles: download, config, validate, register, service - `main.yml` handles: prune, integration_test (NOT install_runner — avoids duplicates) - `systemctl --user` tasks must be guarded by `docker_rootless_setup` -- Template creation tasks are NOT guarded by `docker_rootless_setup` (they just create files) +- Template creation tasks are NOT guarded by `docker_rootless_setup` (they only create files) - `apt` tasks use `cache_valid_time: 3600` to avoid unnecessary cache updates - `remove-runner.yml` runs `loginctl disable-linger` and removes subuid/subgid entries diff --git a/docs/tech/decision-log.md b/docs/tech/decision-log.md index 5abe4f7..5f65370 100644 --- a/docs/tech/decision-log.md +++ b/docs/tech/decision-log.md @@ -94,7 +94,7 @@ Key technical decisions for the GRM project, extracted from `CHANGELOG.md` and ` **Decision:** Classify changed files into user-facing and workflow-only categories using `devx.ci.classify_changes`. Only user-facing changes trigger a release; workflow-only changes (CI, docs, tests, lint config) do not. -**Rationale:** Not all changes require a new release. CI workflow updates, documentation improvements, and test additions should not produce a new version tag. The classification is config-driven via `[tool.devx.classify]` in `pyproject.toml`. The strategy is safe-by-default: any file NOT in the explicit workflow-only allowlist is treated as user-facing, preventing new file types from accidentally skipping releases. User-facing paths include `src/grm/**` (except `__init__.py`) and `ansible/**`. Workflow-only paths include `.gitea/**`, `docs/**`, `tests/**`, `scripts/**`, and various config files. +**Rationale:** Not all changes require a new release. CI workflow updates, documentation improvements, and test additions should not produce a new version tag. The classification is config-driven via `[tool.devx.classify]` in `pyproject.toml`. The strategy is safe-by-default: any file NOT in the explicit workflow-only allowlist is treated as user-facing, preventing new file types from accidentally skipping releases. User-facing paths include `src/grm/**` (except `__init__.py`) and `ansible/**`. Workflow-only paths include `.gitea/**`, `docs/**`, `tests/**`, `scripts/**`, and other config files. **Source:** `AGENTS.md` (Smart CI: User-Facing vs Workflow-Only Changes), `pyproject.toml` (`[tool.devx.classify]`) diff --git a/docs/user/cli-commands.md b/docs/user/cli-commands.md index 47896fd..ab484e6 100644 --- a/docs/user/cli-commands.md +++ b/docs/user/cli-commands.md @@ -14,7 +14,7 @@ GRM provides the following CLI commands for managing Gitea Actions runners. The | `grm enable` | `` | Enable a runner to start on boot | | `grm disable` | `` | Disable and deregister a runner | | `grm status` | `` | Check the status of a registered runner | -| `grm remove` | `` | Remove a runner completely | +| `grm remove` | `` | Remove a runner entirely | | `grm list` | — | List all registered runners with live status | | `grm health` | `[runner_name]` | Run health check (Docker, runner service, disk) on one or all runners | | `grm trigger-workflow` | `` | Trigger a Gitea Actions workflow via the API | @@ -244,7 +244,7 @@ grm status [options] ## remove -Remove a registered Gitea Runner completely. +Remove a registered Gitea Runner entirely. ```bash grm remove [options] diff --git a/docs/user/faq.md b/docs/user/faq.md index fe3af17..64494ee 100644 --- a/docs/user/faq.md +++ b/docs/user/faq.md @@ -1,6 +1,6 @@ # FAQ -## How do I obtain the Gitea registration token? +## How to obtain the Gitea registration token There are three levels of registration tokens, depending on which repositories the runner should serve: @@ -10,15 +10,15 @@ There are three levels of registration tokens, depending on which repositories t Set the token as `GITEA_REGISTRATION_TOKEN` in your `.env` file or pass it via `--token` on the command line. -## What is the CI_GITEA_TOKEN and do I need it? +## What is the CI_GITEA_TOKEN and is it needed? `CI_GITEA_TOKEN` is a Gitea admin API token used for optional post-install verification. When set, GRM queries the Gitea API after installation to confirm the runner appears in the runner list. This is purely informational — the integration test passes/fails based on the `.runner` file and systemd service, not the API check. To generate one: Settings → Applications → Generate New Token, with the `admin` scope (or at minimum `read:user`, `read:repository`, `read:admin`). -If you skip it, GRM will still verify the runner correctly — it just won't show the extra API confirmation. +If you skip it, GRM will still verify the runner correctly — it won't show the extra API confirmation. -## How do I skip the sudo password prompt for automation? +## How to skip the sudo password prompt for automation Configure passwordless sudo on the remote host and pass `--no-ask-become-pass` to the CLI command. This is recommended for CI/CD pipelines. @@ -34,7 +34,7 @@ Then use: grm install 192.168.1.10 --user ubuntu --key ~/.ssh/id_ed25519 --name prod-runner --no-ask-become-pass ``` -## Can I run multiple runners on the same host? +## Can multiple runners run on the same host? Yes. Each runner instance is fully isolated with its own system user (`grm-`), rootless Docker daemon, data directory, and systemd user service. Install additional runners with different `--name` values and manage them independently by name. @@ -46,7 +46,7 @@ grm list Runners on the same host never interfere with each other or with the host's Docker installation. -## Why does my runner appear offline after installation? +## Why does a runner appear offline after installation? Check that `GITEA_URL` and `GITEA_REGISTRATION_TOKEN` are correct, verify the runner service is running with `sudo -u grm- systemctl --user status gitea-runner`, and check the logs for registration errors. You can also confirm the runner appears as **Online** in the Gitea UI under **Actions → Runners**. @@ -64,7 +64,7 @@ This is a harmless cleanup traceback from Molecule's Docker driver when the test GRM stores each runner's connection details (host, user, SSH key, Gitea URL, labels) in a local JSON registry at `~/.local/share/grm/runners.json`. After installation, lifecycle commands work by runner name only — you can override any stored value by passing the corresponding flag. -## How do I update the gitea_runner binary? +## How to update the gitea_runner binary Use the `grm update` command: @@ -80,7 +80,7 @@ grm update 192.168.1.10 --user ubuntu --version 1.0.8 The update command downloads the new binary and replaces the existing one at `/usr/local/bin/gitea_runner`. The runner service is restarted automatically. -## How do I completely remove a runner? +## How to remove a runner entirely Use the `grm remove` command: @@ -99,13 +99,13 @@ grm remove prod-runner --force ## What is the difference between disable and remove? - **`grm disable `** — Deregisters the runner from Gitea and stops the service, but leaves the user, directories, and service files in place. The runner can be re-enabled later with `grm enable` and re-registered with a new token. -- **`grm remove `** — Completely removes the runner: deregisters from Gitea, stops and disables the service, removes the system user, deletes all directories, and removes the local registry entry. This is irreversible. +- **`grm remove `** — Removes the runner entirely: deregisters from Gitea, stops and disables the service, removes the system user, deletes all directories, and removes the local registry entry. This is irreversible. ## What operating systems are supported? GRM supports Arch Linux (rolling), Ubuntu 22.04/24.04, and Debian 12. All supported OSes are tested in CI via Molecule scenarios on every PR that changes Ansible files. -## How do I change the UI language? +## How to change the UI language Set the `GRM_LANG` environment variable to one of the supported languages: `en` (English, default), `bg` (Bulgarian), `de` (German), `ru` (Russian), `zh` (Chinese), `pl` (Polish). @@ -119,7 +119,7 @@ Or set it in your `.env` file: GRM_LANG=bg ``` -## How do I enable debug logging? +## How to enable debug logging Set the `GRM_LOG_LEVEL` environment variable to `DEBUG`: @@ -129,7 +129,7 @@ GRM_LOG_LEVEL=DEBUG grm install 192.168.1.10 --user ubuntu --name prod-runner The log file at `~/.local/state/grm/logs/grm.log` always captures DEBUG level regardless of this setting. Ansible execution logs are stored in timestamped files at `~/.local/state/grm/logs/ansible-.log`. -## What runner labels should I use? +## What runner labels should be used? By default, runners are registered with `docker,ubuntu-latest:docker://runner-images:ubuntu-22.04`. You can override this with `--labels` or the `GITEA_RUNNER_LABELS` environment variable. @@ -151,7 +151,7 @@ Yes. GRM is designed with security as a first-class concern: - **No shell injection**: The CLI never uses `shell=True` with subprocess. - **Bandit security scan**: The CI pipeline runs Bandit on every PR. -## Can I install GRM via pip? +## Can GRM be installed via pip? Yes: diff --git a/docs/user/installation.md b/docs/user/installation.md index d2ce7a2..2fee952 100644 --- a/docs/user/installation.md +++ b/docs/user/installation.md @@ -223,7 +223,7 @@ make update HOST=192.168.1.10 USER=ubuntu VERSION=1.0.8 ## Removing Runners -To remove a runner completely (deregisters from Gitea, removes user, directories, and service files): +To remove a runner entirely (deregisters from Gitea, removes user, directories, and service files): ```bash grm remove prod-runner --token diff --git a/pyproject.toml b/pyproject.toml index 997f5e0..3016f9f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,7 @@ ci = [ "build==1.5.0", "twine==6.2.0", # Reusable CI/CD and dev tools (auto-merge, pr-review, pre-push checks, etc.) - "devx==0.35.1", + "devx==0.37.0", ] # Lint and type-checking tools (quality job) lint = [ @@ -54,7 +54,7 @@ molecule = [ dev = [ "grm[ci,lint,molecule]", # Reusable CI/CD and dev tools (pre-push hooks, create-task, create-pr) - "devx==0.35.1", + "devx==0.37.0", # Non-Python dev dependency: checkmake (Makefile linter) # Install via: go install github.com/checkmake/checkmake/cmd/checkmake@latest ] @@ -101,6 +101,11 @@ vikunja_project_id = 6 repo_owner = "oblachno-oss" repo_name = "grm" +# grm doesn't have its own CI scripts — it uses devx's CI modules. +# Skip CI script checks (doc_coverage would otherwise look for src/ci/). +[tool.devx.doc_coverage] +ci_scripts_dir = "" + # Molecule test weights for LPT scheduling. # GRM has a single role (gitea-runner) with 7 scenarios. # Weights are estimates — recalibrate from CI logs after next run.