Table of Contents
- Contributing Guide
- Key Conventions
- Code Style Rules
- Commit Rules
- Branch Naming
- PR Workflow Summary
- 1. Create Vikunja task
- 2. Create branch
- 3. Implement changes
- 4. Commit (conventional commits)
- 5. Push and create PR
- 6. Review the PR
- 7. Address review comments
- 8. Approve and merge
- Branch Protection (Required Gitea Settings)
- Build & Test Commands
- Ansible Role Conventions
- Change Classification
- Known Issues
Contributing Guide
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-Ntask ID - Line length: 120 chars
- Secrets are passed via temp JSON files, never on the command line (CWE-214)
- CI triggers only on
openedandsynchronizePR events (notlabeled) - No
print()— useclick.echo()viaui.say()for console output - No bare
except— catch specific exceptions - No
TODO/FIXMEcomments in committed code - No functions longer than 50 lines
- No
shell=Truewith subprocess - No
eval()orexec() - No raw strings in
click.echo()without_()wrapper (i18n) - No
open()withoutwithstatement - No
Popen()without cleanup
Code Style Rules
- Python version: 3.12+ (ruff and pyright target
py312) - Line length: 120 characters
- Test coverage: 100% required (
--cov-fail-under=100) - Secrets handling: Secrets are passed via temp JSON files with
0600permissions, never on the command line (CWE-214). Extra-vars are written to a temporary JSON file and passed via--extra-vars @tempfile, which is deleted after execution. This prevents secrets from being visible in the process list (ps aux). - Linting:
make lint-allruns ruff + pyright + bandit + ansible-lint + checkmake + actionlint - Formatting:
ruff formatwith double quotes and space indentation - Type checking:
pyrightin strict mode forsrc/grm/ - Security scanning:
bandit -r src/on every PR - Import rules:
src/grm/NEVER imports from devx — the GRM tool is self-contained
Commit Rules
Branch commits use conventional commit format (no GRM-N: prefix):
feat: add new feature
fix: resolve bug
docs: update README
ci: update workflow
refactor: simplify executor
test: add molecule scenario
chore: update dependencies
The pre-commit hook validates that commit messages follow the conventional commit format. Non-conventional commits are rejected.
Version Bumping Rules
| 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) |
Branch Naming
| 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 |
PR Workflow Summary
Every change to master goes through this workflow. No exceptions.
- Create Vikunja task — get a
GRM-Nidentifier (Vikunja project 6) - Create branch —
GRM-N-short-description - Implement — write code, tests (100% coverage), update docs
- Commit — conventional commits (no
GRM-N:prefix on branch) - Push & create PR — title:
GRM-N: <vikunja task title>, body: summary +Closes GRM-N - Review — review the full diff focusing on: functional completeness, edge cases, technical excellence (architecture, SRP, deduplication, code smells, best practices, code quality, reusability, clean code, readability, maintainability, extensibility), performance, security, UX, documentation completeness/relevance. Post review comments via
devx.ci.pr_review. - Address comments — fix each comment, commit, push, re-review
- Approve — post an
APPROVEreview viadevx.ci.pr_review - Add
ready-to-mergelabel — auto-merge workflow squash-merges with titleGRM-N <conventional commit message>, post-merge workflow marks the Vikunja task as done, release-and-maintain job automatically versions and tags
1. Create Vikunja task
Create a task in Vikunja project 6 to get a GRM-N identifier.
2. Create branch
git checkout master && git pull
git checkout -b GRM-N-short-description
3. Implement changes
- Write code following conventions above
- Write/update tests (100% coverage required)
- Update documentation (CHANGELOG, README, AGENTS.md, docs/ as needed)
4. Commit (conventional commits)
Branch commits use conventional commit format (no GRM-N: prefix):
feat: add new feature
fix: resolve bug
docs: update README
5. Push and create PR
- PR title format:
GRM-N: <vikunja task title>(must match the Vikunja task title exactly) - PR body: summary of changes,
Closes GRM-N - Add
ready-to-mergelabel only after review is complete
6. Review the PR
Review the full diff (git diff master...HEAD) focusing on:
- Functional completeness: Does the code do what it claims? Are all requirements met?
- Edge cases: Are boundary conditions, empty inputs, error paths handled?
- Technical excellence:
- Architecture compliance and evolution
- Single Responsibility Principle (SRP)
- Deduplication (no copy-paste, single source of truth)
- Code smells detection and removal
- Best industry practices
- Industry-grade code quality
- Reusability
- Clean code
- Readability
- Maintainability
- Extensibility
- Performance: No unnecessary allocations, O(n) vs O(n^2), efficient data structures
- Security: No secrets in logs/process list, input validation, no injection vectors
- User experience: Clear error messages, intuitive CLI flags, helpful output
- Documentation: Completeness and relevance of docs, CHANGELOG entries, AGENTS.md updates
Post review comments using devx.ci.review_pr:
CI_GITEA_TOKEN=<token> python -m devx.ci.review_pr <pr_number> <owner/repo> \
--event REQUEST_CHANGES \
--body "Review summary" \
--comments-json comments.json
7. Address review comments
Fix each comment one by one, commit, and push. Re-review until satisfied.
8. Approve and merge
Once all comments are addressed, post an approval review:
CI_GITEA_TOKEN=<token> python -m devx.ci.review_pr <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."
Then add the ready-to-merge label. The auto-merge workflow will:
- Validate PR title format and match against Vikunja task title
- Check that at least one APPROVE review exists
- Wait for all CI checks to pass
- Squash-merge with title:
GRM-N <conventional commit message>(space-separated) - The post-merge workflow marks the Vikunja task as done
- The release-and-maintain job automatically versions, tags, and publishes
Important
: Never manually merge PRs via the API. Always use the auto-merge workflow by adding the
ready-to-mergelabel. Manual merges bypass theGRM-N <conventional>format enforcement.
Branch Protection (Required Gitea Settings)
Branch protection is automatically configured by the configure-repo step in the detect-and-configure job of the post-merge workflow. The following rules are enforced for master:
- Require pull request: No direct pushes to master
- Require approval review: At least 1
APPROVEreview 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.
Build & Test Commands
make setup # Create venv, install deps, set up hooks, install CI tools
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-check # Static lint + dry-run of workflow YAML
Ansible Role Conventions
main.yml → systemd_check → user_setup → rootless_docker → install_runner → prune → integration_test
install_runner.ymlhandles: download, config, validate, register, servicemain.ymlhandles: prune, integration_test (NOT install_runner — avoids duplicates)systemctl --usertasks must be guarded bydocker_rootless_setup- Template creation tasks are NOT guarded by
docker_rootless_setup(they only create files) apttasks usecache_valid_time: 3600to avoid unnecessary cache updatesremove-runner.ymlrunsloginctl disable-lingerand removes subuid/subgid entries
Change Classification
Not all changes require a new release. The project classifies changes using devx.ci.classify_changes:
Workflow-only paths (no release needed):
.gitea/**,docs/**,tests/**,scripts/**AGENTS.md,README.md,CHANGELOG.md,Makefile,cliff.toml- Lint config files,
.env.example,.gitignore
User-facing paths (release needed):
src/grm/**(except__init__.py)ansible/**pyproject.toml
When working on workflow/CI/docs-only changes, use ci: or docs: commit prefixes. Do NOT bump the version or create tags for workflow-only changes.
Known Issues
ansible-lintmay warn aboutcommand-instead-of-moduleforsystemctl --usercalls — 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