Add /docs/ directory with user and technical documentation extracted from README, AGENTS.md, and source code. Add scripts/sync_wiki.py to sync docs to Gitea wiki via API. Add scripts/doc_coverage.py to check CLI commands, modules, and CI scripts are documented. Add sync-wiki.yml workflow for auto-sync on merge and release. Slim down README.md to lean entry point. 28 new unit tests, 100% coverage maintained. Closes GRM-36
232 lines
9.3 KiB
Markdown
232 lines
9.3 KiB
Markdown
# CI/CD Workflow
|
|
|
|
Every change to master goes through a mandatory PR workflow. No exceptions.
|
|
|
|
## PR Workflow
|
|
|
|
### 1. Create Vikunja Task
|
|
|
|
Create a task in Vikunja project 6 to get a `GRM-N` identifier.
|
|
|
|
### 2. Create Branch
|
|
|
|
```bash
|
|
git checkout master && git pull
|
|
git checkout -b GRM-N-short-description
|
|
```
|
|
|
|
### 3. Implement Changes
|
|
|
|
- Write code following conventions
|
|
- 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):
|
|
|
|
```
|
|
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-merge` label **only after review is complete**
|
|
|
|
### 6. Review the PR (Mandatory — Before Adding ready-to-merge Label)
|
|
|
|
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²), 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 `scripts/review_pr.py`:
|
|
|
|
```bash
|
|
REPO_TOKEN=<token> python3 scripts/review_pr.py <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:
|
|
|
|
```bash
|
|
REPO_TOKEN=<token> python3 scripts/review_pr.py <pr_number> <owner/repo> \
|
|
--event APPROVE \
|
|
--body "All comments addressed. LGTM."
|
|
```
|
|
|
|
Then add the `ready-to-merge` label. The auto-merge workflow will:
|
|
|
|
1. **Validate** PR title format and match against Vikunja task title
|
|
2. **Check** that at least one APPROVE review exists
|
|
3. Wait for all CI checks to pass
|
|
4. Squash-merge with title: `GRM-N <conventional commit message>` (space-separated)
|
|
5. The post-merge workflow marks the Vikunja task as done
|
|
6. The release workflow automatically versions, tags, and publishes
|
|
|
|
### 9. Post-Merge Automation
|
|
|
|
After the squash-merge:
|
|
|
|
- The **post-merge workflow** (`.gitea/workflows/post-merge.yml`) triggers on push to `master` and runs `scripts/post_merge.py` to mark the Vikunja task as done, extracting the task ID from the merge commit message.
|
|
- The **release workflow** (`.gitea/workflows/release.yml`) triggers on push to `master` and automatically versions, tags, and publishes (see below).
|
|
|
|
## Branch Protection (Required Gitea Settings)
|
|
|
|
Configure the following branch protection rules for `master` in Gitea repo settings:
|
|
|
|
- **Require pull request**: No direct pushes to master
|
|
- **Require approval review**: At least 1 `APPROVE` review before merge
|
|
- **Require status checks**: CI quality + 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.
|
|
|
|
## CI Path Filtering
|
|
|
|
The CI workflow (`.gitea/workflows/ci.yml`) includes a `detect-changes` job 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.
|
|
|
|
The `detect-changes` job:
|
|
|
|
- For pull requests: compares `origin/master` against the PR head SHA
|
|
- For pushes to master: compares `HEAD~1` against `HEAD`
|
|
- Outputs `ansible-changed` as `true` or `false`
|
|
|
|
The `molecule-tests` job depends on both `quality` and `detect-changes`, and only runs if `ansible-changed == 'true'`.
|
|
|
|
CI triggers only on `opened` and `synchronize` PR events (not `labeled`).
|
|
|
|
## CI Quality Job
|
|
|
|
The `quality` job in `.gitea/workflows/ci.yml` runs:
|
|
|
|
1. `make setup` — full environment setup
|
|
2. `make lint-all` — ruff + pyright + bandit + ansible-lint + checkmake
|
|
3. `make pytest-cov` — unit tests with 100% coverage enforcement
|
|
4. `python3 scripts/check_test_speed.py --max-seconds 10` — verify unit tests run fast
|
|
5. `PYTHONPATH=src python3 scripts/release.py --dry-run` — release dry-run validation
|
|
|
|
## Automated Release Pipeline
|
|
|
|
After a PR is merged to master, the release pipeline runs automatically.
|
|
|
|
### Release Workflow (`.gitea/workflows/release.yml`)
|
|
|
|
- Triggers on push to `master`
|
|
- Sets up full dev environment (`make setup`) so lint and tests can run
|
|
- Installs git-cliff (version 2.13.0)
|
|
- Configures git as `grm-ci-bot`
|
|
- Runs `scripts/release.py` which uses **git-cliff** to:
|
|
- Calculate the next semver version from conventional commits since the last tag
|
|
- Update `__version__` in `src/gitea_runner_manager/__init__.py` (single source of truth)
|
|
- Update `CHANGELOG.md` with the new version section
|
|
- **Run `make lint-ruff` and `make pytest-cov`** to verify the release is healthy
|
|
- If lint or tests fail, **abort immediately** — no commit, no tag
|
|
- Commit with `release: vX.Y.Z` prefix (cleaner than `chore(release):`)
|
|
- Create an annotated tag `vX.Y.Z` on the release commit
|
|
- Push both the commit and tag to master
|
|
- `--skip-tests` flag bypasses test verification (emergency use only, not recommended)
|
|
- Loops are prevented by `has_unreleased_changes` — after a release commit is tagged, the next run finds no unreleased changes and exits
|
|
- On failure, creates a Gitea issue via `scripts/notify_failure.py`
|
|
|
|
### Publish Workflow (`.gitea/workflows/publish.yml`)
|
|
|
|
- Triggers on tag push (`v*`)
|
|
- Installs git-cliff (version 2.13.0)
|
|
- Installs build tools (`build`, `twine`, `requests`, `python-dotenv`, `click`)
|
|
- Validates `PYPI_TOKEN` is set (warns if missing)
|
|
- Builds the Python package
|
|
- Optionally publishes to PyPI (if `PYPI_TOKEN` is set)
|
|
- Creates a Gitea release with git-cliff-generated release notes
|
|
- Uses `scripts/publish.py` for build and publish orchestration
|
|
- On failure, creates a Gitea issue via `scripts/notify_failure.py`
|
|
|
|
### Auto-Merge Workflow (`.gitea/workflows/auto-merge.yml`)
|
|
|
|
- Triggers on `pull_request` labeled events
|
|
- Runs `scripts/auto_merge.py` with the branch name, PR title, repository, PR number, and label name
|
|
- Validates PR title format, checks for APPROVE review, waits for CI, and squash-merges
|
|
|
|
### Post-Merge Workflow (`.gitea/workflows/post-merge.yml`)
|
|
|
|
- Triggers on push to `master`
|
|
- Runs `scripts/post_merge.py` with the latest commit message and commit SHA
|
|
- Marks the corresponding Vikunja task as done
|
|
|
|
## 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:
|
|
|
|
```toml
|
|
commit_preprocessors = [
|
|
# Strip GRM-N task ID prefix from merge commits so git-cliff sees conventional commits
|
|
{ pattern = "^GRM-\\d+\\s+", replace = "" },
|
|
]
|
|
```
|
|
|
|
This ensures all merged work appears in the changelog.
|
|
|
|
### git-cliff Configuration Highlights (`cliff.toml`)
|
|
|
|
- `conventional_commits = true` — parse conventional commit format
|
|
- `filter_unconventional = true` — skip non-conventional commits
|
|
- `render_always = true` — always render the changelog
|
|
- `trim = true` — trim whitespace
|
|
- Commit parsers group commits into: Features, Bug Fixes, Documentation, Performance, Refactor, Styling, Testing, Miscellaneous Tasks, Security, Revert, Other
|
|
- `chore(release): prepare for`, `chore(deps.*)`, `chore(pr)`, `chore(pull)` commits are skipped
|
|
- `sort_commits = "oldest"` — oldest commits first
|
|
|
|
## 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) |
|
|
|
|
From `cliff.toml` `[bump]` section:
|
|
|
|
- `features_always_bump_minor = true`
|
|
- `breaking_always_bump_major = false`
|
|
- `initial_tag = "0.1.0"`
|
|
|
|
The version source is `__version__` in `src/gitea_runner_manager/__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` |
|