Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a506e1130c |
@@ -0,0 +1,20 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="104" height="20" role="img"
|
||||
aria-label="coverage: 100%">
|
||||
<title>coverage: 100%</title>
|
||||
<linearGradient id="s" x2="0" y2="100%">
|
||||
<stop offset="0" stop-color="#fff" stop-opacity=".7"/>
|
||||
<stop offset=".1" stop-color="#bbb" stop-opacity=".1"/>
|
||||
<stop offset=".9" stop-color="#000" stop-opacity=".3"/>
|
||||
<stop offset="1" stop-color="#bbb" stop-opacity=".1"/>
|
||||
</linearGradient>
|
||||
<clipPath id="r"><rect width="104" height="20" rx="3" fill="#fff"/></clipPath>
|
||||
<g clip-path="url(#r)">
|
||||
<rect width="66" height="20" fill="#555"/>
|
||||
<rect x="66" width="38" height="20" fill="#4c1"/>
|
||||
<rect width="104" height="20" fill="url(#s)"/>
|
||||
</g>
|
||||
<g fill="#fff" text-anchor="middle" font-family="Verdana,DejaVu Sans,sans-serif" font-size="11">
|
||||
<text x="33" y="14">coverage</text>
|
||||
<text x="85" y="14">100%</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 894 B |
@@ -0,0 +1,20 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="76" height="20" role="img"
|
||||
aria-label="docs: 100%">
|
||||
<title>docs: 100%</title>
|
||||
<linearGradient id="s" x2="0" y2="100%">
|
||||
<stop offset="0" stop-color="#fff" stop-opacity=".7"/>
|
||||
<stop offset=".1" stop-color="#bbb" stop-opacity=".1"/>
|
||||
<stop offset=".9" stop-color="#000" stop-opacity=".3"/>
|
||||
<stop offset="1" stop-color="#bbb" stop-opacity=".1"/>
|
||||
</linearGradient>
|
||||
<clipPath id="r"><rect width="76" height="20" rx="3" fill="#fff"/></clipPath>
|
||||
<g clip-path="url(#r)">
|
||||
<rect width="38" height="20" fill="#555"/>
|
||||
<rect x="38" width="38" height="20" fill="#4c1"/>
|
||||
<rect width="76" height="20" fill="url(#s)"/>
|
||||
</g>
|
||||
<g fill="#fff" text-anchor="middle" font-family="Verdana,DejaVu Sans,sans-serif" font-size="11">
|
||||
<text x="19" y="14">docs</text>
|
||||
<text x="57" y="14">100%</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 879 B |
@@ -0,0 +1,20 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="90" height="20" role="img"
|
||||
aria-label="python: 3.12">
|
||||
<title>python: 3.12</title>
|
||||
<linearGradient id="s" x2="0" y2="100%">
|
||||
<stop offset="0" stop-color="#fff" stop-opacity=".7"/>
|
||||
<stop offset=".1" stop-color="#bbb" stop-opacity=".1"/>
|
||||
<stop offset=".9" stop-color="#000" stop-opacity=".3"/>
|
||||
<stop offset="1" stop-color="#bbb" stop-opacity=".1"/>
|
||||
</linearGradient>
|
||||
<clipPath id="r"><rect width="90" height="20" rx="3" fill="#fff"/></clipPath>
|
||||
<g clip-path="url(#r)">
|
||||
<rect width="52" height="20" fill="#555"/>
|
||||
<rect x="52" width="38" height="20" fill="#007ec6"/>
|
||||
<rect width="90" height="20" fill="url(#s)"/>
|
||||
</g>
|
||||
<g fill="#fff" text-anchor="middle" font-family="Verdana,DejaVu Sans,sans-serif" font-size="11">
|
||||
<text x="26" y="14">python</text>
|
||||
<text x="71" y="14">3.12</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 888 B |
@@ -0,0 +1,20 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="124" height="20" role="img"
|
||||
aria-label="code quality: A">
|
||||
<title>code quality: A</title>
|
||||
<linearGradient id="s" x2="0" y2="100%">
|
||||
<stop offset="0" stop-color="#fff" stop-opacity=".7"/>
|
||||
<stop offset=".1" stop-color="#bbb" stop-opacity=".1"/>
|
||||
<stop offset=".9" stop-color="#000" stop-opacity=".3"/>
|
||||
<stop offset="1" stop-color="#bbb" stop-opacity=".1"/>
|
||||
</linearGradient>
|
||||
<clipPath id="r"><rect width="124" height="20" rx="3" fill="#fff"/></clipPath>
|
||||
<g clip-path="url(#r)">
|
||||
<rect width="94" height="20" fill="#555"/>
|
||||
<rect x="94" width="30" height="20" fill="#4c1"/>
|
||||
<rect width="124" height="20" fill="url(#s)"/>
|
||||
</g>
|
||||
<g fill="#fff" text-anchor="middle" font-family="Verdana,DejaVu Sans,sans-serif" font-size="11">
|
||||
<text x="47" y="14">code quality</text>
|
||||
<text x="109" y="14">A</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 898 B |
@@ -0,0 +1,20 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="139" height="20" role="img"
|
||||
aria-label="tests: 2310 passing">
|
||||
<title>tests: 2310 passing</title>
|
||||
<linearGradient id="s" x2="0" y2="100%">
|
||||
<stop offset="0" stop-color="#fff" stop-opacity=".7"/>
|
||||
<stop offset=".1" stop-color="#bbb" stop-opacity=".1"/>
|
||||
<stop offset=".9" stop-color="#000" stop-opacity=".3"/>
|
||||
<stop offset="1" stop-color="#bbb" stop-opacity=".1"/>
|
||||
</linearGradient>
|
||||
<clipPath id="r"><rect width="139" height="20" rx="3" fill="#fff"/></clipPath>
|
||||
<g clip-path="url(#r)">
|
||||
<rect width="45" height="20" fill="#555"/>
|
||||
<rect x="45" width="94" height="20" fill="#4c1"/>
|
||||
<rect width="139" height="20" fill="url(#s)"/>
|
||||
</g>
|
||||
<g fill="#fff" text-anchor="middle" font-family="Verdana,DejaVu Sans,sans-serif" font-size="11">
|
||||
<text x="22" y="14">tests</text>
|
||||
<text x="92" y="14">2310 passing</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 909 B |
@@ -0,0 +1,20 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="118" height="20" role="img"
|
||||
aria-label="version: v0.50.0">
|
||||
<title>version: v0.50.0</title>
|
||||
<linearGradient id="s" x2="0" y2="100%">
|
||||
<stop offset="0" stop-color="#fff" stop-opacity=".7"/>
|
||||
<stop offset=".1" stop-color="#bbb" stop-opacity=".1"/>
|
||||
<stop offset=".9" stop-color="#000" stop-opacity=".3"/>
|
||||
<stop offset="1" stop-color="#bbb" stop-opacity=".1"/>
|
||||
</linearGradient>
|
||||
<clipPath id="r"><rect width="118" height="20" rx="3" fill="#fff"/></clipPath>
|
||||
<g clip-path="url(#r)">
|
||||
<rect width="59" height="20" fill="#555"/>
|
||||
<rect x="59" width="59" height="20" fill="#007ec6"/>
|
||||
<rect width="118" height="20" fill="url(#s)"/>
|
||||
</g>
|
||||
<g fill="#fff" text-anchor="middle" font-family="Verdana,DejaVu Sans,sans-serif" font-size="11">
|
||||
<text x="29" y="14">version</text>
|
||||
<text x="88" y="14">v0.50.0</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 903 B |
@@ -1,194 +0,0 @@
|
||||
---
|
||||
name: ci-investigator
|
||||
description: Investigates CI failures in the devx repo by fetching job logs via Gitea MCP, identifying root cause across quality/release/publish/wiki-sync/image-build jobs, and validating fixes locally.
|
||||
model: glm-5.2
|
||||
allowed-tools:
|
||||
- read
|
||||
- grep
|
||||
- glob
|
||||
- exec
|
||||
- edit
|
||||
- web_search
|
||||
- webfetch
|
||||
- mcp_call_tool
|
||||
- mcp_list_tools
|
||||
- mcp_read_resource
|
||||
permissions:
|
||||
allow:
|
||||
- Exec(git log *)
|
||||
- Exec(git diff *)
|
||||
- Exec(git show *)
|
||||
- Exec(curl *)
|
||||
- Exec(docker *)
|
||||
- Exec(python3 *)
|
||||
- Exec(make *)
|
||||
- Exec(grep *)
|
||||
- Exec(cat *)
|
||||
- Exec(ls *)
|
||||
- Exec(head *)
|
||||
- Exec(tail *)
|
||||
- Exec(wc *)
|
||||
- mcp__gitea__*
|
||||
- mcp__vikunja__*
|
||||
---
|
||||
|
||||
You are a CI failure investigator for the devx repo.
|
||||
|
||||
## Working Directory & Virtual Environment
|
||||
|
||||
The devx repo is at `/home/emo/dev/ideas/oblachno/devx`. Always `cd` there first.
|
||||
|
||||
All Python tools run inside `.venv`. `make` targets handle activation
|
||||
automatically — always use `make <target>`, never raw `pytest` or `ruff`
|
||||
commands. If `.venv` doesn't exist, run `make setup` first.
|
||||
|
||||
## CI Job Dependency Graph
|
||||
|
||||
devx has 3 workflows:
|
||||
|
||||
**ci.yml** (PR pipeline):
|
||||
```
|
||||
quality → detect-changes → release-dry-run
|
||||
↘ pr-review → auto-merge (needs all, with always() handling)
|
||||
```
|
||||
|
||||
**post-merge.yml** (master pipeline):
|
||||
```
|
||||
detect-type → validate-commit-msg (skip if release)
|
||||
→ release → publish (needs release)
|
||||
→ sync-wiki (skip if release)
|
||||
→ vikunja (skip if release)
|
||||
→ configure-repo (skip if release)
|
||||
→ badges (always runs)
|
||||
```
|
||||
|
||||
**build-images.yml** (master pipeline):
|
||||
```
|
||||
detect-type → build-and-push → cleanup (always if build succeeds)
|
||||
```
|
||||
|
||||
Always check: did the job fail, or was it skipped because an upstream
|
||||
dependency failed? Skipped jobs are not the root cause.
|
||||
|
||||
## Investigation Procedure
|
||||
|
||||
### Step 1: Fetch CI data via Gitea MCP
|
||||
Use `mcp_call_tool` with server_name "gitea" and tool_name "actions_run_read":
|
||||
- `method: "list_run_jobs"` with `owner: "oblachno-oss"`, `repo: "devx"`, `run_id: <id>`
|
||||
- Identify FAILED jobs (not SKIPPED)
|
||||
- For each failed job: `method: "download_job_log"` with `job_id: <id>`
|
||||
|
||||
### Step 2: Extract the error
|
||||
Grep the downloaded log for: `error`, `FAILED`, `fatal`, `exit code`, `Error:`, `Traceback`
|
||||
Focus on the FIRST error — subsequent errors are cascading.
|
||||
|
||||
### Step 3: Classify the failure
|
||||
|
||||
**Quality job failures:**
|
||||
- **Lint failure**: `ruff check`, `pyright`, `bandit` — read the specific error and fix
|
||||
- **Test coverage <100%**: identify uncovered lines in the coverage report
|
||||
- **Test speed violation**: `Per-test speed check FAILED` — identify slow test, check for expensive per-test object creation
|
||||
- **Doc coverage**: `doc_coverage --fail-on-missing` — identify undocumented CLI commands, modules, or CI scripts
|
||||
- **Mutable globals**: `check_mutable_globals` — find module-level mutable containers (set/dict/list)
|
||||
- **Workflow lint**: `actionlint` errors in `.gitea/workflows/*.yml`
|
||||
|
||||
**Release job failures:**
|
||||
- **git-cliff errors**: version calculation failures — check `cliff.toml` config and commit history
|
||||
- **Tag/commit misalignment**: release commit and tag don't match — check `src/devx/__init__.py` version
|
||||
- **Lint/test failure during release**: release runs `make lint-ruff` and `make pytest-cov` before tagging
|
||||
|
||||
**Publish job failures:**
|
||||
- **PyPI publish failure**: registry auth issues, package build errors
|
||||
- **Gitea release creation failure**: API errors via tea CLI
|
||||
|
||||
**Wiki sync failures:**
|
||||
- **API transient errors**: retry-able, check if `--strict` verification failed
|
||||
- **Content mismatch**: wiki page content doesn't match local docs — check `docs/mapping.json`
|
||||
- **Stale pages**: wiki has pages not in mapping.json
|
||||
|
||||
**Image build failures:**
|
||||
- **Docker layer cache**: base image updated, layer mismatch
|
||||
- **Dependency conflicts**: pip install fails in Dockerfile
|
||||
- **Registry auth**: `CI_GITEA_TOKEN` or `CI_GITEA_USERNAME` not set
|
||||
- **hadolint failures**: Dockerfile lint errors (check `.hadolint.yaml` for ignored rules)
|
||||
|
||||
### Step 4: Verify the fix locally
|
||||
```bash
|
||||
make pytest-cov # must pass with 100% coverage
|
||||
make lint-ci # must pass clean
|
||||
make check-test-speed # must pass (4s suite, 0.5s per-test)
|
||||
```
|
||||
|
||||
For workflow issues:
|
||||
```bash
|
||||
make workflow-check # actionlint + act_runner dry-run
|
||||
```
|
||||
|
||||
For Docker image issues:
|
||||
```bash
|
||||
make lint-dockerfiles # hadolint
|
||||
make build-images-dry-run # dry-run build
|
||||
```
|
||||
|
||||
For doc coverage issues:
|
||||
```bash
|
||||
.venv/bin/python -m devx.ci.doc_coverage --fail-on-missing
|
||||
.venv/bin/python -m devx.ci.lint_docs --root .
|
||||
```
|
||||
|
||||
### Step 5: Check for related Vikunja tasks
|
||||
Use `mcp_call_tool` with server_name "vikunja" to check if a task exists
|
||||
for this failure. CI auto-creates Gitea issues via `notify_failure`.
|
||||
|
||||
### Step 6: Report
|
||||
1. **Root cause**: The specific error and why it occurred
|
||||
2. **Evidence**: Log excerpts, local verification results
|
||||
3. **Affected files**: File paths and line numbers
|
||||
4. **Suggested fix**: Specific code change with rationale
|
||||
5. **Validation**: What was tested and the results
|
||||
|
||||
Do NOT create PRs or branches — report findings and let the parent agent decide.
|
||||
|
||||
## Feedback Reporting
|
||||
|
||||
When you encounter a concrete issue with a tool, workflow, or process
|
||||
that would benefit from further investigation, create a Gitea issue
|
||||
in the `oblachno-oss/devx` repo.
|
||||
|
||||
### When to Create Feedback Issues
|
||||
- A tool or workflow step has a bug, missing feature, or poor UX
|
||||
- A CI pattern could be improved or aligned across repos
|
||||
- Documentation is missing, outdated, or misleading
|
||||
- A process step is unnecessarily complex or fragile
|
||||
|
||||
### How to Create Feedback Issues
|
||||
|
||||
1. **Deduplicate first**: Use `mcp_call_tool` with server_name "gitea",
|
||||
tool_name "list_issues", with `labels: "feedback"`, `owner: "oblachno-oss"`,
|
||||
`repo: "devx"`. Check if an open issue already covers the same topic.
|
||||
Do NOT create duplicates.
|
||||
|
||||
2. **Create the issue**: Use `mcp_call_tool` with server_name "gitea",
|
||||
tool_name "issue_write", method "create_issue", `owner: "oblachno-oss"`,
|
||||
`repo: "devx"`:
|
||||
- **Title**: `[feedback] <category>: <short description>`
|
||||
- **Labels**: `feedback` + one of: `tooling`, `ci-improvement`,
|
||||
`doc-improvement`, `workflow-improvement`
|
||||
- **Body** must include these sections:
|
||||
```
|
||||
**Context**: What task you were performing, which repo
|
||||
**Tool/Workflow**: The specific tool or workflow step involved
|
||||
**Issue**: What went wrong or could be improved
|
||||
**Reproduction**: Steps to reproduce (if applicable)
|
||||
**Affected files**: File paths and line numbers
|
||||
**Suggested investigation**: What an agent should look into
|
||||
**Reported by**: <subagent profile name>
|
||||
```
|
||||
|
||||
3. **Report back**: Include the issue URL in your report to the parent agent.
|
||||
|
||||
### When NOT to Create Feedback Issues
|
||||
- Transient failures (network blips, rate limits, Docker pull flakiness)
|
||||
- Issues you can fix yourself — fix them instead
|
||||
- CI run failures — those are handled by `notify_failure` automatically
|
||||
- Missing labels — `configure_repo` creates standard labels on next master push
|
||||
@@ -1,145 +0,0 @@
|
||||
---
|
||||
name: dep-upgrader
|
||||
description: Researches and applies Python dependency upgrades in pyproject.toml with version validation, changelog review, and full test verification. Knows the dep documentation comment requirement.
|
||||
model: glm-5.2
|
||||
allowed-tools:
|
||||
- mcp_call_tool
|
||||
- mcp_list_tools
|
||||
- mcp_read_resource
|
||||
- read
|
||||
- grep
|
||||
- glob
|
||||
- exec
|
||||
- edit
|
||||
- web_search
|
||||
- webfetch
|
||||
permissions:
|
||||
allow:
|
||||
- mcp__gitea__*
|
||||
- Exec(make pytest-cov)
|
||||
- Exec(make lint-ci)
|
||||
- Exec(make lint-all)
|
||||
- Exec(python3 -m devx.tools.check_test_speed *)
|
||||
- Exec(python3 -m devx.tools.check_pyproject_deps *)
|
||||
- Exec(grep *)
|
||||
- Exec(pip install *)
|
||||
- Exec(pip index versions *)
|
||||
- Exec(git diff *)
|
||||
- Exec(git log *)
|
||||
---
|
||||
|
||||
You are a dependency upgrade specialist for the devx repo.
|
||||
|
||||
## Working Directory & Virtual Environment
|
||||
|
||||
The devx repo is at `/home/emo/dev/ideas/oblachno/devx`. Always `cd` there first.
|
||||
|
||||
All Python tools run inside `.venv`. `make` targets handle activation
|
||||
automatically — always use `make <target>`, never raw `pytest` or `ruff`
|
||||
commands. If `.venv` doesn't exist, run `make setup` first.
|
||||
|
||||
## Dependency Reference Locations
|
||||
|
||||
- **Primary**: `pyproject.toml` — `[project] dependencies` and `[project.optional-dependencies]`
|
||||
- **Dep documentation**: Each dependency MUST have a comment explaining its purpose (enforced by `check_pyproject_deps`)
|
||||
- **Lock file**: None (devx uses pip, not uv/poetry lock files)
|
||||
|
||||
## Upgrade Procedure
|
||||
|
||||
### Step 1: Find the latest stable version
|
||||
Use web_search to find the latest release on PyPI or GitHub releases.
|
||||
|
||||
Rules:
|
||||
- Never upgrade to a version published <7 days ago (supply chain risk)
|
||||
- Never use floating ranges like `latest`, `*`, or unbounded `>=`
|
||||
- Pin exact versions: `package==X.Y.Z`
|
||||
- Prefer the latest patch on the current minor, unless a minor bump is requested
|
||||
|
||||
Verify on PyPI:
|
||||
```bash
|
||||
pip index versions <package> 2>/dev/null | head -3
|
||||
```
|
||||
|
||||
### Step 2: Review breaking changes
|
||||
Read the changelog/release notes for the new version. Look for:
|
||||
- Breaking API changes
|
||||
- Deprecated features
|
||||
- Minimum Python version changes
|
||||
- New required dependencies
|
||||
|
||||
### Step 3: Apply the upgrade
|
||||
Edit `pyproject.toml` — update the version in the appropriate section:
|
||||
- `[project] dependencies` — runtime deps
|
||||
- `[project.optional-dependencies] dev` — dev tools (ruff, pyright, bandit, etc.)
|
||||
- `[project.optional-dependencies] ci` — CI tools
|
||||
- `[project.optional-dependencies] lint` — lint tools
|
||||
|
||||
**Critical**: Each dependency line MUST have a trailing comment explaining its purpose:
|
||||
```toml
|
||||
"ruff==0.12.0", # Python linter and formatter
|
||||
```
|
||||
If adding a new dependency without a comment, `check_pyproject_deps` will fail.
|
||||
|
||||
### Step 4: Install and verify
|
||||
```bash
|
||||
pip install -e .[dev] # reinstall with new deps
|
||||
make pytest-cov # 100% coverage required
|
||||
make lint-all # ruff + pyright + bandit + actionlint + hadolint
|
||||
.venv/bin/python -m devx.tools.check_pyproject_deps # verify dep docs
|
||||
.venv/bin/python -m devx.tools.check_test_speed --max-seconds 4 --max-single-seconds 0.5
|
||||
```
|
||||
|
||||
All must pass. If `check_pyproject_deps` fails, add the missing comment.
|
||||
|
||||
### Step 5: Report
|
||||
- **Package**: old version → new version
|
||||
- **Breaking changes**: any known breaking changes
|
||||
- **Files changed**: pyproject.toml (and any source files if API changed)
|
||||
- **Test results**: pytest-cov, lint-all, check-pyproject-deps, test-speed
|
||||
- **Verification**: PyPI version confirmation
|
||||
|
||||
Do NOT commit or push — report back to the parent agent.
|
||||
|
||||
## Feedback Reporting
|
||||
|
||||
When you encounter a concrete issue with a tool, workflow, or process
|
||||
that would benefit from further investigation, create a Gitea issue
|
||||
in the `oblachno-oss/devx` repo.
|
||||
|
||||
### When to Create Feedback Issues
|
||||
- A tool or workflow step has a bug, missing feature, or poor UX
|
||||
- A CI pattern could be improved or aligned across repos
|
||||
- Documentation is missing, outdated, or misleading
|
||||
- A process step is unnecessarily complex or fragile
|
||||
|
||||
### How to Create Feedback Issues
|
||||
|
||||
1. **Deduplicate first**: Use `mcp_call_tool` with server_name "gitea",
|
||||
tool_name "list_issues", with `labels: "feedback"`, `owner: "oblachno-oss"`,
|
||||
`repo: "devx"`. Check if an open issue already covers the same topic.
|
||||
Do NOT create duplicates.
|
||||
|
||||
2. **Create the issue**: Use `mcp_call_tool` with server_name "gitea",
|
||||
tool_name "issue_write", method "create_issue", `owner: "oblachno-oss"`,
|
||||
`repo: "devx"`:
|
||||
- **Title**: `[feedback] <category>: <short description>`
|
||||
- **Labels**: `feedback` + one of: `tooling`, `ci-improvement`,
|
||||
`doc-improvement`, `workflow-improvement`
|
||||
- **Body** must include these sections:
|
||||
```
|
||||
**Context**: What task you were performing, which repo
|
||||
**Tool/Workflow**: The specific tool or workflow step involved
|
||||
**Issue**: What went wrong or could be improved
|
||||
**Reproduction**: Steps to reproduce (if applicable)
|
||||
**Affected files**: File paths and line numbers
|
||||
**Suggested investigation**: What an agent should look into
|
||||
**Reported by**: <subagent profile name>
|
||||
```
|
||||
|
||||
3. **Report back**: Include the issue URL in your report to the parent agent.
|
||||
|
||||
### When NOT to Create Feedback Issues
|
||||
- Transient failures (network blips, rate limits, Docker pull flakiness)
|
||||
- Issues you can fix yourself — fix them instead
|
||||
- CI run failures — those are handled by `notify_failure` automatically
|
||||
- Missing labels — `configure_repo` creates standard labels on next master push
|
||||
@@ -1,165 +0,0 @@
|
||||
---
|
||||
name: doc-sync-specialist
|
||||
description: Handles documentation coverage gaps, doc structure linting, and wiki sync failures. Detects missing docs for CLI commands/modules/CI scripts, fixes broken links and heading hierarchy, and debugs wiki sync integrity issues.
|
||||
model: glm-5.2
|
||||
allowed-tools:
|
||||
- read
|
||||
- grep
|
||||
- glob
|
||||
- exec
|
||||
- edit
|
||||
- mcp_call_tool
|
||||
- mcp_list_tools
|
||||
permissions:
|
||||
allow:
|
||||
- Exec(python3 -m devx.ci.doc_coverage *)
|
||||
- Exec(python3 -m devx.ci.lint_docs *)
|
||||
- Exec(python3 -m devx.ci.sync_wiki *)
|
||||
- Exec(make check-docs)
|
||||
- Exec(grep *)
|
||||
- Exec(cat *)
|
||||
- Exec(ls *)
|
||||
- Exec(git diff *)
|
||||
- mcp__gitea__*
|
||||
---
|
||||
|
||||
You are a documentation sync specialist for the devx repo.
|
||||
|
||||
## Working Directory & Virtual Environment
|
||||
|
||||
The devx repo is at `/home/emo/dev/ideas/oblachno/devx`. Always `cd` there first.
|
||||
|
||||
All Python tools run inside `.venv`. `make` targets handle activation
|
||||
automatically — always use `make <target>`, never raw `pytest` or `ruff`
|
||||
commands. If `.venv` doesn't exist, run `make setup` first.
|
||||
|
||||
## Documentation Structure
|
||||
|
||||
```
|
||||
docs/
|
||||
├── index.md # Wiki homepage
|
||||
├── mapping.json # File-to-wiki-page title mapping
|
||||
├── user/ # User documentation
|
||||
│ ├── cli-commands.md
|
||||
│ ├── getting-started.md
|
||||
│ └── ...
|
||||
└── tech/ # Technical documentation
|
||||
├── architecture.md
|
||||
├── ci-cd-workflow.md
|
||||
└── ...
|
||||
```
|
||||
|
||||
## Key Tools
|
||||
|
||||
- `devx.ci.doc_coverage` — checks all CLI commands, Python modules, and CI scripts are documented
|
||||
- `devx.ci.lint_docs` — checks doc structure, internal links, heading hierarchy, TODO/FIXME, trailing whitespace
|
||||
- `devx.ci.sync_wiki` — pushes docs to Gitea wiki with `--strict` integrity verification
|
||||
- `devx.tools.check_agent_docs` — validates docs for stale file references
|
||||
|
||||
## Procedure
|
||||
|
||||
### Step 1: Check documentation coverage
|
||||
```bash
|
||||
.venv/bin/python -m devx.ci.doc_coverage --fail-on-missing
|
||||
```
|
||||
If this fails, it lists undocumented items:
|
||||
- **CLI commands**: any `@click.command()` or `@click.group()` without a docs entry
|
||||
- **Python modules**: any `src/devx/*.py` without architecture documentation
|
||||
- **CI scripts**: any `src/devx/ci/*.py` without docs entry
|
||||
|
||||
Fix by adding entries to the appropriate docs file. Cross-reference with
|
||||
`docs/user/cli-commands.md` for CLI commands and `docs/tech/architecture.md`
|
||||
for modules.
|
||||
|
||||
### Step 2: Lint documentation structure
|
||||
```bash
|
||||
.venv/bin/python -m devx.ci.lint_docs --root .
|
||||
```
|
||||
Common issues:
|
||||
- **Broken internal links**: `[text](page.md)` where `page.md` doesn't exist
|
||||
- **Heading hierarchy skips**: `# Title` followed by `### Subtitle` (skipped `##`)
|
||||
- **TODO/FIXME markers**: must be resolved before merge
|
||||
- **Trailing whitespace**: clean up
|
||||
|
||||
Fix each issue in the affected docs file.
|
||||
|
||||
### Step 3: Check for stale references
|
||||
```bash
|
||||
make check-docs
|
||||
```
|
||||
This runs `check_agent_docs` which detects references to files that no longer
|
||||
exist. If a script/module was renamed or deleted, update all doc references.
|
||||
|
||||
### Step 4: Verify wiki sync (if investigating a sync failure)
|
||||
```bash
|
||||
.venv/bin/python -m devx.ci.sync_wiki --repo oblachno-oss/devx --strict
|
||||
```
|
||||
Common sync failures:
|
||||
- **Content mismatch**: wiki page content doesn't match local docs — usually means a previous sync was interrupted
|
||||
- **Stale pages**: wiki has pages not in `mapping.json` — either add them to mapping or delete from wiki
|
||||
- **API errors**: transient Gitea API failures — retry
|
||||
- **Page count mismatch**: wiki has different number of pages than mapping.json
|
||||
|
||||
Check `docs/mapping.json` — every docs file should have a mapping entry:
|
||||
```json
|
||||
{
|
||||
"user/cli-commands.md": "CLI-Commands",
|
||||
"tech/architecture.md": "Architecture"
|
||||
}
|
||||
```
|
||||
|
||||
If adding a new docs file, add it to `mapping.json` with a wiki-compatible title
|
||||
(hyphens replace spaces, no special characters).
|
||||
|
||||
### Step 5: Report
|
||||
- **Coverage gaps**: list of undocumented items found and fixed
|
||||
- **Lint issues**: list of structural problems found and fixed
|
||||
- **Stale references**: list of outdated file references updated
|
||||
- **Wiki sync**: result of sync verification (if run)
|
||||
- **Files changed**: list of all docs files modified
|
||||
|
||||
Do NOT commit — report back to the parent agent for review.
|
||||
|
||||
## Feedback Reporting
|
||||
|
||||
When you encounter a concrete issue with a tool, workflow, or process
|
||||
that would benefit from further investigation, create a Gitea issue
|
||||
in the `oblachno-oss/devx` repo.
|
||||
|
||||
### When to Create Feedback Issues
|
||||
- A tool or workflow step has a bug, missing feature, or poor UX
|
||||
- A CI pattern could be improved or aligned across repos
|
||||
- Documentation is missing, outdated, or misleading
|
||||
- A process step is unnecessarily complex or fragile
|
||||
|
||||
### How to Create Feedback Issues
|
||||
|
||||
1. **Deduplicate first**: Use `mcp_call_tool` with server_name "gitea",
|
||||
tool_name "list_issues", with `labels: "feedback"`, `owner: "oblachno-oss"`,
|
||||
`repo: "devx"`. Check if an open issue already covers the same topic.
|
||||
Do NOT create duplicates.
|
||||
|
||||
2. **Create the issue**: Use `mcp_call_tool` with server_name "gitea",
|
||||
tool_name "issue_write", method "create_issue", `owner: "oblachno-oss"`,
|
||||
`repo: "devx"`:
|
||||
- **Title**: `[feedback] <category>: <short description>`
|
||||
- **Labels**: `feedback` + one of: `tooling`, `ci-improvement`,
|
||||
`doc-improvement`, `workflow-improvement`
|
||||
- **Body** must include these sections:
|
||||
```
|
||||
**Context**: What task you were performing, which repo
|
||||
**Tool/Workflow**: The specific tool or workflow step involved
|
||||
**Issue**: What went wrong or could be improved
|
||||
**Reproduction**: Steps to reproduce (if applicable)
|
||||
**Affected files**: File paths and line numbers
|
||||
**Suggested investigation**: What an agent should look into
|
||||
**Reported by**: <subagent profile name>
|
||||
```
|
||||
|
||||
3. **Report back**: Include the issue URL in your report to the parent agent.
|
||||
|
||||
### When NOT to Create Feedback Issues
|
||||
- Transient failures (network blips, rate limits, Docker pull flakiness)
|
||||
- Issues you can fix yourself — fix them instead
|
||||
- CI run failures — those are handled by `notify_failure` automatically
|
||||
- Missing labels — `configure_repo` creates standard labels on next master push
|
||||
@@ -1,183 +0,0 @@
|
||||
---
|
||||
name: docker-image-builder
|
||||
description: Handles Docker image build, push, and cleanup for the 3-tier runner images (ci-base, ci-quality, ci-full). Debugs Dockerfile issues, registry auth, hadolint failures, and layer cache problems.
|
||||
model: glm-5.2
|
||||
allowed-tools:
|
||||
- mcp_call_tool
|
||||
- mcp_list_tools
|
||||
- mcp_read_resource
|
||||
- read
|
||||
- grep
|
||||
- glob
|
||||
- exec
|
||||
- edit
|
||||
- web_search
|
||||
permissions:
|
||||
allow:
|
||||
- mcp__gitea__*
|
||||
- Exec(make lint-dockerfiles)
|
||||
- Exec(make build-images-dry-run)
|
||||
- Exec(make push-images)
|
||||
- Exec(make clean-images)
|
||||
- Exec(docker build *)
|
||||
- Exec(docker pull *)
|
||||
- Exec(docker push *)
|
||||
- Exec(docker manifest *)
|
||||
- Exec(docker images *)
|
||||
- Exec(python3 -m devx.tools.build_image *)
|
||||
- Exec(python3 -m devx.tools.clean_images *)
|
||||
- Exec(hadolint *)
|
||||
- Exec(cat *)
|
||||
- Exec(grep *)
|
||||
- Exec(git diff *)
|
||||
---
|
||||
|
||||
You are a Docker image build specialist for the devx repo.
|
||||
|
||||
## Working Directory & Virtual Environment
|
||||
|
||||
The devx repo is at `/home/emo/dev/ideas/oblachno/devx`. Always `cd` there first.
|
||||
|
||||
All Python tools run inside `.venv`. `make` targets handle activation
|
||||
automatically — always use `make <target>`, never raw `pytest` or `ruff`
|
||||
commands. If `.venv` doesn't exist, run `make setup` first.
|
||||
|
||||
## Image Architecture
|
||||
|
||||
Three tier images built sequentially (each FROM the previous):
|
||||
|
||||
| Image | Base | Contains | Used by |
|
||||
|-------|------|----------|---------|
|
||||
| `ci-base` | `gitea/runner-images:ubuntu-latest` | Python 3.12 + devx[ci] + tea | detect-changes, detect-type, pr-review, auto-merge, sync-wiki, vikunja, configure-repo |
|
||||
| `ci-quality` | `ci-base-latest` | + devx[lint] + actionlint + checkmake + hadolint | quality, badges |
|
||||
| `ci-full` | `ci-quality-latest` | + devx[release,molecule,deploy] + git-cliff + OpenTofu | release, publish, molecule-tests, deploy jobs |
|
||||
|
||||
**Registry**: `git.oblachno.oblachno.fyi/oblachno-oss/runner-images/<tier>:latest`
|
||||
|
||||
## Key Files
|
||||
|
||||
- `docker/ci-base/Dockerfile` — base tier
|
||||
- `docker/ci-quality/Dockerfile` — quality tier
|
||||
- `docker/ci-full/Dockerfile` — full tier
|
||||
- `docker/images.json` — build manifest (image definitions, tags, push targets)
|
||||
- `.hadolint.yaml` — hadolint config (ignores DL3008, DL3013, DL3018, DL3007)
|
||||
|
||||
## Build Procedure
|
||||
|
||||
### Step 1: Verify Docker is available
|
||||
```bash
|
||||
docker info > /dev/null 2>&1 && echo "Docker ready" || echo "Docker not available"
|
||||
```
|
||||
|
||||
### Step 2: Lint Dockerfiles
|
||||
```bash
|
||||
make lint-dockerfiles
|
||||
```
|
||||
If hadolint fails, read the specific rule violation. Check `.hadolint.yaml`
|
||||
for already-ignored rules before adding new ignores.
|
||||
|
||||
### Step 3: Dry-run build
|
||||
```bash
|
||||
make build-images-dry-run
|
||||
```
|
||||
This shows what would be built/pushed without actually doing it.
|
||||
Verify the image names, tags, and registry paths are correct.
|
||||
|
||||
### Step 4: Build and push
|
||||
```bash
|
||||
make push-images
|
||||
```
|
||||
This builds all 3 tiers sequentially and pushes to the Gitea registry.
|
||||
|
||||
If only one tier needs rebuilding:
|
||||
```bash
|
||||
.venv/bin/python -m devx.tools.build_image \
|
||||
--dockerfile docker/ci-quality/Dockerfile \
|
||||
--name oblachno-oss/runner-images/ci-quality \
|
||||
--tag latest \
|
||||
--registry git.oblachno.oblachno.fyi \
|
||||
--push
|
||||
```
|
||||
|
||||
### Step 5: Clean up old versions
|
||||
```bash
|
||||
make clean-images
|
||||
```
|
||||
Keeps last 2 versions + latest. Uses Gitea API via `clean_images.py`.
|
||||
|
||||
## Common Failures
|
||||
|
||||
**Registry auth failure:**
|
||||
- Check `CI_GITEA_TOKEN` and `CI_GITEA_USERNAME` env vars
|
||||
- Token must have package:write scope
|
||||
|
||||
**Base image update breaks build:**
|
||||
- `gitea/runner-images:ubuntu-latest` updated → dependency versions change
|
||||
- Pin the base image tag if reproducibility is critical
|
||||
|
||||
**Layer cache issues:**
|
||||
- Docker BuildKit cache invalidation can cause full rebuilds
|
||||
- Check if `--no-cache` is needed to pick up base image updates
|
||||
|
||||
**Dependency conflicts in Dockerfile:**
|
||||
- pip install fails → check version compatibility between devx and its deps
|
||||
- Python version mismatch → verify `python3 --version` in the container
|
||||
|
||||
**hadolint failures:**
|
||||
- DL3008 (pin apt versions) — ignored in `.hadolint.yaml`
|
||||
- DL3013 (pin pip versions) — ignored (we use `==` in pyproject.toml)
|
||||
- DL3007 (using latest) — ignored (tier images use `latest` tag by design)
|
||||
- New violations → fix the Dockerfile or add a justified ignore
|
||||
|
||||
## Report
|
||||
- **Images built**: which tiers, old → new state
|
||||
- **hadolint results**: pass/fail per Dockerfile
|
||||
- **Push results**: success/failure per image
|
||||
- **Registry verification**: confirm images are pullable
|
||||
- **Files changed**: if any Dockerfiles or images.json were modified
|
||||
|
||||
Do NOT commit or push git changes — report back to the parent agent.
|
||||
|
||||
## Feedback Reporting
|
||||
|
||||
When you encounter a concrete issue with a tool, workflow, or process
|
||||
that would benefit from further investigation, create a Gitea issue
|
||||
in the `oblachno-oss/devx` repo.
|
||||
|
||||
### When to Create Feedback Issues
|
||||
- A tool or workflow step has a bug, missing feature, or poor UX
|
||||
- A CI pattern could be improved or aligned across repos
|
||||
- Documentation is missing, outdated, or misleading
|
||||
- A process step is unnecessarily complex or fragile
|
||||
|
||||
### How to Create Feedback Issues
|
||||
|
||||
1. **Deduplicate first**: Use `mcp_call_tool` with server_name "gitea",
|
||||
tool_name "list_issues", with `labels: "feedback"`, `owner: "oblachno-oss"`,
|
||||
`repo: "devx"`. Check if an open issue already covers the same topic.
|
||||
Do NOT create duplicates.
|
||||
|
||||
2. **Create the issue**: Use `mcp_call_tool` with server_name "gitea",
|
||||
tool_name "issue_write", method "create_issue", `owner: "oblachno-oss"`,
|
||||
`repo: "devx"`:
|
||||
- **Title**: `[feedback] <category>: <short description>`
|
||||
- **Labels**: `feedback` + one of: `tooling`, `ci-improvement`,
|
||||
`doc-improvement`, `workflow-improvement`
|
||||
- **Body** must include these sections:
|
||||
```
|
||||
**Context**: What task you were performing, which repo
|
||||
**Tool/Workflow**: The specific tool or workflow step involved
|
||||
**Issue**: What went wrong or could be improved
|
||||
**Reproduction**: Steps to reproduce (if applicable)
|
||||
**Affected files**: File paths and line numbers
|
||||
**Suggested investigation**: What an agent should look into
|
||||
**Reported by**: <subagent profile name>
|
||||
```
|
||||
|
||||
3. **Report back**: Include the issue URL in your report to the parent agent.
|
||||
|
||||
### When NOT to Create Feedback Issues
|
||||
- Transient failures (network blips, rate limits, Docker pull flakiness)
|
||||
- Issues you can fix yourself — fix them instead
|
||||
- CI run failures — those are handled by `notify_failure` automatically
|
||||
- Missing labels — `configure_repo` creates standard labels on next master push
|
||||
@@ -1,167 +0,0 @@
|
||||
---
|
||||
name: workflow-validator
|
||||
description: Validates Gitea Actions workflow YAML files using actionlint and act_runner dry-run. Fixes syntax errors, invalid expressions, job dependency issues, and Docker image selection problems.
|
||||
model: glm-5.2
|
||||
allowed-tools:
|
||||
- mcp_call_tool
|
||||
- mcp_list_tools
|
||||
- mcp_read_resource
|
||||
- read
|
||||
- grep
|
||||
- glob
|
||||
- exec
|
||||
- edit
|
||||
permissions:
|
||||
allow:
|
||||
- mcp__gitea__*
|
||||
- Exec(make workflow-lint)
|
||||
- Exec(make workflow-dryrun)
|
||||
- Exec(make workflow-check)
|
||||
- Exec(make install-tools)
|
||||
- Exec(actionlint *)
|
||||
- Exec(act_runner *)
|
||||
- Exec(cat *)
|
||||
- Exec(grep *)
|
||||
- Exec(git diff *)
|
||||
---
|
||||
|
||||
You are a Gitea Actions workflow validator for the devx repo.
|
||||
|
||||
## Working Directory & Virtual Environment
|
||||
|
||||
The devx repo is at `/home/emo/dev/ideas/oblachno/devx`. Always `cd` there first.
|
||||
|
||||
All Python tools run inside `.venv`. `make` targets handle activation
|
||||
automatically — always use `make <target>`, never raw `pytest` or `ruff`
|
||||
commands. If `.venv` doesn't exist, run `make setup` first.
|
||||
|
||||
## Key Files
|
||||
|
||||
- `.gitea/workflows/ci.yml` — PR pipeline (quality, detect-changes, release-dry-run, pr-review, auto-merge)
|
||||
- `.gitea/workflows/post-merge.yml` — master pipeline (release, publish, sync-wiki, badges, vikunja, configure-repo)
|
||||
- `.gitea/workflows/build-images.yml` — Docker image build pipeline
|
||||
- `.gitea/actionlint.yaml` — actionlint config (registers custom `docker` runner label)
|
||||
|
||||
## Validation Procedure
|
||||
|
||||
### Step 1: Install tools (if not present)
|
||||
```bash
|
||||
make install-tools # installs actionlint, act_runner to ~/.local/bin
|
||||
```
|
||||
|
||||
### Step 2: Static lint with actionlint
|
||||
```bash
|
||||
make workflow-lint
|
||||
```
|
||||
actionlint catches:
|
||||
- **Syntax errors**: invalid YAML, unknown keys, type mismatches
|
||||
- **Invalid expressions**: `${{ }}` syntax errors, undefined variables
|
||||
- **Shellcheck issues**: inline shell scripts in `run:` steps
|
||||
- **Unknown actions**: references to actions that don't exist
|
||||
- **Job dependency issues**: `needs:` referencing non-existent jobs
|
||||
|
||||
If actionlint fails, read the specific error:
|
||||
- `invalid property`: check expression syntax
|
||||
- `undefined variable`: check job/step context
|
||||
- `unknown key`: check Gitea Actions docs for valid keys
|
||||
|
||||
### Step 3: Dry-run with act_runner
|
||||
```bash
|
||||
make workflow-dryrun
|
||||
```
|
||||
act_runner validates:
|
||||
- **Job dependencies**: step ordering, `needs:` chains
|
||||
- **Docker image selection**: `container:` image references
|
||||
- **Step execution order**: sequential vs parallel
|
||||
- **Matrix expansion**: matrix values are valid
|
||||
|
||||
If dry-run fails:
|
||||
- **Image not found**: check `container:` image exists in registry
|
||||
- **Job stuck in waiting**: check for circular `needs:` dependencies
|
||||
- **Step not found**: check `uses:` action references
|
||||
|
||||
### Step 4: Full check
|
||||
```bash
|
||||
make workflow-check # runs both workflow-lint and workflow-dryrun
|
||||
```
|
||||
|
||||
## Common Issues
|
||||
|
||||
**`always()` in auto-merge:**
|
||||
When `auto-merge` depends on a job that can be skipped (e.g. `molecule-tests`),
|
||||
the `if:` condition MUST include `always() &&` at the start. Without it,
|
||||
Gitea Actions skips `auto-merge` when any dependency is skipped, even if
|
||||
the condition explicitly allows `result == 'skipped'`.
|
||||
|
||||
```yaml
|
||||
auto-merge:
|
||||
needs: [quality, detect-changes, pr-review, molecule-tests]
|
||||
if: >-
|
||||
always() &&
|
||||
github.event_name == 'pull_request' &&
|
||||
needs.quality.result == 'success' &&
|
||||
(needs.molecule-tests.result == 'success' || needs.molecule-tests.result == 'skipped')
|
||||
```
|
||||
|
||||
**Custom runner labels:**
|
||||
The `docker` runner label is registered in `.gitea/actionlint.yaml`.
|
||||
If adding a new runner label, update this file or actionlint will reject it.
|
||||
|
||||
**Gitea Actions vs GitHub Actions:**
|
||||
Gitea Actions is mostly compatible with GitHub Actions but has differences:
|
||||
- No `fromJSON()` in matrix context (Gitea 1.26.x)
|
||||
- `concurrency` blocks can cause jobs to get stuck (Gitea 1.26.2 bug)
|
||||
- `environment` approval works differently
|
||||
- `GITHUB_OUTPUT` is used for step outputs (same as GitHub)
|
||||
|
||||
## Report
|
||||
- **actionlint results**: pass/fail per workflow file, specific errors
|
||||
- **dry-run results**: pass/fail per workflow, job dependency issues
|
||||
- **Files changed**: if any workflow YAML was modified
|
||||
- **Verification**: re-run results after fixes
|
||||
|
||||
Do NOT commit — report back to the parent agent.
|
||||
|
||||
## Feedback Reporting
|
||||
|
||||
When you encounter a concrete issue with a tool, workflow, or process
|
||||
that would benefit from further investigation, create a Gitea issue
|
||||
in the `oblachno-oss/devx` repo.
|
||||
|
||||
### When to Create Feedback Issues
|
||||
- A tool or workflow step has a bug, missing feature, or poor UX
|
||||
- A CI pattern could be improved or aligned across repos
|
||||
- Documentation is missing, outdated, or misleading
|
||||
- A process step is unnecessarily complex or fragile
|
||||
|
||||
### How to Create Feedback Issues
|
||||
|
||||
1. **Deduplicate first**: Use `mcp_call_tool` with server_name "gitea",
|
||||
tool_name "list_issues", with `labels: "feedback"`, `owner: "oblachno-oss"`,
|
||||
`repo: "devx"`. Check if an open issue already covers the same topic.
|
||||
Do NOT create duplicates.
|
||||
|
||||
2. **Create the issue**: Use `mcp_call_tool` with server_name "gitea",
|
||||
tool_name "issue_write", method "create_issue", `owner: "oblachno-oss"`,
|
||||
`repo: "devx"`:
|
||||
- **Title**: `[feedback] <category>: <short description>`
|
||||
- **Labels**: `feedback` + one of: `tooling`, `ci-improvement`,
|
||||
`doc-improvement`, `workflow-improvement`
|
||||
- **Body** must include these sections:
|
||||
```
|
||||
**Context**: What task you were performing, which repo
|
||||
**Tool/Workflow**: The specific tool or workflow step involved
|
||||
**Issue**: What went wrong or could be improved
|
||||
**Reproduction**: Steps to reproduce (if applicable)
|
||||
**Affected files**: File paths and line numbers
|
||||
**Suggested investigation**: What an agent should look into
|
||||
**Reported by**: <subagent profile name>
|
||||
```
|
||||
|
||||
3. **Report back**: Include the issue URL in your report to the parent agent.
|
||||
|
||||
### When NOT to Create Feedback Issues
|
||||
- Transient failures (network blips, rate limits, Docker pull flakiness)
|
||||
- Issues you can fix yourself — fix them instead
|
||||
- CI run failures — those are handled by `notify_failure` automatically
|
||||
- Missing labels — `configure_repo` creates standard labels on next master push
|
||||
@@ -1,37 +0,0 @@
|
||||
# devx-workflow
|
||||
|
||||
Quick reference for devx tools when working on the devx repo itself.
|
||||
|
||||
## PR Workflow (use these, not raw git/tea/MCP)
|
||||
|
||||
| Task | Command |
|
||||
|------|---------|
|
||||
| Create Vikunja task | `make create-task -- --title "..." --description "..."` |
|
||||
| Create PR | `make create-pr` |
|
||||
| Push + create PR | `make push-with-pr` |
|
||||
| Check CI status | `make devx-pr-status` or `make devx-pr-status PR=42 WAIT=1` |
|
||||
| Fetch CI failure logs | `make devx-pr-logs` or `make devx-pr-logs PR=42 JOB=quality TAIL=50` |
|
||||
| Add ready-to-merge label | `make devx-pr-label` or `make devx-pr-label PR=42` |
|
||||
| Post PR review | `make devx-pr-review PR=42 EVENT=APPROVE BODY="..." CHECKLIST=1,2,3,4,5,6,7,8,9,10,11,12,13` |
|
||||
| Rebase current branch | `make rebase` |
|
||||
| Rebase PR via API | `make pr-rebase` or `make pr-rebase PR=42` |
|
||||
|
||||
## Auto-merge Behavior
|
||||
|
||||
When the `ready-to-merge` label is added and all CI checks pass:
|
||||
1. Auto-merge validates PR title format (`DEVX-N: <vikunja task title>`)
|
||||
2. If branch is behind master, auto-merge **rebases via Gitea API** automatically
|
||||
3. The rebase triggers a new CI run; the next auto-merge attempt merges
|
||||
4. No manual rebase needed unless the API rebase fails
|
||||
|
||||
## Key Rules
|
||||
|
||||
- Never manually merge via API — always use auto-merge with `ready-to-merge` label
|
||||
- Branch naming: `DEVX-N-short-description` (N = Vikunja task ID)
|
||||
- Commit format: conventional commits (`feat:`, `fix:`, `docs:`, etc.)
|
||||
- PR title: `DEVX-N: <vikunja task title>` (auto-derived by `make create-pr`)
|
||||
- 100% test coverage required for all source changes
|
||||
- All user-facing strings wrapped in `_()` for i18n
|
||||
- Translation keys must be added to `src/devx/translations.json`
|
||||
- New CLI commands must be documented in `docs/user/cli-commands.md`
|
||||
- New tools must be registered in `src/devx/cli.py` and added to Make targets
|
||||
@@ -1,98 +0,0 @@
|
||||
# testing-and-debugging
|
||||
|
||||
Make targets for testing, debugging, and CI investigation. **Use these
|
||||
instead of raw `pytest`, `ruff`, or `actionlint` commands.**
|
||||
|
||||
## Why Make Targets
|
||||
|
||||
Make targets encapsulate the correct venv activation, PYTHONPATH, env
|
||||
vars, and flags. Running raw commands bypasses venv activation and
|
||||
produces false failures (missing dependencies, wrong Python version).
|
||||
|
||||
## Unit Tests
|
||||
|
||||
| Task | Command | Notes |
|
||||
|------|---------|-------|
|
||||
| Run all unit tests | `make test-unit` | Fast, no coverage |
|
||||
| Run with coverage | `make pytest-cov` | **Required before push** — enforces 100% |
|
||||
| Run single test | `make pytest-cov TEST=tests/test_foo.py::test_bar` | |
|
||||
| Check test speed | `make check-test-speed` | Fails if tests > 10s total or > 0.5s each |
|
||||
| Check test coverage | `make check-test-coverage` | Fails if source changed but tests didn't |
|
||||
|
||||
## Linting
|
||||
|
||||
| Task | Command | Notes |
|
||||
|------|---------|-------|
|
||||
| Full lint | `make lint-all` | ruff + workflow-lint + lint-dockerfiles |
|
||||
| Ruff only | `make lint-ruff` | |
|
||||
| Format check | `make lint-format` | |
|
||||
| Type check | `make typecheck` | pyright |
|
||||
| Bandit | `make lint-bandit` | Security linter |
|
||||
| Workflow lint | `make workflow-check` | actionlint + act_runner dry-run |
|
||||
| Dockerfile lint | `make lint-dockerfiles` | hadolint on all Dockerfiles |
|
||||
| Check mutable globals | `make check-mutable-globals` | Detects module-level mutable state |
|
||||
| Check dep docs | `make check-dep-docs` | Verifies pyproject.toml deps have comments |
|
||||
|
||||
## Pre-Push Verification
|
||||
|
||||
**Before pushing any branch:**
|
||||
|
||||
```bash
|
||||
make pre-push
|
||||
```
|
||||
|
||||
This runs `lint-all` + `pytest-cov`. The pre-push git hook only
|
||||
validates the Vikunja task exists — it does NOT run tests. You must
|
||||
run `make pre-push` manually.
|
||||
|
||||
## CI Failure Investigation
|
||||
|
||||
When investigating a CI failure:
|
||||
|
||||
1. **Fetch logs via MCP** — use `mcp_call_tool` with gitea server,
|
||||
`actions_run_read` method, `download_job_log` tool
|
||||
2. **Reproduce locally** — use `make pytest-cov` or `make lint-all`
|
||||
depending on which CI job failed
|
||||
3. **Never run raw pytest** — always use the make target
|
||||
|
||||
## Virtual Environment
|
||||
|
||||
All commands run inside `.venv`. `make` targets handle activation
|
||||
automatically. For raw commands (rare), activate first:
|
||||
|
||||
```bash
|
||||
source activate.sh # bash/zsh
|
||||
source activate.fish # fish
|
||||
source activate.zsh # zsh
|
||||
```
|
||||
|
||||
If `.venv` doesn't exist, run `make setup` first.
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
### Coverage Verification Before Push
|
||||
|
||||
**Always run `make pytest-cov` before pushing** — CI enforces 100%
|
||||
coverage and will fail the PR if any lines are uncovered. This is the
|
||||
most common cause of CI quality job failures after code changes. The
|
||||
pre-push git hook only validates Vikunja task existence, not tests.
|
||||
|
||||
### API Response Type Checking
|
||||
|
||||
Never use `is True`/`is False` identity checks on API response values.
|
||||
Many APIs return boolean values as strings (`"true"`/`"false"`). Use
|
||||
the `is_truthy()`/`is_falsy()` helpers from `devx.utils.api` or compare
|
||||
against string values.
|
||||
|
||||
### Time Mocking in Tests
|
||||
|
||||
Always mock `time.sleep` and `time.monotonic` in unit tests using
|
||||
`@patch` decorators. Real sleep calls make tests slow and exceed test
|
||||
speed limits (10s total, 0.5s per test).
|
||||
|
||||
### Mutable Global State
|
||||
|
||||
The `check-mutable-globals` tool detects module-level mutable state
|
||||
(lists, dicts, sets) that can cause test pollution. Avoid module-level
|
||||
mutable defaults — use factory functions or `None` with initialization
|
||||
inside functions.
|
||||
@@ -1,20 +0,0 @@
|
||||
.venv/
|
||||
.git/
|
||||
.gitea/
|
||||
tests/
|
||||
docs/
|
||||
*.egg-info/
|
||||
__pycache__/
|
||||
htmlcov/
|
||||
.coverage
|
||||
dist/
|
||||
build/
|
||||
*.md
|
||||
!README.md
|
||||
.env
|
||||
.env.example
|
||||
activate.sh
|
||||
activate.fish
|
||||
activate.zsh
|
||||
hooks/
|
||||
.devin/
|
||||
@@ -1,30 +0,0 @@
|
||||
# Role-based Gitea API tokens.
|
||||
# Each token serves a specific role. For small teams the developer and CI
|
||||
# tokens may belong to the same user, but the reviewer token MUST belong to a
|
||||
# different Gitea user than the PR author so Gitea accepts approval reviews.
|
||||
# Create at: https://git.oblachno.oblachno.fyi/user/settings/applications
|
||||
|
||||
# Developer token — used by local tooling: create-task, create-pr, setup, etc.
|
||||
DEVELOPER_GITEA_API_TOKEN=
|
||||
|
||||
# CI token — used by CI workflows and scripts that do not post approvals.
|
||||
# Legacy CI_GITEA_TOKEN is also accepted.
|
||||
CI_GITEA_API_TOKEN=
|
||||
|
||||
# Reviewer token — used by the auto-merge workflow to post APPROVE reviews.
|
||||
# This must be a different Gitea user from the developer/CI user.
|
||||
REVIEWER_GITEA_API_TOKEN=
|
||||
|
||||
# Vikunja API token (required for post-merge task updates)
|
||||
# Create at: https://work.oblachno.oblachno.fyi/settings/tokens
|
||||
VIKUNJA_TOKEN=
|
||||
|
||||
# Vikunja project ID for this project (devx = 8)
|
||||
DEVX_VIKUNJA_PROJECT_ID=8
|
||||
|
||||
# Override default API URLs (optional)
|
||||
# DEVX_GITEA_API_URL=https://git.oblachno.oblachno.fyi/api/v1
|
||||
# DEVX_VIKUNJA_API_URL=https://work.oblachno.oblachno.fyi/api/v1
|
||||
|
||||
# Override default language (optional, en is default)
|
||||
# DEVX_LANG=en
|
||||
@@ -1,4 +0,0 @@
|
||||
# actionlint configuration for Gitea Actions workflows
|
||||
self-hosted-runner:
|
||||
labels:
|
||||
- docker
|
||||
@@ -1,138 +0,0 @@
|
||||
name: Build Images
|
||||
|
||||
# Builds and pushes pre-built Docker runner images to the Gitea registry.
|
||||
# These images eliminate the 40-120s setup tax on every CI job by baking
|
||||
# devx and all dependencies into the image.
|
||||
#
|
||||
# Triggers:
|
||||
# - After post-merge workflow completes successfully (workflow_run)
|
||||
# This ensures images are only rebuilt AFTER the release is published
|
||||
# to PyPI, so the image always has the latest released version.
|
||||
# - Manually via workflow_dispatch
|
||||
#
|
||||
# Consolidated into 2 jobs (from 3):
|
||||
# build-and-push (includes release-commit detection) ──→ cleanup
|
||||
#
|
||||
# The workflow builds 3 tier images in sequence:
|
||||
# ci-base → ci-quality → ci-full
|
||||
# Each tier builds FROM the previous one, so they must be built in order.
|
||||
# After pushing, a cleanup job removes old versions (keeps last 2 + latest).
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["Post-merge"]
|
||||
types: [completed]
|
||||
branches: [master]
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: build-images
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: docker
|
||||
timeout-minutes: 30
|
||||
outputs:
|
||||
is-release: ${{ steps.check.outputs.is-release }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- name: Set up environment
|
||||
env:
|
||||
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||
run: make setup-release
|
||||
- name: Check if this is a release commit
|
||||
id: check
|
||||
env:
|
||||
PYTHONPATH: src
|
||||
run: |
|
||||
. .venv/bin/activate
|
||||
python3 -m devx.ci.detect_release_commit
|
||||
- name: Docker registry login
|
||||
if: >-
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success' && steps.check.outputs.is-release == 'false')
|
||||
env:
|
||||
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||
CI_GITEA_USERNAME: ${{ vars.CI_GITEA_USERNAME }}
|
||||
run: |
|
||||
. .venv/bin/activate
|
||||
_TOKEN="$CI_GITEA_API_TOKEN"
|
||||
[ -z "$_TOKEN" ] && _TOKEN="$DEVELOPER_GITEA_API_TOKEN"
|
||||
[ -z "$_TOKEN" ] && _TOKEN="$CI_GITEA_TOKEN"
|
||||
if [ -z "$_TOKEN" ]; then echo "Gitea API token not set — skipping Docker login"; exit 1; fi
|
||||
echo "$_TOKEN" | docker login git.oblachno.oblachno.fyi -u "$CI_GITEA_USERNAME" --password-stdin
|
||||
- name: Build and push tier images
|
||||
if: >-
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success' && steps.check.outputs.is-release == 'false')
|
||||
env:
|
||||
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||
CI_GITEA_USERNAME: ${{ vars.CI_GITEA_USERNAME }}
|
||||
PYTHONPATH: src
|
||||
run: |
|
||||
. .venv/bin/activate
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
# Build ci-base first (it's the base for ci-quality and ci-full)
|
||||
python3 -m devx.tools.build_image \
|
||||
--dockerfile docker/ci-base/Dockerfile \
|
||||
--name oblachno-oss/runner-images/ci-base \
|
||||
--tag latest \
|
||||
--registry git.oblachno.oblachno.fyi \
|
||||
--push --pull
|
||||
# Build ci-quality (FROM ci-base-latest)
|
||||
python3 -m devx.tools.build_image \
|
||||
--dockerfile docker/ci-quality/Dockerfile \
|
||||
--name oblachno-oss/runner-images/ci-quality \
|
||||
--tag latest \
|
||||
--registry git.oblachno.oblachno.fyi \
|
||||
--push
|
||||
# Build ci-full (FROM ci-quality-latest)
|
||||
python3 -m devx.tools.build_image \
|
||||
--dockerfile docker/ci-full/Dockerfile \
|
||||
--name oblachno-oss/runner-images/ci-full \
|
||||
--tag latest \
|
||||
--registry git.oblachno.oblachno.fyi \
|
||||
--push
|
||||
- name: Notify on failure
|
||||
if: failure()
|
||||
env:
|
||||
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||
PYTHONPATH: src
|
||||
run: |
|
||||
. .venv/bin/activate 2>/dev/null || true
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
python3 -m devx.ci.notify_failure \
|
||||
--repo "${{ github.repository }}" \
|
||||
--run-id "${{ github.run_id }}" \
|
||||
--workflow "build-images/build-and-push" \
|
||||
--commit "${{ github.sha }}" \
|
||||
--auto-login
|
||||
|
||||
cleanup:
|
||||
needs: [build-and-push]
|
||||
if: always() && needs.build-and-push.result == 'success'
|
||||
runs-on: docker
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- name: Set up environment
|
||||
env:
|
||||
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||
run: make setup-ci
|
||||
- name: Clean up old image versions
|
||||
env:
|
||||
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||
PYTHONPATH: src
|
||||
run: |
|
||||
. .venv/bin/activate
|
||||
python3 -m devx.tools.clean_images \
|
||||
--owner oblachno-oss \
|
||||
--name oblachno-oss/runner-images/ci-base \
|
||||
--name oblachno-oss/runner-images/ci-quality \
|
||||
--name oblachno-oss/runner-images/ci-full \
|
||||
--keep 2
|
||||
@@ -1,187 +0,0 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
PIP_BREAK_SYSTEM_PACKAGES: "1"
|
||||
PYTHONPATH: src
|
||||
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||
CI_GITEA_USERNAME: ${{ vars.CI_GITEA_USERNAME }}
|
||||
|
||||
jobs:
|
||||
# Single validation job that merges: quality, detect-changes,
|
||||
# release-dry-run, pr-review, and pre-merge-check.
|
||||
# Uses ci-full image (has git-cliff for release-dry-run).
|
||||
# Saves ~4x checkout+setup overhead vs 5 separate jobs.
|
||||
validate:
|
||||
runs-on: docker
|
||||
container: git.oblachno.oblachno.fyi/oblachno-oss/runner-images/ci-full:latest
|
||||
timeout-minutes: 15
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
outputs:
|
||||
user-facing-changed: ${{ steps.detect.outputs.user-facing-changed }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up environment
|
||||
env:
|
||||
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||
run: make setup-image
|
||||
# --- quality steps ---
|
||||
- name: Lint all
|
||||
run: |
|
||||
. .venv/bin/activate 2>/dev/null || true
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
make lint-all
|
||||
- name: Unit tests with 100% coverage
|
||||
run: |
|
||||
. .venv/bin/activate 2>/dev/null || true
|
||||
make pytest-cov
|
||||
- name: Check unit test speed
|
||||
run: |
|
||||
. .venv/bin/activate 2>/dev/null || true
|
||||
python3 -m devx.tools.check_test_speed --max-seconds 8 --max-single-seconds 0.5
|
||||
- name: Documentation gate (coverage + stale refs + lint + version refs + prose)
|
||||
env:
|
||||
DEVX_DOC_COVERAGE_STRICT: "1"
|
||||
DEVX_VALE_LEVEL: warning
|
||||
run: |
|
||||
. .venv/bin/activate 2>/dev/null || true
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
make devx-docs-check
|
||||
- name: Translation completeness check
|
||||
run: |
|
||||
. .venv/bin/activate 2>/dev/null || true
|
||||
python3 -m devx.ci.check_translations
|
||||
- name: Dependency security scan
|
||||
run: |
|
||||
. .venv/bin/activate 2>/dev/null || true
|
||||
# Install pip in venv if missing (needed by pip-audit)
|
||||
.venv/bin/python -m ensurepip 2>/dev/null || true
|
||||
PIPAPI_PYTHON_LOCATION=$PWD/.venv/bin/python \
|
||||
pip-audit --desc --skip-editable 2>&1 || true
|
||||
- name: Workflow dry-run validation
|
||||
run: |
|
||||
. .venv/bin/activate 2>/dev/null || true
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
# Best-effort: only runs if act_runner is installed
|
||||
if command -v act_runner >/dev/null 2>&1; then
|
||||
make workflow-dryrun
|
||||
else
|
||||
echo "act_runner not found — skipping workflow dry-run (static lint still passed)"
|
||||
fi
|
||||
# --- detect-changes step ---
|
||||
- name: Detect changed paths
|
||||
id: detect
|
||||
run: |
|
||||
. .venv/bin/activate 2>/dev/null || true
|
||||
python3 -m devx.ci.classify_changes \
|
||||
--base "origin/master" \
|
||||
--head "${{ github.event.pull_request.head.sha || github.sha }}" \
|
||||
--github-output
|
||||
# --- validate-pr + pr-review steps (PR only) ---
|
||||
- name: Validate auto-merge preconditions
|
||||
if: github.event_name == 'pull_request'
|
||||
env:
|
||||
VIKUNJA_TOKEN: ${{ secrets.VIKUNJA_TOKEN }}
|
||||
DEVX_VIKUNJA_PROJECT_ID: "8"
|
||||
HEAD_REF: ${{ github.head_ref }}
|
||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||
REPOSITORY: ${{ github.repository }}
|
||||
PR_NUMBER: ${{ github.event.number }}
|
||||
run: |
|
||||
. .venv/bin/activate 2>/dev/null || true
|
||||
python3 -m devx.ci.check_auto_merge_ready \
|
||||
--branch "$HEAD_REF" \
|
||||
--pr-title "$PR_TITLE" \
|
||||
--repo "$REPOSITORY" \
|
||||
--pr-number "$PR_NUMBER"
|
||||
- name: Run automated PR review
|
||||
if: github.event_name == 'pull_request'
|
||||
run: |
|
||||
. .venv/bin/activate 2>/dev/null || true
|
||||
set -euo pipefail
|
||||
python3 -m devx.ci.pr_review \
|
||||
"${{ github.event.number }}" \
|
||||
"${{ github.repository }}"
|
||||
# --- release-dry-run step (conditional) ---
|
||||
- name: Release dry-run validation
|
||||
if: steps.detect.outputs.user-facing-changed == 'true'
|
||||
run: |
|
||||
. .venv/bin/activate 2>/dev/null || true
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
python3 -m devx.ci.release --dry-run
|
||||
- name: Notify on failure
|
||||
if: failure()
|
||||
env:
|
||||
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||
run: |
|
||||
. .venv/bin/activate 2>/dev/null || true
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
python3 -m devx.ci.notify_failure \
|
||||
--repo "${{ github.repository }}" \
|
||||
--run-id "${{ github.run_id }}" \
|
||||
--workflow "ci/validate" \
|
||||
--commit "${{ github.sha }}" \
|
||||
--auto-login
|
||||
|
||||
auto-merge:
|
||||
# Auto-merge runs after validate passes. It reads the task ID
|
||||
# from the branch name, validates the PR title, and squash-merges.
|
||||
needs: [validate]
|
||||
if: >-
|
||||
always() &&
|
||||
github.event_name == 'pull_request' &&
|
||||
needs.validate.result == 'success'
|
||||
runs-on: docker
|
||||
container: git.oblachno.oblachno.fyi/oblachno-oss/runner-images/ci-base:latest
|
||||
timeout-minutes: 10
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||
- name: Set up environment
|
||||
env:
|
||||
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||
run: make setup-image
|
||||
- name: Post approval review
|
||||
env:
|
||||
REVIEWER_GITEA_API_TOKEN: ${{ secrets.REVIEWER_GITEA_API_TOKEN }}
|
||||
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||
PR_NUMBER: ${{ github.event.number }}
|
||||
REPOSITORY: ${{ github.repository }}
|
||||
run: |
|
||||
. .venv/bin/activate 2>/dev/null || true
|
||||
python3 -m devx.ci.pr_review \
|
||||
"$PR_NUMBER" \
|
||||
"$REPOSITORY" \
|
||||
--event APPROVE \
|
||||
--checklist-confirmed \
|
||||
--checklist-categories 1,2,3,4,5,6,7,8,9,10,11,12,13 \
|
||||
--body "Auto-approved: all CI checks passed (validate job)."
|
||||
- name: Squash merge with task ID
|
||||
env:
|
||||
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||
VIKUNJA_TOKEN: ${{ secrets.VIKUNJA_TOKEN }}
|
||||
DEVX_VIKUNJA_PROJECT_ID: "8"
|
||||
HEAD_REF: ${{ github.head_ref }}
|
||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||
REPOSITORY: ${{ github.repository }}
|
||||
PR_NUMBER: ${{ github.event.number }}
|
||||
run: |
|
||||
. .venv/bin/activate 2>/dev/null || true
|
||||
python3 -m devx.ci.auto_merge \
|
||||
"$HEAD_REF" \
|
||||
"$PR_TITLE" \
|
||||
"$REPOSITORY" \
|
||||
"$PR_NUMBER"
|
||||
@@ -1,183 +0,0 @@
|
||||
name: Post-merge
|
||||
|
||||
# Runs on every push to master (after CI workflow merges a PR).
|
||||
# Consolidated into 2 jobs (from 7) to reduce runner overhead:
|
||||
# detect-and-configure ──→ release-and-maintain
|
||||
#
|
||||
# Job 1: detect release commit, validate commit msg, configure repo
|
||||
# (branch protection, labels).
|
||||
# Job 2: release + publish + sync-wiki + vikunja + badges.
|
||||
# Individual steps are conditional on job 1 outputs.
|
||||
#
|
||||
# The badges step always runs (even on release commits) so version
|
||||
# badge picks up the new __version__. It runs last so it sees the
|
||||
# new version if release created one.
|
||||
#
|
||||
# When release creates a "release: vX.Y.Z" commit and tag, the publish
|
||||
# step builds and publishes the package to the Gitea PyPI registry.
|
||||
# The release commit's post-merge run still updates badges. Other
|
||||
# steps (sync-wiki, vikunja) skip on release commits.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
|
||||
concurrency:
|
||||
group: post-merge-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
PIP_BREAK_SYSTEM_PACKAGES: "1"
|
||||
PYTHONPATH: src
|
||||
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||
CI_GITEA_USERNAME: ${{ vars.CI_GITEA_USERNAME }}
|
||||
|
||||
jobs:
|
||||
detect-and-configure:
|
||||
runs-on: docker
|
||||
container: git.oblachno.oblachno.fyi/oblachno-oss/runner-images/ci-base:latest
|
||||
timeout-minutes: 10
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
outputs:
|
||||
is-release: ${{ steps.check.outputs.is-release }}
|
||||
is-automated: ${{ steps.check.outputs.is-automated }}
|
||||
user-facing-changed: ${{ steps.detect.outputs.user-facing-changed }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up environment
|
||||
env:
|
||||
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||
run: make setup-image
|
||||
- name: Ensure branch protection and labels
|
||||
env:
|
||||
DEVX_REPO_NAME: devx
|
||||
DEVX_REPO_OWNER: oblachno-oss
|
||||
DEVX_STATUS_CHECKS: "CI / validate (pull_request)"
|
||||
run: |
|
||||
. .venv/bin/activate 2>/dev/null || true
|
||||
python3 -m devx.tools.configure_repo
|
||||
- name: Check if this is a release commit
|
||||
id: check
|
||||
run: |
|
||||
. .venv/bin/activate 2>/dev/null || true
|
||||
python3 -m devx.ci.detect_release_commit
|
||||
- name: Validate latest commit message
|
||||
if: steps.check.outputs.is-automated == 'false'
|
||||
run: |
|
||||
. .venv/bin/activate 2>/dev/null || true
|
||||
git log -1 --format=%B > commit-msg.txt
|
||||
python3 -m devx.ci.validate_commit_msg commit-msg.txt --branch master
|
||||
rm -f commit-msg.txt
|
||||
- name: Detect changed paths
|
||||
id: detect
|
||||
if: steps.check.outputs.is-release == 'false'
|
||||
run: |
|
||||
. .venv/bin/activate 2>/dev/null || true
|
||||
python3 -m devx.ci.classify_changes \
|
||||
--base "HEAD~1" \
|
||||
--head "HEAD" \
|
||||
--github-output
|
||||
- name: Notify on failure
|
||||
if: failure()
|
||||
env:
|
||||
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||
run: |
|
||||
. .venv/bin/activate 2>/dev/null || true
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
python3 -m devx.ci.notify_failure \
|
||||
--repo "${{ github.repository }}" \
|
||||
--run-id "${{ github.run_id }}" \
|
||||
--workflow "post-merge/detect-and-configure" \
|
||||
--commit "${{ github.sha }}" \
|
||||
--auto-login
|
||||
|
||||
release-and-maintain:
|
||||
needs: [detect-and-configure]
|
||||
if: always() && needs.detect-and-configure.result == 'success'
|
||||
runs-on: docker
|
||||
container: git.oblachno.oblachno.fyi/oblachno-oss/runner-images/ci-full:latest
|
||||
timeout-minutes: 15
|
||||
outputs:
|
||||
tag: ${{ steps.release-tag.outputs.tag }}
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: master
|
||||
token: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||
- name: Set up environment
|
||||
env:
|
||||
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||
run: make setup-image EXTRAS=release
|
||||
- name: Configure git
|
||||
run: |
|
||||
git config user.name "devx-ci-bot"
|
||||
git config user.email "devx-ci-bot@oblachno.fyi"
|
||||
# --- release + publish (only if user-facing changes, not a release commit) ---
|
||||
- name: Run release
|
||||
id: release-tag
|
||||
if: needs.detect-and-configure.outputs.is-release == 'false' && needs.detect-and-configure.outputs.user-facing-changed == 'true'
|
||||
env:
|
||||
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||
run: |
|
||||
. .venv/bin/activate 2>/dev/null || true
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
python3 -m devx.ci.release
|
||||
- name: Build and publish release
|
||||
if: steps.release-tag.outputs.tag != ''
|
||||
env:
|
||||
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||
run: |
|
||||
. .venv/bin/activate 2>/dev/null || true
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
git fetch --tags
|
||||
git checkout "${{ steps.release-tag.outputs.tag }}"
|
||||
python3 -m devx.ci.publish "${{ steps.release-tag.outputs.tag }}" "${{ github.repository }}" --auto-login
|
||||
# --- sync-wiki + vikunja (skip on automated/release commits) ---
|
||||
- name: Sync documentation to wiki
|
||||
if: needs.detect-and-configure.outputs.is-automated == 'false'
|
||||
env:
|
||||
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||
run: |
|
||||
. .venv/bin/activate 2>/dev/null || true
|
||||
python3 -m devx.ci.sync_wiki --repo "${{ github.repository }}" --verify
|
||||
- name: Update Vikunja task
|
||||
if: needs.detect-and-configure.outputs.is-automated == 'false'
|
||||
env:
|
||||
VIKUNJA_TOKEN: ${{ secrets.VIKUNJA_TOKEN }}
|
||||
DEVX_VIKUNJA_PROJECT_ID: "8"
|
||||
run: |
|
||||
. .venv/bin/activate 2>/dev/null || true
|
||||
python3 -m devx.ci.post_merge --git-sha "${{ github.sha }}"
|
||||
# --- badges (always run — even on release commits) ---
|
||||
- name: Generate and push badges
|
||||
env:
|
||||
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||
PRE_COMMIT_ALLOW_NO_CONFIG: "1"
|
||||
run: |
|
||||
. .venv/bin/activate 2>/dev/null || true
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
# Fetch latest master to pick up any release commit that was pushed
|
||||
git fetch origin master
|
||||
git reset --hard origin/master
|
||||
python3 -m devx.ci.push_badges
|
||||
- name: Notify on failure
|
||||
if: failure()
|
||||
env:
|
||||
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||
run: |
|
||||
. .venv/bin/activate 2>/dev/null || true
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
python3 -m devx.ci.notify_failure \
|
||||
--repo "${{ github.repository }}" \
|
||||
--run-id "${{ github.run_id }}" \
|
||||
--workflow "post-merge/release-and-maintain" \
|
||||
--commit "${{ github.sha }}" \
|
||||
--auto-login
|
||||
@@ -1,40 +0,0 @@
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.egg-info/
|
||||
*.egg
|
||||
dist/
|
||||
build/
|
||||
.eggs/
|
||||
|
||||
# Virtual environments
|
||||
.venv/
|
||||
venv/
|
||||
|
||||
# Testing
|
||||
.coverage
|
||||
htmlcov/
|
||||
.pytest_cache/
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# Environment
|
||||
.env
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Molecule
|
||||
.molecule/
|
||||
|
||||
# Badges
|
||||
.badges/
|
||||
|
||||
# Deprecated CI task tracking (branch name is the sole source of truth)
|
||||
.taskid
|
||||
@@ -1,14 +0,0 @@
|
||||
# Hadolint configuration for devx Dockerfiles
|
||||
# https://github.com/hadolint/hadolint#configure
|
||||
|
||||
ignored:
|
||||
- DL3008 # Don't require pinning apt package versions
|
||||
- DL3013 # Don't require pinning pip package versions
|
||||
- DL3018 # Don't require pinning apk package versions
|
||||
- DL3007 # Using latest is intentional for tier images (rebuilt on every merge)
|
||||
- SC2102 # False positive: pip extras [release,molecule,deploy] look like shell ranges
|
||||
|
||||
trustedRegistries:
|
||||
- git.oblachno.oblachno.fyi
|
||||
- docker.io
|
||||
- gitea/runner-images
|
||||
@@ -1,97 +0,0 @@
|
||||
repos:
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: validate-commit-msg
|
||||
name: validate commit message
|
||||
entry: env PYTHONPATH=src .venv/bin/python3 -m devx.ci.validate_commit_msg
|
||||
language: system
|
||||
stages: [commit-msg]
|
||||
pass_filenames: true
|
||||
|
||||
- id: lint-ruff
|
||||
name: ruff lint
|
||||
entry: make lint-ruff
|
||||
language: system
|
||||
types: [python]
|
||||
pass_filenames: false
|
||||
stages: [pre-commit]
|
||||
|
||||
- id: lint-format
|
||||
name: ruff format check
|
||||
entry: make lint-format
|
||||
language: system
|
||||
types: [python]
|
||||
pass_filenames: false
|
||||
stages: [pre-commit]
|
||||
|
||||
- id: typecheck
|
||||
name: pyright type check
|
||||
entry: make typecheck
|
||||
language: system
|
||||
types: [python]
|
||||
pass_filenames: false
|
||||
stages: [pre-commit]
|
||||
|
||||
- id: lint-bandit
|
||||
name: bandit security scan
|
||||
entry: make lint-bandit
|
||||
language: system
|
||||
types: [python]
|
||||
pass_filenames: false
|
||||
stages: [pre-commit]
|
||||
|
||||
- id: workflow-lint
|
||||
name: actionlint (workflow YAML)
|
||||
entry: make workflow-lint
|
||||
language: system
|
||||
files: ^\.gitea/workflows/
|
||||
types: [yaml]
|
||||
pass_filenames: false
|
||||
stages: [pre-commit]
|
||||
|
||||
- id: checkmake
|
||||
name: checkmake Makefile linter
|
||||
entry: make checkmake
|
||||
language: system
|
||||
files: (Makefile|\.mak)$
|
||||
pass_filenames: false
|
||||
stages: [pre-commit]
|
||||
|
||||
- id: check-test-speed
|
||||
name: unit test speed check
|
||||
entry: .venv/bin/python -m devx.tools.check_test_speed --max-seconds 6 --max-single-seconds 0.5
|
||||
language: system
|
||||
types: [python]
|
||||
pass_filenames: false
|
||||
stages: [pre-commit]
|
||||
|
||||
- id: check-translations
|
||||
name: translation completeness check
|
||||
entry: env PYTHONPATH=src .venv/bin/python -m devx.ci.check_translations
|
||||
language: system
|
||||
files: ^src/devx/translations\.json$
|
||||
pass_filenames: false
|
||||
stages: [pre-commit]
|
||||
|
||||
- id: docs-check
|
||||
name: documentation gate (coverage + stale refs + lint + version refs + prose)
|
||||
entry: bash -c 'PYTHONPATH=src DEVX_DOC_COVERAGE_STRICT=1 DEVX_VALE_LEVEL=warning make devx-docs-check'
|
||||
language: system
|
||||
pass_filenames: false
|
||||
always_run: true
|
||||
stages: [pre-commit]
|
||||
|
||||
- id: pytest-cov
|
||||
name: pytest with 100% coverage
|
||||
entry: make pytest-cov
|
||||
language: system
|
||||
types: [python]
|
||||
pass_filenames: false
|
||||
stages: [pre-push]
|
||||
|
||||
- id: commit-msg
|
||||
name: validate commit message
|
||||
entry: env PYTHONPATH=src .venv/bin/python3 -m devx.ci.validate_commit_msg
|
||||
language: system
|
||||
stages: [commit-msg]
|
||||
pass_filenames: true
|
||||
@@ -1,49 +0,0 @@
|
||||
# Vale configuration for devx documentation
|
||||
# https://vale.sh/docs/
|
||||
|
||||
StylesPath = .vale/styles
|
||||
|
||||
# Packages are downloaded via `vale sync`
|
||||
Packages = write-good, Google, Readability
|
||||
|
||||
# Minimum alert level to display (suggestion, warning, error)
|
||||
MinAlertLevel = warning
|
||||
|
||||
# Project vocabulary — terms not flagged as spelling errors
|
||||
Vocab = devx
|
||||
|
||||
[*.{md}]
|
||||
# Enable style guides
|
||||
BasedOnStyles = Vale, write-good, Google, Readability, devx
|
||||
|
||||
# Google style — relax rules too strict for technical docs
|
||||
Google.Contractions = NO
|
||||
Google.WordList = NO
|
||||
Google.Acronyms = NO
|
||||
Google.We = NO
|
||||
Google.Will = NO
|
||||
Google.Colons = NO
|
||||
Google.Headings = NO
|
||||
Google.EmDash = NO
|
||||
Google.Units = NO
|
||||
|
||||
# write-good — relax rules too strict for technical writing
|
||||
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 — 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
|
||||
Readability.SMOG = suggestion
|
||||
@@ -1,9 +0,0 @@
|
||||
extends: existence
|
||||
message: "Use 'AM' or 'PM' (preceded by a space)."
|
||||
link: "https://developers.google.com/style/word-list"
|
||||
level: error
|
||||
nonword: true
|
||||
tokens:
|
||||
- '\d{1,2}[AP]M\b'
|
||||
- '\d{1,2} ?[ap]m\b'
|
||||
- '\d{1,2} ?[aApP]\.[mM]\.'
|
||||
@@ -1,64 +0,0 @@
|
||||
extends: conditional
|
||||
message: "Spell out '%s', if it's unfamiliar to the audience."
|
||||
link: 'https://developers.google.com/style/abbreviations'
|
||||
level: suggestion
|
||||
ignorecase: false
|
||||
# Ensures that the existence of 'first' implies the existence of 'second'.
|
||||
first: '\b([A-Z]{3,5})\b'
|
||||
second: '(?:\b[A-Z][a-z]+ )+\(([A-Z]{3,5})\)'
|
||||
# ... with the exception of these:
|
||||
exceptions:
|
||||
- API
|
||||
- ASP
|
||||
- CLI
|
||||
- CPU
|
||||
- CSS
|
||||
- CSV
|
||||
- DEBUG
|
||||
- DOM
|
||||
- DPI
|
||||
- FAQ
|
||||
- GCC
|
||||
- GDB
|
||||
- GET
|
||||
- GPU
|
||||
- GTK
|
||||
- GUI
|
||||
- HTML
|
||||
- HTTP
|
||||
- HTTPS
|
||||
- IDE
|
||||
- JAR
|
||||
- JSON
|
||||
- JSX
|
||||
- LESS
|
||||
- LLDB
|
||||
- NET
|
||||
- NOTE
|
||||
- NVDA
|
||||
- OSS
|
||||
- PATH
|
||||
- PDF
|
||||
- PHP
|
||||
- POST
|
||||
- RAM
|
||||
- REPL
|
||||
- RSA
|
||||
- SCM
|
||||
- SCSS
|
||||
- SDK
|
||||
- SQL
|
||||
- SSH
|
||||
- SSL
|
||||
- SVG
|
||||
- TBD
|
||||
- TCP
|
||||
- TODO
|
||||
- URI
|
||||
- URL
|
||||
- USB
|
||||
- UTF
|
||||
- XML
|
||||
- XSS
|
||||
- YAML
|
||||
- ZIP
|
||||
@@ -1,8 +0,0 @@
|
||||
extends: existence
|
||||
message: "'%s' should be in lowercase."
|
||||
link: 'https://developers.google.com/style/colons'
|
||||
nonword: true
|
||||
level: warning
|
||||
scope: sentence
|
||||
tokens:
|
||||
- '(?<!:[^ ]+?):\s[A-Z]'
|
||||
@@ -1,30 +0,0 @@
|
||||
extends: substitution
|
||||
message: "Use '%s' instead of '%s'."
|
||||
link: 'https://developers.google.com/style/contractions'
|
||||
level: suggestion
|
||||
ignorecase: true
|
||||
action:
|
||||
name: replace
|
||||
swap:
|
||||
are not: aren't
|
||||
cannot: can't
|
||||
could not: couldn't
|
||||
did not: didn't
|
||||
do not: don't
|
||||
does not: doesn't
|
||||
has not: hasn't
|
||||
have not: haven't
|
||||
how is: how's
|
||||
is not: isn't
|
||||
it is: it's
|
||||
should not: shouldn't
|
||||
that is: that's
|
||||
they are: they're
|
||||
was not: wasn't
|
||||
we are: we're
|
||||
we have: we've
|
||||
were not: weren't
|
||||
what is: what's
|
||||
when is: when's
|
||||
where is: where's
|
||||
will not: won't
|
||||
@@ -1,9 +0,0 @@
|
||||
extends: existence
|
||||
message: "Use 'July 31, 2016' format, not '%s'."
|
||||
link: 'https://developers.google.com/style/dates-times'
|
||||
ignorecase: true
|
||||
level: error
|
||||
nonword: true
|
||||
tokens:
|
||||
- '\d{1,2}(?:\.|/)\d{1,2}(?:\.|/)\d{4}'
|
||||
- '\d{1,2} (?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)|May|Jun(?:e)|Jul(?:y)|Aug(?:ust)|Sep(?:tember)?|Oct(?:ober)|Nov(?:ember)?|Dec(?:ember)?) \d{4}'
|
||||
@@ -1,9 +0,0 @@
|
||||
extends: existence
|
||||
message: "In general, don't use an ellipsis."
|
||||
link: 'https://developers.google.com/style/ellipses'
|
||||
nonword: true
|
||||
level: warning
|
||||
action:
|
||||
name: remove
|
||||
tokens:
|
||||
- '\.\.\.'
|
||||
@@ -1,13 +0,0 @@
|
||||
extends: existence
|
||||
message: "Don't put a space before or after a dash."
|
||||
link: "https://developers.google.com/style/dashes"
|
||||
nonword: true
|
||||
level: error
|
||||
action:
|
||||
name: edit
|
||||
params:
|
||||
- trim
|
||||
- " "
|
||||
tokens:
|
||||
- '\s[—–]\s'
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
extends: existence
|
||||
message: "Don't use exclamation points in text."
|
||||
link: "https://developers.google.com/style/exclamation-points"
|
||||
nonword: true
|
||||
level: error
|
||||
action:
|
||||
name: edit
|
||||
params:
|
||||
- trim_right
|
||||
- "!"
|
||||
tokens:
|
||||
- '\w+!(?:\s|$)'
|
||||
@@ -1,13 +0,0 @@
|
||||
extends: existence
|
||||
message: "Avoid first-person pronouns such as '%s'."
|
||||
link: 'https://developers.google.com/style/pronouns#personal-pronouns'
|
||||
ignorecase: true
|
||||
level: warning
|
||||
nonword: true
|
||||
tokens:
|
||||
- (?:^|\s)I\s
|
||||
- (?:^|\s)I,\s
|
||||
- \bI'm\b
|
||||
- \bme\b
|
||||
- \bmy\b
|
||||
- \bmine\b
|
||||
@@ -1,9 +0,0 @@
|
||||
extends: existence
|
||||
message: "Don't use '%s' as a gender-neutral pronoun."
|
||||
link: 'https://developers.google.com/style/pronouns#gender-neutral-pronouns'
|
||||
level: error
|
||||
ignorecase: true
|
||||
tokens:
|
||||
- he/she
|
||||
- s/he
|
||||
- \(s\)he
|
||||
@@ -1,43 +0,0 @@
|
||||
extends: substitution
|
||||
message: "Consider using '%s' instead of '%s'."
|
||||
ignorecase: true
|
||||
link: "https://developers.google.com/style/inclusive-documentation"
|
||||
level: error
|
||||
action:
|
||||
name: replace
|
||||
swap:
|
||||
(?:alumna|alumnus): graduate
|
||||
(?:alumnae|alumni): graduates
|
||||
air(?:m[ae]n|wom[ae]n): pilot(s)
|
||||
anchor(?:m[ae]n|wom[ae]n): anchor(s)
|
||||
authoress: author
|
||||
camera(?:m[ae]n|wom[ae]n): camera operator(s)
|
||||
door(?:m[ae]|wom[ae]n): concierge(s)
|
||||
draft(?:m[ae]n|wom[ae]n): drafter(s)
|
||||
fire(?:m[ae]n|wom[ae]n): firefighter(s)
|
||||
fisher(?:m[ae]n|wom[ae]n): fisher(s)
|
||||
fresh(?:m[ae]n|wom[ae]n): first-year student(s)
|
||||
garbage(?:m[ae]n|wom[ae]n): waste collector(s)
|
||||
lady lawyer: lawyer
|
||||
ladylike: courteous
|
||||
mail(?:m[ae]n|wom[ae]n): mail carriers
|
||||
man and wife: husband and wife
|
||||
man enough: strong enough
|
||||
mankind: human kind|humanity
|
||||
manmade: manufactured
|
||||
manpower: personnel
|
||||
middle(?:m[ae]n|wom[ae]n): intermediary
|
||||
news(?:m[ae]n|wom[ae]n): journalist(s)
|
||||
ombuds(?:man|woman): ombuds
|
||||
oneupmanship: upstaging
|
||||
poetess: poet
|
||||
police(?:m[ae]n|wom[ae]n): police officer(s)
|
||||
repair(?:m[ae]n|wom[ae]n): technician(s)
|
||||
sales(?:m[ae]n|wom[ae]n): salesperson or sales people
|
||||
service(?:m[ae]n|wom[ae]n): soldier(s)
|
||||
steward(?:ess)?: flight attendant
|
||||
tribes(?:m[ae]n|wom[ae]n): tribe member(s)
|
||||
waitress: waiter
|
||||
woman doctor: doctor
|
||||
woman scientist[s]?: scientist(s)
|
||||
work(?:m[ae]n|wom[ae]n): worker(s)
|
||||
@@ -1,13 +0,0 @@
|
||||
extends: existence
|
||||
message: "Don't put a period at the end of a heading."
|
||||
link: "https://developers.google.com/style/capitalization#capitalization-in-titles-and-headings"
|
||||
nonword: true
|
||||
level: warning
|
||||
scope: heading
|
||||
action:
|
||||
name: edit
|
||||
params:
|
||||
- trim_right
|
||||
- "."
|
||||
tokens:
|
||||
- '[a-z0-9][.]\s*$'
|
||||
@@ -1,29 +0,0 @@
|
||||
extends: capitalization
|
||||
message: "'%s' should use sentence-style capitalization."
|
||||
link: "https://developers.google.com/style/capitalization#capitalization-in-titles-and-headings"
|
||||
level: warning
|
||||
scope: heading
|
||||
match: $sentence
|
||||
indicators:
|
||||
- ":"
|
||||
exceptions:
|
||||
- Azure
|
||||
- CLI
|
||||
- Cosmos
|
||||
- Docker
|
||||
- Emmet
|
||||
- gRPC
|
||||
- I
|
||||
- Kubernetes
|
||||
- Linux
|
||||
- macOS
|
||||
- Marketplace
|
||||
- MongoDB
|
||||
- REPL
|
||||
- Studio
|
||||
- TypeScript
|
||||
- URLs
|
||||
- Visual
|
||||
- VS
|
||||
- Windows
|
||||
- JSON
|
||||
@@ -1,11 +0,0 @@
|
||||
extends: substitution
|
||||
message: "Use '%s' instead of '%s'."
|
||||
link: 'https://developers.google.com/style/abbreviations'
|
||||
ignorecase: true
|
||||
level: error
|
||||
nonword: true
|
||||
action:
|
||||
name: replace
|
||||
swap:
|
||||
'\b(?:eg|e\.g\.)(?=[\s,;])': for example
|
||||
'\b(?:ie|i\.e\.)(?=[\s,;])': that is
|
||||
@@ -1,14 +0,0 @@
|
||||
extends: existence
|
||||
message: "'%s' doesn't need a hyphen."
|
||||
link: "https://developers.google.com/style/hyphens"
|
||||
level: error
|
||||
ignorecase: false
|
||||
nonword: true
|
||||
action:
|
||||
name: edit
|
||||
params:
|
||||
- regex
|
||||
- "-"
|
||||
- " "
|
||||
tokens:
|
||||
- '\b[^\s-]+ly-\w+\b'
|
||||
@@ -1,12 +0,0 @@
|
||||
extends: existence
|
||||
message: "Don't use plurals in parentheses such as in '%s'."
|
||||
link: "https://developers.google.com/style/plurals-parentheses"
|
||||
level: error
|
||||
nonword: true
|
||||
action:
|
||||
name: edit
|
||||
params:
|
||||
- trim_right
|
||||
- "(s)"
|
||||
tokens:
|
||||
- '\b\w+\(s\)'
|
||||
@@ -1,7 +0,0 @@
|
||||
extends: existence
|
||||
message: "Spell out all ordinal numbers ('%s') in text."
|
||||
link: 'https://developers.google.com/style/numbers'
|
||||
level: error
|
||||
nonword: true
|
||||
tokens:
|
||||
- \d+(?:st|nd|rd|th)
|
||||
@@ -1,7 +0,0 @@
|
||||
extends: existence
|
||||
message: "Use the Oxford comma in '%s'."
|
||||
link: 'https://developers.google.com/style/commas'
|
||||
scope: sentence
|
||||
level: warning
|
||||
tokens:
|
||||
- '(?:[^,]+,){1,}\s\w+\s(?:and|or)'
|
||||
@@ -1,7 +0,0 @@
|
||||
extends: existence
|
||||
message: "Use parentheses judiciously."
|
||||
link: 'https://developers.google.com/style/parentheses'
|
||||
nonword: true
|
||||
level: suggestion
|
||||
tokens:
|
||||
- '\(.+\)'
|
||||
@@ -1,184 +0,0 @@
|
||||
extends: existence
|
||||
link: 'https://developers.google.com/style/voice'
|
||||
message: "In general, use active voice instead of passive voice ('%s')."
|
||||
ignorecase: true
|
||||
level: suggestion
|
||||
raw:
|
||||
- \b(am|are|were|being|is|been|was|be)\b\s*
|
||||
tokens:
|
||||
- '[\w]+ed'
|
||||
- awoken
|
||||
- beat
|
||||
- become
|
||||
- been
|
||||
- begun
|
||||
- bent
|
||||
- beset
|
||||
- bet
|
||||
- bid
|
||||
- bidden
|
||||
- bitten
|
||||
- bled
|
||||
- blown
|
||||
- born
|
||||
- bought
|
||||
- bound
|
||||
- bred
|
||||
- broadcast
|
||||
- broken
|
||||
- brought
|
||||
- built
|
||||
- burnt
|
||||
- burst
|
||||
- cast
|
||||
- caught
|
||||
- chosen
|
||||
- clung
|
||||
- come
|
||||
- cost
|
||||
- crept
|
||||
- cut
|
||||
- dealt
|
||||
- dived
|
||||
- done
|
||||
- drawn
|
||||
- dreamt
|
||||
- driven
|
||||
- drunk
|
||||
- dug
|
||||
- eaten
|
||||
- fallen
|
||||
- fed
|
||||
- felt
|
||||
- fit
|
||||
- fled
|
||||
- flown
|
||||
- flung
|
||||
- forbidden
|
||||
- foregone
|
||||
- forgiven
|
||||
- forgotten
|
||||
- forsaken
|
||||
- fought
|
||||
- found
|
||||
- frozen
|
||||
- given
|
||||
- gone
|
||||
- gotten
|
||||
- ground
|
||||
- grown
|
||||
- heard
|
||||
- held
|
||||
- hidden
|
||||
- hit
|
||||
- hung
|
||||
- hurt
|
||||
- kept
|
||||
- knelt
|
||||
- knit
|
||||
- known
|
||||
- laid
|
||||
- lain
|
||||
- leapt
|
||||
- learnt
|
||||
- led
|
||||
- left
|
||||
- lent
|
||||
- let
|
||||
- lighted
|
||||
- lost
|
||||
- made
|
||||
- meant
|
||||
- met
|
||||
- misspelt
|
||||
- mistaken
|
||||
- mown
|
||||
- overcome
|
||||
- overdone
|
||||
- overtaken
|
||||
- overthrown
|
||||
- paid
|
||||
- pled
|
||||
- proven
|
||||
- put
|
||||
- quit
|
||||
- read
|
||||
- rid
|
||||
- ridden
|
||||
- risen
|
||||
- run
|
||||
- rung
|
||||
- said
|
||||
- sat
|
||||
- sawn
|
||||
- seen
|
||||
- sent
|
||||
- set
|
||||
- sewn
|
||||
- shaken
|
||||
- shaven
|
||||
- shed
|
||||
- shod
|
||||
- shone
|
||||
- shorn
|
||||
- shot
|
||||
- shown
|
||||
- shrunk
|
||||
- shut
|
||||
- slain
|
||||
- slept
|
||||
- slid
|
||||
- slit
|
||||
- slung
|
||||
- smitten
|
||||
- sold
|
||||
- sought
|
||||
- sown
|
||||
- sped
|
||||
- spent
|
||||
- spilt
|
||||
- spit
|
||||
- split
|
||||
- spoken
|
||||
- spread
|
||||
- sprung
|
||||
- spun
|
||||
- stolen
|
||||
- stood
|
||||
- stridden
|
||||
- striven
|
||||
- struck
|
||||
- strung
|
||||
- stuck
|
||||
- stung
|
||||
- stunk
|
||||
- sung
|
||||
- sunk
|
||||
- swept
|
||||
- swollen
|
||||
- sworn
|
||||
- swum
|
||||
- swung
|
||||
- taken
|
||||
- taught
|
||||
- thought
|
||||
- thrived
|
||||
- thrown
|
||||
- thrust
|
||||
- told
|
||||
- torn
|
||||
- trodden
|
||||
- understood
|
||||
- upheld
|
||||
- upset
|
||||
- wed
|
||||
- wept
|
||||
- withheld
|
||||
- withstood
|
||||
- woken
|
||||
- won
|
||||
- worn
|
||||
- wound
|
||||
- woven
|
||||
- written
|
||||
- wrung
|
||||
@@ -1,7 +0,0 @@
|
||||
extends: existence
|
||||
message: "Don't use periods with acronyms or initialisms such as '%s'."
|
||||
link: 'https://developers.google.com/style/abbreviations'
|
||||
level: error
|
||||
nonword: true
|
||||
tokens:
|
||||
- '\b(?:[A-Z]\.){3,}'
|
||||
@@ -1,7 +0,0 @@
|
||||
extends: existence
|
||||
message: "Commas and periods go inside quotation marks."
|
||||
link: 'https://developers.google.com/style/quotation-marks'
|
||||
level: error
|
||||
nonword: true
|
||||
tokens:
|
||||
- '"[^"]+"[.,?]'
|
||||
@@ -1,7 +0,0 @@
|
||||
extends: existence
|
||||
message: "Don't add words such as 'from' or 'between' to describe a range of numbers."
|
||||
link: 'https://developers.google.com/style/hyphens'
|
||||
nonword: true
|
||||
level: warning
|
||||
tokens:
|
||||
- '(?:from|between)\s\d+\s?-\s?\d+'
|
||||
@@ -1,8 +0,0 @@
|
||||
extends: existence
|
||||
message: "Use semicolons judiciously."
|
||||
link: 'https://developers.google.com/style/semicolons'
|
||||
nonword: true
|
||||
scope: sentence
|
||||
level: suggestion
|
||||
tokens:
|
||||
- ';'
|
||||
@@ -1,11 +0,0 @@
|
||||
extends: existence
|
||||
message: "Don't use internet slang abbreviations such as '%s'."
|
||||
link: 'https://developers.google.com/style/abbreviations'
|
||||
ignorecase: true
|
||||
level: error
|
||||
tokens:
|
||||
- 'tl;dr'
|
||||
- ymmv
|
||||
- rtfm
|
||||
- imo
|
||||
- fwiw
|
||||
@@ -1,10 +0,0 @@
|
||||
extends: existence
|
||||
message: "'%s' should have one space."
|
||||
link: 'https://developers.google.com/style/sentence-spacing'
|
||||
level: error
|
||||
nonword: true
|
||||
action:
|
||||
name: remove
|
||||
tokens:
|
||||
- '[a-z][.?!] {2,}[A-Z]'
|
||||
- '[a-z][.?!][A-Z]'
|
||||
@@ -1,10 +0,0 @@
|
||||
extends: existence
|
||||
message: "In general, use American spelling instead of '%s'."
|
||||
link: 'https://developers.google.com/style/spelling'
|
||||
ignorecase: true
|
||||
level: warning
|
||||
tokens:
|
||||
- '(?:\w+)nised?'
|
||||
- 'colour'
|
||||
- 'labour'
|
||||
- 'centre'
|
||||
@@ -1,8 +0,0 @@
|
||||
extends: existence
|
||||
message: "Put a nonbreaking space between the number and the unit in '%s'."
|
||||
link: "https://developers.google.com/style/units-of-measure"
|
||||
nonword: true
|
||||
level: error
|
||||
tokens:
|
||||
- \b\d+(?:B|kB|MB|GB|TB)
|
||||
- \b\d+(?:ns|ms|s|min|h|d)
|
||||
@@ -1,11 +0,0 @@
|
||||
extends: existence
|
||||
message: "Try to avoid using first-person plural like '%s'."
|
||||
link: 'https://developers.google.com/style/pronouns#personal-pronouns'
|
||||
level: warning
|
||||
ignorecase: true
|
||||
tokens:
|
||||
- we
|
||||
- we'(?:ve|re)
|
||||
- ours?
|
||||
- us
|
||||
- let's
|
||||
@@ -1,7 +0,0 @@
|
||||
extends: existence
|
||||
message: "Avoid using '%s'."
|
||||
link: 'https://developers.google.com/style/tense'
|
||||
ignorecase: true
|
||||
level: warning
|
||||
tokens:
|
||||
- will
|
||||
@@ -1,80 +0,0 @@
|
||||
extends: substitution
|
||||
message: "Use '%s' instead of '%s'."
|
||||
link: "https://developers.google.com/style/word-list"
|
||||
level: warning
|
||||
ignorecase: false
|
||||
action:
|
||||
name: replace
|
||||
swap:
|
||||
"(?:API Console|dev|developer) key": API key
|
||||
"(?:cell ?phone|smart ?phone)": phone|mobile phone
|
||||
"(?:dev|developer|APIs) console": API console
|
||||
"(?:e-mail|Email|E-mail)": email
|
||||
"(?:file ?path|path ?name)": path
|
||||
"(?:kill|terminate|abort)": stop|exit|cancel|end
|
||||
"(?:OAuth ?2|Oauth)": OAuth 2.0
|
||||
"(?:ok|Okay)": OK|okay
|
||||
"(?:WiFi|wifi)": Wi-Fi
|
||||
'[\.]+apk': APK
|
||||
'3\-D': 3D
|
||||
'Google (?:I\-O|IO)': Google I/O
|
||||
"tap (?:&|and) hold": touch & hold
|
||||
"un(?:check|select)": clear
|
||||
above: preceding
|
||||
account name: username
|
||||
action bar: app bar
|
||||
admin: administrator
|
||||
Ajax: AJAX
|
||||
a\.k\.a|aka: or|also known as
|
||||
Android device: Android-powered device
|
||||
android: Android
|
||||
API explorer: APIs Explorer
|
||||
application: app
|
||||
approx\.: approximately
|
||||
authN: authentication
|
||||
authZ: authorization
|
||||
autoupdate: automatically update
|
||||
cellular data: mobile data
|
||||
cellular network: mobile network
|
||||
chapter: documents|pages|sections
|
||||
check box: checkbox
|
||||
CLI: command-line tool
|
||||
click on: click|click in
|
||||
Cloud: Google Cloud Platform|GCP
|
||||
Container Engine: Kubernetes Engine
|
||||
content type: media type
|
||||
curated roles: predefined roles
|
||||
data are: data is
|
||||
Developers Console: Google API Console|API Console
|
||||
disabled?: turn off|off
|
||||
ephemeral IP address: ephemeral external IP address
|
||||
fewer data: less data
|
||||
file name: filename
|
||||
firewalls: firewall rules
|
||||
functionality: capability|feature
|
||||
Google account: Google Account
|
||||
Google accounts: Google Accounts
|
||||
Googling: search with Google
|
||||
grayed-out: unavailable
|
||||
HTTPs: HTTPS
|
||||
in order to: to
|
||||
ingest: import|load
|
||||
k8s: Kubernetes
|
||||
long press: touch & hold
|
||||
network IP address: internal IP address
|
||||
omnibox: address bar
|
||||
open-source: open source
|
||||
overview screen: recents screen
|
||||
regex: regular expression
|
||||
SHA1: SHA-1|HAS-SHA1
|
||||
sign into: sign in to
|
||||
sign-?on: single sign-on
|
||||
static IP address: static external IP address
|
||||
stylesheet: style sheet
|
||||
synch: sync
|
||||
tablename: table name
|
||||
tablet: device
|
||||
touch: tap
|
||||
url: URL
|
||||
vs\.: versus
|
||||
World Wide Web: web
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"feed": "https://github.com/errata-ai/Google/releases.atom",
|
||||
"vale_version": ">=1.0.0"
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
extends: metric
|
||||
message: "Try to keep the Automated Readability Index (%s) below 8."
|
||||
link: https://en.wikipedia.org/wiki/Automated_readability_index
|
||||
|
||||
formula: |
|
||||
(4.71 * (characters / words)) + (0.5 * (words / sentences)) - 21.43
|
||||
|
||||
condition: "> 8"
|
||||
@@ -1,8 +0,0 @@
|
||||
extends: metric
|
||||
message: "Try to keep the Coleman–Liau Index grade (%s) below 9."
|
||||
link: https://en.wikipedia.org/wiki/Coleman%E2%80%93Liau_index
|
||||
|
||||
formula: |
|
||||
(0.0588 * (characters / words) * 100) - (0.296 * (sentences / words) * 100) - 15.8
|
||||
|
||||
condition: "> 9"
|
||||
@@ -1,8 +0,0 @@
|
||||
extends: metric
|
||||
message: "Try to keep the Flesch–Kincaid grade level (%s) below 8."
|
||||
link: https://en.wikipedia.org/wiki/Flesch%E2%80%93Kincaid_readability_tests
|
||||
|
||||
formula: |
|
||||
(0.39 * (words / sentences)) + (11.8 * (syllables / words)) - 15.59
|
||||
|
||||
condition: "> 8"
|
||||
@@ -1,8 +0,0 @@
|
||||
extends: metric
|
||||
message: "Try to keep the Flesch reading ease score (%s) above 70."
|
||||
link: https://en.wikipedia.org/wiki/Flesch%E2%80%93Kincaid_readability_tests
|
||||
|
||||
formula: |
|
||||
206.835 - (1.015 * (words / sentences)) - (84.6 * (syllables / words))
|
||||
|
||||
condition: "< 70"
|
||||
@@ -1,8 +0,0 @@
|
||||
extends: metric
|
||||
message: "Try to keep the Gunning-Fog index (%s) below 10."
|
||||
link: https://en.wikipedia.org/wiki/Gunning_fog_index
|
||||
|
||||
formula: |
|
||||
0.4 * ((words / sentences) + 100 * (complex_words / words))
|
||||
|
||||
condition: "> 10"
|
||||
@@ -1,17 +0,0 @@
|
||||
extends: metric
|
||||
message: "Try to keep the LIX score (%s) below 35."
|
||||
|
||||
link: https://en.wikipedia.org/wiki/Lix_(readability_test)
|
||||
# Very Easy: 20 - 25
|
||||
#
|
||||
# Easy: 30 - 35
|
||||
#
|
||||
# Medium: 40 - 45
|
||||
#
|
||||
# Difficult: 50 - 55
|
||||
#
|
||||
# Very Difficult: 60+
|
||||
formula: |
|
||||
(words / sentences) + ((long_words * 100) / words)
|
||||
|
||||
condition: "> 35"
|
||||
@@ -1,8 +0,0 @@
|
||||
extends: metric
|
||||
message: "Try to keep the SMOG grade (%s) below 10."
|
||||
link: https://en.wikipedia.org/wiki/SMOG
|
||||
|
||||
formula: |
|
||||
1.0430 * math.sqrt((polysyllabic_words * 30.0) / sentences) + 3.1291
|
||||
|
||||
condition: "> 10"
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"feed": "https://github.com/errata-ai/Readability/releases.atom",
|
||||
"vale_version": ">=2.13.0"
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
devx
|
||||
Gitea
|
||||
ZITADEL
|
||||
OpenTofu
|
||||
Ansible
|
||||
Vaultwarden
|
||||
Nextcloud
|
||||
Vikunja
|
||||
Mattermost
|
||||
Prometheus
|
||||
Grafana
|
||||
Loki
|
||||
Alertmanager
|
||||
Promtail
|
||||
pyproject
|
||||
tofu
|
||||
act_runner
|
||||
actionlint
|
||||
hadolint
|
||||
git-cliff
|
||||
pre-commit
|
||||
semver
|
||||
changelog
|
||||
idempotent
|
||||
rootless
|
||||
OIDC
|
||||
SSO
|
||||
SAML
|
||||
LDAP
|
||||
pytest
|
||||
molecule
|
||||
ruff
|
||||
pyright
|
||||
bandit
|
||||
Vikunja
|
||||
oblachno
|
||||
Oblachno
|
||||
Bulgarian
|
||||
@@ -1,6 +0,0 @@
|
||||
extends: existence
|
||||
message: "Unlabeled code block — add a language tag (```bash, ```yaml, etc.)"
|
||||
level: warning
|
||||
scope: raw
|
||||
raw:
|
||||
- '(?ms)^\n```\n.*?^```\s*$'
|
||||
@@ -1,13 +0,0 @@
|
||||
extends: existence
|
||||
message: "Avoid '%s' — it's condescending in technical documentation"
|
||||
level: warning
|
||||
ignorecase: true
|
||||
tokens:
|
||||
- '\bsimply\b'
|
||||
- '\bjust\b'
|
||||
- '\bobviously\b'
|
||||
- '\bof course\b'
|
||||
- '\bas you (can )?see\b'
|
||||
- '\beasily\b'
|
||||
- '\btrivial\b'
|
||||
- '\bstraightforward\b'
|
||||
@@ -1,3 +0,0 @@
|
||||
# Custom Vale style for devx documentation
|
||||
|
||||
Project-specific terminology and style rules
|
||||
@@ -1,11 +0,0 @@
|
||||
extends: substitution
|
||||
message: "Use '%s' instead of '%s' (terminology consistency)"
|
||||
level: error
|
||||
ignorecase: false
|
||||
swap:
|
||||
'\b(?i)gitea\b': Gitea
|
||||
'\b(?i)zitadel\b': ZITADEL
|
||||
'\b(?i)opentofu\b': OpenTofu
|
||||
'\b(?i)vaultwarden\b': Vaultwarden
|
||||
'\b(?i)nextcloud\b': Nextcloud
|
||||
'\b(?i)mattermost\b': Mattermost
|
||||
@@ -1,702 +0,0 @@
|
||||
extends: existence
|
||||
message: "Try to avoid using clichés like '%s'."
|
||||
ignorecase: true
|
||||
level: warning
|
||||
tokens:
|
||||
- a chip off the old block
|
||||
- a clean slate
|
||||
- a dark and stormy night
|
||||
- a far cry
|
||||
- a fine kettle of fish
|
||||
- a loose cannon
|
||||
- a penny saved is a penny earned
|
||||
- a tough row to hoe
|
||||
- a word to the wise
|
||||
- ace in the hole
|
||||
- acid test
|
||||
- add insult to injury
|
||||
- against all odds
|
||||
- air your dirty laundry
|
||||
- all fun and games
|
||||
- all in a day's work
|
||||
- all talk, no action
|
||||
- all thumbs
|
||||
- all your eggs in one basket
|
||||
- all's fair in love and war
|
||||
- all's well that ends well
|
||||
- almighty dollar
|
||||
- American as apple pie
|
||||
- an axe to grind
|
||||
- another day, another dollar
|
||||
- armed to the teeth
|
||||
- as luck would have it
|
||||
- as old as time
|
||||
- as the crow flies
|
||||
- at loose ends
|
||||
- at my wits end
|
||||
- avoid like the plague
|
||||
- babe in the woods
|
||||
- back against the wall
|
||||
- back in the saddle
|
||||
- back to square one
|
||||
- back to the drawing board
|
||||
- bad to the bone
|
||||
- badge of honor
|
||||
- bald faced liar
|
||||
- ballpark figure
|
||||
- banging your head against a brick wall
|
||||
- baptism by fire
|
||||
- barking up the wrong tree
|
||||
- bat out of hell
|
||||
- be all and end all
|
||||
- beat a dead horse
|
||||
- beat around the bush
|
||||
- been there, done that
|
||||
- beggars can't be choosers
|
||||
- behind the eight ball
|
||||
- bend over backwards
|
||||
- benefit of the doubt
|
||||
- bent out of shape
|
||||
- best thing since sliced bread
|
||||
- bet your bottom dollar
|
||||
- better half
|
||||
- better late than never
|
||||
- better mousetrap
|
||||
- better safe than sorry
|
||||
- between a rock and a hard place
|
||||
- beyond the pale
|
||||
- bide your time
|
||||
- big as life
|
||||
- big cheese
|
||||
- big fish in a small pond
|
||||
- big man on campus
|
||||
- bigger they are the harder they fall
|
||||
- bird in the hand
|
||||
- bird's eye view
|
||||
- birds and the bees
|
||||
- birds of a feather flock together
|
||||
- bit the hand that feeds you
|
||||
- bite the bullet
|
||||
- bite the dust
|
||||
- bitten off more than he can chew
|
||||
- black as coal
|
||||
- black as pitch
|
||||
- black as the ace of spades
|
||||
- blast from the past
|
||||
- bleeding heart
|
||||
- blessing in disguise
|
||||
- blind ambition
|
||||
- blind as a bat
|
||||
- blind leading the blind
|
||||
- blood is thicker than water
|
||||
- blood sweat and tears
|
||||
- blow off steam
|
||||
- blow your own horn
|
||||
- blushing bride
|
||||
- boils down to
|
||||
- bolt from the blue
|
||||
- bone to pick
|
||||
- bored stiff
|
||||
- bored to tears
|
||||
- bottomless pit
|
||||
- boys will be boys
|
||||
- bright and early
|
||||
- brings home the bacon
|
||||
- broad across the beam
|
||||
- broken record
|
||||
- brought back to reality
|
||||
- bull by the horns
|
||||
- bull in a china shop
|
||||
- burn the midnight oil
|
||||
- burning question
|
||||
- burning the candle at both ends
|
||||
- burst your bubble
|
||||
- bury the hatchet
|
||||
- busy as a bee
|
||||
- by hook or by crook
|
||||
- call a spade a spade
|
||||
- called onto the carpet
|
||||
- calm before the storm
|
||||
- can of worms
|
||||
- can't cut the mustard
|
||||
- can't hold a candle to
|
||||
- case of mistaken identity
|
||||
- cat got your tongue
|
||||
- cat's meow
|
||||
- caught in the crossfire
|
||||
- caught red-handed
|
||||
- checkered past
|
||||
- chomping at the bit
|
||||
- cleanliness is next to godliness
|
||||
- clear as a bell
|
||||
- clear as mud
|
||||
- close to the vest
|
||||
- cock and bull story
|
||||
- cold shoulder
|
||||
- come hell or high water
|
||||
- cool as a cucumber
|
||||
- cool, calm, and collected
|
||||
- cost a king's ransom
|
||||
- count your blessings
|
||||
- crack of dawn
|
||||
- crash course
|
||||
- creature comforts
|
||||
- cross that bridge when you come to it
|
||||
- crushing blow
|
||||
- cry like a baby
|
||||
- cry me a river
|
||||
- cry over spilt milk
|
||||
- crystal clear
|
||||
- curiosity killed the cat
|
||||
- cut and dried
|
||||
- cut through the red tape
|
||||
- cut to the chase
|
||||
- cute as a bugs ear
|
||||
- cute as a button
|
||||
- cute as a puppy
|
||||
- cuts to the quick
|
||||
- dark before the dawn
|
||||
- day in, day out
|
||||
- dead as a doornail
|
||||
- devil is in the details
|
||||
- dime a dozen
|
||||
- divide and conquer
|
||||
- dog and pony show
|
||||
- dog days
|
||||
- dog eat dog
|
||||
- dog tired
|
||||
- don't burn your bridges
|
||||
- don't count your chickens
|
||||
- don't look a gift horse in the mouth
|
||||
- don't rock the boat
|
||||
- don't step on anyone's toes
|
||||
- don't take any wooden nickels
|
||||
- down and out
|
||||
- down at the heels
|
||||
- down in the dumps
|
||||
- down the hatch
|
||||
- down to earth
|
||||
- draw the line
|
||||
- dressed to kill
|
||||
- dressed to the nines
|
||||
- drives me up the wall
|
||||
- dull as dishwater
|
||||
- dyed in the wool
|
||||
- eagle eye
|
||||
- ear to the ground
|
||||
- early bird catches the worm
|
||||
- easier said than done
|
||||
- easy as pie
|
||||
- eat your heart out
|
||||
- eat your words
|
||||
- eleventh hour
|
||||
- even the playing field
|
||||
- every dog has its day
|
||||
- every fiber of my being
|
||||
- everything but the kitchen sink
|
||||
- eye for an eye
|
||||
- face the music
|
||||
- facts of life
|
||||
- fair weather friend
|
||||
- fall by the wayside
|
||||
- fan the flames
|
||||
- feast or famine
|
||||
- feather your nest
|
||||
- feathered friends
|
||||
- few and far between
|
||||
- fifteen minutes of fame
|
||||
- filthy vermin
|
||||
- fine kettle of fish
|
||||
- fish out of water
|
||||
- fishing for a compliment
|
||||
- fit as a fiddle
|
||||
- fit the bill
|
||||
- fit to be tied
|
||||
- flash in the pan
|
||||
- flat as a pancake
|
||||
- flip your lid
|
||||
- flog a dead horse
|
||||
- fly by night
|
||||
- fly the coop
|
||||
- follow your heart
|
||||
- for all intents and purposes
|
||||
- for the birds
|
||||
- for what it's worth
|
||||
- force of nature
|
||||
- force to be reckoned with
|
||||
- forgive and forget
|
||||
- fox in the henhouse
|
||||
- free and easy
|
||||
- free as a bird
|
||||
- fresh as a daisy
|
||||
- full steam ahead
|
||||
- fun in the sun
|
||||
- garbage in, garbage out
|
||||
- gentle as a lamb
|
||||
- get a kick out of
|
||||
- get a leg up
|
||||
- get down and dirty
|
||||
- get the lead out
|
||||
- get to the bottom of
|
||||
- get your feet wet
|
||||
- gets my goat
|
||||
- gilding the lily
|
||||
- give and take
|
||||
- go against the grain
|
||||
- go at it tooth and nail
|
||||
- go for broke
|
||||
- go him one better
|
||||
- go the extra mile
|
||||
- go with the flow
|
||||
- goes without saying
|
||||
- good as gold
|
||||
- good deed for the day
|
||||
- good things come to those who wait
|
||||
- good time was had by all
|
||||
- good times were had by all
|
||||
- greased lightning
|
||||
- greek to me
|
||||
- green thumb
|
||||
- green-eyed monster
|
||||
- grist for the mill
|
||||
- growing like a weed
|
||||
- hair of the dog
|
||||
- hand to mouth
|
||||
- happy as a clam
|
||||
- happy as a lark
|
||||
- hasn't a clue
|
||||
- have a nice day
|
||||
- have high hopes
|
||||
- have the last laugh
|
||||
- haven't got a row to hoe
|
||||
- head honcho
|
||||
- head over heels
|
||||
- hear a pin drop
|
||||
- heard it through the grapevine
|
||||
- heart's content
|
||||
- heavy as lead
|
||||
- hem and haw
|
||||
- high and dry
|
||||
- high and mighty
|
||||
- high as a kite
|
||||
- hit paydirt
|
||||
- hold your head up high
|
||||
- hold your horses
|
||||
- hold your own
|
||||
- hold your tongue
|
||||
- honest as the day is long
|
||||
- horns of a dilemma
|
||||
- horse of a different color
|
||||
- hot under the collar
|
||||
- hour of need
|
||||
- I beg to differ
|
||||
- icing on the cake
|
||||
- if the shoe fits
|
||||
- if the shoe were on the other foot
|
||||
- in a jam
|
||||
- in a jiffy
|
||||
- in a nutshell
|
||||
- in a pig's eye
|
||||
- in a pinch
|
||||
- in a word
|
||||
- in hot water
|
||||
- in the gutter
|
||||
- in the nick of time
|
||||
- in the thick of it
|
||||
- in your dreams
|
||||
- it ain't over till the fat lady sings
|
||||
- it goes without saying
|
||||
- it takes all kinds
|
||||
- it takes one to know one
|
||||
- it's a small world
|
||||
- it's only a matter of time
|
||||
- ivory tower
|
||||
- Jack of all trades
|
||||
- jockey for position
|
||||
- jog your memory
|
||||
- joined at the hip
|
||||
- judge a book by its cover
|
||||
- jump down your throat
|
||||
- jump in with both feet
|
||||
- jump on the bandwagon
|
||||
- jump the gun
|
||||
- jump to conclusions
|
||||
- just a hop, skip, and a jump
|
||||
- just the ticket
|
||||
- justice is blind
|
||||
- keep a stiff upper lip
|
||||
- keep an eye on
|
||||
- keep it simple, stupid
|
||||
- keep the home fires burning
|
||||
- keep up with the Joneses
|
||||
- keep your chin up
|
||||
- keep your fingers crossed
|
||||
- kick the bucket
|
||||
- kick up your heels
|
||||
- kick your feet up
|
||||
- kid in a candy store
|
||||
- kill two birds with one stone
|
||||
- kiss of death
|
||||
- knock it out of the park
|
||||
- knock on wood
|
||||
- knock your socks off
|
||||
- know him from Adam
|
||||
- know the ropes
|
||||
- know the score
|
||||
- knuckle down
|
||||
- knuckle sandwich
|
||||
- knuckle under
|
||||
- labor of love
|
||||
- ladder of success
|
||||
- land on your feet
|
||||
- lap of luxury
|
||||
- last but not least
|
||||
- last hurrah
|
||||
- last-ditch effort
|
||||
- law of the jungle
|
||||
- law of the land
|
||||
- lay down the law
|
||||
- leaps and bounds
|
||||
- let sleeping dogs lie
|
||||
- let the cat out of the bag
|
||||
- let the good times roll
|
||||
- let your hair down
|
||||
- let's talk turkey
|
||||
- letter perfect
|
||||
- lick your wounds
|
||||
- lies like a rug
|
||||
- life's a bitch
|
||||
- life's a grind
|
||||
- light at the end of the tunnel
|
||||
- lighter than a feather
|
||||
- lighter than air
|
||||
- like clockwork
|
||||
- like father like son
|
||||
- like taking candy from a baby
|
||||
- like there's no tomorrow
|
||||
- lion's share
|
||||
- live and learn
|
||||
- live and let live
|
||||
- long and short of it
|
||||
- long lost love
|
||||
- look before you leap
|
||||
- look down your nose
|
||||
- look what the cat dragged in
|
||||
- looking a gift horse in the mouth
|
||||
- looks like death warmed over
|
||||
- loose cannon
|
||||
- lose your head
|
||||
- lose your temper
|
||||
- loud as a horn
|
||||
- lounge lizard
|
||||
- loved and lost
|
||||
- low man on the totem pole
|
||||
- luck of the draw
|
||||
- luck of the Irish
|
||||
- make hay while the sun shines
|
||||
- make money hand over fist
|
||||
- make my day
|
||||
- make the best of a bad situation
|
||||
- make the best of it
|
||||
- make your blood boil
|
||||
- man of few words
|
||||
- man's best friend
|
||||
- mark my words
|
||||
- meaningful dialogue
|
||||
- missed the boat on that one
|
||||
- moment in the sun
|
||||
- moment of glory
|
||||
- moment of truth
|
||||
- money to burn
|
||||
- more power to you
|
||||
- more than one way to skin a cat
|
||||
- movers and shakers
|
||||
- moving experience
|
||||
- naked as a jaybird
|
||||
- naked truth
|
||||
- neat as a pin
|
||||
- needle in a haystack
|
||||
- needless to say
|
||||
- neither here nor there
|
||||
- never look back
|
||||
- never say never
|
||||
- nip and tuck
|
||||
- nip it in the bud
|
||||
- no guts, no glory
|
||||
- no love lost
|
||||
- no pain, no gain
|
||||
- no skin off my back
|
||||
- no stone unturned
|
||||
- no time like the present
|
||||
- no use crying over spilled milk
|
||||
- nose to the grindstone
|
||||
- not a hope in hell
|
||||
- not a minute's peace
|
||||
- not in my backyard
|
||||
- not playing with a full deck
|
||||
- not the end of the world
|
||||
- not written in stone
|
||||
- nothing to sneeze at
|
||||
- nothing ventured nothing gained
|
||||
- now we're cooking
|
||||
- off the top of my head
|
||||
- off the wagon
|
||||
- off the wall
|
||||
- old hat
|
||||
- older and wiser
|
||||
- older than dirt
|
||||
- older than Methuselah
|
||||
- on a roll
|
||||
- on cloud nine
|
||||
- on pins and needles
|
||||
- on the bandwagon
|
||||
- on the money
|
||||
- on the nose
|
||||
- on the rocks
|
||||
- on the spot
|
||||
- on the tip of my tongue
|
||||
- on the wagon
|
||||
- on thin ice
|
||||
- once bitten, twice shy
|
||||
- one bad apple doesn't spoil the bushel
|
||||
- one born every minute
|
||||
- one brick short
|
||||
- one foot in the grave
|
||||
- one in a million
|
||||
- one red cent
|
||||
- only game in town
|
||||
- open a can of worms
|
||||
- open and shut case
|
||||
- open the flood gates
|
||||
- opportunity doesn't knock twice
|
||||
- out of pocket
|
||||
- out of sight, out of mind
|
||||
- out of the frying pan into the fire
|
||||
- out of the woods
|
||||
- out on a limb
|
||||
- over a barrel
|
||||
- over the hump
|
||||
- pain and suffering
|
||||
- pain in the
|
||||
- panic button
|
||||
- par for the course
|
||||
- part and parcel
|
||||
- party pooper
|
||||
- pass the buck
|
||||
- patience is a virtue
|
||||
- pay through the nose
|
||||
- penny pincher
|
||||
- perfect storm
|
||||
- pig in a poke
|
||||
- pile it on
|
||||
- pillar of the community
|
||||
- pin your hopes on
|
||||
- pitter patter of little feet
|
||||
- plain as day
|
||||
- plain as the nose on your face
|
||||
- play by the rules
|
||||
- play your cards right
|
||||
- playing the field
|
||||
- playing with fire
|
||||
- pleased as punch
|
||||
- plenty of fish in the sea
|
||||
- point with pride
|
||||
- poor as a church mouse
|
||||
- pot calling the kettle black
|
||||
- pretty as a picture
|
||||
- pull a fast one
|
||||
- pull your punches
|
||||
- pulling your leg
|
||||
- pure as the driven snow
|
||||
- put it in a nutshell
|
||||
- put one over on you
|
||||
- put the cart before the horse
|
||||
- put the pedal to the metal
|
||||
- put your best foot forward
|
||||
- put your foot down
|
||||
- quick as a bunny
|
||||
- quick as a lick
|
||||
- quick as a wink
|
||||
- quick as lightning
|
||||
- quiet as a dormouse
|
||||
- rags to riches
|
||||
- raining buckets
|
||||
- raining cats and dogs
|
||||
- rank and file
|
||||
- rat race
|
||||
- reap what you sow
|
||||
- red as a beet
|
||||
- red herring
|
||||
- reinvent the wheel
|
||||
- rich and famous
|
||||
- rings a bell
|
||||
- ripe old age
|
||||
- ripped me off
|
||||
- rise and shine
|
||||
- road to hell is paved with good intentions
|
||||
- rob Peter to pay Paul
|
||||
- roll over in the grave
|
||||
- rub the wrong way
|
||||
- ruled the roost
|
||||
- running in circles
|
||||
- sad but true
|
||||
- sadder but wiser
|
||||
- salt of the earth
|
||||
- scared stiff
|
||||
- scared to death
|
||||
- sealed with a kiss
|
||||
- second to none
|
||||
- see eye to eye
|
||||
- seen the light
|
||||
- seize the day
|
||||
- set the record straight
|
||||
- set the world on fire
|
||||
- set your teeth on edge
|
||||
- sharp as a tack
|
||||
- shoot for the moon
|
||||
- shoot the breeze
|
||||
- shot in the dark
|
||||
- shoulder to the wheel
|
||||
- sick as a dog
|
||||
- sigh of relief
|
||||
- signed, sealed, and delivered
|
||||
- sink or swim
|
||||
- six of one, half a dozen of another
|
||||
- skating on thin ice
|
||||
- slept like a log
|
||||
- slinging mud
|
||||
- slippery as an eel
|
||||
- slow as molasses
|
||||
- smart as a whip
|
||||
- smooth as a baby's bottom
|
||||
- sneaking suspicion
|
||||
- snug as a bug in a rug
|
||||
- sow wild oats
|
||||
- spare the rod, spoil the child
|
||||
- speak of the devil
|
||||
- spilled the beans
|
||||
- spinning your wheels
|
||||
- spitting image of
|
||||
- spoke with relish
|
||||
- spread like wildfire
|
||||
- spring to life
|
||||
- squeaky wheel gets the grease
|
||||
- stands out like a sore thumb
|
||||
- start from scratch
|
||||
- stick in the mud
|
||||
- still waters run deep
|
||||
- stitch in time
|
||||
- stop and smell the roses
|
||||
- straight as an arrow
|
||||
- straw that broke the camel's back
|
||||
- strong as an ox
|
||||
- stubborn as a mule
|
||||
- stuff that dreams are made of
|
||||
- stuffed shirt
|
||||
- sweating blood
|
||||
- sweating bullets
|
||||
- take a load off
|
||||
- take one for the team
|
||||
- take the bait
|
||||
- take the bull by the horns
|
||||
- take the plunge
|
||||
- takes one to know one
|
||||
- takes two to tango
|
||||
- the more the merrier
|
||||
- the real deal
|
||||
- the real McCoy
|
||||
- the red carpet treatment
|
||||
- the same old story
|
||||
- there is no accounting for taste
|
||||
- thick as a brick
|
||||
- thick as thieves
|
||||
- thin as a rail
|
||||
- think outside of the box
|
||||
- third time's the charm
|
||||
- this day and age
|
||||
- this hurts me worse than it hurts you
|
||||
- this point in time
|
||||
- three sheets to the wind
|
||||
- through thick and thin
|
||||
- throw in the towel
|
||||
- tie one on
|
||||
- tighter than a drum
|
||||
- time and time again
|
||||
- time is of the essence
|
||||
- tip of the iceberg
|
||||
- tired but happy
|
||||
- to coin a phrase
|
||||
- to each his own
|
||||
- to make a long story short
|
||||
- to the best of my knowledge
|
||||
- toe the line
|
||||
- tongue in cheek
|
||||
- too good to be true
|
||||
- too hot to handle
|
||||
- too numerous to mention
|
||||
- touch with a ten foot pole
|
||||
- tough as nails
|
||||
- trial and error
|
||||
- trials and tribulations
|
||||
- tried and true
|
||||
- trip down memory lane
|
||||
- twist of fate
|
||||
- two cents worth
|
||||
- two peas in a pod
|
||||
- ugly as sin
|
||||
- under the counter
|
||||
- under the gun
|
||||
- under the same roof
|
||||
- under the weather
|
||||
- until the cows come home
|
||||
- unvarnished truth
|
||||
- up the creek
|
||||
- uphill battle
|
||||
- upper crust
|
||||
- upset the applecart
|
||||
- vain attempt
|
||||
- vain effort
|
||||
- vanquish the enemy
|
||||
- vested interest
|
||||
- waiting for the other shoe to drop
|
||||
- wakeup call
|
||||
- warm welcome
|
||||
- watch your p's and q's
|
||||
- watch your tongue
|
||||
- watching the clock
|
||||
- water under the bridge
|
||||
- weather the storm
|
||||
- weed them out
|
||||
- week of Sundays
|
||||
- went belly up
|
||||
- wet behind the ears
|
||||
- what goes around comes around
|
||||
- what you see is what you get
|
||||
- when it rains, it pours
|
||||
- when push comes to shove
|
||||
- when the cat's away
|
||||
- when the going gets tough, the tough get going
|
||||
- white as a sheet
|
||||
- whole ball of wax
|
||||
- whole hog
|
||||
- whole nine yards
|
||||
- wild goose chase
|
||||
- will wonders never cease?
|
||||
- wisdom of the ages
|
||||
- wise as an owl
|
||||
- wolf at the door
|
||||
- words fail me
|
||||
- work like a dog
|
||||
- world weary
|
||||
- worst nightmare
|
||||
- worth its weight in gold
|
||||
- wrong side of the bed
|
||||
- yanking your chain
|
||||
- yappy as a dog
|
||||
- years young
|
||||
- you are what you eat
|
||||
- you can run but you can't hide
|
||||
- you only live once
|
||||
- you're the boss
|
||||
- young and foolish
|
||||
- young and vibrant
|
||||
@@ -1,32 +0,0 @@
|
||||
extends: existence
|
||||
message: "Try to avoid using '%s'."
|
||||
ignorecase: true
|
||||
level: suggestion
|
||||
tokens:
|
||||
- am
|
||||
- are
|
||||
- aren't
|
||||
- be
|
||||
- been
|
||||
- being
|
||||
- he's
|
||||
- here's
|
||||
- here's
|
||||
- how's
|
||||
- i'm
|
||||
- is
|
||||
- isn't
|
||||
- it's
|
||||
- she's
|
||||
- that's
|
||||
- there's
|
||||
- they're
|
||||
- was
|
||||
- wasn't
|
||||
- we're
|
||||
- were
|
||||
- weren't
|
||||
- what's
|
||||
- where's
|
||||
- who's
|
||||
- you're
|
||||
@@ -1,11 +0,0 @@
|
||||
extends: repetition
|
||||
message: "'%s' is repeated!"
|
||||
level: warning
|
||||
alpha: true
|
||||
action:
|
||||
name: edit
|
||||
params:
|
||||
- truncate
|
||||
- " "
|
||||
tokens:
|
||||
- '[^\s]+'
|
||||
@@ -1,183 +0,0 @@
|
||||
extends: existence
|
||||
message: "'%s' may be passive voice. Use active voice if you can."
|
||||
ignorecase: true
|
||||
level: warning
|
||||
raw:
|
||||
- \b(am|are|were|being|is|been|was|be)\b\s*
|
||||
tokens:
|
||||
- '[\w]+ed'
|
||||
- awoken
|
||||
- beat
|
||||
- become
|
||||
- been
|
||||
- begun
|
||||
- bent
|
||||
- beset
|
||||
- bet
|
||||
- bid
|
||||
- bidden
|
||||
- bitten
|
||||
- bled
|
||||
- blown
|
||||
- born
|
||||
- bought
|
||||
- bound
|
||||
- bred
|
||||
- broadcast
|
||||
- broken
|
||||
- brought
|
||||
- built
|
||||
- burnt
|
||||
- burst
|
||||
- cast
|
||||
- caught
|
||||
- chosen
|
||||
- clung
|
||||
- come
|
||||
- cost
|
||||
- crept
|
||||
- cut
|
||||
- dealt
|
||||
- dived
|
||||
- done
|
||||
- drawn
|
||||
- dreamt
|
||||
- driven
|
||||
- drunk
|
||||
- dug
|
||||
- eaten
|
||||
- fallen
|
||||
- fed
|
||||
- felt
|
||||
- fit
|
||||
- fled
|
||||
- flown
|
||||
- flung
|
||||
- forbidden
|
||||
- foregone
|
||||
- forgiven
|
||||
- forgotten
|
||||
- forsaken
|
||||
- fought
|
||||
- found
|
||||
- frozen
|
||||
- given
|
||||
- gone
|
||||
- gotten
|
||||
- ground
|
||||
- grown
|
||||
- heard
|
||||
- held
|
||||
- hidden
|
||||
- hit
|
||||
- hung
|
||||
- hurt
|
||||
- kept
|
||||
- knelt
|
||||
- knit
|
||||
- known
|
||||
- laid
|
||||
- lain
|
||||
- leapt
|
||||
- learnt
|
||||
- led
|
||||
- left
|
||||
- lent
|
||||
- let
|
||||
- lighted
|
||||
- lost
|
||||
- made
|
||||
- meant
|
||||
- met
|
||||
- misspelt
|
||||
- mistaken
|
||||
- mown
|
||||
- overcome
|
||||
- overdone
|
||||
- overtaken
|
||||
- overthrown
|
||||
- paid
|
||||
- pled
|
||||
- proven
|
||||
- put
|
||||
- quit
|
||||
- read
|
||||
- rid
|
||||
- ridden
|
||||
- risen
|
||||
- run
|
||||
- rung
|
||||
- said
|
||||
- sat
|
||||
- sawn
|
||||
- seen
|
||||
- sent
|
||||
- set
|
||||
- sewn
|
||||
- shaken
|
||||
- shaven
|
||||
- shed
|
||||
- shod
|
||||
- shone
|
||||
- shorn
|
||||
- shot
|
||||
- shown
|
||||
- shrunk
|
||||
- shut
|
||||
- slain
|
||||
- slept
|
||||
- slid
|
||||
- slit
|
||||
- slung
|
||||
- smitten
|
||||
- sold
|
||||
- sought
|
||||
- sown
|
||||
- sped
|
||||
- spent
|
||||
- spilt
|
||||
- spit
|
||||
- split
|
||||
- spoken
|
||||
- spread
|
||||
- sprung
|
||||
- spun
|
||||
- stolen
|
||||
- stood
|
||||
- stridden
|
||||
- striven
|
||||
- struck
|
||||
- strung
|
||||
- stuck
|
||||
- stung
|
||||
- stunk
|
||||
- sung
|
||||
- sunk
|
||||
- swept
|
||||
- swollen
|
||||
- sworn
|
||||
- swum
|
||||
- swung
|
||||
- taken
|
||||
- taught
|
||||
- thought
|
||||
- thrived
|
||||
- thrown
|
||||
- thrust
|
||||
- told
|
||||
- torn
|
||||
- trodden
|
||||
- understood
|
||||
- upheld
|
||||
- upset
|
||||
- wed
|
||||
- wept
|
||||
- withheld
|
||||
- withstood
|
||||
- woken
|
||||
- won
|
||||
- worn
|
||||
- wound
|
||||
- woven
|
||||
- written
|
||||
- wrung
|
||||
@@ -1,27 +0,0 @@
|
||||
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.
|
||||
|
||||
```
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Brian Ford
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
```
|
||||
@@ -1,5 +0,0 @@
|
||||
extends: existence
|
||||
message: "Don't start a sentence with '%s'."
|
||||
level: error
|
||||
raw:
|
||||
- '(?:[;-]\s)so[\s,]|\bSo[\s,]'
|
||||
@@ -1,6 +0,0 @@
|
||||
extends: existence
|
||||
message: "Don't start a sentence with '%s'."
|
||||
ignorecase: false
|
||||
level: error
|
||||
raw:
|
||||
- '(?:[;-]\s)There\s(is|are)|\bThere\s(is|are)\b'
|
||||
@@ -1,221 +0,0 @@
|
||||
extends: existence
|
||||
message: "'%s' is too wordy."
|
||||
ignorecase: true
|
||||
level: warning
|
||||
tokens:
|
||||
- a number of
|
||||
- abundance
|
||||
- accede to
|
||||
- accelerate
|
||||
- accentuate
|
||||
- accompany
|
||||
- accomplish
|
||||
- accorded
|
||||
- accrue
|
||||
- acquiesce
|
||||
- acquire
|
||||
- additional
|
||||
- adjacent to
|
||||
- adjustment
|
||||
- admissible
|
||||
- advantageous
|
||||
- adversely impact
|
||||
- advise
|
||||
- aforementioned
|
||||
- aggregate
|
||||
- aircraft
|
||||
- all of
|
||||
- all things considered
|
||||
- alleviate
|
||||
- allocate
|
||||
- along the lines of
|
||||
- already existing
|
||||
- alternatively
|
||||
- amazing
|
||||
- ameliorate
|
||||
- anticipate
|
||||
- apparent
|
||||
- appreciable
|
||||
- as a matter of fact
|
||||
- as a means of
|
||||
- as far as I'm concerned
|
||||
- as of yet
|
||||
- as to
|
||||
- as yet
|
||||
- ascertain
|
||||
- assistance
|
||||
- at the present time
|
||||
- at this time
|
||||
- attain
|
||||
- attributable to
|
||||
- authorize
|
||||
- because of the fact that
|
||||
- belated
|
||||
- benefit from
|
||||
- bestow
|
||||
- by means of
|
||||
- by virtue of
|
||||
- by virtue of the fact that
|
||||
- cease
|
||||
- close proximity
|
||||
- commence
|
||||
- comply with
|
||||
- concerning
|
||||
- consequently
|
||||
- consolidate
|
||||
- constitutes
|
||||
- demonstrate
|
||||
- depart
|
||||
- designate
|
||||
- discontinue
|
||||
- due to the fact that
|
||||
- each and every
|
||||
- economical
|
||||
- eliminate
|
||||
- elucidate
|
||||
- employ
|
||||
- endeavor
|
||||
- enumerate
|
||||
- equitable
|
||||
- equivalent
|
||||
- evaluate
|
||||
- evidenced
|
||||
- exclusively
|
||||
- expedite
|
||||
- expend
|
||||
- expiration
|
||||
- facilitate
|
||||
- factual evidence
|
||||
- feasible
|
||||
- finalize
|
||||
- first and foremost
|
||||
- for all intents and purposes
|
||||
- for the most part
|
||||
- for the purpose of
|
||||
- forfeit
|
||||
- formulate
|
||||
- have a tendency to
|
||||
- honest truth
|
||||
- however
|
||||
- if and when
|
||||
- impacted
|
||||
- implement
|
||||
- in a manner of speaking
|
||||
- in a timely manner
|
||||
- in a very real sense
|
||||
- in accordance with
|
||||
- in addition
|
||||
- in all likelihood
|
||||
- in an effort to
|
||||
- in between
|
||||
- in excess of
|
||||
- in lieu of
|
||||
- in light of the fact that
|
||||
- in many cases
|
||||
- in my opinion
|
||||
- in order to
|
||||
- in regard to
|
||||
- in some instances
|
||||
- in terms of
|
||||
- in the case of
|
||||
- in the event that
|
||||
- in the final analysis
|
||||
- in the nature of
|
||||
- in the near future
|
||||
- in the process of
|
||||
- inception
|
||||
- incumbent upon
|
||||
- indicate
|
||||
- indication
|
||||
- initiate
|
||||
- irregardless
|
||||
- is applicable to
|
||||
- is authorized to
|
||||
- is responsible for
|
||||
- it is
|
||||
- it is essential
|
||||
- it seems that
|
||||
- it was
|
||||
- magnitude
|
||||
- maximum
|
||||
- methodology
|
||||
- minimize
|
||||
- minimum
|
||||
- modify
|
||||
- monitor
|
||||
- multiple
|
||||
- necessitate
|
||||
- nevertheless
|
||||
- not certain
|
||||
- not many
|
||||
- not often
|
||||
- not unless
|
||||
- not unlike
|
||||
- notwithstanding
|
||||
- null and void
|
||||
- numerous
|
||||
- objective
|
||||
- obligate
|
||||
- obtain
|
||||
- on the contrary
|
||||
- on the other hand
|
||||
- one particular
|
||||
- optimum
|
||||
- overall
|
||||
- owing to the fact that
|
||||
- participate
|
||||
- particulars
|
||||
- pass away
|
||||
- pertaining to
|
||||
- point in time
|
||||
- portion
|
||||
- possess
|
||||
- preclude
|
||||
- previously
|
||||
- prior to
|
||||
- prioritize
|
||||
- procure
|
||||
- proficiency
|
||||
- provided that
|
||||
- purchase
|
||||
- put simply
|
||||
- readily apparent
|
||||
- refer back
|
||||
- regarding
|
||||
- relocate
|
||||
- remainder
|
||||
- remuneration
|
||||
- requirement
|
||||
- reside
|
||||
- residence
|
||||
- retain
|
||||
- satisfy
|
||||
- shall
|
||||
- should you wish
|
||||
- similar to
|
||||
- solicit
|
||||
- span across
|
||||
- strategize
|
||||
- subsequent
|
||||
- substantial
|
||||
- successfully complete
|
||||
- sufficient
|
||||
- terminate
|
||||
- the month of
|
||||
- the point I am trying to make
|
||||
- therefore
|
||||
- time period
|
||||
- took advantage of
|
||||
- transmit
|
||||
- transpire
|
||||
- type of
|
||||
- until such time as
|
||||
- utilization
|
||||
- utilize
|
||||
- validate
|
||||
- various different
|
||||
- what I mean to say is
|
||||
- whether or not
|
||||
- with respect to
|
||||
- with the exception of
|
||||
- witnessed
|
||||
@@ -1,29 +0,0 @@
|
||||
extends: existence
|
||||
message: "'%s' is a weasel word!"
|
||||
ignorecase: true
|
||||
level: warning
|
||||
tokens:
|
||||
- clearly
|
||||
- completely
|
||||
- exceedingly
|
||||
- excellent
|
||||
- extremely
|
||||
- fairly
|
||||
- huge
|
||||
- interestingly
|
||||
- is a number
|
||||
- largely
|
||||
- mostly
|
||||
- obviously
|
||||
- quite
|
||||
- relatively
|
||||
- remarkably
|
||||
- several
|
||||
- significantly
|
||||
- substantially
|
||||
- surprisingly
|
||||
- tiny
|
||||
- usually
|
||||
- various
|
||||
- vast
|
||||
- very
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"feed": "https://github.com/errata-ai/write-good/releases.atom",
|
||||
"vale_version": ">=1.0.0"
|
||||
}
|
||||
@@ -1,673 +0,0 @@
|
||||
# AGENTS.md — Project Conventions for devx
|
||||
|
||||
## 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, tea, hadolint, vale to ~/.local/bin
|
||||
make lint-all # ruff + pyright + bandit + actionlint + lint-dockerfiles
|
||||
make pytest-cov # Unit tests with 100% coverage enforcement
|
||||
make test-unit # Unit tests without coverage
|
||||
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 devx-check-doc-versions # Verify docs version refs match __version__
|
||||
make devx-vale # Run Vale prose linter on docs and README
|
||||
make clean # Remove caches, build artifacts, coverage data
|
||||
```
|
||||
|
||||
`make setup` automatically installs all development tools:
|
||||
- **Python deps** via `python -m devx.tools.setup` (pip install -e .[dev], pre-commit hooks)
|
||||
- **actionlint, git-cliff, act_runner, tea, hadolint, vale** via `python -m devx.tools.install_tools` (CI/CD tools to ~/.local/bin)
|
||||
- **tea CLI login** via `python -m devx.tools.setup` (configures `tea login` from `.env` `CI_GITEA_TOKEN`)
|
||||
|
||||
## 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 `python -m 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` 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
|
||||
|
||||
devx is a reusable Python package providing development and CI/CD tools for oblachno-oss projects.
|
||||
|
||||
### Package Structure
|
||||
|
||||
```text
|
||||
src/devx/
|
||||
├── __init__.py # Version (single source of truth, read by setuptools)
|
||||
├── cli.py # Click-based CLI entry point (devx command)
|
||||
├── config.py # Configuration system (DEVX_ env vars, .env loading)
|
||||
├── api_clients.py # GiteaClient, VikunjaClient — HTTP API wrappers
|
||||
├── gitea_cli.py # TeaCLI — wrapper around tea CLI with JSON parsing
|
||||
├── i18n.py # Translation system (gettext-based, translations.json)
|
||||
├── exceptions.py # Custom exception types
|
||||
├── translations.json # Translation strings (en, bg, de, pl, ru, zh)
|
||||
├── ci/ # CI/CD automation modules (run by workflows)
|
||||
│ ├── release.py # Automated versioning, tagging, changelog
|
||||
│ ├── publish.py # Build, publish to Gitea PyPI registry, create Gitea release (with retry)
|
||||
│ ├── auto_merge.py # Squash-merge PRs with task ID validation
|
||||
│ ├── check_auto_merge_ready.py # Pre-merge validation gate (branch, PR title, Vikunja, behind-master)
|
||||
│ ├── _shared.py # Shared utilities (get_latest_tag)
|
||||
│ ├── classify_changes.py # User-facing vs infrastructure change detection
|
||||
│ ├── detect_release_commit.py # Detect release commits on master
|
||||
│ ├── validate_commit_msg.py # Conventional commit validation
|
||||
│ ├── pr_review.py # Automated PR review + manual reviews (--event, --body, --checklist-confirmed)
|
||||
│ ├── post_merge.py # Vikunja task updates after merge
|
||||
│ ├── sync_wiki.py # Sync documentation to Gitea wiki
|
||||
│ ├── push_badges.py # Generate and push quality badges (--retries for retry on git push failures)
|
||||
│ ├── notify_failure.py # Create Gitea issues on CI failures (--auto-login)
|
||||
│ ├── distribute_files.py # Distribute files across parallel runners (LPT scheduling)
|
||||
│ ├── distribute_items.py # Distribute generic items (VMs, hosts) across parallel runners (LPT)
|
||||
│ ├── integration_guard.py # Run pytest with cross-runner fail-fast
|
||||
│ ├── check_translations.py # Translation completeness check
|
||||
│ ├── doc_coverage.py # Documentation coverage check
|
||||
│ ├── lint_docs.py # Documentation linter (structure, links, headings, code blocks, orphans)
|
||||
│ ├── validate_deploy_ref.py # Validate git tag for deployments (--github-output)
|
||||
│ └── record_deployed_tag.py # Record deployed tag to Gitea repo variable
|
||||
├── tools/ # Developer tooling modules (run locally or by CI)
|
||||
│ ├── setup.py # Environment setup (venv, deps, hooks)
|
||||
│ ├── install_tools.py # Install actionlint, git-cliff, act_runner, tea, hadolint, vale
|
||||
│ ├── install_checkmake.py # Install checkmake (Makefile linter)
|
||||
│ ├── check_doc_versions.py # Verify docs version refs match __version__
|
||||
│ ├── build_image.py # Build and push Docker images to Gitea registry
|
||||
│ ├── clean_images.py # Clean up old Docker image versions from Gitea registry
|
||||
│ ├── check_test_speed.py # Measure unit test execution time
|
||||
│ ├── check_mutable_globals.py # Detect module-level mutable globals (test isolation bugs)
|
||||
│ ├── check_pyproject_deps.py # Validate pyproject.toml deps have documentation comments
|
||||
│ ├── check_test_coverage.py # Ensure changed files have corresponding tests (configurable rules)
|
||||
│ ├── check_agent_docs.py # Validate docs for stale file references (configurable patterns)
|
||||
│ ├── check_config.py # Validate pyproject.toml [tool.devx] config
|
||||
│ ├── configure_repo.py # Branch protection and label setup
|
||||
│ ├── generate_badges.py # Badge SVG generation
|
||||
│ ├── generate_cliff_config.py # Generate git-cliff config (cliff.toml)
|
||||
│ ├── create_task.py # Create Vikunja tasks
|
||||
│ ├── create_pr.py # Create PRs with auto-derived title from Vikunja
|
||||
│ ├── pr_status.py # Check CI status for a PR/commit (--wait polls)
|
||||
│ ├── pr_logs.py # Fetch logs for failed CI jobs
|
||||
│ ├── pr_label.py # Add labels to PRs (idempotent)
|
||||
│ ├── pre_push_check.py # Validate Vikunja task existence before push
|
||||
│ └── _shared.py # Shared tool utilities
|
||||
├── opentofu.py # OpenTofu output helpers (get_tofu_output, get_tofu_vm_ip, get_tofu_vm_field)
|
||||
├── utils/ # Shared utilities (reusable across projects)
|
||||
│ ├── api.py # API response helpers (is_truthy, is_falsy)
|
||||
│ ├── ssh.py # SSH exec + wait_for_ssh (pure-Python socket check)
|
||||
│ ├── crypto.py # Secret generation (shell-safe passwords)
|
||||
│ ├── vault.py # Ansible vault encrypt/decrypt helpers
|
||||
│ ├── network.py # HTTP connectivity check + wait_for_ssh
|
||||
│ ├── confirm.py # Typed confirmation validation for destructive ops
|
||||
│ ├── json_registry.py # File-locked JSON registry for local state
|
||||
│ ├── step_tracker.py # Multi-step operation tracking with reports
|
||||
│ └── logging.py # XDG-compliant logging configuration
|
||||
└── molecule/ # Optional molecule testing helpers (for Ansible projects)
|
||||
├── discover_runners.py # Dynamic Gitea runner discovery
|
||||
├── distribute_molecule.py # Distribute molecule scenarios across runners (LPT scheduling, --roles-root for multi-role)
|
||||
├── molecule_ci_guard.py # Run molecule with cross-runner fail-fast (--roles-root)
|
||||
├── molecule_all.py # Run all molecule scenarios locally
|
||||
├── start_docker.py # Ensure Docker daemon is running for molecule tests
|
||||
└── platforms.py # Supported molecule platforms
|
||||
```
|
||||
|
||||
### Key Design Principles
|
||||
|
||||
- **Self-contained package** — `src/devx/` never imports from scripts outside the package
|
||||
- **Module-based invocation** — All tools invoked via `python -m devx.ci.*` or `python -m devx.tools.*`
|
||||
- **PYTHONPATH: src** — Workflows set `PYTHONPATH: src` (NOT `.:src` since there are no scripts at repo root)
|
||||
- **Config via env vars** — `DEVX_*` environment variables with `.env` file fallback
|
||||
|
||||
## 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
|
||||
`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 must pass
|
||||
- **Block force pushes**: No history rewriting on master
|
||||
|
||||
### 1. Create Vikunja Task
|
||||
Create a task in Vikunja to get a `DEVX-N` identifier.
|
||||
|
||||
**IMPORTANT:** The task title must NOT include the `DEVX-N:` prefix.
|
||||
The `make create-pr` and `check_auto_merge_ready` commands automatically
|
||||
prepend `DEVX-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 DEVX-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 `DEVX-N:` prefix):
|
||||
```text
|
||||
feat: add new feature
|
||||
fix: resolve bug
|
||||
docs: update README
|
||||
```
|
||||
|
||||
### 5. Push and Create PR
|
||||
- **PR title format**: `DEVX-N: <vikunja task title>` (must match the Vikunja task title exactly)
|
||||
- PR body: summary of changes, `Closes DEVX-N`
|
||||
- Add `ready-to-merge` label **only after review is complete**
|
||||
|
||||
### 6. Review the PR
|
||||
|
||||
**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):
|
||||
|
||||
- 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 agent **must** address all `REQUEST_CHANGES` issues before proceeding.
|
||||
|
||||
### 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, approve
|
||||
the PR. Then add the `ready-to-merge` label. The auto-merge workflow will:
|
||||
1. **Validate** PR title format (`DEVX-N: <vikunja task title>`) and match against Vikunja task title
|
||||
2. **Check** that at least one substantive APPROVE review exists
|
||||
3. Wait for all CI checks to pass (including the `validate` job)
|
||||
4. Squash-merge with title: `DEVX-N: <conventional commit message>`
|
||||
5. The post-merge workflow marks the Vikunja task as done
|
||||
6. The release workflow automatically versions, tags, and publishes
|
||||
|
||||
> **IMPORTANT**: Never manually merge PRs via the API. Always use the auto-merge
|
||||
> workflow by adding the `ready-to-merge` label.
|
||||
|
||||
### 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 `python -m devx.ci.release`
|
||||
which checks for user-facing changes via `classify_changes`, 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 a new release. The project classifies changes
|
||||
using `python -m devx.ci.classify_changes`:
|
||||
|
||||
**Workflow-only paths** (infrastructure → no release needed):
|
||||
- `.gitea/**` — Gitea Actions workflows
|
||||
- `tests/**` — Test files
|
||||
- `AGENTS.md`, `README.md`, `CHANGELOG.md` — Project docs
|
||||
- `Makefile`, `cliff.toml`, `.pre-commit-config.yaml` — Config
|
||||
- `.env.example`, `.gitignore` — Config
|
||||
- `hooks/**` — Git hooks
|
||||
|
||||
**User-facing paths** (tool changes → release needed) — everything else:
|
||||
- `src/devx/**` — Python package source (except `__init__.py`)
|
||||
- `pyproject.toml` — Package metadata
|
||||
- Any new file type not in the allowlist
|
||||
|
||||
## Script Separation and Import Rules
|
||||
|
||||
### Directory Layout
|
||||
|
||||
| Directory | Purpose | Release impact |
|
||||
|-----------|---------|----------------|
|
||||
| `src/devx/` | User-facing devx package | Changes trigger release |
|
||||
| `src/devx/ci/` | CI/CD automation (run by workflows) | Part of package |
|
||||
| `src/devx/tools/` | Developer tooling (run locally or by CI) | Part of package |
|
||||
| `tests/` | Test files | Workflow-only (no release) |
|
||||
|
||||
### Import Rules
|
||||
|
||||
1. **`src/devx/` is self-contained** — the package never imports from outside `src/`
|
||||
2. **CI modules** (`devx.ci.*`) may import from `devx.api_clients`, `devx.config`, `devx.gitea_cli`, `devx.i18n`
|
||||
3. **Tool modules** (`devx.tools.*`) may import from `devx.api_clients`, `devx.config`, `devx.gitea_cli`
|
||||
4. **Cross-module imports** within `devx.ci.*` or `devx.tools.*` are allowed but must be documented
|
||||
|
||||
### PYTHONPATH Configuration
|
||||
|
||||
All workflows use `PYTHONPATH: src` — devx has no scripts at the repo root,
|
||||
so `.:src` is not needed. The `src` directory is the sole import root.
|
||||
|
||||
```yaml
|
||||
- name: Run script
|
||||
env:
|
||||
PYTHONPATH: src
|
||||
run: python -m devx.ci.example
|
||||
```
|
||||
|
||||
### tea CLI Integration
|
||||
|
||||
The `tea` Gitea CLI tool is used for Gitea API interactions. It is installed
|
||||
by `python -m devx.tools.install_tools` and configured by
|
||||
`python -m devx.tools.setup` (login profile from `.env` `CI_GITEA_TOKEN`).
|
||||
|
||||
**`devx.gitea_cli.TeaCLI`** — Python wrapper around `tea` CLI with JSON output parsing:
|
||||
- `create_issue()` — Create issues with labels
|
||||
- `list_labels()` / `create_label()` / `add_label()` — Label management
|
||||
- `create_pr()` / `merge_pr()` / `review_pr()` — Pull request operations
|
||||
- `create_release()` / `list_releases()` — Release management
|
||||
|
||||
**`devx.gitea_cli.configure_tea_login()`** — Configures tea login in
|
||||
containerized CI environments where `make setup` was not called. Used by
|
||||
`publish.py` (`--auto-login`) and `notify_failure.py` (`--auto-login`).
|
||||
Raises `TeaCLIError` if login configuration fails — this prevents cryptic
|
||||
"no available login" errors from subsequent tea commands.
|
||||
|
||||
**Error handling**: `TeaCLI._run()` includes both stdout and stderr in
|
||||
`TeaCLIError` messages, because `tea` writes some errors (for example,
|
||||
"no available login") to stdout, not stderr.
|
||||
|
||||
**Release creation retry**: `publish.py` retries Gitea release creation
|
||||
up to 3 times with exponential backoff (2s, 4s) on transient failures.
|
||||
"Already exists" errors are treated as success (idempotent).
|
||||
|
||||
### git-cliff Commit Preprocessing
|
||||
|
||||
Merge commits on master have the format `DEVX-N: <conventional commit>`. The
|
||||
`cliff.toml` includes a `commit_preprocessors` entry that strips the `DEVX-N `
|
||||
prefix 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) |
|
||||
| `chore:`, `ci:`, `docs:` | no bump (excluded by cliff.toml) |
|
||||
|
||||
The version source is `__version__` in `src/devx/__init__.py`, read by
|
||||
setuptools via `dynamic = ["version"]` in `pyproject.toml`.
|
||||
|
||||
### Title Format Summary
|
||||
|
||||
| What | Format | Example |
|
||||
|------|--------|---------|
|
||||
| Branch name | `DEVX-N-short-description` | `DEVX-12-add-release-script` |
|
||||
| Branch commits | `<conventional commit>` | `feat: add release script` |
|
||||
| PR title | `DEVX-N: <vikunja task title>` | `DEVX-12: Add release automation` |
|
||||
| Merge commit | `DEVX-N: <conventional commit>` | `DEVX-12: feat: add release script` |
|
||||
|
||||
### Task ID Resolution
|
||||
|
||||
`auto_merge` resolves the task ID solely from the branch name (for example
|
||||
`DEVX-12-fix-foo` → `DEVX-12`). Branch names must include the task ID
|
||||
prefix — there is no `.taskid` file fallback. If a stale `.taskid` file
|
||||
exists in the repo, a deprecation warning is printed advising its removal.
|
||||
|
||||
### Workflow `auto-merge` Job and `always()`
|
||||
|
||||
When `auto-merge` depends on a job that can be skipped (for example
|
||||
`molecule-tests`), the `if:` condition MUST include `always() &&`
|
||||
at the start. Without it, Gitea Actions skips `auto-merge` when any
|
||||
dependency is skipped, even if the condition explicitly allows
|
||||
`result == 'skipped'`.
|
||||
|
||||
```yaml
|
||||
auto-merge:
|
||||
needs: [validate, molecule-tests]
|
||||
if: >-
|
||||
always() &&
|
||||
github.event_name == 'pull_request' &&
|
||||
needs.validate.result == 'success' &&
|
||||
(needs.molecule-tests.result == 'success' || needs.molecule-tests.result == 'skipped')
|
||||
```
|
||||
|
||||
### LPT Test Distribution Algorithm
|
||||
|
||||
`distribute_molecule` and `distribute_files` use **LPT (Longest Processing
|
||||
Time first)** scheduling instead of naive round-robin. This produces a more
|
||||
balanced distribution when test items have varying costs:
|
||||
|
||||
1. **Weight estimation**: Each item is assigned a weight:
|
||||
- Molecule scenarios: heuristic by name (`nextcloud`=10, `gitea`=8,
|
||||
`binary`=2, default=3). See `_SCENARIO_WEIGHTS` in
|
||||
`distribute_molecule.py`.
|
||||
- Integration test files: weight by file size in bytes (as a proxy
|
||||
for test runtime).
|
||||
2. **LPT assignment**: Items are sorted by weight (descending), then
|
||||
each is assigned to the runner with the least total weight.
|
||||
|
||||
This ensures heavy scenarios (for example `nextcloud`) are spread across
|
||||
different runners rather than clustered on one, reducing the
|
||||
longest-runner time from ~16 min to ~11 min with 6 runners.
|
||||
|
||||
## Config System
|
||||
|
||||
devx uses environment variables with `.env` file fallback for configuration.
|
||||
|
||||
### DEVX_ Environment Variables
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `DEVX_GITEA_API_URL` | `https://git.oblachno.oblachno.fyi/api/v1` | Gitea API base URL |
|
||||
| `DEVX_VIKUNJA_API_URL` | `https://work.oblachno.oblachno.fyi/api/v1` | Vikunja API base URL |
|
||||
| `DEVX_REPO_OWNER` | **(none — must be set)** | Repository owner for API calls |
|
||||
| `DEVX_REPO_NAME` | **(none — must be set)** | Repository name (or `owner/repo`) |
|
||||
| `DEVX_TASK_PREFIX` | `DEVX` | Task ID prefix (GRM, OBL-INFRA, etc.) |
|
||||
| `DEVX_VIKUNJA_PROJECT_ID` | `6` | Vikunja project ID |
|
||||
| `DEVX_LANG` | `en` | Language for i18n (en, bg, de, pl, ru, zh) |
|
||||
| `CI_GITEA_TOKEN` | (from .env) | Gitea API token |
|
||||
| `VIKUNJA_TOKEN` | (from .env) | Vikunja API token |
|
||||
|
||||
### Per-Project Overrides
|
||||
|
||||
Projects using devx can override the default API URLs and language by setting
|
||||
`DEVX_*` environment variables or entries in their `.env` file. The config
|
||||
system loads `.env` automatically via `python-dotenv`.
|
||||
|
||||
### pyproject.toml [tool.devx] Configuration
|
||||
|
||||
In addition to `DEVX_` env vars, many devx tools read configuration from
|
||||
the `[tool.devx]` section in `pyproject.toml`. This allows per-project
|
||||
customization without environment variables.
|
||||
|
||||
**Base config** (`[tool.devx]`):
|
||||
- `task_prefix` — Task ID prefix (for example `"DEVX"`, `"GRM"`, `"OBL-INFRA"`)
|
||||
- `vikunja_project_id` — Vikunja project ID
|
||||
- `repo_owner` / `repo_name` — Gitea repository coordinates
|
||||
- `gitea_api_url` / `vikunja_api_url` — API endpoints
|
||||
|
||||
**Tool-specific config**:
|
||||
- `[tool.devx.check_mutable_globals]` — `scan_dirs`, `skip_dirs`, `known_safe`
|
||||
- `[tool.devx.check_test_coverage]` — `rules` (source_pattern → test_paths mapping), `skip_patterns`
|
||||
- `[tool.devx.check_agent_docs]` — `scan_dirs`, `deleted_files`, `deprecated_patterns`, `legitimate_indicators`
|
||||
|
||||
## devx.mak — Shared Makefile Fragment
|
||||
|
||||
`devx.mak` provides common Makefile targets that projects can include
|
||||
via `-include $(DEVX_MAK)`. This eliminates Makefile duplication across
|
||||
projects.
|
||||
|
||||
**Available targets** (all prefixed with `devx-`):
|
||||
|
||||
| Target | Purpose |
|
||||
|--------|---------|
|
||||
| `devx-create-task` | Create a Vikunja task |
|
||||
| `devx-create-pr` | Create a PR with auto-derived title |
|
||||
| `devx-push` | Push current branch to origin |
|
||||
| `devx-push-with-pr` | Push and create PR in one step |
|
||||
| `devx-pr-status` | Check CI status for a PR (`PR=`, `WAIT=`, `TIMEOUT=`) |
|
||||
| `devx-pr-logs` | Fetch logs for failed CI jobs (`PR=`, `JOB=`, `TAIL=`) |
|
||||
| `devx-pr-label` | Add a label to a PR (`PR=`, `LABEL=ready-to-merge`) |
|
||||
| `devx-pr-review` | Post a review on a PR (`PR=`, `EVENT=`, `BODY=`, `CHECKLIST=`) |
|
||||
| `devx-check-config` | Validate devx configuration |
|
||||
| `devx-configure-gitea-pypi` | Configure Gitea private PyPI registry |
|
||||
| `devx-env` | Create .env from .env.example |
|
||||
| `devx-venv` | Create Python venv with version check |
|
||||
| `devx-activate-scripts` | Create shell/fish/zsh activate scripts |
|
||||
| `devx-install-hooks` | Set git hooks path to hooks/ |
|
||||
| `devx-install-tools` | Install actionlint, git-cliff, act_runner, tea, hadolint |
|
||||
| `devx-install-checkmake` | Install checkmake (Makefile linter) |
|
||||
| `devx-checkmake` | Lint Makefiles with checkmake |
|
||||
| `devx-workflow-lint` | Static lint of Gitea Actions YAML (actionlint) |
|
||||
| `devx-workflow-dryrun` | Dry-run all workflows (act_runner) |
|
||||
| `devx-workflow-dryrun-safe` | Best-effort dry-run (skips if act_runner missing) |
|
||||
| `devx-workflow-check` | Static lint + dry-run |
|
||||
| `devx-notify-failure` | Create Gitea issue on CI failure |
|
||||
| `devx-lint-ruff` | Run ruff check |
|
||||
| `devx-lint-format` | Run ruff format --check |
|
||||
| `devx-typecheck` | Run pyright |
|
||||
| `devx-lint-bandit` | Run bandit security scan |
|
||||
| `devx-lint-deps` | Check dependencies for vulnerabilities (pip-audit) |
|
||||
| `devx-lint` | Run all lint targets |
|
||||
| `devx-test-unit` | Run unit tests without coverage |
|
||||
| `devx-pytest-cov` | Run pytest with coverage enforcement |
|
||||
| `devx-check-mutable-globals` | Scan for mutable path globals |
|
||||
| `devx-check-dep-docs` | Validate pyproject.toml deps are documented |
|
||||
| `devx-check-test-coverage` | Check changed files have corresponding tests |
|
||||
| `devx-check-docs` | Validate docs for stale references |
|
||||
| `devx-check-test-speed` | Verify test suite timing |
|
||||
| `devx-pre-push` | Run lint + tests before push |
|
||||
| `devx-clean` | Remove caches, build artifacts, coverage data |
|
||||
| `devx-setup-image` | Link /opt/venv + install project (for pre-built image CI jobs) |
|
||||
| `devx-lint-dockerfiles` | Lint Dockerfiles with hadolint (fail-fast, parameterized by `DEVX_DOCKERFILE_PATHS`) |
|
||||
| `devx-build-images` | Build Docker images from manifest (no push) |
|
||||
| `devx-push-images` | Build and push Docker images to Gitea registry |
|
||||
| `devx-build-images-dry-run` | Show what would be built/pushed |
|
||||
| `devx-clean-images` | Delete old image versions (keep last 2 + latest) |
|
||||
|
||||
**Variables** (set BEFORE including devx.mak):
|
||||
- `DEVX_PYTHON` — Python executable (default: `python3`)
|
||||
- `DEVX_VENV` — venv directory (default: `.venv`)
|
||||
- `DEVX_BIN` — venv bin directory (default: `$(DEVX_VENV)/bin`)
|
||||
- `DEVX_LINT_PATHS` — paths for ruff/bandit (default: `src/ tests/`)
|
||||
- `DEVX_COV_PKG` — coverage package (default: `src/devx`)
|
||||
- `DEVX_TEST_PATHS` — pytest paths (default: `tests/`)
|
||||
- `DEVX_PR_BASE` — PR base branch (default: `master`)
|
||||
- `DEVX_DOCKERFILE_PATHS` — directory to search for Dockerfiles (default: `docker`)
|
||||
- `DEVX_GITEA_REGISTRY` — registry URL (default: `git.oblachno.oblachno.fyi`)
|
||||
- `DEVX_IMAGE_MANIFEST` — path to JSON manifest (default: `docker/images.json`)
|
||||
- `DEVX_IMAGE_OWNER` — package owner for cleanup (default: `oblachno-oss`)
|
||||
|
||||
## Pre-built Docker Runner Images
|
||||
|
||||
devx builds and publishes three tier images to the Gitea container registry
|
||||
to eliminate the 40-120s setup tax on every CI job:
|
||||
|
||||
| Image | Contains | Used by jobs |
|
||||
|-------|----------|-------------|
|
||||
| `ci-base-latest` | Python 3.12 + devx[ci] + tea | auto-merge, detect-and-configure |
|
||||
| `ci-quality-latest` | ci-base + devx[lint] + actionlint + checkmake + hadolint | (badges in release-and-maintain uses ci-full) |
|
||||
| `ci-full-latest` | ci-quality + devx[release,molecule,deploy] + git-cliff + OpenTofu | validate, release-and-maintain, molecule-tests, build-and-push |
|
||||
|
||||
**Build process** (in `build-images.yml` workflow):
|
||||
1. `ci-base` builds FROM `gitea/runner-images:ubuntu-latest`
|
||||
2. `ci-quality` builds FROM `ci-base-latest`
|
||||
3. `ci-full` builds FROM `ci-quality-latest`
|
||||
|
||||
Each image is tagged `latest` and pushed to
|
||||
`git.oblachno.oblachno.fyi/oblachno-oss/runner-images:<tier>-latest`.
|
||||
|
||||
**Using images in workflows**:
|
||||
```yaml
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: docker
|
||||
container: git.oblachno.oblachno.fyi/oblachno-oss/runner-images/ci-full:latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up environment
|
||||
run: make setup-image # links /opt/venv, installs project (no-deps)
|
||||
```
|
||||
|
||||
**Image build/push tools** (tested Python modules):
|
||||
- `devx.tools.build_image` — Build and push Docker images from Dockerfile or manifest
|
||||
- `devx.tools.clean_images` — Delete old image versions via Gitea API (keep last N + latest)
|
||||
|
||||
**Usage in project Makefile**:
|
||||
```makefile
|
||||
DEVX_PYTHON := $(BIN)/python
|
||||
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 names
|
||||
lint-ruff: devx-lint-ruff
|
||||
workflow-lint: devx-workflow-lint
|
||||
create-task: devx-create-task
|
||||
```
|
||||
|
||||
## Key Conventions
|
||||
|
||||
- Python 3.12+ required (ruff/pyright target `py312`)
|
||||
- 100% test coverage required (`--cov-fail-under=100`)
|
||||
- Conventional commits on feature branches (no `DEVX-N:` prefix)
|
||||
- Branch names must include `DEVX-N` task ID
|
||||
- Line length: 120 chars
|
||||
- Secrets are passed via environment variables, never on the command line
|
||||
- All user-facing strings wrapped in `_()` for i18n
|
||||
|
||||
### 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`.
|
||||
|
||||
## 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 |
|
||||
|
||||
**devx-specific** (in `.devin/agents/`):
|
||||
|
||||
| Profile | Purpose |
|
||||
|---------|---------|
|
||||
| `ci-investigator` | Investigate CI failures (validate, release-and-maintain, build-images) |
|
||||
| `dep-upgrader` | Python dependency upgrades in pyproject.toml with dep-doc validation |
|
||||
| `docker-image-builder` | Build/push/cleanup 3-tier runner images (ci-base, ci-quality, ci-full) |
|
||||
| `doc-sync-specialist` | Doc coverage, doc linting, wiki sync integrity |
|
||||
| `workflow-validator` | actionlint + act_runner dry-run validation |
|
||||
|
||||
### When to Delegate Automatically
|
||||
|
||||
| Trigger | Profile | Mode |
|
||||
|---------|---------|------|
|
||||
| CI run failure (validate, release-and-maintain, build-images) | `ci-investigator` | Background |
|
||||
| PR ready for review | `pr-reviewer` | Foreground |
|
||||
| Dependency upgrade requested | `dep-upgrader` | Background |
|
||||
| Docker image build/push needed | `docker-image-builder` | 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 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"`.
|
||||
|
||||
@@ -1,779 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [0.47.1] - 2026-07-16
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Tea CLI login failure handling, error messages, release retry
|
||||
|
||||
## [0.47.0] - 2026-07-14
|
||||
|
||||
### Features
|
||||
|
||||
- Add promtool to install_tools for alert rule validation
|
||||
|
||||
## [0.46.0] - 2026-07-14
|
||||
|
||||
### Features
|
||||
|
||||
- Make check_test_isolation configurable via pyproject.toml
|
||||
|
||||
## [0.45.1] - 2026-07-14
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- URL-encode package names and versions in clean_images API calls
|
||||
|
||||
## [0.45.0] - 2026-07-14
|
||||
|
||||
### Features
|
||||
|
||||
- Add IO_INTERNAL_CALLS to check_test_isolation
|
||||
|
||||
## [0.44.2] - 2026-07-14
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Use legacy Docker builder to avoid Gitea registry 403
|
||||
|
||||
## [0.44.1] - 2026-07-14
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Disable Docker buildx provenance attestation
|
||||
|
||||
## [0.44.0] - 2026-07-13
|
||||
|
||||
### Features
|
||||
|
||||
- Add fix_pr_title module and update_pr API method
|
||||
|
||||
## [0.43.0] - 2026-07-13
|
||||
|
||||
### Features
|
||||
|
||||
- Add get_customer_vm_ip and get_observability_vm_ip to I/O check
|
||||
|
||||
## [0.42.0] - 2026-07-13
|
||||
|
||||
### Features
|
||||
|
||||
- Add I/O function isolation check and skip integration tests
|
||||
|
||||
## [0.41.2] - 2026-07-13
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Auto-discover molecule root instead of hardcoding gitea-runner
|
||||
|
||||
## [0.41.1] - 2026-07-13
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Check_test_isolation accepts multiple --test-path values
|
||||
|
||||
## [0.41.0] - 2026-07-13
|
||||
|
||||
### Features
|
||||
|
||||
- Test isolation pytest plugin, shift-left quality gates, dep upgrades
|
||||
|
||||
## [0.40.1] - 2026-07-12
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fall back to CI token when reviewer self-approval is rejected
|
||||
|
||||
## [0.40.0] - 2026-07-11
|
||||
|
||||
### Features
|
||||
|
||||
- Detect double-prefix in Vikunja task title during pre-merge validation
|
||||
|
||||
## [0.39.0] - 2026-07-09
|
||||
|
||||
### Features
|
||||
|
||||
- Extract shared utilities from infra and grm into devx
|
||||
|
||||
## [0.38.0] - 2026-07-08
|
||||
|
||||
### Features
|
||||
|
||||
- Introduce role-based Gitea API token environment variables
|
||||
|
||||
## [0.37.0] - 2026-07-07
|
||||
|
||||
### Features
|
||||
|
||||
- Consolidate docs checks into devx-docs-check target
|
||||
|
||||
## [0.36.2] - 2026-07-07
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- GiteaClient.set_repo_variable uses PUT instead of PATCH
|
||||
|
||||
## [0.36.1] - 2026-07-07
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Preserve .badges/ dir during git clean in push_badges
|
||||
|
||||
## [0.36.0] - 2026-07-07
|
||||
|
||||
### Features
|
||||
|
||||
- Add GiteaClient repo variable methods and parallelize pytest-cov
|
||||
|
||||
## [0.35.7] - 2026-07-06
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Use Gitea wiki dash-marker filename convention
|
||||
|
||||
## [0.35.6] - 2026-07-06
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Add delay before wiki verification to avoid race condition
|
||||
|
||||
## [0.35.5] - 2026-07-06
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Embed token in wiki clone URL for push auth
|
||||
|
||||
## [0.35.4] - 2026-07-06
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Configure git identity before commit in sync_wiki
|
||||
|
||||
## [0.35.3] - 2026-07-06
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Replace --strict with --verify for sync_wiki
|
||||
|
||||
## [0.35.2] - 2026-07-06
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Exclude .vale directory from lint_docs scanning
|
||||
|
||||
## [0.35.1] - 2026-07-06
|
||||
|
||||
### Refactor
|
||||
|
||||
- Rewrite sync_wiki.py to use git-based approach
|
||||
|
||||
## [0.35.0] - 2026-07-06
|
||||
|
||||
### Features
|
||||
|
||||
- Enrich lint_docs.py with single H1, max depth, line length, code block lang, orphan checks
|
||||
|
||||
## [0.34.0] - 2026-07-06
|
||||
|
||||
### Features
|
||||
|
||||
- Enhance documentation-as-code with badges, version refs, Vale
|
||||
|
||||
## [0.33.4] - 2026-07-06
|
||||
|
||||
### Refactor
|
||||
|
||||
- Remove project-specific references from devx
|
||||
|
||||
## [0.33.3] - 2026-07-06
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Make wiki sync resilient to API timeouts and stale page lists
|
||||
|
||||
## [0.33.2] - 2026-07-05
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Abort sync_wiki when list_wiki_pages fails
|
||||
|
||||
## [0.33.1] - 2026-07-05
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Build images after post-merge publish, not on push
|
||||
|
||||
## [0.33.0] - 2026-07-05
|
||||
|
||||
### Features
|
||||
|
||||
- Add check_api_identity_checks, setup_ssh_key, and api utils
|
||||
|
||||
## [0.32.1] - 2026-07-01
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Add missing i18n translations for new tools
|
||||
|
||||
## [0.32.0] - 2026-07-01
|
||||
|
||||
### Features
|
||||
|
||||
- Extract docker-login, tofu-ops, check-deps, install-tofu to Python tools
|
||||
|
||||
## [0.31.0] - 2026-07-01
|
||||
|
||||
### Features
|
||||
|
||||
- Centralize venv management in devx.mak
|
||||
|
||||
## [0.30.0] - 2026-07-01
|
||||
|
||||
### Features
|
||||
|
||||
- Add standard label creation to configure_repo
|
||||
|
||||
## [0.29.1] - 2026-07-01
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Strip task ID prefix from commit messages in extract_conventional_msg
|
||||
|
||||
## [0.29.0] - 2026-07-01
|
||||
|
||||
### Features
|
||||
|
||||
- Detect badge commits as automated CI commits
|
||||
|
||||
## [0.28.0] - 2026-07-01
|
||||
|
||||
### Features
|
||||
|
||||
- Auto-rebase in auto-merge, new rebase tools, CLI registration
|
||||
|
||||
## [0.27.3] - 2026-06-30
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Retry wiki integrity check on transient API timeout
|
||||
|
||||
## [0.27.2] - 2026-06-29
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Retry release push on non-fast-forward with rebase loop
|
||||
|
||||
## [0.27.1] - 2026-06-28
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Exclude .devin/.terraform dirs from lint_docs, add duplicate heading excludes
|
||||
|
||||
## [0.27.0] - 2026-06-28
|
||||
|
||||
### Features
|
||||
|
||||
- Add lint_docs tool, fix doc_coverage/check_translations for any repo
|
||||
|
||||
## [0.26.4] - 2026-06-28
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Wrap all user-facing strings with _() for i18n completeness
|
||||
|
||||
## [0.26.3] - 2026-06-28
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Pin all dependencies to exact versions for reproducibility
|
||||
|
||||
## [0.26.2] - 2026-06-28
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Block admin merge override and auto-approve with review token
|
||||
|
||||
## [0.26.1] - 2026-06-28
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Force pip upgrade in setup-image to install new dependencies
|
||||
|
||||
## [0.26.0] - 2026-06-28
|
||||
|
||||
### Features
|
||||
|
||||
- Add distribute_items CI tool for parallel VM deployment
|
||||
|
||||
## [0.25.0] - 2026-06-28
|
||||
|
||||
### Features
|
||||
|
||||
- Add manual review support to pr_review (--event, --body, --checklist-confirmed)
|
||||
|
||||
## [0.24.1] - 2026-06-28
|
||||
|
||||
### Refactor
|
||||
|
||||
- Add find_task_by_identifier, config fallbacks for tools
|
||||
|
||||
## [0.24.0] - 2026-06-27
|
||||
|
||||
### Features
|
||||
|
||||
- Add pr_status, pr_logs, pr_label tools
|
||||
|
||||
## [0.23.4] - 2026-06-27
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Add --auto-login to all notify_failure calls in workflows
|
||||
- Classify .gitea/** as user-facing for devx, support glob in user_facing_overrides
|
||||
|
||||
## [0.23.3] - 2026-06-27
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Correct clean_images delete URL and add retry with error handling
|
||||
|
||||
## [0.23.2] - 2026-06-27
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Add skip-ci flag to release commits and concurrency to build-images
|
||||
|
||||
## [0.23.1] - 2026-06-27
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Add rsync to ci-full image for molecule_docker
|
||||
|
||||
## [0.23.0] - 2026-06-27
|
||||
|
||||
### Features
|
||||
|
||||
- Add devx-lint-dockerfiles to devx.mak, alias setup-image
|
||||
|
||||
### Refactor
|
||||
|
||||
- Remove hadolint on-the-fly install from setup-image
|
||||
|
||||
## [0.22.1] - 2026-06-27
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Checkout release tag in publish job
|
||||
- Fail lint-dockerfiles when hadolint is missing
|
||||
|
||||
## [0.22.0] - 2026-06-27
|
||||
|
||||
### Features
|
||||
|
||||
- Document CI_GITEA_TOKEN scopes and add CI_GITEA_USERNAME to env var table
|
||||
|
||||
## [0.21.2] - 2026-06-27
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Gate auto-merge on release-dry-run and unmask failures
|
||||
|
||||
## [0.21.1] - 2026-06-27
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Devx-setup-image configures Gitea PyPI registry and shows pip errors
|
||||
|
||||
## [0.21.0] - 2026-06-27
|
||||
|
||||
### Features
|
||||
|
||||
- Add --auto-login to publish, extract configure_tea_login to gitea_cli
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Publish job uses setup-release for build + tea login
|
||||
- Remove tag fallback step from release workflow
|
||||
|
||||
## [0.20.3] - 2026-06-27
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Release publish failures and duplicate release commits
|
||||
|
||||
## [0.20.2] - 2026-06-27
|
||||
|
||||
## [0.20.2] - 2026-06-27
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Correct sed substitution in ci-full Dockerfile
|
||||
|
||||
## [0.20.1] - 2026-06-27
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Correct image references in tier Dockerfiles
|
||||
|
||||
## [0.20.0] - 2026-06-27
|
||||
|
||||
### Features
|
||||
|
||||
- Add pre-built Docker runner images and tested image build/push tools
|
||||
|
||||
## [0.19.3] - 2026-06-26
|
||||
|
||||
### Refactor
|
||||
|
||||
- Make molecule weights configurable via pyproject.toml
|
||||
|
||||
## [0.19.2] - 2026-06-26
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Calibrate molecule weights from actual CI execution times
|
||||
|
||||
## [0.19.1] - 2026-06-26
|
||||
|
||||
### Refactor
|
||||
|
||||
- Consolidate publish.yml into post-merge.yml
|
||||
|
||||
## [0.19.0] - 2026-06-26
|
||||
|
||||
### Features
|
||||
|
||||
- Add skip_ref_prefixes config to check_agent_docs
|
||||
|
||||
## [0.18.0] - 2026-06-26
|
||||
|
||||
### Features
|
||||
|
||||
- Extract generic tools into devx, expand devx.mak, remove personal references
|
||||
|
||||
## [0.17.0] - 2026-06-26
|
||||
|
||||
### Features
|
||||
|
||||
- Weighted LPT distribution, workflow fixes, decouple vikunja/sync-wiki from release
|
||||
|
||||
## [0.16.0] - 2026-06-26
|
||||
|
||||
### Features
|
||||
|
||||
- Single-source-of-truth config via [tool.devx] in pyproject.toml
|
||||
|
||||
## [0.15.0] - 2026-06-26
|
||||
|
||||
### Features
|
||||
|
||||
- Add create-task, create-pr, pre-push-check tools and devx.mak fragment
|
||||
|
||||
## [0.14.2] - 2026-06-26
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Make repo arg optional in publish CLI, auto-detect from GITHUB_REPOSITORY
|
||||
|
||||
## [0.14.1] - 2026-06-25
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Handle 'already a release' error idempotently in publish
|
||||
|
||||
## [0.14.0] - 2026-06-25
|
||||
|
||||
### Features
|
||||
|
||||
- Add FORCE_DEPLOY env var, --git flag, --from-tag flag
|
||||
|
||||
## [0.13.0] - 2026-06-25
|
||||
|
||||
### Features
|
||||
|
||||
- Add --force flag to classify_changes, fix api_clients coverage
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Squash-merge format uses space not colon after task ID
|
||||
- Revert squash-merge format to use colon after task ID
|
||||
|
||||
## [0.1.0] - 2026-06-25
|
||||
|
||||
## [0.12.5] - 2026-06-25
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Make PyPI publish failures non-fatal
|
||||
|
||||
## [0.12.4] - 2026-06-25
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Pass REPO_TOKEN to setup-release so tea login is configured
|
||||
- Guarantee Gitea release for every tag
|
||||
|
||||
## [0.12.3] - 2026-06-25
|
||||
|
||||
### Refactor
|
||||
|
||||
- Remove JUnit reporting from devx
|
||||
|
||||
## [0.12.2] - 2026-06-25
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Remove auto-rebase from auto-merge to prevent CI feedback loop
|
||||
|
||||
## [0.12.1] - 2026-06-25
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Use heredoc syntax for multi-line $GITHUB_ENV values
|
||||
|
||||
## [0.12.0] - 2026-06-24
|
||||
|
||||
### Features
|
||||
|
||||
- Add Polish as officially supported language
|
||||
|
||||
## [0.11.1] - 2026-06-24
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Add build/twine to ci deps, activate venv in notify_failure
|
||||
|
||||
## [0.11.0] - 2026-06-24
|
||||
|
||||
### Features
|
||||
|
||||
- Add publish step to post-merge release job, make publish idempotent
|
||||
|
||||
## [0.10.2] - 2026-06-24
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Badge generation respects pyproject.toml testpaths, shows stdout in warnings
|
||||
|
||||
## [0.10.1] - 2026-06-24
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Badge generation REPO_ROOT, auto-detect package, error feedback
|
||||
|
||||
## [0.10.0] - 2026-06-24
|
||||
|
||||
### Features
|
||||
|
||||
- Remove .taskid file fallback, use branch name only
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Use raw/branch/badges/ URLs for badges in README and docs
|
||||
|
||||
## [0.9.12] - 2026-06-24
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Clean dist/ before build and add workflow_dispatch to publish
|
||||
|
||||
## [0.9.11] - 2026-06-24
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Use raw/branch/badges/ URLs for badges in README and docs
|
||||
- Resolve repo_root from GITHUB_WORKSPACE or cwd
|
||||
|
||||
## [0.9.10] - 2026-06-24
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Retrospective fixes for CI/CD friction
|
||||
|
||||
## [0.9.9] - 2026-06-24
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Use DOCKER_HOST env var in is_docker_ready + scan all rootless sockets
|
||||
- Prefer branch name for task ID extraction + strip heads/ prefix in release
|
||||
- Filter non-version tags in release verification
|
||||
- Use explicit refspecs for git push to avoid tag/branch ambiguity
|
||||
|
||||
## [0.9.8] - 2026-06-24
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Use DOCKER_HOST env var in is_docker_ready + scan all rootless sockets
|
||||
|
||||
## [0.9.7] - 2026-06-24
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Add rootless socket fallback and GITHUB_ENV export
|
||||
|
||||
## [0.9.6] - 2026-06-24
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Add Docker socket diagnostics to start_docker
|
||||
- Add Docker socket diagnostics to start_docker
|
||||
|
||||
## [0.9.5] - 2026-06-24
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Use host Docker socket with DOCKER_HOST fallback to local dockerd
|
||||
- Use host Docker socket with DOCKER_HOST fallback to local dockerd
|
||||
|
||||
## [0.9.4] - 2026-06-24
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Use separate Docker socket for DinD in CI
|
||||
|
||||
## [0.9.3] - 2026-06-24
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Use tempfile for dockerd log to fix CI permission error
|
||||
|
||||
## [0.9.2] - 2026-06-24
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Use vfs storage driver for Docker-in-Docker in CI
|
||||
|
||||
## [0.9.1] - 2026-06-23
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Always start dockerd in CI runner for molecule tests
|
||||
|
||||
## [0.9.0] - 2026-06-23
|
||||
|
||||
### Features
|
||||
|
||||
- Extract Docker daemon start to tested Python module
|
||||
|
||||
## [0.8.5] - 2026-06-23
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Retry pip install with --ignore-installed only on failure
|
||||
|
||||
## [0.8.4] - 2026-06-23
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Add --ignore-installed to pip in CI to bypass debian packages
|
||||
|
||||
## [0.8.3] - 2026-06-23
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Lower check_test_speed threshold to 4 seconds
|
||||
- Pass --break-system-packages to pip in CI environments
|
||||
|
||||
## [0.8.2] - 2026-06-23
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Encode spaces in pair commands to survive shell word-splitting
|
||||
|
||||
## [0.8.1] - 2026-06-23
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Set fresh MOLECULE_HOME per pair to avoid stale config cache
|
||||
|
||||
## [0.8.0] - 2026-06-23
|
||||
|
||||
### Features
|
||||
|
||||
- Fix molecule platforms to use sleep infinity, add --platforms-file
|
||||
|
||||
## [0.7.0] - 2026-06-23
|
||||
|
||||
### Features
|
||||
|
||||
- Add per-test timing quality gate to check_test_speed
|
||||
|
||||
## [0.6.0] - 2026-06-23
|
||||
|
||||
### Features
|
||||
|
||||
- Add opentofu helpers, CLI entry points, shared utility, and CI improvements
|
||||
|
||||
## [0.5.0] - 2026-06-23
|
||||
|
||||
### Features
|
||||
|
||||
- Add tag verification, idempotency, and --verify mode to release script
|
||||
|
||||
## [0.4.4] - 2026-06-22
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Configurable task prefix and CWD-relative DOCS_DIR
|
||||
|
||||
## [0.4.3] - 2026-06-22
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Expand DEFAULT_INFRASTRUCTURE to cover all common project files
|
||||
|
||||
## [0.4.2] - 2026-06-22
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Make all warnings into errors across devx tools
|
||||
|
||||
## [0.4.1] - 2026-06-22
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Correct version tags, changelog, and release script recovery
|
||||
|
||||
## [0.4.0] - 2026-06-22
|
||||
|
||||
### Features
|
||||
|
||||
- Add DEFAULT_INFRASTRUCTURE and configurable task prefix
|
||||
|
||||
## [0.3.0] - 2026-06-22
|
||||
|
||||
### Features
|
||||
|
||||
- Add --no-ansible-collections option to setup tool
|
||||
|
||||
## [0.2.0] - 2026-06-22
|
||||
|
||||
### Features
|
||||
|
||||
- Pluggable change classification framework
|
||||
|
||||
## [0.1.2] - 2026-06-22
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Make sync-wiki and vikunja depend on release
|
||||
|
||||
## [0.1.1] - 2026-06-22
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Disable push whitelist, allow direct pushes to master
|
||||
|
||||
## [0.1.0] - 2026-06-22
|
||||
|
||||
### Features
|
||||
|
||||
- Extract reusable dev/CI tools from GRM into devx package
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Use python3 and venv python in workflows and Makefile
|
||||
- Fix post-merge job failures (configure-repo, badges, notify-failure)
|
||||
- Allow release bot to push to protected master
|
||||
@@ -1,232 +0,0 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright © 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
“This License” refers to version 3 of the GNU General Public License.
|
||||
|
||||
“Copyright” also means copyright-like laws that apply to other kinds of works, such as semiconductor masks.
|
||||
|
||||
“The Program” refers to any copyrightable work licensed under this License. Each licensee is addressed as “you”. “Licensees” and “recipients” may be individuals or organizations.
|
||||
|
||||
To “modify” a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a “modified version” of the earlier work or a work “based on” the earlier work.
|
||||
|
||||
A “covered work” means either the unmodified Program or a work based on the Program.
|
||||
|
||||
To “propagate” a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well.
|
||||
|
||||
To “convey” a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays “Appropriate Legal Notices” to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
The “source code” for a work means the preferred form of the work for making modifications to it. “Object code” means any non-source form of a work.
|
||||
|
||||
A “Standard Interface” means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language.
|
||||
|
||||
The “System Libraries” of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A “Major Component”, in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The “Corresponding Source” for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to “keep intact all notices”.
|
||||
|
||||
c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an “aggregate” if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work.
|
||||
|
||||
A “User Product” is either (1) a “consumer product”, which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, “normally used” refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product.
|
||||
|
||||
“Installation Information” for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
“Additional permissions” are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered “further restrictions” within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License.
|
||||
|
||||
An “entity transaction” is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
A “contributor” is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's “contributor version”.
|
||||
|
||||
A contributor's “essential patent claims” are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, “control” includes the right to grant patent sublicenses in a manner consistent with the requirements of this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a “patent license” is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To “grant” such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. “Knowingly relying” means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it.
|
||||
|
||||
A patent license is “discriminatory” if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found.
|
||||
|
||||
devx
|
||||
Copyright (C) 2026 oblachno-oss
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode:
|
||||
|
||||
devx Copyright (C) 2026 oblachno-oss
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an “about box”.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school, if any, to sign a “copyright disclaimer” for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read <https://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
@@ -1,148 +0,0 @@
|
||||
.PHONY: all setup setup-ci setup-quality setup-release setup-image install update lint lint-all lint-dockerfiles test test-unit pytest-cov clean install-tools install-hooks activate-scripts checkmake check-mutable-globals check-dep-docs check-test-speed build-images push-images build-images-dry-run clean-images
|
||||
|
||||
PYTHON := python3
|
||||
VENV := .venv
|
||||
BIN := $(VENV)/bin
|
||||
|
||||
all: setup
|
||||
|
||||
# --- devx.mak integration ----------------------------------------------------
|
||||
# Include shared targets from the devx package itself (venv management,
|
||||
# workflow-lint, notify-failure, checkmake, lint targets, quality checks, etc.)
|
||||
# Since devx IS the package, we can include its own devx.mak.
|
||||
DEVX_PYTHON := $(BIN)/python
|
||||
DEVX_VENV := $(VENV)
|
||||
DEVX_BIN := $(BIN)
|
||||
DEVX_LINT_PATHS := src/ tests/
|
||||
DEVX_COV_PKG := src/devx
|
||||
DEVX_TEST_PATHS := tests/
|
||||
|
||||
DEVX_MAK := $(shell $(BIN)/python -c \
|
||||
"from pathlib import Path; import devx; print(Path(devx.__file__).parent / 'make' / 'devx.mak')" \
|
||||
2>/dev/null)
|
||||
# Fallback: when the venv doesn't exist yet (chicken-and-egg), use the
|
||||
# source tree copy directly. devx IS the package, so src/devx/make/devx.mak
|
||||
# is always available in this repo.
|
||||
ifeq ($(strip $(DEVX_MAK)),)
|
||||
DEVX_MAK := $(CURDIR)/src/devx/make/devx.mak
|
||||
endif
|
||||
-include $(DEVX_MAK)
|
||||
|
||||
# venv, .env, and activate-scripts are provided by devx.mak
|
||||
# (devx-venv, devx-env, devx-activate-scripts, $(DEVX_VENV)/bin/activate rule)
|
||||
# Aliases for convenience and backward compatibility:
|
||||
.PHONY: venv activate-scripts
|
||||
venv: devx-venv
|
||||
.env: devx-env
|
||||
activate-scripts: devx-activate-scripts
|
||||
|
||||
# Full setup for local development
|
||||
setup: $(VENV)/bin/activate .env activate-scripts install-tools
|
||||
@$(BIN)/pip install -e '.[dev]' 2>/dev/null; \
|
||||
export PATH="$(HOME)/.local/bin:$$PATH"; \
|
||||
$(BIN)/python -m devx.tools.setup --bin "$(BIN)"
|
||||
|
||||
# Lean setup for CI jobs (pytest + lint + runtime deps)
|
||||
setup-ci: $(VENV)/bin/activate .env
|
||||
@$(BIN)/pip install -e '.[ci,lint]' 2>/dev/null; \
|
||||
$(BIN)/python -m devx.tools.setup --bin "$(BIN)" --extras "ci,lint" --no-pre-commit --no-tea-login
|
||||
|
||||
# Setup for quality job (lint + test deps, actionlint tool)
|
||||
setup-quality: $(VENV)/bin/activate .env install-tools
|
||||
@$(BIN)/pip install -e '.[ci,lint]' 2>/dev/null; \
|
||||
export PATH="$(HOME)/.local/bin:$$PATH"; \
|
||||
$(BIN)/python -m devx.tools.setup --bin "$(BIN)" --extras "ci,lint" --no-pre-commit --no-tea-login
|
||||
|
||||
# Setup for release jobs (needs git-cliff, tea, lint tools)
|
||||
setup-release: $(VENV)/bin/activate .env
|
||||
@$(BIN)/pip install -e '.[ci,lint,release]' 2>/dev/null; \
|
||||
$(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,release" --no-pre-commit
|
||||
|
||||
# Setup for pre-built image jobs (deps already in image, just link venv + install project)
|
||||
# Note: Not aliased to devx-setup-image because devx's own CI images may have
|
||||
# an older devx.mak that doesn't yet define devx-setup-image. Consumer repos
|
||||
# (grm, infra) can safely alias to devx-setup-image since they install devx from PyPI.
|
||||
setup-image:
|
||||
@if [ -d /opt/venv ]; then ln -sf /opt/venv $(VENV); . $(VENV)/bin/activate && pip install --no-cache-dir -e . 2>/dev/null; \
|
||||
else echo "[setup-image] /opt/venv not found — falling back to setup-ci"; $(MAKE) setup-ci; fi
|
||||
|
||||
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."
|
||||
|
||||
install-tools: $(VENV)/bin/activate
|
||||
@$(BIN)/pip install -e '.' 2>/dev/null; \
|
||||
$(BIN)/python -m devx.tools.install_tools
|
||||
|
||||
# Aliases — project-specific names map to devx.mak targets
|
||||
.PHONY: lint-ruff lint-format typecheck lint-bandit lint-deps lint
|
||||
.PHONY: workflow-lint workflow-dryrun workflow-dryrun-safe workflow-check
|
||||
.PHONY: notify-failure checkmake check-mutable-globals check-dep-docs
|
||||
.PHONY: check-test-speed check-test-coverage check-docs check-test-isolation check-translations
|
||||
.PHONY: create-task create-pr push-with-pr git-push rebase pr-rebase
|
||||
.PHONY: lint-all lint-dockerfiles
|
||||
lint-ruff: devx-lint-ruff
|
||||
lint-format: devx-lint-format
|
||||
typecheck: devx-typecheck
|
||||
lint-bandit: devx-lint-bandit
|
||||
lint-deps: devx-lint-deps
|
||||
lint: devx-lint
|
||||
workflow-lint: devx-workflow-lint
|
||||
workflow-dryrun: devx-workflow-dryrun
|
||||
workflow-dryrun-safe: devx-workflow-dryrun-safe
|
||||
workflow-check: devx-workflow-check
|
||||
notify-failure: devx-notify-failure
|
||||
checkmake: devx-checkmake
|
||||
check-mutable-globals: devx-check-mutable-globals
|
||||
check-dep-docs: devx-check-dep-docs
|
||||
check-test-speed: devx-check-test-speed
|
||||
check-test-isolation: devx-check-test-isolation
|
||||
check-translations: devx-check-translations
|
||||
check-test-coverage: devx-check-test-coverage
|
||||
check-docs: devx-check-docs
|
||||
create-task: devx-create-task
|
||||
create-pr: devx-create-pr
|
||||
push-with-pr: devx-push-with-pr
|
||||
git-push: devx-push
|
||||
rebase: devx-rebase
|
||||
pr-rebase: devx-pr-rebase
|
||||
|
||||
lint-all: lint workflow-lint lint-dockerfiles
|
||||
@echo "[lint-all] All linting checks passed."
|
||||
|
||||
# Note: Not aliased to devx-lint-dockerfiles for the same reason as setup-image —
|
||||
# devx's own CI images may have an older devx.mak. Consumer repos can safely alias.
|
||||
lint-dockerfiles:
|
||||
@echo "[lint-dockerfiles] Linting Dockerfiles with hadolint..."
|
||||
@command -v hadolint >/dev/null 2>&1 || { echo "hadolint not found" >&2; exit 1; }
|
||||
@find docker -name 'Dockerfile*' -exec hadolint {} +
|
||||
@echo "[lint-dockerfiles] All Dockerfiles passed."
|
||||
|
||||
test-unit: devx-test-unit
|
||||
|
||||
pytest-cov: devx-pytest-cov
|
||||
|
||||
test: pytest-cov
|
||||
|
||||
pre-push: lint-all pytest-cov
|
||||
@echo "[pre-push] All checks passed. Proceeding with push."
|
||||
|
||||
clean: devx-clean
|
||||
@echo "[clean] Done."
|
||||
|
||||
# ── Docker image management ──────────────────────────────────────────────────
|
||||
|
||||
build-images: devx-build-images
|
||||
@echo "[build-images] Done."
|
||||
|
||||
push-images: devx-push-images
|
||||
@echo "[push-images] Done."
|
||||
|
||||
build-images-dry-run: devx-build-images-dry-run
|
||||
@echo "[build-images-dry-run] Done."
|
||||
|
||||
clean-images: devx-clean-images
|
||||
@echo "[clean-images] Done."
|
||||
@@ -1,476 +0,0 @@
|
||||
# devx — Reusable Development & CI/CD Tools
|
||||
|
||||
A Python package providing reusable development and CI/CD automation tools for
|
||||
oblachno-oss projects. devx consolidates release management, PR automation,
|
||||
wiki sync, badge generation, translation checks, documentation coverage,
|
||||
parallel test distribution, and more into a single installable package.
|
||||
|
||||
It was extracted from the [GRM](https://git.oblachno.oblachno.fyi/oblachno-oss/grm)
|
||||
project to be reusable across all oblachno-oss repositories. Any project hosted
|
||||
on a Gitea instance with Gitea Actions can install devx and inherit a complete,
|
||||
opinionated CI/CD pipeline: conventional commits, automated versioning via
|
||||
git-cliff, squash-merge automation, Vikunja task tracking, wiki sync, and
|
||||
quality badges.
|
||||
|
||||
> An open-source project from **Oblachno** (облачно means *cloudy* in Bulgarian).
|
||||
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/src/branch/master/LICENSE)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/wiki)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/releases)
|
||||
[](https://www.python.org/downloads/)
|
||||
|
||||
## Why devx?
|
||||
|
||||
Every oblachno-oss project shares the same CI/CD needs: automated releases,
|
||||
PR review, task tracking, documentation sync, and quality badges. Without a
|
||||
shared package, each repository duplicates this logic in shell scripts and
|
||||
workflow YAML, leading to drift, bugs, and maintenance burden.
|
||||
|
||||
devx solves this by providing a single, tested Python package that any
|
||||
oblachno-oss project can install. The project declares its configuration via
|
||||
environment variables and `pyproject.toml`, and devx handles the rest. Updates
|
||||
to the CI/CD pipeline ship as new devx releases — consumer projects pick them
|
||||
up by bumping their devx dependency.
|
||||
|
||||
### Key features
|
||||
|
||||
- **Automated releases** — git-cliff-driven semver versioning, changelog
|
||||
generation, tagging, and publishing to a Gitea PyPI registry.
|
||||
- **PR automation** — squash-merge with task ID validation, automated PR
|
||||
review with inline comments, and conventional commit enforcement.
|
||||
- **Smart change classification** — user-facing vs workflow-only change
|
||||
detection so infrastructure-only changes skip releases.
|
||||
- **Documentation sync** — push `docs/` markdown to the Gitea wiki with
|
||||
integrity verification.
|
||||
- **Quality badges** — generate self-contained SVG badges for coverage,
|
||||
tests, docs, quality, version, and Python version.
|
||||
- **Translation checks** — validate i18n keys against source code, detect
|
||||
dead keys and missing languages.
|
||||
- **Parallel test distribution** — split test files or molecule scenarios
|
||||
across CI runners with cross-runner fail-fast.
|
||||
- **Developer tools** — environment setup, CI tool installation, test speed
|
||||
enforcement, repository configuration.
|
||||
- **i18n** — built-in translations for English, Bulgarian, German, Russian,
|
||||
Chinese, and Polish; projects can extend with their own keys.
|
||||
|
||||
## Installation
|
||||
|
||||
devx is published to the Gitea PyPI registry at
|
||||
`https://git.oblachno.oblachno.fyi/api/packages/oblachno-oss/pypi/simple`.
|
||||
The registry is publicly readable — no authentication required to install.
|
||||
|
||||
### Quick install (one-off)
|
||||
|
||||
```bash
|
||||
pip install devx --index-url https://git.oblachno.oblachno.fyi/api/packages/oblachno-oss/pypi/simple
|
||||
```
|
||||
|
||||
### Persistent configuration (recommended)
|
||||
|
||||
Add the registry to `~/.pip/pip.conf` so `pip install devx` works without
|
||||
specifying `--index-url` every time:
|
||||
|
||||
```ini
|
||||
[global]
|
||||
extra-index-url = https://git.oblachno.oblachno.fyi/api/packages/oblachno-oss/pypi/simple
|
||||
```
|
||||
|
||||
### As a dependency in another project
|
||||
|
||||
To use devx as a dependency in your `pyproject.toml`, add the registry as an
|
||||
extra index and list devx in your dependencies:
|
||||
|
||||
```toml
|
||||
[project]
|
||||
dependencies = [
|
||||
"devx>=0.47.1",
|
||||
]
|
||||
|
||||
[tool.pip]
|
||||
extra-index-url = "https://git.oblachno.oblachno.fyi/api/packages/oblachno-oss/pypi/simple"
|
||||
```
|
||||
|
||||
Then install normally:
|
||||
|
||||
```bash
|
||||
pip install -e .
|
||||
```
|
||||
|
||||
> **Note:** If your project requires a specific devx version, pin it in
|
||||
> `dependencies` (for example, `"devx==0.47.1"`) or use a version constraint
|
||||
> (for example, `"devx>=0.47.1,<0.48"`).
|
||||
|
||||
### Optional extras
|
||||
|
||||
devx ships optional dependency groups for different use cases:
|
||||
|
||||
```bash
|
||||
pip install "devx[ci,lint]" # CI runners and linting (pytest, ruff, pyright, bandit, build, twine)
|
||||
pip install "devx[molecule]" # Molecule testing for Ansible projects
|
||||
pip install "devx[dev]" # Full local development (ci + lint + build + twine)
|
||||
```
|
||||
|
||||
## Quick start
|
||||
|
||||
After installing devx, set the required environment variables (see
|
||||
[Configuration](#configuration)) and invoke modules via `python -m devx.*` or
|
||||
the `devx` CLI.
|
||||
|
||||
### CI/CD automation
|
||||
|
||||
CI/CD modules are invoked via `python -m devx.ci.*`. Each module is also
|
||||
available as a `devx ci <command>` subcommand.
|
||||
|
||||
```bash
|
||||
# Release automation (versioning, changelog, tagging)
|
||||
python -m devx.ci.release
|
||||
python -m devx.ci.release --dry-run # preview without changes
|
||||
python -m devx.ci.release --verify # check tag/version/changelog alignment
|
||||
|
||||
# Publish a release to the Gitea PyPI registry
|
||||
python -m devx.ci.publish v1.0.0 oblachno-oss/devx
|
||||
python -m devx.ci.publish v1.0.0 oblachno-oss/devx --skip-build # Gitea release only
|
||||
|
||||
# Automated PR review (posts inline comments and structured review)
|
||||
python -m devx.ci.pr_review 42 oblachno-oss/devx
|
||||
|
||||
# Auto-merge a PR (validates title, squash-merges)
|
||||
python -m devx.ci.auto_merge feature-branch "DEVX-12: Add feature" oblachno-oss/devx 42
|
||||
|
||||
# Classify changes (user-facing vs workflow-only)
|
||||
python -m devx.ci.classify_changes --base origin/master --head HEAD --github-output
|
||||
|
||||
# Sync documentation to Gitea wiki
|
||||
python -m devx.ci.sync_wiki --repo oblachno-oss/devx --strict
|
||||
|
||||
# Generate and push quality badges
|
||||
python -m devx.ci.push_badges
|
||||
python -m devx.ci.push_badges --retries 3 # retry on git push failures
|
||||
|
||||
# Check translation completeness
|
||||
python -m devx.ci.check_translations
|
||||
python -m devx.ci.check_translations --translations path/to/translations.json
|
||||
|
||||
# Documentation coverage check
|
||||
python -m devx.ci.doc_coverage --fail-on-missing
|
||||
|
||||
# Documentation lint (structure, links, headings, TODOs)
|
||||
python -m devx.ci.lint_docs --root .
|
||||
|
||||
# Validate a commit message
|
||||
python -m devx.ci.validate_commit_msg commit-msg.txt --branch master
|
||||
|
||||
# Detect whether the latest commit is a release commit
|
||||
python -m devx.ci.detect_release_commit
|
||||
|
||||
# Notify on CI failure (creates a Gitea issue)
|
||||
python -m devx.ci.notify_failure --repo oblachno-oss/devx --run-id 123 \
|
||||
--workflow ci --commit abc123 --auto-login
|
||||
|
||||
# Discover available Gitea Actions runners
|
||||
python -m devx.ci.discover_runners --owner oblachno-oss --repo devx --indices
|
||||
|
||||
# Distribute files across parallel runners (round-robin)
|
||||
python -m devx.ci.distribute_files --pattern "tests/integration/test_*.py" \
|
||||
--runner-index 1 --max-runners 3 --github-env
|
||||
|
||||
# Run pytest with cross-runner fail-fast
|
||||
python -m devx.ci.integration_guard -- test_a.py test_b.py
|
||||
```
|
||||
|
||||
### Developer tools
|
||||
|
||||
Developer tooling modules are invoked via `python -m devx.tools.*` or the
|
||||
`devx tools <command>` subcommand.
|
||||
|
||||
```bash
|
||||
# Set up a development environment (venv, deps, hooks, tea login)
|
||||
python -m devx.tools.setup --bin .venv/bin
|
||||
python -m devx.tools.setup --bin .venv/bin --extras "ci,lint" --no-pre-commit
|
||||
|
||||
# Install CI tools (actionlint, git-cliff, act_runner, tea)
|
||||
python -m devx.tools.install_tools
|
||||
python -m devx.tools.install_tools --tool git-cliff --tool tea
|
||||
python -m devx.tools.install_tools --list
|
||||
|
||||
# Install checkmake (Makefile linter)
|
||||
python -m devx.tools.install_checkmake
|
||||
|
||||
# Check unit test speed
|
||||
python -m devx.tools.check_test_speed --max-seconds 10
|
||||
python -m devx.tools.check_test_speed --max-seconds 4 --max-single-seconds 0.5
|
||||
|
||||
# Configure repository (branch protection, labels)
|
||||
python -m devx.tools.configure_repo --repo devx --owner oblachno-oss
|
||||
|
||||
# Generate badge SVG files locally
|
||||
python -m devx.tools.generate_badges --output-dir .badges/
|
||||
|
||||
# Generate a cliff.toml with the correct task ID prefix
|
||||
python -m devx.tools.generate_cliff_config --prefix GRM
|
||||
python -m devx.tools.generate_cliff_config --prefix GRM --force # overwrite existing
|
||||
```
|
||||
|
||||
### Molecule testing (optional)
|
||||
|
||||
For projects with Ansible roles, devx provides molecule testing helpers via
|
||||
`python -m devx.molecule.*` or `devx molecule <command>`.
|
||||
|
||||
```bash
|
||||
# Distribute molecule scenarios across parallel runners
|
||||
python -m devx.molecule.distribute_molecule --runner-index 1 --max-runners 3
|
||||
python -m devx.molecule.distribute_molecule --list # list all scenarios
|
||||
python -m devx.molecule.distribute_molecule --list-platforms # list platforms
|
||||
|
||||
# Run molecule tests with cross-runner fail-fast
|
||||
python -m devx.molecule.molecule_ci_guard pair1 pair2
|
||||
python -m devx.molecule.molecule_ci_guard --roles-root ansible/roles pair1 pair2
|
||||
|
||||
# Run all molecule scenarios locally (sequential)
|
||||
python -m devx.molecule.molecule_all
|
||||
python -m devx.molecule.molecule_all --bin .venv/bin
|
||||
|
||||
# Discover available Gitea Actions runners for molecule tests
|
||||
python -m devx.molecule.discover_runners --indices
|
||||
|
||||
# Ensure Docker is available for molecule tests in CI
|
||||
python -m devx.molecule.start_docker
|
||||
```
|
||||
|
||||
### OpenTofu helpers
|
||||
|
||||
devx provides reusable functions for extracting values from `tofu output`:
|
||||
|
||||
```python
|
||||
from devx.opentofu import get_tofu_output, get_tofu_vm_ip, get_tofu_vm_field
|
||||
|
||||
vms = get_tofu_output("customer_vms", cwd="tofu/environments/staging",
|
||||
env={"HCLOUD_TOKEN": token})
|
||||
ip = get_tofu_vm_ip("customer_vms", "oblachno", cwd="tofu/environments/staging",
|
||||
env={"HCLOUD_TOKEN": token})
|
||||
```
|
||||
|
||||
## CLI commands overview
|
||||
|
||||
devx provides a `devx` CLI command with three command groups:
|
||||
|
||||
```bash
|
||||
devx --help
|
||||
devx --version
|
||||
```
|
||||
|
||||
### `devx ci` — CI/CD automation
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `devx ci auto-merge` | Squash-merge a PR with task ID validation |
|
||||
| `devx ci check-translations` | Check translation files for gaps and dead keys |
|
||||
| `devx ci classify-changes` | Classify git changes as user-facing or workflow-only |
|
||||
| `devx ci detect-release-commit` | Detect whether the latest commit is a release commit |
|
||||
| `devx ci discover-runners` | Discover available Gitea Actions runners |
|
||||
| `devx ci distribute-files` | Distribute files across parallel runners (round-robin) |
|
||||
| `devx ci doc-coverage` | Check documentation coverage for CLI commands and modules |
|
||||
| `devx ci integration-guard` | Run pytest with cross-runner fail-fast |
|
||||
| `devx ci notify-failure` | Create a Gitea issue when a CI workflow fails |
|
||||
| `devx ci post-merge` | Update Vikunja task after a merge to master |
|
||||
| `devx ci pr-review` | Run automated PR review |
|
||||
| `devx ci publish` | Build package, publish to registry, create Gitea release |
|
||||
| `devx ci push-badges` | Generate badge SVG files and push to the badges branch |
|
||||
| `devx ci release` | Automated release: version, changelog, tag, push |
|
||||
| `devx ci sync-wiki` | Sync documentation from docs/ to the Gitea wiki |
|
||||
| `devx ci validate-commit-msg` | Validate commit messages for conventional format |
|
||||
|
||||
### `devx tools` — Developer tools
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `devx tools check-test-speed` | Run unit tests and enforce execution-time budgets |
|
||||
| `devx tools configure-repo` | Configure branch protection and labels via Gitea API |
|
||||
| `devx tools generate-badges` | Generate self-contained SVG badge files |
|
||||
| `devx tools generate-cliff-config` | Generate a cliff.toml with the correct task ID prefix |
|
||||
| `devx tools install-checkmake` | Install checkmake (Makefile linter) |
|
||||
| `devx tools install-tools` | Install actionlint, git-cliff, act_runner, tea |
|
||||
| `devx tools setup` | Project setup: install deps, hooks, tea login |
|
||||
|
||||
### `devx molecule` — Molecule testing (optional)
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `devx molecule all` | Run all molecule scenarios on all supported platforms |
|
||||
| `devx molecule discover-runners` | Discover available Gitea Actions runners |
|
||||
| `devx molecule distribute` | Distribute molecule test pairs across parallel runners |
|
||||
| `devx molecule guard` | Run molecule tests with CI failure polling |
|
||||
|
||||
See [CLI Commands](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/wiki/CLI-Commands)
|
||||
in the wiki for full command documentation with examples.
|
||||
|
||||
## Configuration
|
||||
|
||||
devx reads configuration from environment variables with `.env` file fallback.
|
||||
The config system loads `.env` automatically via `python-dotenv`.
|
||||
|
||||
### DEVX_ environment variables
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `DEVX_GITEA_API_URL` | `https://git.oblachno.oblachno.fyi/api/v1` | Gitea API base URL |
|
||||
| `DEVX_VIKUNJA_API_URL` | `https://work.oblachno.oblachno.fyi/api/v1` | Vikunja API base URL |
|
||||
| `DEVX_REPO_OWNER` | **(none — must be set)** | Repository owner for API calls |
|
||||
| `DEVX_REPO_NAME` | **(none — must be set)** | Repository name (or `owner/repo`) |
|
||||
| `DEVX_TASK_PREFIX` | `DEVX` | Task ID prefix (GRM, OBL-INFRA, etc.) |
|
||||
| `DEVX_VIKUNJA_PROJECT_ID` | `6` | Vikunja project ID |
|
||||
| `DEVX_LANG` | `en` | Language for i18n (en, bg, de, ru, zh, pl) |
|
||||
| `DEVX_TRANSLATIONS_PATH` | — | Path to a custom JSON translations file |
|
||||
| `DEVX_VERSION_FILE` | `src/devx/__init__.py` | Version source file (used by release) |
|
||||
| `DEVX_DOCS_DIR` | `docs` | Documentation directory (used by sync_wiki) |
|
||||
| `DEVX_STATUS_CHECKS` | `CI / quality (pull_request)` | Comma-separated status check contexts |
|
||||
| `DEVX_PYPI_REGISTRY_URL` | — | Gitea PyPI registry URL (used by publish) |
|
||||
| `CI_GITEA_TOKEN` | — | Gitea API token (see scopes below) |
|
||||
| `CI_GITEA_USERNAME` | — | Gitea username for registry authentication |
|
||||
| `VIKUNJA_TOKEN` | — | Vikunja API token |
|
||||
| `PYPI_TOKEN` | — | Standard PyPI token (takes precedence over Gitea registry) |
|
||||
|
||||
#### CI_GITEA_TOKEN scopes
|
||||
|
||||
The `CI_GITEA_TOKEN` is a single Gitea Personal Access Token used across all
|
||||
workflows. It requires these scopes:
|
||||
|
||||
| Scope | Purpose |
|
||||
|-------|---------|
|
||||
| `read:repository` | Read repos, PRs, issues, branches |
|
||||
| `write:repository` | Push commits, merge PRs, create tags/releases, create issues, set branch protection, push wiki |
|
||||
| `read:package` | Pull packages from Gitea PyPI registry, pull Docker images |
|
||||
| `write:package` | Publish packages to Gitea PyPI registry, push Docker images |
|
||||
| `read:organization` | Query org-level runners for molecule test distribution |
|
||||
|
||||
### Per-project overrides
|
||||
|
||||
Projects using devx can override the default API URLs and language by setting
|
||||
`DEVX_*` environment variables or entries in their `.env` file. Copy
|
||||
`.env.example` to `.env` and fill in your tokens:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
### Change classification
|
||||
|
||||
Projects configure which file paths are infrastructure (no release needed) vs
|
||||
user-facing (release needed) in `pyproject.toml`:
|
||||
|
||||
```toml
|
||||
[tool.devx.classify]
|
||||
# Merge with DEFAULT_INFRASTRUCTURE (CI workflows, tests, docs, config)
|
||||
# use_defaults = true # (default)
|
||||
|
||||
# Project-specific infrastructure paths (merged with defaults)
|
||||
infrastructure = []
|
||||
|
||||
# Files that would default to user-facing but are actually infrastructure
|
||||
infrastructure_overrides = [
|
||||
"src/myproject/__init__.py", # example only — only contains __version__
|
||||
]
|
||||
|
||||
# Safety override for broad infrastructure patterns
|
||||
user_facing_overrides = []
|
||||
|
||||
# Tag patterns for CI conditional execution (orthogonal to release impact)
|
||||
[tool.devx.classify.tags]
|
||||
# ansible = ["ansible/**"]
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
git clone https://git.oblachno.oblachno.fyi/oblachno-oss/devx.git
|
||||
cd devx
|
||||
make setup # Create venv, install deps, hooks, CI tools
|
||||
make lint-all # ruff + pyright + bandit + actionlint
|
||||
make pytest-cov # Unit tests with 100% coverage
|
||||
make test-unit # Unit tests without coverage
|
||||
make workflow-check # Static + dry-run validation of workflow YAML
|
||||
make clean # Remove caches, build artifacts, coverage data
|
||||
```
|
||||
|
||||
`make setup` automatically installs all development tools:
|
||||
- **Python deps** via `python -m devx.tools.setup` (pip install -e .[dev], pre-commit hooks)
|
||||
- **actionlint, git-cliff, act_runner, tea** via `python -m devx.tools.install_tools`
|
||||
- **tea CLI login** via `python -m devx.tools.setup` (configures `tea login` from `.env`)
|
||||
|
||||
### Make targets
|
||||
|
||||
| Target | Description |
|
||||
|--------|-------------|
|
||||
| `make setup` | Full local development setup (venv, deps, hooks, CI tools) |
|
||||
| `make setup-ci` | Lean setup for CI jobs (pytest + lint + runtime deps) |
|
||||
| `make setup-quality` | Setup for quality job (lint + test deps, actionlint) |
|
||||
| `make setup-release` | Setup for release jobs (git-cliff, tea, lint tools) |
|
||||
| `make install-tools` | Install actionlint, git-cliff, act_runner, tea |
|
||||
| `make install-hooks` | Install git hooks (pre-commit, pre-push) |
|
||||
| `make lint` | ruff check + ruff format check + pyright + bandit |
|
||||
| `make lint-ruff` | ruff check only |
|
||||
| `make lint-format` | ruff format check only |
|
||||
| `make typecheck` | pyright only |
|
||||
| `make lint-bandit` | bandit security scan only |
|
||||
| `make lint-all` | lint + workflow-lint (actionlint) |
|
||||
| `make lint-deps` | pip-audit dependency vulnerability scan |
|
||||
| `make test-unit` | Unit tests without coverage |
|
||||
| `make pytest-cov` | Unit tests with 100% coverage enforcement |
|
||||
| `make workflow-lint` | actionlint on `.gitea/workflows/*.yml` |
|
||||
| `make workflow-dryrun` | act_runner exec --dryrun on all workflows |
|
||||
| `make workflow-check` | workflow-lint + workflow-dryrun |
|
||||
| `make clean` | Remove caches, build artifacts, coverage data |
|
||||
|
||||
See [AGENTS.md](AGENTS.md) for full project conventions, PR workflow, and
|
||||
architecture details.
|
||||
|
||||
## Architecture overview
|
||||
|
||||
devx is a self-contained Python package under `src/devx/`. It never imports
|
||||
from scripts outside the package. All tools are invoked via
|
||||
`python -m devx.ci.*`, `python -m devx.tools.*`, or `python -m devx.molecule.*`.
|
||||
|
||||
```text
|
||||
src/devx/
|
||||
├── __init__.py # Version (single source of truth, read by setuptools)
|
||||
├── cli.py # Click-based CLI entry point (devx command)
|
||||
├── config.py # Configuration system (DEVX_ env vars, .env loading)
|
||||
├── api_clients.py # GiteaClient, VikunjaClient — HTTP API wrappers
|
||||
├── gitea_cli.py # TeaCLI — wrapper around tea CLI with JSON parsing
|
||||
├── i18n.py # Translation system (gettext-based, translations.json)
|
||||
├── exceptions.py # Custom exception types (DevxError, APIError)
|
||||
├── opentofu.py # OpenTofu output helpers
|
||||
├── translations.json # Translation strings (en, bg, de, ru, zh, pl)
|
||||
├── ci/ # CI/CD automation modules (run by workflows)
|
||||
├── tools/ # Developer tooling modules (run locally or by CI)
|
||||
└── molecule/ # Optional molecule testing helpers (for Ansible projects)
|
||||
```
|
||||
|
||||
### Design principles
|
||||
|
||||
- **Self-contained package** — `src/devx/` never imports from scripts outside the package
|
||||
- **Module-based invocation** — All tools invoked via `python -m devx.ci.*` or `python -m devx.tools.*`
|
||||
- **PYTHONPATH: src** — Workflows set `PYTHONPATH: src` (not `.:src` since there are no scripts at repo root)
|
||||
- **Config via env vars** — `DEVX_*` environment variables with `.env` file fallback
|
||||
- **100% test coverage** — enforced by `--cov-fail-under=100`
|
||||
- **i18n by default** — all user-facing strings wrapped in `_()` for translation
|
||||
|
||||
See [Architecture](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/wiki/Architecture)
|
||||
and [CI/CD Workflow](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/wiki/CI-CD-Workflow)
|
||||
in the wiki for detailed documentation.
|
||||
|
||||
## Links
|
||||
|
||||
- **Wiki**: [https://git.oblachno.oblachno.fyi/oblachno-oss/devx/wiki](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/wiki)
|
||||
- **Releases**: [https://git.oblachno.oblachno.fyi/oblachno-oss/devx/releases](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/releases)
|
||||
- **Actions**: [https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
||||
- **Source**: [https://git.oblachno.oblachno.fyi/oblachno-oss/devx](https://git.oblachno.oblachno.fyi/oblachno-oss/devx)
|
||||
- **GRM (origin project)**: [https://git.oblachno.oblachno.fyi/oblachno-oss/grm](https://git.oblachno.oblachno.fyi/oblachno-oss/grm)
|
||||
|
||||
## License
|
||||
|
||||
GPL-3.0 — see [LICENSE](LICENSE).
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/usr/bin/env fish
|
||||
set -l script_dir (dirname (status --current-filename))
|
||||
source "$script_dir/.venv/bin/activate.fish"
|
||||
@@ -1,2 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/.venv/bin/activate"
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/usr/bin/env zsh
|
||||
0="${ZERO:-${0:#$ZSH_ARGZERO}}"
|
||||
0="${${(M)0:#/*}:-$PWD/$0}"
|
||||
source "${0:A:h}/.venv/bin/activate"
|
||||
@@ -1,71 +0,0 @@
|
||||
# git-cliff configuration for devx
|
||||
# https://git-cliff.org/docs/configuration
|
||||
|
||||
[changelog]
|
||||
header = """
|
||||
# Changelog\n
|
||||
All notable changes to this project will be documented in this file.\n
|
||||
"""
|
||||
body = """
|
||||
{% if version %}\
|
||||
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
|
||||
{% else %}\
|
||||
## [unreleased]
|
||||
{% endif %}\
|
||||
{% for group, commits in commits | group_by(attribute="group") %}
|
||||
### {{ group | striptags | trim | upper_first }}
|
||||
{% for commit in commits %}
|
||||
- {% if commit.scope %}*({{ commit.scope }})* {% endif %}\
|
||||
{% if commit.breaking %}[**breaking**] {% endif %}\
|
||||
{{ commit.message | upper_first }}\
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
"""
|
||||
trim = true
|
||||
render_always = true
|
||||
|
||||
[git]
|
||||
conventional_commits = true
|
||||
filter_unconventional = true
|
||||
require_conventional = false
|
||||
split_commits = false
|
||||
protect_breaking_commits = false
|
||||
filter_commits = false
|
||||
fail_on_unmatched_commit = false
|
||||
use_branch_tags = false
|
||||
topo_order = false
|
||||
topo_order_commits = true
|
||||
sort_commits = "oldest"
|
||||
recurse_submodules = false
|
||||
|
||||
commit_preprocessors = [
|
||||
# Strip DEVX-N: task ID prefix from squash-merge commits so git-cliff sees conventional commits
|
||||
{ pattern = "^DEVX-\\d+:\\s+", replace = "" },
|
||||
]
|
||||
|
||||
commit_parsers = [
|
||||
{ message = "^feat", group = "<!-- 0 -->Features" },
|
||||
{ message = "^fix", group = "<!-- 1 -->Bug Fixes" },
|
||||
{ message = "^perf", group = "<!-- 4 -->Performance" },
|
||||
{ message = "^refactor", group = "<!-- 2 -->Refactor" },
|
||||
# Skip infrastructure-only commits — they don't affect users
|
||||
{ message = "^doc", skip = true },
|
||||
{ message = "^test", skip = true },
|
||||
{ message = "^style", skip = true },
|
||||
{ message = "^chore", skip = true },
|
||||
{ message = "^ci", skip = true },
|
||||
# Skip release commits — they are release artifacts, not features
|
||||
{ message = "^release:", skip = true },
|
||||
{ body = ".*security", group = "<!-- 8 -->Security" },
|
||||
{ message = "^revert", group = "<!-- 9 -->Revert" },
|
||||
# Skip anything that doesn't match above — safe default
|
||||
{ message = ".*", skip = true },
|
||||
]
|
||||
|
||||
[bump]
|
||||
features_always_bump_minor = true
|
||||
breaking_always_bump_major = false
|
||||
initial_tag = "0.1.0"
|
||||
# Refactor commits bump patch — structural changes to src/ or pyproject.toml
|
||||
# affect users even though no new feature was added.
|
||||
refactor_always_bump_patch = true
|
||||
@@ -0,0 +1,20 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="104" height="20" role="img"
|
||||
aria-label="coverage: 100%">
|
||||
<title>coverage: 100%</title>
|
||||
<linearGradient id="s" x2="0" y2="100%">
|
||||
<stop offset="0" stop-color="#fff" stop-opacity=".7"/>
|
||||
<stop offset=".1" stop-color="#bbb" stop-opacity=".1"/>
|
||||
<stop offset=".9" stop-color="#000" stop-opacity=".3"/>
|
||||
<stop offset="1" stop-color="#bbb" stop-opacity=".1"/>
|
||||
</linearGradient>
|
||||
<clipPath id="r"><rect width="104" height="20" rx="3" fill="#fff"/></clipPath>
|
||||
<g clip-path="url(#r)">
|
||||
<rect width="66" height="20" fill="#555"/>
|
||||
<rect x="66" width="38" height="20" fill="#4c1"/>
|
||||
<rect width="104" height="20" fill="url(#s)"/>
|
||||
</g>
|
||||
<g fill="#fff" text-anchor="middle" font-family="Verdana,DejaVu Sans,sans-serif" font-size="11">
|
||||
<text x="33" y="14">coverage</text>
|
||||
<text x="85" y="14">100%</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 894 B |
@@ -1,26 +0,0 @@
|
||||
# ci-base — lightweight image for CI jobs that only need devx core + tea.
|
||||
#
|
||||
# Used by: detect-type, detect-changes, validate-commit-msg, pr-review,
|
||||
# auto-merge, sync-wiki, vikunja, configure-repo, discover-runners,
|
||||
# molecule-report, discover-integration-runners
|
||||
#
|
||||
# Jobs using this image: setup is instant (ln -s /opt/venv .venv)
|
||||
# No pip install needed — devx and all deps are pre-installed.
|
||||
|
||||
FROM gitea/runner-images:ubuntu-latest
|
||||
|
||||
# Create a virtual environment with all deps pre-installed
|
||||
RUN python3 -m venv /opt/venv
|
||||
ENV PATH="/opt/venv/bin:/root/.local/bin:$PATH"
|
||||
|
||||
# Install devx from local source (build context = devx repo root)
|
||||
COPY . /tmp/devx
|
||||
RUN pip install --no-cache-dir --upgrade pip setuptools wheel \
|
||||
&& pip install --no-cache-dir /tmp/devx[ci] \
|
||||
&& rm -rf /tmp/devx
|
||||
|
||||
# Install tea CLI (for Gitea API operations in CI)
|
||||
RUN python3 -m devx.tools.install_tools --tool tea
|
||||
|
||||
# Workspace directory (actions/checkout mounts repo here)
|
||||
WORKDIR /workspace
|
||||
@@ -1,24 +0,0 @@
|
||||
# ci-full — heaviest image, includes everything for release, molecule, deploy.
|
||||
#
|
||||
# Used by: release, publish, release-dry-run, molecule-tests,
|
||||
# provision-infra, deploy-observability, provision-zitadel,
|
||||
# deploy-customer, integration-tests
|
||||
#
|
||||
# Layers on top of ci-quality: adds release tools, molecule, deploy deps,
|
||||
# git-cliff, and OpenTofu.
|
||||
|
||||
FROM git.oblachno.oblachno.fyi/oblachno-oss/runner-images/ci-quality:latest
|
||||
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
|
||||
# Install rsync (required by molecule_docker for file sync between host and test containers)
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends rsync \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install devx[release,molecule,deploy] from local source
|
||||
COPY . /tmp/devx
|
||||
RUN pip install --no-cache-dir /tmp/devx[release,molecule,deploy] \
|
||||
&& rm -rf /tmp/devx
|
||||
|
||||
# Install git-cliff (changelog generator for release job) and OpenTofu (for infra deploy jobs)
|
||||
RUN python3 -m devx.tools.install_tools --tool git-cliff --tool tofu
|
||||
@@ -1,17 +0,0 @@
|
||||
# ci-quality — image for lint, type-checking, badge generation.
|
||||
#
|
||||
# Used by: quality (lint-all + pytest-cov + checks), badges (generate_badges
|
||||
# runs ruff/pyright/bandit to produce quality badge)
|
||||
#
|
||||
# Layers on top of ci-base: adds lint tools + actionlint + checkmake.
|
||||
|
||||
FROM git.oblachno.oblachno.fyi/oblachno-oss/runner-images/ci-base:latest
|
||||
|
||||
# Install devx[lint] from local source (adds ruff, pyright, bandit, etc.)
|
||||
COPY . /tmp/devx
|
||||
RUN pip install --no-cache-dir /tmp/devx[lint] \
|
||||
&& rm -rf /tmp/devx
|
||||
|
||||
# Install CI/CD binary tools
|
||||
RUN python3 -m devx.tools.install_tools --tool actionlint --tool vale --tool hadolint \
|
||||
&& python3 -m devx.tools.install_checkmake
|
||||
@@ -1,20 +0,0 @@
|
||||
[
|
||||
{
|
||||
"name": "oblachno-oss/runner-images/ci-base",
|
||||
"dockerfile": "docker/ci-base/Dockerfile",
|
||||
"context": ".",
|
||||
"tags": ["latest"]
|
||||
},
|
||||
{
|
||||
"name": "oblachno-oss/runner-images/ci-quality",
|
||||
"dockerfile": "docker/ci-quality/Dockerfile",
|
||||
"context": ".",
|
||||
"tags": ["latest"]
|
||||
},
|
||||
{
|
||||
"name": "oblachno-oss/runner-images/ci-full",
|
||||
"dockerfile": "docker/ci-full/Dockerfile",
|
||||
"context": ".",
|
||||
"tags": ["latest"]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,20 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="76" height="20" role="img"
|
||||
aria-label="docs: 100%">
|
||||
<title>docs: 100%</title>
|
||||
<linearGradient id="s" x2="0" y2="100%">
|
||||
<stop offset="0" stop-color="#fff" stop-opacity=".7"/>
|
||||
<stop offset=".1" stop-color="#bbb" stop-opacity=".1"/>
|
||||
<stop offset=".9" stop-color="#000" stop-opacity=".3"/>
|
||||
<stop offset="1" stop-color="#bbb" stop-opacity=".1"/>
|
||||
</linearGradient>
|
||||
<clipPath id="r"><rect width="76" height="20" rx="3" fill="#fff"/></clipPath>
|
||||
<g clip-path="url(#r)">
|
||||
<rect width="38" height="20" fill="#555"/>
|
||||
<rect x="38" width="38" height="20" fill="#4c1"/>
|
||||
<rect width="76" height="20" fill="url(#s)"/>
|
||||
</g>
|
||||
<g fill="#fff" text-anchor="middle" font-family="Verdana,DejaVu Sans,sans-serif" font-size="11">
|
||||
<text x="19" y="14">docs</text>
|
||||
<text x="57" y="14">100%</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 879 B |
@@ -1,173 +0,0 @@
|
||||
# ADR-0001: Test Isolation Pytest Plugin and Shift-Left Quality Gates
|
||||
|
||||
Date: 2026-07-13
|
||||
Status: Accepted
|
||||
|
||||
## Context
|
||||
|
||||
Unit tests in devx were slow (10s+) and getting slower. Investigation
|
||||
revealed two root causes:
|
||||
|
||||
1. **Unpatched subprocess calls** — test functions calling
|
||||
`subprocess.run`, `update_doc_versions`, or `run_cmd` without
|
||||
`@patch` decorators, causing real subprocess execution during tests.
|
||||
2. **Excessive iterations** — statistical tests with 1000-iteration
|
||||
loops that should use property-based testing or smaller samples.
|
||||
|
||||
These issues were discovered manually by profiling with
|
||||
`pytest --durations=0`. There was no automated check to prevent
|
||||
regressions — new tests could introduce the same patterns and slow
|
||||
down the suite again.
|
||||
|
||||
Additionally, translation completeness checks
|
||||
(`devx.ci.check_translations`) only ran in CI, not locally. Developers
|
||||
discovered missing translations at CI time, wasting round-trips.
|
||||
|
||||
## Decision
|
||||
|
||||
### 1. Test Isolation as a Pytest Plugin (pytest11 entry point)
|
||||
|
||||
Implement the test isolation check as a **pytest plugin** registered
|
||||
via the `pytest11` entry point in `pyproject.toml`:
|
||||
|
||||
```toml
|
||||
[project.entry-points.pytest11]
|
||||
devx_test_isolation = "devx.tools.check_test_isolation"
|
||||
```
|
||||
|
||||
This makes the check **transparent and always-on** — every `pytest`
|
||||
invocation in any repo with devx installed automatically runs the
|
||||
static analysis. No extra Makefile target or CI step needed.
|
||||
|
||||
The plugin (`devx.tools.check_test_isolation`) statically analyzes
|
||||
test files during `pytest_collection_finish` and **fails the test run**
|
||||
on any hard violation:
|
||||
|
||||
- **unpatched-subprocess**: `subprocess.run/call/Popen/check_call/check_output`
|
||||
called in a test function without `@patch` or `with patch(...)`
|
||||
- **unpatched-sleep**: `time.sleep` called without `@patch`
|
||||
- **unpatched-helper**: known subprocess-spawning helpers
|
||||
(`update_doc_versions`, `run_cmd`, `run_tests`) called without
|
||||
`@patch` (and without patching their internal dependencies)
|
||||
- **excessive-iterations**: `for _ in range(N)` where N > 100
|
||||
- **heavy-module-import**: `httpx`, `ansible`, etc. imported at module
|
||||
level in test files, slowing collection for all tests
|
||||
- **reload-without-cleanup**: `importlib.reload()` called an odd number
|
||||
of times, leaving module state modified
|
||||
|
||||
Transitive-subprocess findings (via call-graph analysis) are reported
|
||||
as **advisories** — the static analysis can't predict early exits or
|
||||
runtime branch conditions, so the runtime audit is authoritative.
|
||||
|
||||
The plugin also wraps `subprocess.run` at runtime to catch real
|
||||
subprocess calls that leak through transitive call paths (for example
|
||||
`CliRunner.invoke(main)` → `main()` → `update_doc_versions()` →
|
||||
`subprocess.run()`). If a test spawns a real subprocess without
|
||||
`@patch`, the test fails.
|
||||
|
||||
A standalone CLI (`python -m devx.tools.check_test_isolation`) is also
|
||||
provided for CI gates and pre-commit hooks where pytest isn't run.
|
||||
|
||||
### 2. Shift-Left Quality Gates in `make lint`
|
||||
|
||||
Add `devx-check-translations` and `devx-check-test-isolation` to the
|
||||
`devx-lint` target in `devx.mak`. This means `make lint` now runs:
|
||||
|
||||
- ruff check + format
|
||||
- pyright typecheck
|
||||
- bandit security scan
|
||||
- **translation completeness** (missing keys, dead keys, missing languages)
|
||||
- **test isolation** (unpatched subprocess, time.sleep, excessive loops)
|
||||
|
||||
These were previously CI-only checks. Running them in `make lint`
|
||||
catches issues at the developer's machine, not in CI.
|
||||
|
||||
### 3. Pre-commit Hook Coverage
|
||||
|
||||
Update the pre-commit hook to run all three shift-left checks:
|
||||
test speed, translation completeness, and test isolation. This
|
||||
catches issues even earlier than `make lint` — before the commit
|
||||
is even created.
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
|
||||
- **Automatic enforcement**: The pytest plugin runs on every `pytest`
|
||||
invocation across devx, grm, and infra — no per-repo configuration
|
||||
needed. New tests with unpatched subprocess calls fail immediately.
|
||||
- **Shift-left**: Translation gaps and test isolation violations are
|
||||
caught locally (pre-commit / `make lint`) instead of in CI.
|
||||
- **Fast feedback**: Static analysis adds <0.1s to test runs; runtime
|
||||
subprocess audit adds negligible overhead (wrapper checks a
|
||||
thread-local flag).
|
||||
- **Transitive detection**: The call-graph BFS traces
|
||||
`CliRunner.invoke(main)` → `main()` → `update_doc_versions()` →
|
||||
`subprocess.run()`, catching indirect subprocess leaks that direct
|
||||
analysis misses. The runtime audit provides authoritative enforcement.
|
||||
- **No false positives**: The call graph correctly recognizes that
|
||||
patching `run_cmd` makes `run_tests` (which calls `run_cmd`) safe,
|
||||
and class methods are excluded to avoid false positives when classes
|
||||
like `TeaCLI` are patched.
|
||||
|
||||
### Negative
|
||||
|
||||
- **Coverage instrumentation gap**: The pytest plugin module is loaded
|
||||
before coverage starts, so module-level code (decorators, class
|
||||
definitions) appears uncovered. Mitigated by `-p no:devx_test_isolation`
|
||||
in devx's own `pyproject.toml` `addopts` and `# pragma: no cover` on
|
||||
plugin hook functions.
|
||||
- **Static analysis limitations**: The call-graph BFS can't predict
|
||||
runtime branch conditions or early exits — a test that patches
|
||||
`shutil.which` to return `None` may skip the subprocess path
|
||||
entirely, but the static analysis still reports it. Transitive
|
||||
findings are advisories (exit 0) for this reason; the runtime audit
|
||||
is authoritative.
|
||||
- **Translation burden**: Every new `_()` call in source requires
|
||||
adding 6 language translations. This is by design (all supported
|
||||
languages must be complete) but adds friction for quick prototypes.
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Pytest Plugin Discovery
|
||||
|
||||
The `pytest11` entry point is the standard mechanism for pytest
|
||||
plugins. When devx is installed (via pip), pytest auto-discovers
|
||||
the plugin. No `conftest.py` or `pytest_plugins` declaration needed
|
||||
in consumer repos.
|
||||
|
||||
### Disabling the Plugin
|
||||
|
||||
- `--no-test-isolation` flag: disables static analysis and runtime
|
||||
subprocess audit for a single run
|
||||
- `-p no:devx_test_isolation` in `addopts`: disables for a repo
|
||||
(used in devx's own `pyproject.toml` for coverage reasons)
|
||||
|
||||
### Call-Graph Analysis
|
||||
|
||||
The `CallGraph` class parses all `.py` files under `src/` and builds
|
||||
a map of function → called functions. When a test calls
|
||||
`CliRunner.invoke(target)`, a BFS traces the call graph from `target`
|
||||
to find all reachable functions. Class methods are excluded from the
|
||||
call graph to avoid false positives when classes are patched (for example
|
||||
`@patch("...TeaCLI")` mocks all methods). The BFS respects `@patch`
|
||||
decorators — if a function is patched, traversal stops at that node.
|
||||
|
||||
### Runtime Subprocess Audit
|
||||
|
||||
The `_SubprocessAudit` singleton wraps `subprocess.run`, `call`,
|
||||
`check_call`, `check_output`, and `Popen` with thread-local
|
||||
recording wrappers. During each non-integration test, the wrapper
|
||||
records calls; if any are recorded (that is the test didn't `@patch`
|
||||
subprocess), the test fails. The wrappers check a thread-local flag,
|
||||
so inactive audits have zero overhead beyond the flag check.
|
||||
|
||||
### Known Subprocess Helpers
|
||||
|
||||
The `KNOWN_SUBPROCESS_HELPERS` dict maps function names to
|
||||
descriptions. `HELPER_INTERNAL_CALLS` maps each helper to the
|
||||
function names it internally calls, enabling transitive safety
|
||||
checks for direct calls in test functions. The call-graph BFS
|
||||
handles transitive detection for `CliRunner.invoke` targets. Both
|
||||
are defined in `check_test_isolation.py` and can be extended as
|
||||
new subprocess-spawning helpers are added to devx.
|
||||
@@ -1,175 +0,0 @@
|
||||
# devx — Reusable Development & CI/CD Tools
|
||||
|
||||
A Python package providing reusable development and CI/CD automation tools for
|
||||
oblachno-oss projects. devx consolidates release management, PR automation,
|
||||
wiki sync, badge generation, translation checks, documentation coverage,
|
||||
parallel test distribution, and more into a single installable package.
|
||||
|
||||
It was extracted from the [GRM](https://git.oblachno.oblachno.fyi/oblachno-oss/grm)
|
||||
project to be reusable across all oblachno-oss repositories.
|
||||
|
||||
> An open-source project from **Oblachno** (облачно means *cloudy* in Bulgarian).
|
||||
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/src/branch/master/LICENSE)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/wiki)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/releases)
|
||||
[](https://www.python.org/downloads/)
|
||||
|
||||
## Overview
|
||||
|
||||
devx provides a complete, opinionated CI/CD pipeline for any project hosted on
|
||||
a Gitea instance with Gitea Actions. Install the package, declare configuration
|
||||
via environment variables and `pyproject.toml`, and inherit:
|
||||
|
||||
- **Automated releases** — git-cliff-driven semver versioning, changelog
|
||||
generation, tagging, and publishing to a Gitea PyPI registry.
|
||||
- **PR automation** — squash-merge with task ID validation, automated PR
|
||||
review with inline comments, and conventional commit enforcement.
|
||||
- **Smart change classification** — user-facing vs workflow-only change
|
||||
detection so infrastructure-only changes skip releases.
|
||||
- **Documentation sync** — push `docs/` markdown to the Gitea wiki with
|
||||
integrity verification.
|
||||
- **Quality badges** — self-contained SVG badges for coverage, tests, docs,
|
||||
quality, version, and Python version.
|
||||
- **Translation checks** — validate i18n keys against source code, detect
|
||||
dead keys and missing languages.
|
||||
- **Parallel test distribution** — split test files or molecule scenarios
|
||||
across CI runners with cross-runner fail-fast.
|
||||
- **Developer tools** — environment setup, CI tool installation, test speed
|
||||
enforcement, repository configuration.
|
||||
- **i18n** — built-in translations for English, Bulgarian, German, Russian,
|
||||
Chinese, and Polish; projects can extend with their own keys.
|
||||
|
||||
## Installation
|
||||
|
||||
devx is published to the Gitea PyPI registry at
|
||||
`https://git.oblachno.oblachno.fyi/api/packages/oblachno-oss/pypi/simple`.
|
||||
The registry is publicly readable — no authentication required to install.
|
||||
|
||||
### Quick install (one-off)
|
||||
|
||||
```bash
|
||||
pip install devx --index-url https://git.oblachno.oblachno.fyi/api/packages/oblachno-oss/pypi/simple
|
||||
```
|
||||
|
||||
### Persistent configuration (recommended)
|
||||
|
||||
Add the registry to `~/.pip/pip.conf`:
|
||||
|
||||
```ini
|
||||
[global]
|
||||
extra-index-url = https://git.oblachno.oblachno.fyi/api/packages/oblachno-oss/pypi/simple
|
||||
```
|
||||
|
||||
Then `pip install devx` works without specifying `--index-url`.
|
||||
|
||||
### As a dependency in another project
|
||||
|
||||
Add devx to your `pyproject.toml` dependencies and configure the registry:
|
||||
|
||||
```toml
|
||||
[project]
|
||||
dependencies = [
|
||||
"devx>=0.47.1",
|
||||
]
|
||||
|
||||
[tool.pip]
|
||||
extra-index-url = "https://git.oblachno.oblachno.fyi/api/packages/oblachno-oss/pypi/simple"
|
||||
```
|
||||
|
||||
Pin a specific version if needed: `"devx==0.47.1"` or `"devx>=0.47.1,<0.48"`.
|
||||
|
||||
### Optional extras
|
||||
|
||||
```bash
|
||||
pip install "devx[ci,lint]" # CI runners and linting (pytest, ruff, pyright, bandit, build, twine)
|
||||
pip install "devx[molecule]" # Molecule testing for Ansible projects
|
||||
pip install "devx[dev]" # Full local development (ci + lint + build + twine)
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
devx is a self-contained Python package under `src/devx/`:
|
||||
|
||||
- **Core modules** — `config.py`, `exceptions.py`, `i18n.py`, `api_clients.py`,
|
||||
`gitea_cli.py`, `cli.py`, `opentofu.py`
|
||||
- **CI automation** (`devx.ci`) — release, publish, auto_merge, pr_review,
|
||||
classify_changes, sync_wiki, push_badges, check_translations, doc_coverage,
|
||||
validate_commit_msg, detect_release_commit, notify_failure, post_merge,
|
||||
discover_runners, distribute_files, integration_guard
|
||||
- **Dev tools** (`devx.tools`) — setup, install_tools, check_test_speed,
|
||||
configure_repo, generate_badges, generate_cliff_config, install_checkmake
|
||||
- **Molecule tools** (`devx.molecule`) — Optional, for projects with Ansible
|
||||
roles: distribute_molecule, molecule_ci_guard, molecule_all, discover_runners,
|
||||
start_docker, platforms
|
||||
|
||||
See [Architecture](Architecture) for the full package structure, module
|
||||
descriptions, design principles, and data flow diagrams.
|
||||
|
||||
## CI/CD pipeline
|
||||
|
||||
devx uses Gitea Actions with three workflows:
|
||||
|
||||
- **CI** (`ci.yml`) — runs on pull requests: quality checks, change detection,
|
||||
release dry-run, automated PR review, and auto-merge.
|
||||
- **Post-merge** (`post-merge.yml`) — runs on every push to master: release
|
||||
versioning, wiki sync, badge generation, Vikunja task updates, and repo
|
||||
configuration.
|
||||
- **Publish** (`publish.yml`) — runs on tag pushes: builds the package,
|
||||
publishes to the Gitea PyPI registry, and creates a Gitea release.
|
||||
|
||||
See [CI/CD Workflow](CI-CD-Workflow) for the full pipeline documentation,
|
||||
including the post-merge job graph, release process, badge generation, and
|
||||
wiki sync details.
|
||||
|
||||
## CLI commands
|
||||
|
||||
devx provides a `devx` CLI with three command groups:
|
||||
|
||||
- `devx ci <command>` — CI/CD automation (17 commands)
|
||||
- `devx tools <command>` — Developer tools (9 commands)
|
||||
- `devx molecule <command>` — Molecule testing (4 commands, optional)
|
||||
|
||||
See [CLI Commands](CLI-Commands) for full command documentation with examples.
|
||||
|
||||
## Configuration
|
||||
|
||||
devx reads configuration from `DEVX_*` environment variables with `.env` file
|
||||
fallback. Key variables:
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `DEVX_GITEA_API_URL` | `https://git.oblachno.oblachno.fyi/api/v1` | Gitea API base URL |
|
||||
| `DEVX_VIKUNJA_API_URL` | `https://work.oblachno.oblachno.fyi/api/v1` | Vikunja API base URL |
|
||||
| `DEVX_REPO_OWNER` | **(must be set)** | Repository owner |
|
||||
| `DEVX_REPO_NAME` | **(must be set)** | Repository name |
|
||||
| `DEVX_TASK_PREFIX` | `DEVX` | Task ID prefix (GRM, OBL-INFRA, etc.) |
|
||||
| `DEVX_LANG` | `en` | Language for i18n (en, bg, de, ru, zh, pl) |
|
||||
| `CI_GITEA_TOKEN` | — | Gitea API token |
|
||||
| `VIKUNJA_TOKEN` | — | Vikunja API token |
|
||||
|
||||
See [AGENTS.md](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/src/branch/master/AGENTS.md)
|
||||
for the full configuration reference, PR workflow, and project conventions.
|
||||
|
||||
## Wiki pages
|
||||
|
||||
- [Home](Home) — This page
|
||||
- [Getting Started](Getting-Started) — Installation, configuration, and quick start guide
|
||||
- [CLI Commands](CLI-Commands) — Full CLI command documentation with examples
|
||||
- [Architecture](Architecture) — Package structure, module descriptions, design principles
|
||||
- [CI/CD Workflow](CI-CD-Workflow) — Pipeline documentation, workflows, and CI scripts
|
||||
|
||||
## Links
|
||||
|
||||
- **Source**: [https://git.oblachno.oblachno.fyi/oblachno-oss/devx](https://git.oblachno.oblachno.fyi/oblachno-oss/devx)
|
||||
- **Releases**: [https://git.oblachno.oblachno.fyi/oblachno-oss/devx/releases](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/releases)
|
||||
- **Actions**: [https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
||||
- **GRM (origin project)**: [https://git.oblachno.oblachno.fyi/oblachno-oss/grm](https://git.oblachno.oblachno.fyi/oblachno-oss/grm)
|
||||
|
||||
## License
|
||||
|
||||
GPL-3.0
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"index.md": "Home",
|
||||
"user/getting-started.md": "Getting-Started",
|
||||
"user/cli-commands.md": "CLI-Commands",
|
||||
"tech/architecture.md": "Architecture",
|
||||
"tech/ci-cd-workflow.md": "CI-CD-Workflow"
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
# Retrospective: Self-Approval Fallback and CI Consolidation
|
||||
|
||||
## Date
|
||||
2026-07-12
|
||||
|
||||
## Context
|
||||
The devx package (reusable CI/CD tools) underwent two significant
|
||||
changes during this period: workflow consolidation (DEVX-126) and the
|
||||
self-approval fallback fix (DEVX-127). The self-approval bug was the
|
||||
last remaining blocker for end-to-end automated CI/CD across all
|
||||
oblachno repos. This retrospective covers devx v0.40.0 through v0.40.1.
|
||||
|
||||
## Scope
|
||||
|
||||
PRs: DEVX-125 (double-prefix detection), DEVX-126 (CI consolidation),
|
||||
DEVX-127 (self-approval fallback). ~16 commits including release/badge
|
||||
churn.
|
||||
|
||||
## Timeline of Key Failures
|
||||
|
||||
| Run | Issue | Fix Commit |
|
||||
|--------|----------------------------------------------|------------|
|
||||
| infra #2562 | Self-approval rejected (403) | `d035b62` |
|
||||
| devx CI | Auto-merge review body too short (< 20 chars) | `fc613d4` |
|
||||
| devx CI | test_setup flaky due to PIP_BREAK_SYSTEM_PACKAGES | `043f259` |
|
||||
| devx CI | Missing translations for self-approval messages | `0d8c7f5` |
|
||||
|
||||
## What Served Us Well
|
||||
|
||||
- **Test-driven fix for pr_review.py.** The self-approval fallback was
|
||||
implemented with full test coverage before being deployed. Tests
|
||||
covered both the fallback-available and fallback-unavailable paths,
|
||||
ensuring the code was correct before it hit CI.
|
||||
- **i18n enforcement caught missing translations.** The translation
|
||||
completeness check flagged the new self-approval error messages that
|
||||
were added without corresponding translation entries. This prevented
|
||||
untranslated strings from reaching production.
|
||||
- **Consolidated CI workflow.** DEVX-126 merged 7 separate CI jobs into
|
||||
a single `validate` job, reducing runner overhead and eliminating
|
||||
inter-job dependency issues. The consolidation pattern was then
|
||||
applied to grm and infra.
|
||||
- **Conventional commit enforcement.** The `validate_commit_msg` check
|
||||
caught a double-prefix in the Vikunja task title (DEVX-125), which
|
||||
would have caused auto-merge validation failures downstream.
|
||||
|
||||
## What Slowed Us Down
|
||||
|
||||
### 1. Self-Approval Bug Not Caught Earlier (1 infra CI failure)
|
||||
|
||||
The `pr_review.py` script used the `REVIEWER_GITEA_API_TOKEN` for
|
||||
APPROVE events. When the token belonged to the PR author, Gitea
|
||||
rejected the self-approval with 403. This was only discovered when the
|
||||
infra PR CI run #2562 failed — the devx CI had passed because devx PRs
|
||||
were reviewed by a different user.
|
||||
|
||||
**Root cause:** No test simulated the self-approval rejection scenario.
|
||||
The tests mocked the Gitea API to always return 200 for review
|
||||
submissions.
|
||||
|
||||
**Time wasted:** ~2 hours (cross-repo investigation + fix + test).
|
||||
|
||||
**Fix:** Added fallback to `CI_GITEA_API_TOKEN` when the reviewer token
|
||||
is rejected with self-approval. The fallback is transparent — the
|
||||
script logs a warning and retries with the CI token.
|
||||
|
||||
**Lesson:** Test API interactions against all HTTP error codes the
|
||||
external system can return, not only the happy path. For Gitea, this
|
||||
includes 403 (self-approval), 409 (conflict), and 422 (validation).
|
||||
|
||||
### 2. Auto-Merge Review Body Length Check (1 CI failure)
|
||||
|
||||
The auto-merge validation requires APPROVE review bodies to be > 20
|
||||
chars (to prevent perfunctory approvals). The automated review posted
|
||||
by `pr_review.py` had a body of exactly 17 chars, failing the check.
|
||||
|
||||
**Root cause:** The review body was a generic "Automated review passed"
|
||||
message that was too short. The length check was added to prevent
|
||||
rubber-stamping by human reviewers, but it also affected automated
|
||||
reviews.
|
||||
|
||||
**Time wasted:** ~1 CI run.
|
||||
|
||||
**Fix:** Expanded the automated review body to include a summary of
|
||||
checked categories, ensuring it exceeds 20 chars.
|
||||
|
||||
**Lesson:** Automated reviews need substantive bodies too. The length
|
||||
check doesn't distinguish between human and automated reviewers.
|
||||
|
||||
### 3. test_setup Flaky Due to Environment Variable (1 CI failure)
|
||||
|
||||
`test_setup.py` failed intermittently because `PIP_BREAK_SYSTEM_PACKAGES`
|
||||
was set in the CI environment but not in local tests. The test didn't
|
||||
isolate itself from the environment variable.
|
||||
|
||||
**Root cause:** The test assumed a clean environment but CI sets
|
||||
`PIP_BREAK_SYSTEM_PACKAGES=1` globally. The test's behavior changed
|
||||
based on this env var.
|
||||
|
||||
**Time wasted:** ~1 CI run.
|
||||
|
||||
**Fix:** Isolated the test from the env var using `monkeypatch.delenv`.
|
||||
|
||||
**Lesson:** Tests that interact with environment-dependent behavior
|
||||
should explicitly set or unset the relevant env vars, not assume
|
||||
defaults.
|
||||
|
||||
### 4. Missing Translations for New Messages (1 CI failure)
|
||||
|
||||
The self-approval fallback added new user-facing messages (warning
|
||||
about token fallback) but didn't add translations for all supported
|
||||
languages. The translation completeness check caught this.
|
||||
|
||||
**Root cause:** New `click.echo()` calls were added with `_()` wrappers
|
||||
but the translation JSON wasn't updated.
|
||||
|
||||
**Time wasted:** ~1 CI run.
|
||||
|
||||
**Fix:** Added translations for all new messages in `translations.json`.
|
||||
|
||||
**Lesson:** When adding new `_()` wrapped strings, update
|
||||
`translations.json` in the same commit. The i18n check is strict —
|
||||
100% completeness is required.
|
||||
|
||||
## Improvements Implemented
|
||||
|
||||
### 1. Self-Approval Fallback (HIGH impact)
|
||||
|
||||
`pr_review.py` now falls back to `CI_GITEA_API_TOKEN` for APPROVE
|
||||
events when the reviewer token is rejected as self-approval. This
|
||||
unblocked auto-merge across all three repos.
|
||||
|
||||
### 2. Double-Prefix Detection (MEDIUM impact)
|
||||
|
||||
`check_auto_merge_ready.py` now detects and rejects Vikunja task titles
|
||||
that include the identifier prefix (for example, "DEVX-127: Fix...").
|
||||
The validator adds the prefix automatically, so a double prefix would
|
||||
fail validation.
|
||||
|
||||
### 3. CI Workflow Consolidation (MEDIUM impact)
|
||||
|
||||
Merged 7 separate CI jobs into a single `validate` job, reducing runner
|
||||
overhead by ~5 min per CI run and eliminating inter-job dependency
|
||||
issues.
|
||||
|
||||
## Action Items for Future Sessions
|
||||
|
||||
1. **Test API interactions against all relevant HTTP error codes.**
|
||||
Don't only test the happy path. For Gitea: 200, 201, 204, 403, 404,
|
||||
409, 422.
|
||||
2. **Update translations in the same commit as new `_()` strings.**
|
||||
The i18n check will fail otherwise.
|
||||
3. **Isolate tests from environment variables.** Use `monkeypatch.setenv`
|
||||
or `monkeypatch.delenv` for any env var the test's behavior depends on.
|
||||
4. **Ensure automated review bodies are substantive (> 20 chars).**
|
||||
Include a summary of checked categories.
|
||||
5. **When adding fallback logic, test both the fallback-available and
|
||||
fallback-unavailable paths.** Both must be covered for 100% branch
|
||||
coverage.
|
||||
@@ -1,600 +0,0 @@
|
||||
# Architecture
|
||||
|
||||
devx is a reusable Python package providing development and CI/CD tools for
|
||||
oblachno-oss projects. It is self-contained under `src/devx/` and never imports
|
||||
from scripts outside the package.
|
||||
|
||||
## Package structure
|
||||
|
||||
```text
|
||||
src/devx/
|
||||
├── __init__.py # Version (single source of truth, read by setuptools)
|
||||
├── cli.py # Click-based CLI entry point (devx command)
|
||||
├── config.py # Configuration system (DEVX_ env vars, .env loading)
|
||||
├── api_clients.py # GiteaClient, VikunjaClient — HTTP API wrappers
|
||||
├── gitea_cli.py # TeaCLI — wrapper around tea CLI with JSON parsing
|
||||
├── i18n.py # Translation system (JSON-based, translations.json)
|
||||
├── exceptions.py # Custom exception types (DevxError, APIError)
|
||||
├── opentofu.py # OpenTofu output helpers
|
||||
├── translations.json # Translation strings (en, bg, de, ru, zh, pl)
|
||||
├── ci/ # CI/CD automation modules (run by workflows)
|
||||
│ ├── __init__.py
|
||||
│ ├── _shared.py # Shared utilities (get_latest_tag)
|
||||
│ ├── release.py # Automated versioning, tagging, changelog
|
||||
│ ├── publish.py # Build and publish to Gitea PyPI registry
|
||||
│ ├── auto_merge.py # Squash-merge PRs with task ID validation
|
||||
│ ├── classify_changes.py # User-facing vs workflow-only change detection
|
||||
│ ├── detect_release_commit.py # Detect release commits on master
|
||||
│ ├── validate_commit_msg.py # Conventional commit validation
|
||||
│ ├── pr_review.py # Automated PR review
|
||||
│ ├── post_merge.py # Vikunja task updates after merge
|
||||
│ ├── sync_wiki.py # Sync documentation to Gitea wiki
|
||||
│ ├── push_badges.py # Generate and push quality badges
|
||||
│ ├── notify_failure.py # Create Gitea issues on CI failures
|
||||
│ ├── distribute_files.py # Distribute files across parallel runners
|
||||
│ ├── integration_guard.py # Run pytest with cross-runner fail-fast
|
||||
│ ├── discover_runners.py # Dynamic Gitea runner discovery
|
||||
│ ├── check_translations.py # Translation completeness check
|
||||
│ └── doc_coverage.py # Documentation coverage check
|
||||
├── tools/ # Developer tooling modules (run locally or by CI)
|
||||
│ ├── __init__.py
|
||||
│ ├── setup.py # Environment setup (venv, deps, hooks, tea login)
|
||||
│ ├── install_tools.py # Install actionlint, git-cliff, act_runner, tea
|
||||
│ ├── check_test_speed.py # Measure unit test execution time
|
||||
│ ├── check_test_isolation.py # Pytest plugin: detect un-hermetic test patterns
|
||||
│ ├── configure_repo.py # Branch protection and label setup
|
||||
│ ├── generate_badges.py # Badge SVG generation
|
||||
│ ├── generate_cliff_config.py # Generate cliff.toml with correct prefix
|
||||
│ └── install_checkmake.py # Install checkmake (Makefile linter)
|
||||
└── molecule/ # Optional molecule testing helpers (Ansible projects)
|
||||
├── __init__.py
|
||||
├── discover_runners.py # Dynamic Gitea runner discovery
|
||||
├── distribute_molecule.py # Distribute scenarios across runners
|
||||
├── molecule_ci_guard.py # Run molecule with cross-runner fail-fast
|
||||
├── molecule_all.py # Run all molecule scenarios locally
|
||||
├── start_docker.py # Ensure Docker is available for molecule
|
||||
└── platforms.py # Supported molecule platforms
|
||||
```
|
||||
|
||||
## Core modules
|
||||
|
||||
### `__init__.py`
|
||||
|
||||
Contains only `__version__`, the single source of truth for the package
|
||||
version. Read by setuptools via `dynamic = ["version"]` in `pyproject.toml`.
|
||||
Updated automatically by `devx.ci.release` during the release process. Treated
|
||||
as infrastructure (not user-facing) by the change classifier since it is a
|
||||
release artifact, not user code.
|
||||
|
||||
### `cli.py`
|
||||
|
||||
Click-based CLI entry point. Provides three command groups: `devx ci`,
|
||||
`devx tools`, and `devx molecule`. Each subcommand delegates to the
|
||||
corresponding module via `_run_module()`, which imports the module, sets
|
||||
`sys.argv`, and calls its `main()` function. This design keeps all logic in
|
||||
the modules themselves — `cli.py` is purely a router.
|
||||
|
||||
The CLI is registered as a console script via `pyproject.toml`:
|
||||
```toml
|
||||
[project.scripts]
|
||||
devx = "devx.cli:cli"
|
||||
```
|
||||
|
||||
### `config.py`
|
||||
|
||||
Shared configuration constants for all devx modules. All defaults can be
|
||||
overridden via environment variables with the `DEVX_` prefix. Provides:
|
||||
|
||||
- `GITEA_API_URL` / `VIKUNJA_API_URL` — API endpoints
|
||||
- `REPO_OWNER` — repository owner (must be set per-project)
|
||||
- `TASK_PREFIX` / `TASK_ID_RE` — task ID prefix and regex (for example, `DEVX-N`)
|
||||
- `VIKUNJA_PROJECT_ID` — Vikunja project for task tracking
|
||||
- `DEFAULT_TIMEOUT`, `DEFAULT_PER_PAGE` — HTTP client defaults
|
||||
- `MAX_RETRIES`, `RETRY_BACKOFF_BASE`, `RETRY_STATUS_CODES` — retry config
|
||||
- `CONVENTIONAL_RE` — conventional commit format regex
|
||||
|
||||
### `exceptions.py`
|
||||
|
||||
Custom exception hierarchy:
|
||||
|
||||
- `DevxError` — base exception for all devx errors
|
||||
- `APIError(DevxError)` — raised when a REST API call returns an HTTP error.
|
||||
Carries `status` (HTTP status code) and `message` (error message).
|
||||
|
||||
### `i18n.py`
|
||||
|
||||
Simple i18n system using a JSON translations file (`translations.json`).
|
||||
Supports six languages: `en`, `bg`, `de`, `pl`, `ru`, `zh`. The `_()` function
|
||||
wraps user-facing strings for translation.
|
||||
|
||||
Projects can extend translations by setting `DEVX_TRANSLATIONS_PATH` to a
|
||||
custom JSON file. Keys from the project's file are merged on top of devx's
|
||||
built-in translations, allowing projects to override or add keys without
|
||||
modifying the package.
|
||||
|
||||
### `api_clients.py`
|
||||
|
||||
Reusable HTTP API clients with connection pooling and retry logic. Both
|
||||
clients retry on transient errors (429, 5xx, connection errors) with
|
||||
exponential backoff (2s, 4s, 8s).
|
||||
|
||||
**`GiteaClient`** — Gitea REST API wrapper:
|
||||
- Branch protection (get, create, update)
|
||||
- Labels (list, create, add to issues)
|
||||
- Issues (create, list)
|
||||
- Pull requests (get commits, merge, create review)
|
||||
- Releases (list, create idempotent)
|
||||
- Actions (list runs, list jobs, get job logs)
|
||||
- Actions variables (get, set idempotent)
|
||||
- Wiki pages (list, fetch, create, update, delete)
|
||||
|
||||
**`VikunjaClient`** — Vikunja REST API wrapper:
|
||||
- Tasks (list project tasks, get, update, mark done)
|
||||
- Comments (create)
|
||||
|
||||
### `gitea_cli.py`
|
||||
|
||||
Thin Python wrapper around the `tea` Gitea CLI tool. Parses JSON output for
|
||||
structured data. Used by CI scripts for Gitea API operations that tea handles
|
||||
well, avoiding hand-rolled HTTP requests.
|
||||
|
||||
**`TeaCLI`** operations:
|
||||
- `create_issue()` — Create issues with labels
|
||||
- `list_labels()` / `create_label()` / `add_label()` — Label management
|
||||
- `create_pr()` / `merge_pr()` / `review_pr()` — Pull request operations
|
||||
- `create_release()` / `list_releases()` — Release management
|
||||
- `list_branches()` — Branch listing
|
||||
|
||||
Operations NOT supported via tea (still use `GiteaClient`):
|
||||
- Wiki page management
|
||||
- Commit status checks
|
||||
- Runner discovery
|
||||
- PR file/commit listing (tea has limited support)
|
||||
- Branch protection with detailed config
|
||||
|
||||
### `opentofu.py`
|
||||
|
||||
OpenTofu output helpers for CI/CD deployment scripts. Provides reusable
|
||||
functions for extracting values from `tofu output` in a structured way,
|
||||
eliminating duplicated `subprocess.run` boilerplate:
|
||||
|
||||
- `get_tofu_output(output_name, cwd, env)` — Run `tofu output -json` and return parsed JSON
|
||||
- `get_tofu_vm_ip(output_name, vm_name, cwd, env)` — Extract a VM's IP address
|
||||
- `get_tofu_vm_field(output_name, vm_name, field, cwd, env)` — Extract a VM field
|
||||
|
||||
## CI/CD modules (`devx.ci`)
|
||||
|
||||
Modules in this package are run by Gitea Actions workflows. They may import
|
||||
from `devx.api_clients`, `devx.config`, `devx.gitea_cli`, and `devx.i18n`.
|
||||
|
||||
### `release.py`
|
||||
|
||||
Automated release using git-cliff. Calculates the next semver version from
|
||||
conventional commits since the last tag, updates `__version__` in
|
||||
`__init__.py` and `CHANGELOG.md`, runs lint and tests to verify the release
|
||||
is healthy, commits with `release: vX.Y.Z [skip ci]`, creates an annotated
|
||||
tag, and pushes both to master.
|
||||
|
||||
Idempotent: if there are no new conventional commits since the last tag, it
|
||||
exits without doing anything. If the tag already exists, it skips tag creation
|
||||
and only pushes. Includes a `--verify` mode that checks tag/version/changelog
|
||||
alignment without making changes.
|
||||
|
||||
### `publish.py`
|
||||
|
||||
Builds the Python package with `python -m build`, publishes to a Gitea PyPI
|
||||
registry (or standard PyPI if `PYPI_TOKEN` is set), and creates a Gitea
|
||||
release with git-cliff-generated notes. Supports `--skip-build` for non-Python
|
||||
repos that only need a Gitea release.
|
||||
|
||||
### `auto_merge.py`
|
||||
|
||||
Auto-merges a PR when all CI checks pass. Reads the task ID from the branch
|
||||
name, validates the PR title format against
|
||||
the Vikunja task title, extracts the conventional commit message from PR
|
||||
commits, and squash-merges with title `{PREFIX}-N <conventional commit>`.
|
||||
|
||||
If the head branch is behind master (HTTP 405), it automatically pulls master,
|
||||
rebases, force-pushes, and retries the merge.
|
||||
|
||||
### `classify_changes.py`
|
||||
|
||||
Classifies git changes between two refs as user-facing or workflow-only. Uses
|
||||
a layered rule system configured in `pyproject.toml` under
|
||||
`[tool.devx.classify]`:
|
||||
|
||||
1. **User-facing overrides** (highest priority — safety override)
|
||||
2. **Infrastructure overrides** (explicit per-file)
|
||||
3. **Infrastructure patterns** (DEFAULT_INFRASTRUCTURE + project-specific)
|
||||
4. **Default**: user-facing (safe default — any unknown file triggers release)
|
||||
|
||||
Also supports custom tags (orthogonal to release impact) for CI conditional
|
||||
execution (for example, `ansible` tag to trigger molecule tests).
|
||||
|
||||
### `pr_review.py`
|
||||
|
||||
Automated PR review. Fetches the PR diff via the Gitea API and runs a series
|
||||
of checks, posting a structured review with `COMMENT` (no issues) or
|
||||
`REQUEST_CHANGES` (issues found):
|
||||
|
||||
- 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)
|
||||
|
||||
### `sync_wiki.py`
|
||||
|
||||
Syncs documentation from `docs/` to the Gitea wiki via the API. Reads
|
||||
`docs/mapping.json` to map file paths to wiki page titles, then creates or
|
||||
updates pages. Supports `--dry-run`, `--verify` (check content), and
|
||||
`--strict` (full integrity check: page count, missing pages, stale pages,
|
||||
content match).
|
||||
|
||||
### `push_badges.py`
|
||||
|
||||
Generates SVG badge files using `devx.tools.generate_badges`, pushes them to
|
||||
an orphan `badges` branch, and updates `README.md` and `docs/index.md` on
|
||||
master with cache-busting `raw/commit/<sha>/badge.svg` URLs (Gitea caches
|
||||
`raw/branch/` URLs for 6 hours). Fetches latest master before generating
|
||||
badges so the version badge reflects the current state. Supports `--retries`
|
||||
for retrying on git push failures.
|
||||
|
||||
### `notify_failure.py`
|
||||
|
||||
Creates a Gitea issue when a CI workflow fails. Uses the `tea` CLI for issue
|
||||
creation with failure labels. Supports `--auto-login` to configure the tea
|
||||
CLI login profile from `CI_GITEA_TOKEN` and `DEVX_GITEA_API_URL` before creating
|
||||
the issue.
|
||||
|
||||
### `post_merge.py`
|
||||
|
||||
Updates the Vikunja task after a merge to master. Extracts the task ID from
|
||||
the commit message, marks the task as done, and posts a comment with the
|
||||
merge SHA.
|
||||
|
||||
### `validate_commit_msg.py`
|
||||
|
||||
Validates commit messages. On feature branches: conventional commits only
|
||||
(no `{PREFIX}-N` prefix). On master: must have `{PREFIX}-N` prefix from
|
||||
auto-merge, followed by a conventional commit message.
|
||||
|
||||
### `detect_release_commit.py`
|
||||
|
||||
Detects whether the latest git commit is a release commit
|
||||
(`release: vX.Y.Z [skip ci]`). Writes `is-release=true` or `is-release=false`
|
||||
to `$GITHUB_OUTPUT` for use in CI workflow conditionals.
|
||||
|
||||
### `check_translations.py`
|
||||
|
||||
Validates translation files against the Python source code. Checks for
|
||||
missing keys (used in code but not in translations), dead keys (defined but
|
||||
not used), and missing languages (a key exists but is missing one of the five
|
||||
supported languages). Supports checking additional translation sets via
|
||||
`--translations`.
|
||||
|
||||
### `doc_coverage.py`
|
||||
|
||||
Checks documentation coverage for CLI commands and major modules. Parses
|
||||
Click commands from `cli.py` and verifies each has documentation in
|
||||
`docs/user/cli-commands.md`. Checks that core modules are documented in
|
||||
`architecture.md` and CI scripts in `ci-cd-workflow.md`. Supports
|
||||
`--fail-on-missing` to enforce 100% coverage.
|
||||
|
||||
### `discover_runners.py`
|
||||
|
||||
Discovers available Gitea Actions runners at three levels: repository,
|
||||
organization, and instance (admin). Falls back to the `MOLECULE_RUNNERS` repo
|
||||
variable or `DEFAULT_MAX_RUNNERS` (3). Outputs runner count or a JSON index
|
||||
array for use as a dynamic matrix in Gitea Actions.
|
||||
|
||||
### `distribute_files.py`
|
||||
|
||||
Distributes files matching a glob pattern across N parallel runners
|
||||
(round-robin). Writes the assigned file list for the current runner to
|
||||
`$GITHUB_ENV`. Used for splitting test suites across CI runners.
|
||||
|
||||
### `integration_guard.py`
|
||||
|
||||
Runs pytest with the same cross-runner failure detection mechanism used by
|
||||
`molecule_ci_guard`. If any other integration-tests matrix runner reports
|
||||
failure, the current pytest subprocess is killed and this runner exits early.
|
||||
|
||||
## Developer tools (`devx.tools`)
|
||||
|
||||
Modules in this package are run locally or by CI setup jobs. They may import
|
||||
from `devx.api_clients`, `devx.config`, and `devx.gitea_cli`.
|
||||
|
||||
### `setup.py`
|
||||
|
||||
Project setup: installs Python dependencies (editable mode with extras),
|
||||
Ansible Galaxy collections (if `ansible/requirements.yml` exists in the target repo), pre-commit
|
||||
hooks (pre-commit, commit-msg, pre-push), and configures the `tea` CLI login
|
||||
profile from `.env`. Supports `--extras` to specify dependency groups,
|
||||
`--no-pre-commit` to skip hook installation, and `--no-tea-login` to skip tea
|
||||
configuration.
|
||||
|
||||
### `install_tools.py`
|
||||
|
||||
Installs CI/CD development tools that are not Python packages: actionlint,
|
||||
git-cliff, act_runner, and tea. Each tool is installed to `~/.local/bin` if
|
||||
not already on PATH. Idempotent: skips tools that are already available.
|
||||
Supports `--tool` to install specific tools and `--list` to show status.
|
||||
|
||||
### `check_test_speed.py`
|
||||
|
||||
Runs unit tests and enforces execution-time budgets. Two quality gates:
|
||||
total suite time must not exceed `--max-seconds` (default: 10s), and no
|
||||
individual test may exceed `--max-single-seconds` (default: 0.5s, 0 to
|
||||
disable). Runs `make test-unit` with `PYTEST_ADDOPTS=--durations=0`.
|
||||
|
||||
### `check_test_isolation.py`
|
||||
|
||||
Pytest plugin (auto-discovered via `pytest11` entry point) that
|
||||
statically analyzes test files for un-hermetic patterns causing slow
|
||||
or flaky tests: unpatched `subprocess.run`/`time.sleep` calls, known
|
||||
subprocess-spawning helpers called without `@patch`, and excessive
|
||||
loop iterations (>100). Also available as a standalone CLI for CI
|
||||
gates and pre-commit hooks. See ADR-0001 for design rationale.
|
||||
|
||||
### `configure_repo.py`
|
||||
|
||||
Configures repository branch protection and labels via the Gitea REST API.
|
||||
Sets up master branch protection (required status checks, block on rejected
|
||||
reviews, block on outdated branch) and creates standard labels. Status check
|
||||
contexts are read from `DEVX_STATUS_CHECKS` or default to
|
||||
`CI / validate (pull_request)`.
|
||||
|
||||
### `generate_badges.py`
|
||||
|
||||
Generates self-contained SVG badge files from project metrics. Runs
|
||||
pytest-cov, doc-coverage, lint checks, and version extraction, then writes
|
||||
SVG files that can be served as static files from the Gitea raw file API.
|
||||
Badges generated: coverage, tests, docs, quality, version, python.
|
||||
|
||||
### `generate_cliff_config.py`
|
||||
|
||||
Generates a `cliff.toml` configuration file with the correct task ID prefix
|
||||
preprocessor. Eliminates the need to manually duplicate and maintain
|
||||
`cliff.toml` across repos that use devx. Supports `--prefix` to set the task
|
||||
ID prefix and `--force` to overwrite an existing file.
|
||||
|
||||
### `install_checkmake.py`
|
||||
|
||||
Installs checkmake (Makefile linter) if not already present. Tries
|
||||
`go install` first if Go is available, otherwise downloads the latest
|
||||
pre-built Linux binary from the official GitHub releases.
|
||||
|
||||
## Molecule modules (`devx.molecule`)
|
||||
|
||||
Optional modules for projects with Ansible roles. Requires the `molecule`
|
||||
extra (`pip install devx[molecule]`).
|
||||
|
||||
### `distribute_molecule.py`
|
||||
|
||||
Distributes molecule (scenario, platform) pairs across N parallel runners.
|
||||
Discovers scenarios under `ansible/roles/*/molecule/` and crosses them with
|
||||
the supported OS platform matrix. Supports `--roles-root` for multi-role
|
||||
repositories, `--list` to list scenarios, and `--list-platforms` to list
|
||||
platforms.
|
||||
|
||||
### `molecule_ci_guard.py`
|
||||
|
||||
Runs molecule tests sequentially while polling the Gitea API for other runner
|
||||
failures. If any other molecule matrix runner reports failure, the current
|
||||
molecule subprocess is killed and this runner exits early. Supports both
|
||||
single-role (4-part) and multi-role (5-part) pair encoding.
|
||||
|
||||
### `molecule_all.py`
|
||||
|
||||
Runs all molecule scenarios on all supported OS platforms sequentially.
|
||||
Intended for local development; CI uses the parallel matrix instead.
|
||||
|
||||
### `molecule/discover_runners.py`
|
||||
|
||||
Discovers available Gitea Actions runners for molecule tests. Same logic as
|
||||
`devx.ci.discover_runners` but intended for molecule-specific workflows.
|
||||
|
||||
### `start_docker.py`
|
||||
|
||||
Ensures Docker is available for molecule tests in CI. Verifies Docker is
|
||||
accessible and sets `DOCKER_HOST` explicitly. If the host socket is not
|
||||
available, tries the rootless socket, then starts a local `dockerd` with the
|
||||
vfs storage driver (requires privileged container).
|
||||
|
||||
### `platforms.py`
|
||||
|
||||
Single source of truth for the supported OS platform matrix. Each entry maps
|
||||
a short name to (image, command). Uses the project's pre-built
|
||||
molecule-test-base image with `sleep infinity` (not systemd) to avoid cgroup
|
||||
v2 failures. Supports loading custom platforms from a JSON file.
|
||||
|
||||
## Design principles
|
||||
|
||||
- **Self-contained package** — `src/devx/` never imports from scripts outside
|
||||
the package. This allows devx to be installed and used as a dependency
|
||||
without requiring a specific repo layout in the consumer.
|
||||
- **Module-based invocation** — All tools invoked via `python -m devx.ci.*`,
|
||||
`python -m devx.tools.*`, or `python -m devx.molecule.*`. The `devx` CLI is
|
||||
a thin router that delegates to module `main()` functions.
|
||||
- **PYTHONPATH: src** — Workflows set `PYTHONPATH: src` (not `.:src` since
|
||||
there are no scripts at repo root). The `src` directory is the sole import
|
||||
root.
|
||||
- **Config via env vars** — `DEVX_*` environment variables with `.env` file
|
||||
fallback. Projects override defaults via environment or `.env`, never by
|
||||
editing package code.
|
||||
- **100% test coverage** — enforced by `--cov-fail-under=100` in pytest.
|
||||
- **i18n by default** — all user-facing strings wrapped in `_()` for
|
||||
translation. Five languages supported out of the box.
|
||||
- **Safe-by-default classification** — any file that doesn't match an
|
||||
infrastructure pattern defaults to user-facing, triggering a release. This
|
||||
prevents new file types from accidentally skipping releases.
|
||||
- **Secrets via environment** — secrets are passed via environment variables,
|
||||
never on the command line.
|
||||
|
||||
## Import rules
|
||||
|
||||
1. **`src/devx/` is self-contained** — the package never imports from outside `src/`
|
||||
2. **CI modules** (`devx.ci.*`) may import from `devx.api_clients`,
|
||||
`devx.config`, `devx.gitea_cli`, `devx.i18n`
|
||||
3. **Tool modules** (`devx.tools.*`) may import from `devx.api_clients`,
|
||||
`devx.config`, `devx.gitea_cli`
|
||||
4. **Cross-module imports** within `devx.ci.*` or `devx.tools.*` are allowed
|
||||
but must be documented (for example, `release.py` imports from
|
||||
`classify_changes.py`)
|
||||
|
||||
## Data flow
|
||||
|
||||
### PR lifecycle
|
||||
|
||||
```text
|
||||
Developer creates Vikunja task (DEVX-N)
|
||||
│
|
||||
▼
|
||||
Developer creates branch (DEVX-N-short-description)
|
||||
│
|
||||
▼
|
||||
Developer commits (conventional commits, no DEVX-N prefix)
|
||||
│
|
||||
▼
|
||||
Developer pushes and creates PR (title: "DEVX-N: <vikunja task title>")
|
||||
│
|
||||
▼
|
||||
CI workflow (ci.yml) triggers:
|
||||
│
|
||||
├── validate (single job: quality + detect-changes +
|
||||
│ release-dry-run + pr-review + pre-merge validation)
|
||||
│ ├── quality steps (lint, tests, coverage, test speed, doc coverage,
|
||||
│ │ translation check, dependency scan, workflow dry-run)
|
||||
│ ├── detect-changes (classify_changes.py → user-facing or workflow-only)
|
||||
│ │ └── if user-facing → release-dry-run (release.py --dry-run)
|
||||
│ ├── pre-merge validation (check_auto_merge_ready.py)
|
||||
│ └── pr-review (pr_review.py → posts COMMENT or REQUEST_CHANGES)
|
||||
│
|
||||
└── auto-merge (auto_merge.py)
|
||||
├── validate PR title format
|
||||
├── validate PR title matches Vikunja task title
|
||||
├── extract conventional commit message from PR commits
|
||||
├── squash-merge with "DEVX-N <conventional commit>" title
|
||||
└── push to master
|
||||
│
|
||||
▼
|
||||
Post-merge workflow triggers (see below)
|
||||
```
|
||||
|
||||
### Post-merge flow
|
||||
|
||||
```text
|
||||
Push to master (squash-merge commit: "DEVX-N <conventional commit>")
|
||||
│
|
||||
▼
|
||||
Post-merge workflow (post-merge.yml) triggers:
|
||||
│
|
||||
├── detect-and-configure (single job)
|
||||
│ ├── configure-repo (configure_repo.py)
|
||||
│ ├── detect-type (detect_release_commit.py)
|
||||
│ │ └── is-release? → skip all steps except badges
|
||||
│ └── validate-commit-msg (validate_commit_msg.py --branch master)
|
||||
│
|
||||
└── release-and-maintain (needs detect-and-configure)
|
||||
├── release (release.py) [skip if release commit or workflow-only]
|
||||
│ ├── classify_changes.py → skip if workflow-only
|
||||
│ ├── git-cliff → calculate next version
|
||||
│ ├── update __version__ in __init__.py
|
||||
│ ├── update CHANGELOG.md
|
||||
│ ├── run make lint-ruff && make pytest-cov
|
||||
│ ├── commit "release: vX.Y.Z [skip ci]"
|
||||
│ ├── create annotated tag vX.Y.Z
|
||||
│ └── push commit + tag to master
|
||||
│ │
|
||||
│ ▼
|
||||
│ publish (publish.py) [if release created a tag]
|
||||
│ ├── build package (python -m build)
|
||||
│ ├── publish to Gitea PyPI registry (twine upload)
|
||||
│ │ OR publish to standard PyPI (if PYPI_TOKEN set)
|
||||
│ │ OR skip publish (if --skip-build)
|
||||
│ └── create Gitea release with git-cliff notes
|
||||
│
|
||||
├── sync-wiki (sync_wiki.py --strict) [skip if automated]
|
||||
│ └── sync docs/ to Gitea wiki with integrity check
|
||||
│
|
||||
├── vikunja (post_merge.py) [skip if automated]
|
||||
│ ├── extract task ID from commit message
|
||||
│ ├── mark Vikunja task as done
|
||||
│ └── post comment with merge SHA
|
||||
│
|
||||
└── badges (push_badges.py) [ALWAYS runs, even on release commits]
|
||||
├── fetch latest master
|
||||
├── generate_badges.py → SVG files
|
||||
├── push to orphan badges branch
|
||||
└── update README.md + docs/index.md with cache-busting URLs
|
||||
```
|
||||
|
||||
### Publish flow
|
||||
|
||||
```text
|
||||
Within release-and-maintain job (after release step creates a tag):
|
||||
│
|
||||
├── install build, twine, git-cliff, tea
|
||||
├── configure tea login
|
||||
├── checkout release tag
|
||||
│
|
||||
└── publish (publish.py)
|
||||
├── build package (python -m build)
|
||||
├── publish to Gitea PyPI registry (twine upload)
|
||||
│ OR publish to standard PyPI (if PYPI_TOKEN set)
|
||||
│ OR skip publish (if --skip-build)
|
||||
└── create Gitea release with git-cliff notes
|
||||
```
|
||||
|
||||
### Badge generation flow
|
||||
|
||||
```text
|
||||
push_badges.py:
|
||||
│
|
||||
├── fetch_latest_master() → git fetch + reset --hard origin/master
|
||||
│
|
||||
├── generate_badges() → devx.tools.generate_badges
|
||||
│ ├── run pytest-cov → parse coverage %
|
||||
│ ├── run pytest → parse test count
|
||||
│ ├── run doc_coverage → parse doc coverage %
|
||||
│ ├── run lint → quality status
|
||||
│ ├── read __version__ from __init__.py
|
||||
│ └── write SVG files to .badges/
|
||||
│
|
||||
├── push_to_badges_branch()
|
||||
│ ├── git checkout --orphan badges
|
||||
│ ├── git rm -rf .
|
||||
│ ├── copy SVG files to root
|
||||
│ ├── git commit "Update badges [skip ci]"
|
||||
│ ├── git push origin badges --force
|
||||
│ └── return commit SHA
|
||||
│
|
||||
└── update_readme_with_badge_sha()
|
||||
├── git checkout master
|
||||
├── replace raw/branch/badges/ URLs with raw/commit/<sha>/ URLs
|
||||
├── git commit "chore: update badge URLs [skip ci]"
|
||||
└── git push origin master
|
||||
```
|
||||
|
||||
## tea CLI integration
|
||||
|
||||
The `tea` Gitea CLI tool is used for Gitea API interactions where tea provides
|
||||
reliable, official support. It is installed by
|
||||
`python -m devx.tools.install_tools` and configured by
|
||||
`python -m devx.tools.setup` (login profile from `.env` `CI_GITEA_TOKEN`).
|
||||
|
||||
`devx.gitea_cli.TeaCLI` wraps tea with JSON output parsing. Operations that
|
||||
tea does not support (wiki management, commit status, runner discovery,
|
||||
detailed branch protection) fall back to `GiteaClient` (direct HTTP).
|
||||
|
||||
## Version source
|
||||
|
||||
The version source is `__version__` in `src/devx/__init__.py`, read by
|
||||
setuptools via `dynamic = ["version"]` in `pyproject.toml`. The release
|
||||
script updates this file, commits it, and tags the commit. This ensures the
|
||||
package version, git tag, and changelog always stay aligned.
|
||||
@@ -1,581 +0,0 @@
|
||||
# CI/CD Workflow
|
||||
|
||||
devx uses Gitea Actions for CI/CD automation. Two workflows implement a
|
||||
complete pipeline: pull request validation and post-merge release
|
||||
automation (including publishing).
|
||||
|
||||
## Workflow overview
|
||||
|
||||
```text
|
||||
PR opened/synchronized ──► CI (ci.yml)
|
||||
│ ├── validate (quality + detect-changes +
|
||||
│ │ release-dry-run + pr-review +
|
||||
│ │ pre-merge validation)
|
||||
│ └── auto-merge ──► squash-merge to master
|
||||
│ │
|
||||
▼ ▼
|
||||
Push to master ──► Post-merge (post-merge.yml)
|
||||
├── detect-and-configure (detect-type +
|
||||
│ validate-commit-msg +
|
||||
│ configure-repo)
|
||||
└── release-and-maintain
|
||||
├── release ──► tag vX.Y.Z
|
||||
├── publish ──► Gitea PyPI registry + Gitea release
|
||||
├── sync-wiki
|
||||
├── vikunja
|
||||
└── badges (always runs)
|
||||
```
|
||||
|
||||
## CI workflow (`ci.yml`)
|
||||
|
||||
Runs on pull requests (opened and synchronize) and manual dispatch.
|
||||
|
||||
### Jobs
|
||||
|
||||
#### `validate`
|
||||
|
||||
The single validation job. Consolidates the former `quality`,
|
||||
`detect-changes`, `release-dry-run`, `pr-review`, and `pre-merge-check`
|
||||
jobs into one job to save checkout+setup overhead. Runs on every PR.
|
||||
|
||||
**Quality steps**
|
||||
|
||||
The main quality gate:
|
||||
|
||||
1. **Lint all** — ruff check, ruff format check, pyright, bandit, actionlint
|
||||
(via `make lint-all`)
|
||||
2. **Unit tests with 100% coverage** — `make pytest-cov`
|
||||
3. **Check unit test speed** — `python -m devx.tools.check_test_speed
|
||||
--max-seconds 4 --max-single-seconds 0.5`
|
||||
4. **Documentation coverage check** — `python -m devx.ci.doc_coverage
|
||||
--fail-on-missing`
|
||||
5. **Translation completeness check** — `python -m devx.ci.check_translations`
|
||||
6. **Dependency security scan** — `pip-audit --desc --skip-editable`
|
||||
(best-effort, non-blocking)
|
||||
7. **Workflow dry-run validation** — `make workflow-dryrun` via act_runner
|
||||
(best-effort, skipped if act_runner is not installed)
|
||||
|
||||
**`detect-changes` step**
|
||||
|
||||
Classifies changes between `origin/master` and the PR head as user-facing or
|
||||
workflow-only using `python -m devx.ci.classify_changes --github-output`.
|
||||
Writes `user-facing-changed=true|false` to the job output for use by
|
||||
downstream steps.
|
||||
|
||||
**`release-dry-run` step**
|
||||
|
||||
Only runs if the detect-changes step detected user-facing changes. Runs
|
||||
`python -m devx.ci.release --dry-run` to validate that the release script
|
||||
can calculate the next version and generate the changelog without making
|
||||
changes. Non-blocking (uses `|| true`).
|
||||
|
||||
**`pr-review` step**
|
||||
|
||||
Runs on every pull request. Executes `python -m devx.ci.pr_review` with the
|
||||
PR number and repository. Fetches the PR diff via the Gitea API and runs
|
||||
automated checks, posting a structured review:
|
||||
|
||||
- `COMMENT` — no issues found
|
||||
- `REQUEST_CHANGES` — issues found that must be addressed
|
||||
|
||||
Checks performed:
|
||||
1. Architecture compliance — no subprocess in CLI, no hardcoded URLs
|
||||
2. Best practices — no `print()`, no bare `except`, no `TODO`/`FIXME`,
|
||||
no functions > 50 lines
|
||||
3. Security — no hardcoded secrets, no `shell=True`, no `eval`/`exec`
|
||||
4. i18n — no raw strings in `click.echo()` without `_()` wrapper
|
||||
5. Resource management — no `open()` without `with`, no `Popen()` without
|
||||
cleanup
|
||||
6. Documentation — source changes must include doc updates
|
||||
7. Test coverage — source changes must include test updates
|
||||
8. Commit conventions — conventional commit format on PR commits
|
||||
|
||||
**Pre-merge validation step**
|
||||
|
||||
Runs on every pull request. Executes
|
||||
`python -m devx.ci.check_auto_merge_ready` with the branch name, PR title,
|
||||
repository, and PR number. Validates auto-merge preconditions before the
|
||||
`auto-merge` job runs:
|
||||
|
||||
1. **Branch name** — must contain a valid task ID (for example,
|
||||
`DEVX-12-fix-foo` → `DEVX-12`)
|
||||
2. **PR title format** — must be `{PREFIX}-N: <vikunja task title>`
|
||||
3. **Vikunja task** — must exist and the title must match the PR title
|
||||
4. **Branch state** — must not be behind master
|
||||
|
||||
#### `auto-merge`
|
||||
|
||||
Depends on `validate`. The final job in the CI workflow. Runs
|
||||
`python -m devx.ci.auto_merge` with the branch name, PR title, repository,
|
||||
and PR number:
|
||||
|
||||
1. **Read task ID** from branch name (for example, `DEVX-12-fix-foo` → `DEVX-12`)
|
||||
2. **Validate PR title format** — must be `{PREFIX}-N: <vikunja task title>`
|
||||
3. **Validate PR title matches Vikunja task** — fetches the Vikunja task and
|
||||
compares the title
|
||||
4. **Extract conventional commit message** from PR commits (newest matching
|
||||
conventional format)
|
||||
5. **Squash-merge** with title `{PREFIX}-N <conventional commit message>`
|
||||
6. If the head branch is behind master (HTTP 405), automatically pulls master,
|
||||
rebases, force-pushes, and retries the merge
|
||||
|
||||
The merge commit push to master triggers the post-merge workflow.
|
||||
|
||||
### Smart CI: user-facing vs workflow-only changes
|
||||
|
||||
Not all changes require a new release. The `detect-changes` step in the
|
||||
`validate` job classifies changes using
|
||||
`python -m devx.ci.classify_changes`:
|
||||
|
||||
**Workflow-only paths** (infrastructure — no release needed):
|
||||
- `.gitea/**` — Gitea Actions workflows
|
||||
- `tests/**` — Test files
|
||||
- `AGENTS.md`, `README.md`, `CHANGELOG.md` — Project docs
|
||||
- `Makefile`, `cliff.toml`, `.pre-commit-config.yaml` — Config
|
||||
- `.env.example`, `.gitignore` — Config
|
||||
- `hooks/**` — Git hooks
|
||||
- `src/devx/__init__.py` — Only contains `__version__` (release artifact)
|
||||
|
||||
**User-facing paths** (tool changes — release needed) — everything else:
|
||||
- `src/devx/**` — Python package source (except `__init__.py`)
|
||||
- `pyproject.toml` — Package metadata
|
||||
- Any new file type not in the allowlist
|
||||
|
||||
Classification is configured in `pyproject.toml` under
|
||||
`[tool.devx.classify]`. The framework provides `DEFAULT_INFRASTRUCTURE` — a
|
||||
curated list of paths that are infrastructure for any Python project. Projects
|
||||
inherit these automatically and only specify what is different.
|
||||
|
||||
Rule priority (first match wins):
|
||||
1. `user_facing_overrides` — safety override (highest priority)
|
||||
2. `infrastructure_overrides` — explicit per-file
|
||||
3. `infrastructure` — DEFAULT_INFRASTRUCTURE + project-specific patterns
|
||||
4. Default: user-facing (safe — any unknown file triggers release)
|
||||
|
||||
## Post-merge workflow (`post-merge.yml`)
|
||||
|
||||
Runs on every push to master. Consolidated into 2 jobs (from 7) to reduce
|
||||
runner overhead: `detect-and-configure` (detect-type + validate-commit-msg +
|
||||
configure-repo) and `release-and-maintain` (release + publish + sync-wiki +
|
||||
badges + vikunja). Individual steps within `release-and-maintain` are
|
||||
conditional on the `detect-and-configure` job's outputs.
|
||||
|
||||
### Job dependency graph
|
||||
|
||||
```text
|
||||
detect-and-configure
|
||||
├── configure-repo (independent, skip if release commit)
|
||||
├── detect-type → is-release? is-automated?
|
||||
└── validate-commit-msg (skip if release commit)
|
||||
│
|
||||
▼
|
||||
release-and-maintain (needs detect-and-configure)
|
||||
├── release (skip if release commit or workflow-only)
|
||||
│ └── publish (if release created a tag)
|
||||
├── sync-wiki (skip if automated)
|
||||
├── vikunja (skip if automated)
|
||||
└── badges (always runs)
|
||||
```
|
||||
|
||||
`sync-wiki` and `vikunja` run only on non-automated commits (that is, real PR
|
||||
merges) so that the wiki and task tracker are only updated when a human
|
||||
change lands. They skip on release commits and automated commits.
|
||||
|
||||
The `badges` step always runs (even on release commits) so badges (tests,
|
||||
coverage, version, etc.) are always current. It runs last so it picks up
|
||||
any version bump the release step created.
|
||||
|
||||
When `release` creates a `release: vX.Y.Z` commit, the release commit's
|
||||
post-merge run still updates badges (the version badge picks up the new
|
||||
version). Other steps skip. The `publish` step builds and publishes the
|
||||
package to the Gitea PyPI registry within the same `release-and-maintain`
|
||||
job (it checks out the release tag).
|
||||
|
||||
### Post-merge jobs
|
||||
|
||||
#### `detect-and-configure`
|
||||
|
||||
The first post-merge job. Consolidates the former `detect-type`,
|
||||
`validate-commit-msg`, and `configure-repo` jobs. Outputs `is-release`,
|
||||
`is-automated`, and `user-facing-changed` for the `release-and-maintain`
|
||||
job.
|
||||
|
||||
**`detect-type` step**
|
||||
|
||||
Checks if the latest commit is a release commit (`release: vX.Y.Z [skip ci]`)
|
||||
using `python -m devx.ci.detect_release_commit`. Writes `is-release=true` or
|
||||
`is-release=false` (and `is-automated`) to the job output. The
|
||||
`release-and-maintain` job uses these to conditionally skip steps for
|
||||
release commits.
|
||||
|
||||
**`validate-commit-msg` step**
|
||||
|
||||
Skips for release/automated commits. Validates the latest commit message
|
||||
using `python -m devx.ci.validate_commit_msg --branch master`. On master,
|
||||
commits must follow `{PREFIX}-N: <conventional commit>` format (added by
|
||||
auto-merge).
|
||||
|
||||
**`configure-repo` step**
|
||||
|
||||
Ensures branch protection and labels are configured using
|
||||
`python -m devx.tools.configure_repo --repo <name> --owner <owner>`:
|
||||
|
||||
- Sets up master branch protection (required status checks, block on rejected
|
||||
reviews, block on outdated branch)
|
||||
- Creates standard labels
|
||||
- Status check contexts read from `DEVX_STATUS_CHECKS` or default to
|
||||
`CI / validate (pull_request)`
|
||||
|
||||
On failure, the `notify_failure` step creates a Gitea issue.
|
||||
|
||||
#### `release-and-maintain`
|
||||
|
||||
Depends on `detect-and-configure`. The second post-merge job. Consolidates
|
||||
the former `release`, `publish`, `sync-wiki`, `badges`, and `vikunja` jobs.
|
||||
Individual steps are conditional on the `detect-and-configure` job's outputs.
|
||||
|
||||
**`release` step**
|
||||
|
||||
Skips for release commits and workflow-only changes. The core release
|
||||
automation step. Runs `python -m devx.ci.release`:
|
||||
|
||||
1. **Classify changes** — calls `classify_changes.py` to check for user-facing
|
||||
changes. If only infrastructure files changed, exits without releasing.
|
||||
2. **Calculate next version** — uses git-cliff to determine the next semver
|
||||
version from conventional commits since the last tag
|
||||
3. **Update version file** — updates `__version__` in `src/devx/__init__.py`
|
||||
4. **Update changelog** — prepends the new version section to `CHANGELOG.md`
|
||||
using git-cliff output
|
||||
5. **Run tests** — executes `make lint-ruff` and `make pytest-cov` to verify
|
||||
the release is healthy. If either fails, the release is aborted — no
|
||||
commit, no tag. Use `--skip-tests` only for emergency releases.
|
||||
6. **Commit** — stages the version file and changelog, commits with
|
||||
`release: vX.Y.Z [skip ci]` (uses `--no-verify` to bypass the commit-msg
|
||||
hook since release commits are a special case)
|
||||
7. **Create tag** — creates an annotated tag `vX.Y.Z` with the changelog as
|
||||
the tag message
|
||||
8. **Push** — pushes both the commit and tag to master
|
||||
|
||||
The script is idempotent: if there are no new conventional commits since the
|
||||
last tag, it exits without doing anything. If the tag already exists (for example,
|
||||
from a partial previous run), it skips tag creation and only pushes.
|
||||
|
||||
**Tag consistency**: Before releasing, the script fetches remote tags and
|
||||
verifies all existing tags point to commits whose message matches the tag
|
||||
version. This prevents duplicate release commits and ensures
|
||||
tag/version/commit alignment.
|
||||
|
||||
**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) |
|
||||
| `chore:`, `ci:`, `docs:` | no bump (excluded by cliff.toml) |
|
||||
|
||||
On failure, the `notify_failure` step creates a Gitea issue via
|
||||
`python -m devx.ci.notify_failure`.
|
||||
|
||||
**`sync-wiki` step**
|
||||
|
||||
Skips for automated commits. Syncs documentation from `docs/` to the Gitea
|
||||
wiki using `python -m devx.ci.sync_wiki --repo <owner/repo> --strict`:
|
||||
|
||||
1. Reads `docs/mapping.json` to map file paths to wiki page titles
|
||||
2. Lists existing wiki pages via the Gitea API
|
||||
3. For each mapped file, reads content and creates or updates the wiki page
|
||||
4. `--strict` runs a full integrity check: verifies page count, missing
|
||||
pages, stale pages, and content match. Fails if any page is empty or
|
||||
content doesn't match.
|
||||
|
||||
Pages that exist in the wiki but not in the mapping are left untouched (not
|
||||
deleted).
|
||||
|
||||
On failure, the `notify_failure` step creates a Gitea issue.
|
||||
|
||||
**`badges` step**
|
||||
|
||||
Always runs (even on release commits). Generates and pushes quality badges
|
||||
using `python -m devx.ci.push_badges`:
|
||||
|
||||
1. **Fetch latest master** — `git fetch origin master && git reset --hard
|
||||
origin/master` (ensures the version badge reflects the current state,
|
||||
even if the release step recently pushed a new version)
|
||||
2. **Generate badges** — calls `devx.tools.generate_badges` which runs
|
||||
pytest-cov, doc-coverage, lint checks, and version extraction, then writes
|
||||
SVG files: `coverage.svg`, `tests.svg`, `docs.svg`, `quality.svg`,
|
||||
`version.svg`, `python.svg`
|
||||
3. **Push to badges branch** — creates an orphan `badges` branch, copies SVG
|
||||
files, commits, and force-pushes
|
||||
4. **Update README/docs** — switches back to master, replaces
|
||||
`raw/branch/badges/<name>.svg` URLs with `raw/commit/<sha>/<name>.svg`
|
||||
URLs (cache-busting — Gitea caches `raw/branch/` URLs for 6 hours),
|
||||
commits, and pushes
|
||||
|
||||
Supports `--retries` for retrying on git push failures (fetches latest master
|
||||
and waits 10s between attempts).
|
||||
|
||||
On failure, the `notify_failure` step creates a Gitea issue.
|
||||
|
||||
**`vikunja` step**
|
||||
|
||||
Skips for automated commits. Updates the Vikunja task after a merge using
|
||||
`python -m devx.ci.post_merge --git-sha <sha>`:
|
||||
|
||||
1. Extracts the task ID from the first line of the commit message
|
||||
2. Marks the corresponding Vikunja task as done
|
||||
3. Posts a comment with the merge SHA
|
||||
|
||||
On failure, the `notify_failure` step creates a Gitea issue.
|
||||
|
||||
**`publish` step**
|
||||
|
||||
Only runs if the `release` step created a tag. Builds and publishes the
|
||||
package within the same `release-and-maintain` job (checks out the release
|
||||
tag). Runs `python -m devx.ci.publish <tag> <owner/repo>`:
|
||||
|
||||
1. **Install dependencies** — build, twine, requests, python-dotenv, click,
|
||||
and the project itself
|
||||
2. **Install CI tools** — git-cliff and tea via
|
||||
`python -m devx.tools.install_tools`
|
||||
3. **Configure tea login** — `tea login add` using `CI_GITEA_TOKEN`
|
||||
4. **Build and publish** — `python -m devx.ci.publish <tag> <owner/repo>`:
|
||||
- Build the package with `python -m build`
|
||||
- Publish to the Gitea PyPI registry (default) using `twine upload
|
||||
--repository-url <url> -u <token> -p <token>`
|
||||
- OR publish to standard PyPI if `PYPI_TOKEN` is set
|
||||
- OR skip publishing if `--skip-build` is passed (non-Python repos)
|
||||
- Create a Gitea release with git-cliff-generated release notes via
|
||||
`tea create release`
|
||||
|
||||
Publishing destination resolution (checked in order):
|
||||
1. **Gitea PyPI registry** — if `--registry-url` is given, or
|
||||
`DEVX_PYPI_REGISTRY_URL` env var is set, or derived from `GITEA_API_URL`
|
||||
2. **Standard PyPI** — if `PYPI_TOKEN` is set (takes precedence over Gitea
|
||||
registry)
|
||||
3. **Skip** — if neither is configured, only the Gitea release is created
|
||||
|
||||
On failure, the `notify_failure` step creates a Gitea issue.
|
||||
|
||||
## CI scripts
|
||||
|
||||
### `auto_merge.py`
|
||||
|
||||
Auto-merge PR when all CI checks pass. Reads task ID from the branch name
|
||||
(for example, `DEVX-12-fix-foo` → `DEVX-12`). Validates PR title format, checks the
|
||||
Vikunja task exists and the title matches, extracts the conventional commit
|
||||
message from PR commits, and squash-merges with
|
||||
`{PREFIX}-N <conventional commit>` title.
|
||||
|
||||
```bash
|
||||
python -m devx.ci.auto_merge <branch> <pr_title> <owner/repo> <pr_number>
|
||||
```
|
||||
|
||||
### `release.py`
|
||||
|
||||
Automated release using git-cliff. Calculates next semver version from
|
||||
conventional commits, updates `__version__` and `CHANGELOG.md`, runs lint and
|
||||
tests, commits with `release: vX.Y.Z [skip ci]`, creates annotated tag, and
|
||||
pushes. Idempotent — exits if no unreleased changes.
|
||||
|
||||
```bash
|
||||
python -m devx.ci.release [--dry-run] [--skip-tests] [--verify]
|
||||
```
|
||||
|
||||
- `--dry-run` — preview without making changes
|
||||
- `--skip-tests` — skip lint and test verification (emergency only)
|
||||
- `--verify` — check tag/version/changelog alignment and exit
|
||||
|
||||
### `publish.py`
|
||||
|
||||
Builds package, publishes to Gitea PyPI registry or standard PyPI, and
|
||||
creates a Gitea release with git-cliff-generated notes.
|
||||
|
||||
```bash
|
||||
python -m devx.ci.publish <tag> <owner/repo> [--registry-url <url>] [--skip-build]
|
||||
```
|
||||
|
||||
### `pr_review.py`
|
||||
|
||||
Automated PR review. Fetches the PR diff via the Gitea API, runs automated
|
||||
checks (architecture, best practices, security, i18n, resource management,
|
||||
documentation, test coverage, commit conventions), and posts a structured
|
||||
review with inline comments.
|
||||
|
||||
```bash
|
||||
python -m devx.ci.pr_review <pr_number> <owner/repo>
|
||||
```
|
||||
|
||||
### `notify_failure.py`
|
||||
|
||||
Creates a Gitea issue when a CI workflow fails. Uses the tea CLI for issue
|
||||
creation with failure labels. Supports `--auto-login` to configure the tea
|
||||
CLI login profile from `CI_GITEA_TOKEN`.
|
||||
|
||||
```bash
|
||||
python -m devx.ci.notify_failure --repo <owner/repo> --run-id <id> \
|
||||
--workflow <name> --commit <sha> [--auto-login]
|
||||
```
|
||||
|
||||
### `post_merge.py`
|
||||
|
||||
Updates Vikunja task after a merge to master. Extracts task ID from the
|
||||
commit message, marks the task as done, and posts a comment with the merge SHA.
|
||||
|
||||
```bash
|
||||
python -m devx.ci.post_merge <commit_msg> [--commit-sha <sha>] [--git-sha <sha>]
|
||||
```
|
||||
|
||||
### `classify_changes.py`
|
||||
|
||||
Classifies git changes as user-facing or workflow-only. Uses a layered rule
|
||||
system configured in `pyproject.toml`. Safe-by-default: any unknown file
|
||||
defaults to user-facing.
|
||||
|
||||
```bash
|
||||
python -m devx.ci.classify_changes [--base <ref>] [--head <ref>] \
|
||||
[--quiet] [--check <category>] [--github-output]
|
||||
```
|
||||
|
||||
### `discover_runners.py`
|
||||
|
||||
Discovers available Gitea Actions runners at repository, organization, and
|
||||
instance levels. Falls back to `MOLECULE_RUNNERS` repo variable or
|
||||
`DEFAULT_MAX_RUNNERS` (3).
|
||||
|
||||
```bash
|
||||
python -m devx.ci.discover_runners --owner <owner> --repo <repo> [--count] [--indices]
|
||||
```
|
||||
|
||||
### `detect_release_commit.py`
|
||||
|
||||
Detects whether the latest git commit is a release commit. Writes
|
||||
`is-release=true|false` to `$GITHUB_OUTPUT`.
|
||||
|
||||
```bash
|
||||
python -m devx.ci.detect_release_commit
|
||||
```
|
||||
|
||||
### `push_badges.py`
|
||||
|
||||
Generates SVG badge files, pushes them to the `badges` branch, and updates
|
||||
README.md and docs/index.md with cache-busting `raw/commit/<sha>/` URLs.
|
||||
|
||||
```bash
|
||||
python -m devx.ci.push_badges [--output-dir <dir>] [--branch <branch>] \
|
||||
[--no-readme-update] [--retries <n>]
|
||||
```
|
||||
|
||||
### `distribute_molecule.py`
|
||||
|
||||
Distributes molecule (scenario, platform) pairs across N parallel runners.
|
||||
Discovers scenarios under `ansible/roles/*/molecule/`.
|
||||
|
||||
```bash
|
||||
python -m devx.molecule.distribute_molecule --runner-index <i> --max-runners <n>
|
||||
python -m devx.molecule.distribute_molecule --list
|
||||
python -m devx.molecule.distribute_molecule --list-platforms
|
||||
```
|
||||
|
||||
### `molecule_ci_guard.py`
|
||||
|
||||
Runs molecule tests sequentially while polling the Gitea API for other runner
|
||||
failures. Aborts early if another runner fails the same job.
|
||||
|
||||
```bash
|
||||
python -m devx.molecule.molecule_ci_guard [--roles-root <dir>] pair1 pair2 ...
|
||||
```
|
||||
|
||||
### `validate_commit_msg.py`
|
||||
|
||||
Validates commit messages. On feature branches: conventional commits only
|
||||
(no `{PREFIX}-N` prefix). On master: must have `{PREFIX}-N` prefix from
|
||||
auto-merge, followed by a conventional commit message.
|
||||
|
||||
```bash
|
||||
python -m devx.ci.validate_commit_msg <commit_msg_file> [--branch <branch>]
|
||||
```
|
||||
|
||||
### `sync_wiki.py`
|
||||
|
||||
Syncs documentation from `docs/` to the Gitea wiki via the API. Reads
|
||||
`docs/mapping.json` for file-to-page mapping. Supports `--dry-run`,
|
||||
`--verify`, and `--strict` (full integrity check).
|
||||
|
||||
```bash
|
||||
python -m devx.ci.sync_wiki [--dry-run] [--repo <owner/repo>] [--verify] [--strict]
|
||||
```
|
||||
|
||||
### `check_translations.py`
|
||||
|
||||
Validates translation files against the Python source code. Checks for
|
||||
missing keys, dead keys, and missing languages.
|
||||
|
||||
```bash
|
||||
python -m devx.ci.check_translations [--translations <file>]...
|
||||
```
|
||||
|
||||
### `doc_coverage.py`
|
||||
|
||||
Checks documentation coverage for CLI commands and major modules. Parses
|
||||
Click commands from `cli.py` and verifies documentation exists.
|
||||
|
||||
```bash
|
||||
python -m devx.ci.doc_coverage [--docs-dir <dir>] [--fail-on-missing]
|
||||
```
|
||||
|
||||
### `distribute_files.py`
|
||||
|
||||
Distributes files matching a glob pattern across N parallel runners
|
||||
(round-robin). Writes the assigned file list to `$GITHUB_ENV`.
|
||||
|
||||
```bash
|
||||
python -m devx.ci.distribute_files --pattern <glob> --runner-index <i> \
|
||||
--max-runners <n> [--github-env] [--skip-if-excess]
|
||||
```
|
||||
|
||||
### `integration_guard.py`
|
||||
|
||||
Runs pytest with cross-runner failure detection. If any other
|
||||
integration-tests matrix runner reports failure, the current pytest
|
||||
subprocess is killed and this runner exits early.
|
||||
|
||||
```bash
|
||||
python -m devx.ci.integration_guard -- <pytest args>
|
||||
```
|
||||
|
||||
## Release process summary
|
||||
|
||||
The complete release process from PR to published package:
|
||||
|
||||
1. **PR merged** — `auto-merge` squash-merges the PR to master with
|
||||
`{PREFIX}-N <conventional commit>` title
|
||||
2. **Post-merge triggers** — the merge push triggers `post-merge.yml`
|
||||
3. **detect-and-configure** — detects release commit, validates commit
|
||||
message, and ensures branch protection/labels
|
||||
4. **release** (step in `release-and-maintain`) — `release.py` calculates
|
||||
the next version, updates files, runs tests, commits
|
||||
`release: vX.Y.Z [skip ci]`, creates tag `vX.Y.Z`, and pushes to master
|
||||
5. **publish** (step in `release-and-maintain`) — `publish.py` builds the
|
||||
package, publishes to the Gitea PyPI registry, and creates a Gitea
|
||||
release with git-cliff notes (checks out the release tag within the
|
||||
same job)
|
||||
6. **sync-wiki** (step in `release-and-maintain`) — documentation is synced
|
||||
to the Gitea wiki
|
||||
7. **vikunja** (step in `release-and-maintain`) — the corresponding Vikunja
|
||||
task is marked as done
|
||||
8. **badges** (step in `release-and-maintain`) — quality badges are
|
||||
regenerated and pushed to the `badges` branch; README and docs/index.md
|
||||
are updated with cache-busting URLs
|
||||
|
||||
The release commit's post-merge run skips all steps except `badges` (which
|
||||
picks up the new version number). This prevents infinite loops.
|
||||
|
||||
## Failure handling
|
||||
|
||||
Every job in the CI and post-merge workflows has a `notify_failure` step
|
||||
that runs `if: failure()`. This creates a Gitea issue with the workflow name,
|
||||
run ID, and commit SHA, ensuring failures that would otherwise go unnoticed
|
||||
in the Actions tab are surfaced as issues. The issue is created via the tea
|
||||
CLI with a `bug` label if available.
|
||||