CI / validate (pull_request) Waiting to run
CI / molecule-tests (2) (pull_request) Waiting to run
CI / molecule-tests (5) (pull_request) Waiting to run
CI / molecule-tests (1) (pull_request) Waiting to run
CI / molecule-tests (3) (pull_request) Waiting to run
CI / auto-merge (pull_request) Waiting to run
CI / molecule-tests (6) (pull_request) Waiting to run
CI / molecule-tests (4) (pull_request) Waiting to run
Arch Linux's docker package does not ship the rootless setup scripts (dockerd-rootless-setuptool.sh, dockerd-rootless.sh) or rootlesskit, unlike Debian's docker-ce-rootless-extras. The role now fetches them from moby/moby contrib/ at a pinned ref and installs rootlesskit explicitly. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
626 lines
30 KiB
Markdown
626 lines
30 KiB
Markdown
# AGENTS.md — Project Conventions for GRM
|
|
|
|
## Virtual Environment
|
|
|
|
All Python tools, tests, and scripts run inside a standard `.venv` directory.
|
|
Activate it before running any non-`make` command:
|
|
|
|
```bash
|
|
source activate.sh # bash/zsh
|
|
source activate.fish # fish
|
|
source activate.zsh # zsh
|
|
```
|
|
|
|
If `.venv` doesn't exist, run `make setup` first. The `make` targets handle
|
|
venv activation automatically — always prefer `make <target>` over raw commands.
|
|
|
|
## Build & Test Commands
|
|
|
|
```bash
|
|
make setup # Create venv, install deps, set up hooks, install CI tools
|
|
make install-tools # Install actionlint, git-cliff, act_runner to ~/.local/bin
|
|
make lint-all # ruff + pyright + bandit + ansible-lint + checkmake + actionlint
|
|
make pytest-cov # Unit tests with 100% coverage enforcement
|
|
make test-unit # Unit tests without coverage
|
|
make molecule # All 6 scenarios on Ubuntu 22.04
|
|
make molecule-all # All 6 scenarios on all 4 supported OSes
|
|
make test-all # pytest-cov + molecule
|
|
make workflow-lint # Static lint of .gitea/workflows/*.yml (actionlint)
|
|
make workflow-dryrun # Dry-run all workflows in Docker (act_runner exec --dryrun)
|
|
make workflow-check # workflow-lint + workflow-dryrun
|
|
```
|
|
|
|
`make setup` automatically installs all development 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)
|
|
|
|
## Workflow Verification (Before Push)
|
|
|
|
Workflow YAML files (`.gitea/workflows/*.yml`) are verified with two tools:
|
|
|
|
1. **actionlint** — Static linter that catches syntax errors, invalid
|
|
expressions, unknown keys, type mismatches, and shellcheck issues.
|
|
Config: `.gitea/actionlint.yaml` (registers custom `docker` runner label).
|
|
Installed automatically by `make setup` via `devx.tools.install_tools`.
|
|
|
|
2. **act_runner exec --dryrun** — Gitea's own runner in dry-run mode.
|
|
Validates job dependencies, step ordering, and Docker image selection
|
|
without starting containers. Installed automatically by `make setup`.
|
|
|
|
Both run via `make workflow-check` and are part of `make lint-all`.
|
|
The pre-commit hook runs actionlint automatically when workflow files change.
|
|
The CI `validate` job runs `make setup-image` (which installs all tools) then `make lint-all`.
|
|
CI also runs a best-effort `make workflow-dryrun` step (skipped if act_runner is not installed in the CI Docker image).
|
|
|
|
## Architecture
|
|
|
|
- **Python CLI** (`src/grm/`) — Click-based CLI that delegates to Ansible
|
|
- **Ansible Role** (`ansible/roles/gitea_runner/`) — Idempotent role for rootless Docker runner setup with pasta networking (IPv6 support)
|
|
- **devx package** (installed from git) — Reusable CI/CD tools: auto-merge, post-merge, release, publishing, molecule distribution, PR reviews, failure notifications
|
|
- **Versioning** (`cliff.toml`) — git-cliff configuration for automated semver versioning from conventional commits
|
|
|
|
## PR Workflow (Mandatory)
|
|
|
|
Every change to master goes through this workflow. No exceptions.
|
|
|
|
### Branch Protection (Required Gitea Settings)
|
|
|
|
Branch protection and labels are automatically configured by
|
|
`devx.tools.configure_repo` (run as `python -m devx.tools.configure_repo`),
|
|
which runs as a step in the `detect-and-configure` job in
|
|
the post-merge workflow on every push to master.
|
|
|
|
The following rules are enforced for `master`:
|
|
- **Require pull request**: No direct pushes to master
|
|
- **Require approval review**: At least 1 `APPROVE` review before merge
|
|
- **Require status checks**: CI validate + molecule tests must pass
|
|
- **Block force pushes**: No history rewriting on master
|
|
|
|
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 via `make create-task -- --title "Task title" --description "<h2>...</h2>"` (requires `VIKUNJA_TOKEN` in `.env`). This prints the `GRM-N` identifier and next-step instructions.
|
|
|
|
**IMPORTANT:** The task title must NOT include the `GRM-N:` prefix.
|
|
The `make create-pr` and `check_auto_merge_ready` commands automatically
|
|
prepend `GRM-N: ` to the Vikunja task title when forming the PR title.
|
|
If the Vikunja task title already includes the prefix, the PR title will
|
|
have a double prefix and auto-merge validation will fail.
|
|
|
|
### 2. Create Branch
|
|
```bash
|
|
git checkout master && git pull
|
|
git checkout -b GRM-N-short-description
|
|
```
|
|
|
|
### 3. Implement Changes
|
|
- Write code following conventions below
|
|
- Write/update tests (100% coverage required)
|
|
- Update documentation (CHANGELOG, README, AGENTS.md as needed)
|
|
|
|
### 4. Commit (Conventional Commits)
|
|
Branch commits use conventional commit format (no `GRM-N:` prefix):
|
|
```text
|
|
feat: add new feature
|
|
fix: resolve bug
|
|
docs: update README
|
|
```
|
|
|
|
### 5. Push and Create PR
|
|
- 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: <vikunja task title>`, 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**
|
|
|
|
### 6. Review the PR (Mandatory — Before Adding ready-to-merge Label)
|
|
|
|
**Review checklist:** Every PR is reviewed against 13 categories covering
|
|
architecture, code quality, security, i18n, testing, performance,
|
|
UX, documentation, workflow compliance, maintainability, resource
|
|
management, backwards compatibility, and logging.
|
|
|
|
**Automated review (CI `validate` job):** Every PR triggers an automated
|
|
review via `python -m devx.ci.pr_review` as a step in the `validate` job.
|
|
This posts a review with
|
|
`COMMENT` (no issues) or `REQUEST_CHANGES` (issues found) based on
|
|
the **[auto]** items in the checklist:
|
|
|
|
- Architecture compliance (no subprocess in CLI, no hardcoded URLs)
|
|
- Best practices (no `print()`, no bare `except`, no `TODO`/`FIXME`,
|
|
no functions > 50 lines)
|
|
- Security (no hardcoded secrets, no `shell=True`, no `eval`/`exec`)
|
|
- i18n (no raw strings in `click.echo()` without `_()` wrapper)
|
|
- Resource management (no `open()` without `with`, no `Popen()` without cleanup)
|
|
- Documentation (source changes must include doc updates)
|
|
- Test coverage (source changes must include test updates)
|
|
- Commit conventions (conventional commit format on PR commits)
|
|
|
|
The automated review posts inline comments on specific lines and
|
|
includes a summary of the checklist categories. The agent **must** address all
|
|
`REQUEST_CHANGES` issues before proceeding.
|
|
|
|
**Manual review (agent):** After the automated review passes, the agent
|
|
must go through **every category** listed above and verify
|
|
the **[manual]** items by reviewing the full diff
|
|
(`git diff master...HEAD`).
|
|
|
|
Post review comments using `devx.ci.pr_review` (run as `python -m devx.ci.pr_review`):
|
|
```bash
|
|
CI_GITEA_TOKEN=<token> python -m devx.ci.pr_review <pr_number> <owner/repo> \
|
|
--event REQUEST_CHANGES \
|
|
--body "Review summary"
|
|
```
|
|
|
|
### 7. Address Review Comments
|
|
Fix each comment one by one, commit, and push. Re-review until satisfied.
|
|
|
|
### 8. Approve and Merge
|
|
Once all checklist items are verified and comments are addressed, post
|
|
an approval review with `--checklist-confirmed` and `--checklist-categories`:
|
|
```bash
|
|
CI_GITEA_TOKEN=<token> python -m devx.ci.pr_review <pr_number> <owner/repo> \
|
|
--event APPROVE --checklist-confirmed \
|
|
--checklist-categories 1,2,3,4,5,6,7,8,9,10,11,12,13 \
|
|
--body "All 13 checklist categories verified. Architecture: <summary>. Security: <summary>. Tests: <summary>. Docs: <summary>."
|
|
```
|
|
|
|
The `--checklist-confirmed` flag is **required** for APPROVE events —
|
|
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) — perfunctory approvals like "LGTM" are
|
|
rejected.
|
|
|
|
Then add the `ready-to-merge` label. The auto-merge workflow will:
|
|
1. **Validate** PR title format (`GRM-N: <vikunja task title>`) and match against Vikunja task title
|
|
2. **Check** that at least one substantive APPROVE review exists (body > 20 chars or has inline comments)
|
|
3. Wait for all CI checks to pass (including the `validate` job)
|
|
4. Squash-merge with title: `GRM-N: <conventional commit message>`
|
|
5. The post-merge workflow marks the Vikunja task as done
|
|
6. The release-and-maintain job automatically versions, tags, and publishes (see below)
|
|
|
|
**If the branch is behind master** (another PR merged first), auto-merge
|
|
automatically rebases the PR's head branch via the Gitea API. This triggers
|
|
a new CI run. The next auto-merge attempt will merge successfully.
|
|
No manual rebase needed. To rebase manually: `make rebase` (local) or
|
|
`make pr-rebase` (server-side via API).
|
|
|
|
> **IMPORTANT**: Never manually merge PRs via the API. Always use the auto-merge
|
|
> workflow by adding the `ready-to-merge` label. Manual merges bypass the
|
|
> `GRM-N: <conventional>` format enforcement, producing incorrectly named commits.
|
|
> The auto-merge script validates the PR title matches the Vikunja task ID
|
|
> and conventional commit format before merging.
|
|
|
|
### CI Path Filtering
|
|
|
|
The CI workflow's `validate` job includes a pre-merge validation step
|
|
that validates branch format, PR title, and Vikunja task match. This
|
|
fails fast before expensive molecule tests run.
|
|
|
|
The `validate` job also includes a `detect-changes` step that checks
|
|
whether any files under `ansible/` or `.ansible-lint` have changed. If
|
|
no Ansible files are changed, molecule tests are skipped — this prevents
|
|
non-Ansible changes (e.g., Python scripts, workflow YAML, docs) from
|
|
being blocked by molecule test infrastructure flakiness.
|
|
|
|
### Dynamic Runner Discovery
|
|
|
|
Molecule tests are distributed across available Gitea Actions runners
|
|
dynamically via `devx.molecule.discover_runners`. The `validate` job
|
|
includes a `discover-runners` step (conditional on ansible-changed) that
|
|
queries the Gitea API for runners at all levels (repo, org, instance)
|
|
and generates a dynamic matrix. If the API can't see instance-level runners
|
|
(no admin scope), it falls back to the `MOLECULE_RUNNERS` repo variable,
|
|
then to a default of 3.
|
|
|
|
**When adding/removing Gitea runners:**
|
|
1. Repo/org-level runners are auto-detected via the API
|
|
2. For instance-level runners, update the `MOLECULE_RUNNERS` repo variable
|
|
3. The workflow automatically scales the matrix to match available runners
|
|
|
|
### Automated Release Pipeline
|
|
|
|
After a PR is merged to master, the **post-merge workflow**
|
|
(`.gitea/workflows/post-merge.yml`) runs automatically. Consolidated
|
|
into 2 jobs (from 7) to reduce runner overhead:
|
|
|
|
1. **detect-and-configure** — Configures repo (branch protection, labels),
|
|
detects release commit, validates commit message. Outputs `is-release`
|
|
and `is-automated` for the next job.
|
|
|
|
2. **release-and-maintain** — Runs all post-merge maintenance as
|
|
conditional steps:
|
|
- **release** (if not a release commit) — Runs `devx.ci.release` which
|
|
checks for user-facing changes via `classify_changes` (skips if only
|
|
workflow/infrastructure files changed), uses git-cliff for semver,
|
|
updates `__version__`, updates `CHANGELOG.md`, runs lint+tests, commits
|
|
with `release: vX.Y.Z [skip ci]`, creates annotated tag, pushes to master.
|
|
- **publish** (if release created a tag) — Builds and publishes the
|
|
package to the Gitea PyPI registry. Checks out the release tag
|
|
within the same job.
|
|
- **sync-wiki** (if not automated) — Syncs documentation to the Gitea wiki.
|
|
- **vikunja** (if not automated) — Marks the corresponding Vikunja task as done.
|
|
- **badges** (always) — Generates and pushes quality badge SVGs to the
|
|
`badges` branch. Fetches latest master first to pick up release commits.
|
|
|
|
### Smart CI: User-Facing vs Workflow-Only Changes
|
|
|
|
Not all changes require the full CI pipeline or a new release. The project
|
|
classifies changes into two categories using `devx.ci.classify_changes`:
|
|
|
|
**Classification strategy (safe-by-default):** Any file NOT in the explicit
|
|
infrastructure allowlist is treated as user-facing. This prevents new file
|
|
types from accidentally skipping releases. Classification is config-driven
|
|
via `[tool.devx.classify]` in `pyproject.toml`.
|
|
|
|
**Infrastructure paths** (no release needed):
|
|
- `.gitea/**` — Gitea Actions workflows
|
|
- `scripts/**` — Dev tools and CI/CD automation (not part of installed package)
|
|
- `docs/**` — Documentation
|
|
- `tests/**` — Test files
|
|
- `AGENTS.md`, `README.md`, `CHANGELOG.md`, `TROUBLESHOOTING.md`, `CONTRIBUTING.md` — Project docs
|
|
- `Makefile`, `cliff.toml`, `uv.lock` — Build tooling
|
|
- `.pre-commit-config.yaml`, `.ansible-lint`, `.checkmake.ini` — Lint config (ruff config is in `pyproject.toml`)
|
|
- `.env.example`, `.gitignore` — Config
|
|
- `.devin/**` — Agent/CI tooling config
|
|
- `hooks/**` — Git hooks
|
|
- `activate.sh`, `activate.fish`, `activate.zsh` — Generated venv scripts
|
|
|
|
**User-facing paths** (tool changes → release needed) — everything else:
|
|
- `src/grm/**` — Python CLI source (except `__init__.py`)
|
|
- `ansible/**` — Ansible role
|
|
- `pyproject.toml` — Package metadata
|
|
- Any new file type not in the allowlist
|
|
|
|
**devx module structure** (installed from git, not in this repo):
|
|
- `devx.ci.*` — CI/CD automation (run by workflows): release, publish, auto_merge, classify_changes, detect_release_commit, push_badges, doc_coverage, sync_wiki, distribute_molecule, molecule_ci_guard, discover_runners, notify_failure, post_merge, pr_review, validate_commit_msg
|
|
- `devx.tools.*` — Dev tools (run locally): check_test_speed, configure_repo, install_checkmake, install_tools, setup, generate_badges, create_task, create_pr, pr_status, pr_logs, pr_label, rebase, pr_rebase
|
|
- `devx.molecule.*` — Molecule helpers: molecule_all, platforms, discover_runners, distribute_molecule, molecule_ci_guard
|
|
- `devx.gitea_cli` — Tea CLI wrapper
|
|
- `devx.i18n` — i18n translation system
|
|
- `devx.config` — Shared configuration (DEVX_* env vars)
|
|
- `devx.api_clients` — GiteaClient, VikunjaClient
|
|
- `devx.exceptions` — APIError and other exceptions
|
|
|
|
**CI behavior based on classification:**
|
|
- **Molecule tests**: Only run when `ansible/` or `.ansible-lint` files change
|
|
- **Release dry-run**: Only runs when user-facing files change
|
|
- **Quality job** (lint, unit tests, coverage, doc-coverage): Always runs
|
|
- **Release workflow**: Skips entirely when no user-facing files changed since last tag
|
|
|
|
**AI agents must follow these rules:**
|
|
- When working on workflow/CI/docs-only changes, use `ci:` or `docs:` commit prefixes
|
|
- Do NOT bump the version or create tags for infrastructure-only changes
|
|
- The `classify_changes` module enforces this automatically — no manual intervention needed
|
|
|
|
## Source Code Separation and devx Integration
|
|
|
|
The codebase enforces strict separation between the GRM tool and the devx package:
|
|
|
|
### Directory Layout
|
|
|
|
| Directory | Purpose | Release impact |
|
|
|-----------|---------|----------------|
|
|
| `src/grm/` | User-facing GRM CLI tool | Changes trigger release |
|
|
| `devx` package (installed from git) | Reusable CI/CD and dev tools | Not in this repo (no release impact) |
|
|
| `ansible/` | Ansible role for runner setup | Changes trigger release |
|
|
|
|
### Import Rules
|
|
|
|
1. **`src/grm/` NEVER imports from devx** — the GRM tool is self-contained
|
|
2. **devx MAY import from `grm`** — one-way dependency (devx uses the tool's API clients, config, i18n)
|
|
3. **Cross-module imports within devx** are allowed (devx modules importing from other devx modules) and must be documented
|
|
4. **`devx.gitea_cli`** is a shared wrapper around the `tea` CLI — devx modules import from it for Gitea API operations (issues, labels, PRs, releases, reviews)
|
|
|
|
### tea CLI Integration
|
|
|
|
The `tea` Gitea CLI tool is used for Gitea API interactions in devx. It is installed by `devx.tools.install_tools` and configured by `devx.tools.setup` (login profile from `.env` `CI_GITEA_TOKEN`).
|
|
|
|
**`devx.gitea_cli`** — Python wrapper around `tea` CLI with JSON output parsing:
|
|
- `TeaCLI.create_issue()` — Create issues with labels
|
|
- `TeaCLI.list_labels()` / `TeaCLI.create_label()` / `TeaCLI.add_label()` — Label management
|
|
- `TeaCLI.create_pr()` / `TeaCLI.merge_pr()` / `TeaCLI.review_pr()` — Pull request operations
|
|
- `TeaCLI.create_release()` / `TeaCLI.list_releases()` — Release management
|
|
- `TeaCLI.list_branches()` — Branch listing
|
|
|
|
**Modules using tea (via `devx.gitea_cli`):**
|
|
- `devx.ci.publish` — Creates Gitea releases via `tea releases create`
|
|
- `devx.ci.notify_failure` — Creates issues via `tea issues create` (falls back to `GiteaClient` if tea not installed)
|
|
- `devx.tools.configure_repo` — Creates labels via `tea labels create` (falls back to `GiteaClient` if tea fails; branch protection still uses `GiteaClient` since tea only supports basic protect/unprotect)
|
|
|
|
**Operations still using `GiteaClient` (not supported by tea):**
|
|
- PR reviews (`devx.ci.pr_review`) — tea v0.14.1 only supports interactive reviews
|
|
- Wiki page management (`devx.ci.sync_wiki`)
|
|
- Commit status checks (`devx.ci.auto_merge`)
|
|
- Runner discovery (`devx.molecule.discover_runners`)
|
|
- Branch protection with detailed config (`devx.tools.configure_repo`)
|
|
- PR file/commit listing (`devx.ci.pr_review`)
|
|
|
|
### PYTHONPATH Configuration
|
|
|
|
Since devx is installed as a package (via `pip install` from git), it is importable directly. Workflows only need `PYTHONPATH=src` when a devx module imports from `grm`:
|
|
|
|
| PYTHONPATH | When to use | Example modules |
|
|
|------------|-------------|-----------------|
|
|
| `src` | Module imports from `grm` | `devx.ci.auto_merge`, `devx.ci.pr_review`, `devx.ci.pr_review`, `devx.ci.sync_wiki`, `devx.ci.post_merge`, `devx.ci.classify_changes`, `devx.molecule.discover_runners`, `devx.ci.doc_coverage` |
|
|
| (none) | Module has no GRM imports | `devx.ci.detect_release_commit`, `devx.molecule.distribute_molecule`, `devx.molecule.molecule_ci_guard`, `devx.ci.push_badges`, `devx.ci.validate_commit_msg` |
|
|
|
|
**In workflows**, always use `env:` blocks (not inline `PYTHONPATH=value`):
|
|
```yaml
|
|
- name: Run module
|
|
env:
|
|
PYTHONPATH: src
|
|
run: python -m devx.ci.example
|
|
```
|
|
|
|
**Locally**, devx is installed as a package, so only `PYTHONPATH=src` is needed if importing from `grm`.
|
|
|
|
### Shared Constants
|
|
|
|
`devx.molecule.platforms` is the single source of truth for the molecule
|
|
platform matrix. Both `devx.molecule.distribute_molecule` (CI) and
|
|
`devx.molecule.molecule_all` (dev tool) import `PLATFORMS` from it — this
|
|
avoids dev tools importing directly from CI modules.
|
|
|
|
2. **Publish step** (in the `release-and-maintain` job, runs after the release step creates a tag):
|
|
- Runs after the release step creates a tag
|
|
- Gets the tag from the release step's output
|
|
- Builds the Python package
|
|
- Publishes to the Gitea PyPI registry
|
|
- Creates a Gitea release with git-cliff-generated release notes
|
|
- On failure, creates a Gitea issue via `devx.ci.notify_failure`
|
|
|
|
### git-cliff Commit Preprocessing
|
|
|
|
Merge commits on master have the format `GRM-N: <conventional commit>`. The
|
|
`GRM-N: ` prefix is not a valid conventional commit prefix, so `cliff.toml`
|
|
includes a `commit_preprocessors` entry that strips it before parsing. This
|
|
ensures all merged work appears in the changelog.
|
|
|
|
### Version Bumping Rules (git-cliff)
|
|
|
|
| Commit type | Version bump |
|
|
|-------------|-------------|
|
|
| `feat:` | minor (0.X.0) |
|
|
| `fix:` | patch (0.0.X) |
|
|
| `feat!:` or `BREAKING CHANGE` | minor (pre-1.0: major would be 1.0.0) |
|
|
| `chore:`, `ci:`, `docs:` | no bump (excluded by cliff.toml) |
|
|
|
|
The version source is `__version__` in `src/grm/__init__.py`, read by setuptools via `dynamic = ["version"]` in `pyproject.toml`. The release script only updates `__init__.py` — no need to touch `pyproject.toml`. `grm --version` reports this version.
|
|
|
|
### Title Format Summary
|
|
|
|
| What | Format | Example |
|
|
|------|--------|---------|
|
|
| Branch name | `GRM-N-short-description` | `GRM-33-add-pr-review-step` |
|
|
| Branch commits | `<conventional commit>` | `feat: add review script` |
|
|
| PR title | `GRM-N: <vikunja task title>` | `GRM-33: Add mandatory PR review step` |
|
|
| Merge commit | `GRM-N: <conventional commit>` | `GRM-33: feat: add review script` |
|
|
|
|
### Configuration
|
|
|
|
The devx package is configured via `DEVX_*` environment variables:
|
|
- `DEVX_TASK_PREFIX=GRM` — Prefix for Vikunja task identifiers
|
|
- `DEVX_VIKUNJA_PROJECT_ID=6` — Vikunja project ID for task tracking
|
|
- `DEVX_VERSION_FILE=src/grm/__init__.py` — Path to the version source file
|
|
|
|
Change classification is config-driven via `[tool.devx.classify]` in `pyproject.toml`, which defines the infrastructure and user-facing path patterns.
|
|
|
|
## Key Conventions
|
|
|
|
- Python 3.12+ required (ruff/pyright target `py312`)
|
|
- 100% test coverage required (`--cov-fail-under=100`)
|
|
- Conventional commits on feature branches (no `GRM-N:` prefix)
|
|
- Branch names must include `GRM-N` task ID
|
|
- Line length: 120 chars
|
|
- Secrets are passed via temp JSON files, never on the command line (CWE-214)
|
|
- CI triggers only on `opened` and `synchronize` PR events (not `labeled`)
|
|
|
|
### Testing Conventions
|
|
|
|
- **Always run `make pytest-cov` before pushing** — CI enforces 100%
|
|
coverage and will fail the PR if any lines are uncovered. The pre-push
|
|
hook only validates Vikunja task existence, not tests.
|
|
- **Never use `is True`/`is False` identity checks on API response
|
|
values** — many APIs return boolean values as strings (`"true"`/
|
|
`"false"`). Use string comparison or truthy/falsy helpers instead.
|
|
- **Always mock `time.sleep` and `time.monotonic` in unit tests** — real
|
|
sleep calls make tests slow and exceed test speed limits. Use
|
|
`@patch("time.sleep")` and `@patch("time.monotonic")` decorators.
|
|
- **Extract complex inline shell from workflows to tested Python tools**
|
|
— SSH loops, curl polling, docker exec chains, and multi-line
|
|
if/then/else shell blocks should be Python scripts in `scripts/`
|
|
with unit tests. Simple variable checks and venv activation are fine
|
|
as inline shell.
|
|
|
|
### Container-Level Fix Verification (Mandatory)
|
|
|
|
**Rule:** Before pushing any fix that modifies container state (CA certs,
|
|
config files, installed packages, daemon restarts), reproduce the exact
|
|
sequence locally with the actual Docker image. Do not push to CI as the
|
|
first test.
|
|
|
|
This is a hard rule, not a suggestion. CI cycles take 20+ minutes and
|
|
ephemeral staging VMs are destroyed after each run, making interactive
|
|
debugging impossible. A local reproduction takes 30 seconds and catches
|
|
silent failures immediately.
|
|
|
|
**Procedure:**
|
|
1. `docker pull <actual_image>`
|
|
2. `docker run -d --name <test> ...` and wait for it to start
|
|
3. Run the exact commands from the Ansible task or script
|
|
4. Verify the state change took effect
|
|
5. Clean up: `docker rm -f <test>`
|
|
|
|
### Verified State Modification (Mandatory)
|
|
|
|
Ansible tasks that modify container state with `changed_when: false`
|
|
MUST include a post-task verification step that confirms the state
|
|
change took effect. `changed_when: false` suppresses both change
|
|
detection AND failure visibility — a task can silently do nothing and
|
|
report `ok`.
|
|
|
|
## Ansible Role Structure
|
|
|
|
```text
|
|
main.yml → systemd_check → user_setup → rootless_docker → install_runner → prune → integration_test
|
|
```
|
|
|
|
- `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 only create files)
|
|
- On Arch Linux, `rootless_docker.yml` fetches the rootless setup scripts
|
|
(`dockerd-rootless-setuptool.sh`, `dockerd-rootless.sh`) from `moby/moby` `contrib/`
|
|
at a pinned ref (`gitea_runner_rootless_scripts_ref`) into `/usr/bin` and installs
|
|
`rootlesskit` — Arch's `docker` package ships neither. These fetch tasks run
|
|
regardless of `docker_rootless_setup` so CI exercises them on the archlinux platform.
|
|
See ADR-011 in the decision log.
|
|
|
|
## Molecule Scenarios
|
|
|
|
7 scenarios: `default`, `multi-instance`, `lifecycle`, `template-content`, `deregister`, `update`, `remove`
|
|
4 platforms: `ubuntu-2204`, `ubuntu-2404`, `debian-12`, `archlinux`
|
|
Platform list is defined in `devx.molecule.platforms` (single source of truth)
|
|
|
|
Note: `make molecule` and `make molecule-all` run 6 scenarios (excluding `remove`, which destroys the test container). CI discovers all 7 scenarios via `devx.molecule.distribute_molecule`.
|
|
|
|
## Known Issues
|
|
|
|
- `ansible-lint` may warn about `command-instead-of-module` for `systemctl --user` calls — this is expected (systemd module doesn't support user services) and skipped in `.ansible-lint`
|
|
- Molecule Docker driver may print "Event loop is closed" warnings on interrupt — harmless
|
|
|
|
## Documentation-as-Code
|
|
|
|
All documentation lives in `/docs/` and is synced to the Gitea wiki automatically.
|
|
|
|
### Structure
|
|
|
|
```text
|
|
docs/
|
|
├── index.md # Wiki homepage
|
|
├── mapping.json # File-to-wiki-page title mapping
|
|
├── user/ # User documentation
|
|
│ ├── getting-started.md
|
|
│ ├── installation.md
|
|
│ ├── cli-commands.md
|
|
│ ├── troubleshooting.md
|
|
│ └── faq.md
|
|
└── tech/ # Technical documentation
|
|
├── architecture.md
|
|
├── development-setup.md
|
|
├── ci-cd-workflow.md
|
|
├── testing-strategy.md
|
|
├── decision-log.md
|
|
└── contributing.md
|
|
```
|
|
|
|
### Wiki Sync
|
|
|
|
- **On merge to master**: `sync-wiki.yml` workflow runs `devx.ci.sync_wiki` which pushes all `/docs/` content to the Gitea wiki via API
|
|
- **On release tag**: Same sync runs, plus the wiki is tagged with the release version
|
|
- `mapping.json` maps each file path to a wiki page title (e.g., `user/getting-started.md` → `Getting-Started`)
|
|
- README.md is a lean entry point with links to the wiki — no detailed content
|
|
|
|
### Documentation Coverage
|
|
|
|
- `devx.ci.doc_coverage` checks that all CLI commands, Python modules, and CI scripts are documented
|
|
- Runs as a CI step in the validate job with `--fail-on-missing` (blocks CI if docs are missing)
|
|
- Enforced: 100% coverage for public CLI commands and major architectural components
|
|
|
|
### Updating Documentation
|
|
|
|
1. Edit files in `/docs/`
|
|
2. If adding a new page, add it to `docs/mapping.json`
|
|
3. Commit and create a PR (standard PR workflow)
|
|
4. On merge, wiki is automatically synced
|
|
|
|
## Subagent Delegation Policy
|
|
|
|
Custom subagent profiles are defined in `.devin/agents/` (project-specific)
|
|
and `~/.config/devin/agents/` (global, shared across repos). The agent MUST
|
|
automatically delegate to the appropriate subagent based on the task —
|
|
the user should not need to specify which profile to use.
|
|
|
|
### Available Profiles
|
|
|
|
**Global** (shared across all projects):
|
|
|
|
| Profile | Location | Purpose |
|
|
|---------|----------|---------|
|
|
| `pr-reviewer` | `~/.config/devin/agents/` | 13-category PR checklist + quality gates |
|
|
| `release-check` | `~/.config/devin/agents/` | Pre-merge readiness validation |
|
|
|
|
**grm-specific** (in `.devin/agents/`):
|
|
|
|
| Profile | Purpose |
|
|
|---------|---------|
|
|
| `ci-investigator` | Investigate CI failures (validate, molecule-tests, release-and-maintain) |
|
|
| `molecule-runner` | Run 7 molecule scenarios across 4 platforms, report pass/fail |
|
|
| `dep-upgrader` | Python + Ansible dependency upgrades with molecule verification |
|
|
| `doc-sync-specialist` | Doc coverage, doc linting, wiki sync for grm docs |
|
|
| `workflow-validator` | actionlint + act_runner dry-run for grm workflows |
|
|
|
|
### When to Delegate Automatically
|
|
|
|
| Trigger | Profile | Mode |
|
|
|---------|---------|------|
|
|
| CI run failure (validate, molecule-tests, release-and-maintain) | `ci-investigator` | Background |
|
|
| PR ready for review | `pr-reviewer` | Foreground |
|
|
| Molecule tests need to run | `molecule-runner` | Background |
|
|
| Dependency upgrade requested | `dep-upgrader` | Background |
|
|
| Doc coverage failure or wiki sync issue | `doc-sync-specialist` | Background |
|
|
| Workflow YAML modified or validation needed | `workflow-validator` | Background |
|
|
| Branch ready for merge | `release-check` | Foreground |
|
|
|
|
### Delegation Rules
|
|
|
|
1. **Auto-select the profile.** Do not ask the user 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 minor work** (<30s, <50 lines of context).
|
|
6. **Compact after subagent returns.**
|
|
7. **Never skip delegation to save time** — it keeps main context small.
|
|
|
|
|
|
## Feedback Issue Handling
|
|
|
|
Subagents create Gitea issues in the current repo when they encounter
|
|
tool, workflow, or process issues that warrant follow-up. These issues
|
|
use the `feedback` label plus a category label (`tooling`,
|
|
`ci-improvement`, `doc-improvement`, `workflow-improvement`).
|
|
|
|
Standard labels are created automatically by `configure_repo` (runs as
|
|
a step in `detect-and-configure` in post-merge on every master push).
|
|
If a label does not exist yet, the
|
|
subagent's issue creation will still succeed — labels can be added
|
|
afterwards.
|
|
|
|
### When a Subagent Reports a Feedback Issue URL
|
|
|
|
1. **Acknowledge it** in your response to the user — mention the issue URL
|
|
2. **Do NOT close or modify** the issue — it is for follow-up work
|
|
3. **Do NOT create a PR** to address it unless the user explicitly asks
|
|
4. If the user asks to address feedback, spawn a subagent to investigate
|
|
the issue and implement a fix
|
|
|
|
### Creating Feedback Issues Manually
|
|
|
|
As the parent agent, you can also create feedback issues directly using
|
|
the Gitea MCP (`issue_write` with `create_issue` method). Follow the
|
|
same format as subagents:
|
|
|
|
- Title: `[feedback] <category>: <short description>`
|
|
- Labels: `feedback` + category label
|
|
- Body: include context, tool/workflow, issue, reproduction, affected
|
|
files, suggested investigation, and "Reported by: parent agent"
|
|
|
|
Always deduplicate first via `list_issues` with `labels: "feedback"`.
|
|
|