DEVX-91: feat: add pr_status, pr_logs, pr_label tools
Post-merge / detect-type (push) Successful in 9s
Post-merge / validate-commit-msg (push) Successful in 11s
Post-merge / vikunja (push) Successful in 13s
Post-merge / sync-wiki (push) Successful in 17s
Post-merge / configure-repo (push) Successful in 10s
Post-merge / release (push) Successful in 30s
Build Images / detect-type (push) Successful in 48s
Post-merge / badges (push) Successful in 41s
Post-merge / publish (push) Successful in 16s
Build Images / build-and-push (push) Successful in 4m41s
Build Images / cleanup (push) Successful in 2m23s

This commit was merged in pull request #143.
This commit is contained in:
2026-06-27 23:38:16 +00:00
parent 3c421dd1ad
commit 81dc30ecff
14 changed files with 1456 additions and 11 deletions
+39
View File
@@ -223,6 +223,20 @@ class GiteaClient:
r = self._request("GET", f"/pulls/{pr_number}/files")
return r.json()
def add_pr_label(self, pr_number: str | int, label_names: list[str]) -> None:
"""Attach labels to a PR/issue by name.
Args:
pr_number: PR or issue number.
label_names: List of label names to attach.
"""
self._request("POST", f"/issues/{pr_number}/labels", json={"labels": label_names})
def get_pr_label_names(self, pr_number: str | int) -> list[str]:
"""Return label names currently attached to a PR/issue."""
r = self._request("GET", f"/issues/{pr_number}/labels")
return [label.get("name", "") for label in r.json()]
def get_pr_commits(self, pr_number: str | int) -> list[dict[str, Any]]:
"""Fetch the commits included in a pull request."""
r = self._request("GET", f"/pulls/{pr_number}/commits")
@@ -301,6 +315,31 @@ class GiteaClient:
return existing
return self.create_release(tag=tag, name=name, body=body, draft=draft, prerelease=prerelease)
# -- actions (CI/CD) --
def list_action_runs(self, **params: Any) -> dict[str, Any]:
"""List workflow runs for the repository.
Returns the raw API response dict (includes ``workflow_runs`` and
``total_count`` keys per Gitea API).
"""
r = self._request("GET", "/actions/runs", params=params)
return r.json()
def get_action_run_jobs(self, run_id: str | int) -> list[dict[str, Any]]:
"""List jobs for a specific workflow run."""
r = self._request("GET", f"/actions/runs/{run_id}/jobs")
data = r.json()
return data.get("jobs", [])
def get_action_job_logs(self, job_id: str | int) -> str:
"""Fetch logs for a specific CI job.
Returns the raw log text. Raises APIError if logs are unavailable.
"""
r = self._request("GET", f"/actions/jobs/{job_id}/logs")
return r.text
class VikunjaClient:
"""Low-level Vikunja REST API client with connection pooling."""
+1
View File
@@ -74,6 +74,7 @@ VIKUNJA_API_URL = _get("vikunja_api_url", "DEVX_VIKUNJA_API_URL", "https://work.
# Organization defaults — each project MUST set DEVX_REPO_OWNER explicitly.
# No default: prevents silent 404s when the wrong owner is used.
REPO_OWNER = _get("repo_owner", "DEVX_REPO_OWNER", "")
REPO_NAME = _get("repo_name", "DEVX_REPO_NAME", "")
# Task prefix for Vikunja task IDs — each project sets its own (GRM, DEVX, INFRA, etc.)
TASK_PREFIX = _get("task_prefix", "DEVX_TASK_PREFIX", "DEVX")
+30
View File
@@ -63,6 +63,7 @@ DEVX_PIP_INSTALL := if [ -z "$$CI_GITEA_TOKEN" ]; then . ./.env 2>/dev/null; fi;
$(DEVX_BIN)/pip
.PHONY: devx-create-task devx-create-pr devx-push devx-push-with-pr devx-check-config
.PHONY: devx-pr-status devx-pr-logs devx-pr-label
.PHONY: devx-configure-gitea-pypi devx-install-tools devx-install-checkmake devx-checkmake
.PHONY: devx-workflow-lint devx-workflow-dryrun devx-workflow-dryrun-safe devx-workflow-check
.PHONY: devx-notify-failure devx-install-hooks devx-activate-scripts
@@ -94,6 +95,35 @@ devx-check-config:
# Push and create PR in one step
devx-push-with-pr: devx-push devx-create-pr
# Check CI status for a PR (auto-detects current branch's PR)
# Usage: make devx-pr-status
# make devx-pr-status PR=42
# make devx-pr-status PR=42 WAIT=1 TIMEOUT=600
devx-pr-status:
@$(DEVX_PYTHON) -m devx.tools.pr_status \
$(if $(PR),--pr $(PR)) \
$(if $(WAIT),--wait) \
$(if $(TIMEOUT),--timeout $(TIMEOUT))
# Fetch logs for failed CI jobs on a PR
# Usage: make devx-pr-logs
# make devx-pr-logs PR=42
# make devx-pr-logs PR=42 JOB=quality TAIL=50
devx-pr-logs:
@$(DEVX_PYTHON) -m devx.tools.pr_logs \
$(if $(PR),--pr $(PR)) \
$(if $(JOB),--job $(JOB)) \
$(if $(TAIL),--tail $(TAIL))
# Add a label to a PR (default: ready-to-merge)
# Usage: make devx-pr-label
# make devx-pr-label PR=42
# make devx-pr-label PR=42 LABEL=ready-to-merge
devx-pr-label:
@$(DEVX_PYTHON) -m devx.tools.pr_label \
$(if $(PR),--pr $(PR)) \
--label $(or $(LABEL),ready-to-merge)
# ── Environment setup ─────────────────────────────────────────────────────────
# Configure Gitea private PyPI registry so pip can find devx and other
+5 -2
View File
@@ -34,6 +34,7 @@ from devx.api_clients import GiteaClient, VikunjaClient
from devx.config import (
DEFAULT_PER_PAGE,
GITEA_API_URL,
REPO_NAME,
REPO_OWNER,
TASK_ID_RE,
TASK_PREFIX,
@@ -46,15 +47,17 @@ load_dotenv()
def get_repo_name() -> str:
"""Auto-detect repository name from env vars or git remote."""
"""Auto-detect repository name from env vars, pyproject.toml, or git remote."""
name = os.environ.get("DEVX_REPO_NAME", "")
if name:
return name
github_repo = os.environ.get("GITHUB_REPOSITORY", "")
if github_repo and "/" in github_repo:
return github_repo.split("/", 1)[1]
if REPO_NAME:
return REPO_NAME
raise click.ClickException(
_("Repository name not set. Use DEVX_REPO_NAME or GITHUB_REPOSITORY env var."),
_("Repository name not set. Use DEVX_REPO_NAME, [tool.devx] repo_name, or GITHUB_REPOSITORY env var."),
)
+81
View File
@@ -0,0 +1,81 @@
#!/usr/bin/env python3
"""Add a label to a pull request (idempotent).
Commonly used to add the ``ready-to-merge`` label after CI passes and
review is complete. The operation is idempotent — if the label is already
attached, it succeeds without error.
Usage::
# Add ready-to-merge to PR #42
python -m devx.tools.pr_label --pr 42 --label ready-to-merge
# Add label to current branch's PR
python -m devx.tools.pr_label --label ready-to-merge
# Add multiple labels
python -m devx.tools.pr_label --pr 42 --label ready-to-merge --label reviewed
The repository is auto-detected from ``DEVX_REPO_OWNER`` /
``DEVX_REPO_NAME`` or ``GITHUB_REPOSITORY`` environment variables.
"""
from __future__ import annotations
import os
import click
from dotenv import load_dotenv
from devx.api_clients import GiteaClient
from devx.config import GITEA_API_URL, REPO_OWNER
from devx.i18n import _
from devx.tools.create_pr import get_repo_name
from devx.tools.pr_status import _get_current_branch_pr
load_dotenv()
@click.command()
@click.option("--pr", "pr_number", type=int, default=None, help="PR number (default: auto-detect from current branch).")
@click.option("--label", "labels", multiple=True, required=True, help="Label name(s) to add (can be repeated).")
@click.option("--owner", default=None, help="Repository owner (default: DEVX_REPO_OWNER).")
@click.option("--repo", default=None, help="Repository name (default: DEVX_REPO_NAME or GITHUB_REPOSITORY).")
def cli(
pr_number: int | None,
labels: tuple[str, ...],
owner: str | None,
repo: str | None,
) -> None:
"""Add one or more labels to a pull request (idempotent)."""
token = os.environ.get("CI_GITEA_TOKEN", "")
if not token:
raise click.ClickException(_("CI_GITEA_TOKEN is not set."))
repo_owner = owner or REPO_OWNER
if not repo_owner:
raise click.ClickException(_("Repository owner not set. Use --owner or DEVX_REPO_OWNER env var."))
repo_name = repo or get_repo_name()
client = GiteaClient(GITEA_API_URL, token, repo_owner, repo_name)
if pr_number is None:
pr_number = _get_current_branch_pr(client)
label_list = list(labels)
existing = client.get_pr_label_names(pr_number)
to_add = [lbl for lbl in label_list if lbl not in existing]
already = [lbl for lbl in label_list if lbl in existing]
if already:
for lbl in already:
click.echo(_("Label '{label}' already on PR #{pr}.", label=lbl, pr=pr_number))
if to_add:
client.add_pr_label(pr_number, to_add)
for lbl in to_add:
click.echo(_("Added label '{label}' to PR #{pr}.", label=lbl, pr=pr_number))
if __name__ == "__main__": # pragma: no cover
cli() # pragma: no cover
+187
View File
@@ -0,0 +1,187 @@
#!/usr/bin/env python3
"""Fetch logs for failed CI jobs on a pull request.
Lists CI jobs for the latest workflow run of a PR's branch, then fetches
and prints the logs of any failed jobs. Useful for diagnosing CI failures
without navigating the web UI.
Usage::
# Show failed job logs for PR #42
python -m devx.tools.pr_logs --pr 42
# Show failed job logs for current branch's PR
python -m devx.tools.pr_logs
# Show logs for a specific job (by name)
python -m devx.tools.pr_logs --pr 42 --job quality
# Show last N lines of each failed job's logs
python -m devx.tools.pr_logs --pr 42 --tail 50
The repository is auto-detected from ``DEVX_REPO_OWNER`` /
``DEVX_REPO_NAME`` or ``GITHUB_REPOSITORY`` environment variables.
"""
from __future__ import annotations
import os
import click
from dotenv import load_dotenv
from devx.api_clients import APIError, GiteaClient
from devx.config import GITEA_API_URL, REPO_OWNER
from devx.i18n import _
from devx.tools.create_pr import get_repo_name
from devx.tools.pr_status import _get_current_branch_pr
load_dotenv()
def _get_pr_sha(client: GiteaClient, pr_number: int) -> str:
"""Fetch the head SHA of a PR."""
pr = client.get_pr(pr_number)
return pr.get("head", {}).get("sha", "")
def _find_latest_run_by_sha(client: GiteaClient, sha: str) -> dict | None:
"""Find the latest workflow run for a commit SHA.
Gitea Actions API doesn't set head_branch for pull_request events,
so we filter by head_sha instead.
"""
data = client.list_action_runs(limit=50)
for run in data.get("workflow_runs", []):
if run.get("head_sha", "").startswith(sha):
return run
return None
def _find_failed_jobs(jobs: list[dict]) -> list[dict]:
"""Return jobs with conclusion 'failure'."""
return [j for j in jobs if j.get("conclusion") == "failure"]
def _find_job_by_name(jobs: list[dict], name: str) -> dict | None:
"""Find a job by name (case-insensitive partial match)."""
name_lower = name.lower()
for j in jobs:
if name_lower in j.get("name", "").lower():
return j
return None
def _print_job_summary(jobs: list[dict]) -> None:
"""Print a summary table of all jobs and their status."""
for j in jobs:
name = j.get("name", "?")
conclusion = j.get("conclusion", "pending")
status = j.get("status", "?")
symbol = "[FAIL]" if conclusion == "failure" else "[OK]" if conclusion == "success" else f"[{conclusion}]"
click.echo(f" {symbol} {name} (status: {status}, conclusion: {conclusion})")
def _print_failed_steps(job: dict) -> list[int]:
"""Print failed steps for a job. Returns list of failed step numbers."""
failed_steps = []
for step in job.get("steps", []):
if step.get("conclusion") == "failure":
name = step.get("name", "?")
num = step.get("number", "?")
click.echo(f" FAILED step #{num}: {name}")
failed_steps.append(num)
return failed_steps
def _print_logs(client: GiteaClient, job_id: int, tail: int = 0) -> None:
"""Fetch and print logs for a job. If tail > 0, print only last N lines."""
try:
logs = client.get_action_job_logs(job_id)
except APIError as e:
click.echo(_(" Could not fetch logs: {error}", error=str(e)))
return
if tail > 0:
lines = logs.strip().split("\n")
if len(lines) > tail:
click.echo(f" ... (showing last {tail} of {len(lines)} lines)")
logs = "\n".join(lines[-tail:])
for line in logs.split("\n"):
click.echo(f" {line}")
@click.command()
@click.option("--pr", "pr_number", type=int, default=None, help="PR number (default: auto-detect from current branch).")
@click.option("--job", default=None, help="Job name to show logs for (partial match, case-insensitive).")
@click.option("--tail", type=int, default=80, show_default=True, help="Show last N lines of logs (0 = all).")
@click.option("--owner", default=None, help="Repository owner (default: DEVX_REPO_OWNER).")
@click.option("--repo", default=None, help="Repository name (default: DEVX_REPO_NAME or GITHUB_REPOSITORY).")
def cli(
pr_number: int | None,
job: str | None,
tail: int,
owner: str | None,
repo: str | None,
) -> None:
"""Fetch logs for failed CI jobs on a pull request."""
token = os.environ.get("CI_GITEA_TOKEN", "")
if not token:
raise click.ClickException(_("CI_GITEA_TOKEN is not set."))
repo_owner = owner or REPO_OWNER
if not repo_owner:
raise click.ClickException(_("Repository owner not set. Use --owner or DEVX_REPO_OWNER env var."))
repo_name = repo or get_repo_name()
client = GiteaClient(GITEA_API_URL, token, repo_owner, repo_name)
if pr_number is None:
pr_number = _get_current_branch_pr(client)
click.echo(_("Fetching logs for PR #{pr_number}...", pr_number=pr_number))
sha = _get_pr_sha(client, pr_number)
if not sha:
raise click.ClickException(_("Could not determine head SHA for PR #{pr_number}.", pr_number=pr_number))
run = _find_latest_run_by_sha(client, sha)
if not run:
raise click.ClickException(_("No workflow runs found for SHA {sha}.", sha=sha[:8]))
run_id = run.get("id", 0)
run_status = run.get("status", "?")
click.echo(_("Latest run: #{run_id} (status: {status})", run_id=run_id, status=run_status))
click.echo("")
jobs = client.get_action_run_jobs(run_id)
if not jobs:
click.echo(_("No jobs found for run #{run_id}.", run_id=run_id))
return
_print_job_summary(jobs)
click.echo("")
if job:
target = _find_job_by_name(jobs, job)
if not target:
raise click.ClickException(_("No job matching '{job}' found.", job=job))
click.echo(f"Logs for job '{target.get('name', '?')}' (id={target.get('id')}):")
_print_failed_steps(target)
click.echo("")
_print_logs(client, target["id"], tail)
else:
failed = _find_failed_jobs(jobs)
if not failed:
click.echo(_("No failed jobs."))
return
for fj in failed:
click.echo(f"Logs for failed job '{fj.get('name', '?')}' (id={fj.get('id')}):")
_print_failed_steps(fj)
click.echo("")
_print_logs(client, fj["id"], tail)
click.echo("")
if __name__ == "__main__": # pragma: no cover
cli() # pragma: no cover
+174
View File
@@ -0,0 +1,174 @@
#!/usr/bin/env python3
"""Check CI status for a pull request or commit.
Displays the status of all CI checks for a PR (or a specific commit SHA).
Optionally polls until all checks complete (``--wait``).
Usage::
# Check status of PR #42
python -m devx.tools.pr_status --pr 42
# Check status of current branch's PR
python -m devx.tools.pr_status
# Wait for all checks to complete (timeout 600s)
python -m devx.tools.pr_status --pr 42 --wait --timeout 600
# Check a specific commit SHA
python -m devx.tools.pr_status --sha abc1234
The repository is auto-detected from ``DEVX_REPO_OWNER`` /
``DEVX_REPO_NAME`` or ``GITHUB_REPOSITORY`` environment variables.
"""
from __future__ import annotations
import os
import subprocess # nosec B404
import time
import click
from dotenv import load_dotenv
from devx.api_clients import GiteaClient
from devx.config import GITEA_API_URL, REPO_OWNER
from devx.i18n import _
from devx.tools.create_pr import get_repo_name
load_dotenv()
# Status symbols for terminal output
_STATUS_SYMBOLS = {
"success": "[OK]",
"failure": "[FAIL]",
"error": "[FAIL]",
"pending": "[..]",
"skipped": "[SKIP]",
"none": "[--]",
}
def _get_symbol(status: str) -> str:
return _STATUS_SYMBOLS.get(status, f"[{status}]")
def _get_pr_sha(client: GiteaClient, pr_number: int) -> str:
"""Fetch the head SHA of a PR."""
pr = client.get_pr(pr_number)
return pr.get("head", {}).get("sha", "")
def _get_current_branch_pr(client: GiteaClient) -> int:
"""Find the open PR for the current git branch."""
result = subprocess.run( # nosec
["git", "rev-parse", "--abbrev-ref", "HEAD"],
capture_output=True,
text=True,
check=False,
)
if result.returncode != 0:
raise click.ClickException(_("Could not detect current branch: {error}", error=result.stderr.strip()))
branch = result.stdout.strip()
prs = client.list_prs(state="open")
for pr in prs:
if pr.get("head", {}).get("ref") == branch:
return int(pr["number"])
raise click.ClickException(_("No open PR found for branch '{branch}'.", branch=branch))
def print_status(client: GiteaClient, sha: str) -> str:
"""Print CI check statuses for a commit SHA. Returns the overall state."""
statuses = client.get_commit_status(sha)
if not statuses:
click.echo(_("No CI checks found for commit {sha}.", sha=sha[:8]))
return "none"
overall = "success"
for s in statuses:
context = s.get("context", "?")
status = s.get("status", "pending")
symbol = _get_symbol(status)
click.echo(f" {symbol} {context}")
if status in ("failure", "error"):
overall = "failure"
elif status == "pending" and overall != "failure":
overall = "pending"
elif status == "skipped" and overall == "success":
overall = "success"
click.echo(f"\n Overall: {_get_symbol(overall)} {overall}")
return overall
def wait_for_completion(
client: GiteaClient,
sha: str,
timeout: int = 600,
interval: int = 30,
) -> str:
"""Poll CI status until all checks complete or timeout. Returns final state."""
click.echo(_("Waiting for CI checks to complete (timeout: {timeout}s)...", timeout=timeout))
deadline = time.time() + timeout
while time.time() < deadline:
state = print_status(client, sha)
if state in ("success", "failure", "error", "none"):
return state
click.echo(f" ...still pending, retrying in {interval}s\n")
time.sleep(interval)
click.echo(_("Timeout reached after {timeout}s.", timeout=timeout))
return "pending"
@click.command()
@click.option("--pr", "pr_number", type=int, default=None, help="PR number (default: auto-detect from current branch).")
@click.option("--sha", default=None, help="Commit SHA to check (alternative to --pr).")
@click.option("--wait", "do_wait", is_flag=True, help="Poll until all checks complete.")
@click.option("--timeout", type=int, default=600, show_default=True, help="Wait timeout in seconds.")
@click.option("--interval", type=int, default=30, show_default=True, help="Poll interval in seconds.")
@click.option("--owner", default=None, help="Repository owner (default: DEVX_REPO_OWNER).")
@click.option("--repo", default=None, help="Repository name (default: DEVX_REPO_NAME or GITHUB_REPOSITORY).")
def cli(
pr_number: int | None,
sha: str | None,
do_wait: bool,
timeout: int,
interval: int,
owner: str | None,
repo: str | None,
) -> None:
"""Check CI status for a pull request or commit."""
token = os.environ.get("CI_GITEA_TOKEN", "")
if not token:
raise click.ClickException(_("CI_GITEA_TOKEN is not set."))
repo_owner = owner or REPO_OWNER
if not repo_owner:
raise click.ClickException(_("Repository owner not set. Use --owner or DEVX_REPO_OWNER env var."))
repo_name = repo or get_repo_name()
client = GiteaClient(GITEA_API_URL, token, repo_owner, repo_name)
if sha is None:
if pr_number is None:
pr_number = _get_current_branch_pr(client)
click.echo(_("Checking status for PR #{pr_number}...", pr_number=pr_number))
pr = client.get_pr(pr_number)
sha = pr.get("head", {}).get("sha", "")
if not sha:
raise click.ClickException(_("Could not determine head SHA for PR #{pr_number}.", pr_number=pr_number))
click.echo(_("Commit: {sha}", sha=sha[:12]))
click.echo("")
state = wait_for_completion(client, sha, timeout, interval) if do_wait else print_status(client, sha)
if state in ("failure", "error"):
raise click.ClickException(_("CI checks failed."))
if state == "pending" and do_wait:
raise click.ClickException(_("CI checks did not complete within timeout."))
if __name__ == "__main__": # pragma: no cover
cli() # pragma: no cover
+160 -8
View File
@@ -1471,14 +1471,6 @@
"ru": "Repository in owner/name format",
"zh": "Repository in owner/name format"
},
"Repository name not set. Use DEVX_REPO_NAME or GITHUB_REPOSITORY env var.": {
"bg": "Името на хранилището не е зададено. Използвайте DEVX_REPO_NAME или GITHUB_REPOSITORY env var.",
"de": "Repository-Name nicht gesetzt. Verwende DEVX_REPO_NAME oder GITHUB_REPOSITORY env var.",
"en": "Repository name not set. Use DEVX_REPO_NAME or GITHUB_REPOSITORY env var.",
"pl": "Nazwa repozytorium nie jest ustawiona. Użyj DEVX_REPO_NAME lub GITHUB_REPOSITORY env var.",
"ru": "Имя репозитория не установлено. Используйте DEVX_REPO_NAME или GITHUB_REPOSITORY env var.",
"zh": "仓库名称未设置。使用 DEVX_REPO_NAME 或 GITHUB_REPOSITORY 环境变量。"
},
"Repository owner not set. Use --owner or DEVX_REPO_OWNER env var.": {
"bg": "Собственикът на хранилището не е зададен. Използвайте --owner или DEVX_REPO_OWNER env var.",
"de": "Repository-Owner nicht gesetzt. Verwende --owner oder DEVX_REPO_OWNER env var.",
@@ -2038,5 +2030,165 @@
"pl": "Configuring tea login '{name}' for {url}...",
"ru": "Configuring tea login '{name}' for {url}...",
"zh": "Configuring tea login '{name}' for {url}..."
},
" Could not fetch logs: {error}": {
"en": " Could not fetch logs: {error}",
"bg": " Could not fetch logs: {error}",
"de": " Could not fetch logs: {error}",
"pl": " Could not fetch logs: {error}",
"ru": " Could not fetch logs: {error}",
"zh": " Could not fetch logs: {error}"
},
"Added label '{label}' to PR #{pr}.": {
"en": "Added label '{label}' to PR #{pr}.",
"bg": "Added label '{label}' to PR #{pr}.",
"de": "Added label '{label}' to PR #{pr}.",
"pl": "Added label '{label}' to PR #{pr}.",
"ru": "Added label '{label}' to PR #{pr}.",
"zh": "Added label '{label}' to PR #{pr}."
},
"CI checks did not complete within timeout.": {
"en": "CI checks did not complete within timeout.",
"bg": "CI checks did not complete within timeout.",
"de": "CI checks did not complete within timeout.",
"pl": "CI checks did not complete within timeout.",
"ru": "CI checks did not complete within timeout.",
"zh": "CI checks did not complete within timeout."
},
"CI checks failed.": {
"en": "CI checks failed.",
"bg": "CI checks failed.",
"de": "CI checks failed.",
"pl": "CI checks failed.",
"ru": "CI checks failed.",
"zh": "CI checks failed."
},
"CI_GITEA_TOKEN is not set.": {
"en": "CI_GITEA_TOKEN is not set.",
"bg": "CI_GITEA_TOKEN is not set.",
"de": "CI_GITEA_TOKEN is not set.",
"pl": "CI_GITEA_TOKEN is not set.",
"ru": "CI_GITEA_TOKEN is not set.",
"zh": "CI_GITEA_TOKEN is not set."
},
"Checking status for PR #{pr_number}...": {
"en": "Checking status for PR #{pr_number}...",
"bg": "Checking status for PR #{pr_number}...",
"de": "Checking status for PR #{pr_number}...",
"pl": "Checking status for PR #{pr_number}...",
"ru": "Checking status for PR #{pr_number}...",
"zh": "Checking status for PR #{pr_number}..."
},
"Commit: {sha}": {
"en": "Commit: {sha}",
"bg": "Commit: {sha}",
"de": "Commit: {sha}",
"pl": "Commit: {sha}",
"ru": "Commit: {sha}",
"zh": "Commit: {sha}"
},
"Could not determine head SHA for PR #{pr_number}.": {
"en": "Could not determine head SHA for PR #{pr_number}.",
"bg": "Could not determine head SHA for PR #{pr_number}.",
"de": "Could not determine head SHA for PR #{pr_number}.",
"pl": "Could not determine head SHA for PR #{pr_number}.",
"ru": "Could not determine head SHA for PR #{pr_number}.",
"zh": "Could not determine head SHA for PR #{pr_number}."
},
"Fetching logs for PR #{pr_number}...": {
"en": "Fetching logs for PR #{pr_number}...",
"bg": "Fetching logs for PR #{pr_number}...",
"de": "Fetching logs for PR #{pr_number}...",
"pl": "Fetching logs for PR #{pr_number}...",
"ru": "Fetching logs for PR #{pr_number}...",
"zh": "Fetching logs for PR #{pr_number}..."
},
"Label '{label}' already on PR #{pr}.": {
"en": "Label '{label}' already on PR #{pr}.",
"bg": "Label '{label}' already on PR #{pr}.",
"de": "Label '{label}' already on PR #{pr}.",
"pl": "Label '{label}' already on PR #{pr}.",
"ru": "Label '{label}' already on PR #{pr}.",
"zh": "Label '{label}' already on PR #{pr}."
},
"Latest run: #{run_id} (status: {status})": {
"en": "Latest run: #{run_id} (status: {status})",
"bg": "Latest run: #{run_id} (status: {status})",
"de": "Latest run: #{run_id} (status: {status})",
"pl": "Latest run: #{run_id} (status: {status})",
"ru": "Latest run: #{run_id} (status: {status})",
"zh": "Latest run: #{run_id} (status: {status})"
},
"No CI checks found for commit {sha}.": {
"en": "No CI checks found for commit {sha}.",
"bg": "No CI checks found for commit {sha}.",
"de": "No CI checks found for commit {sha}.",
"pl": "No CI checks found for commit {sha}.",
"ru": "No CI checks found for commit {sha}.",
"zh": "No CI checks found for commit {sha}."
},
"No failed jobs.": {
"en": "No failed jobs.",
"bg": "No failed jobs.",
"de": "No failed jobs.",
"pl": "No failed jobs.",
"ru": "No failed jobs.",
"zh": "No failed jobs."
},
"No job matching '{job}' found.": {
"en": "No job matching '{job}' found.",
"bg": "No job matching '{job}' found.",
"de": "No job matching '{job}' found.",
"pl": "No job matching '{job}' found.",
"ru": "No job matching '{job}' found.",
"zh": "No job matching '{job}' found."
},
"No jobs found for run #{run_id}.": {
"en": "No jobs found for run #{run_id}.",
"bg": "No jobs found for run #{run_id}.",
"de": "No jobs found for run #{run_id}.",
"pl": "No jobs found for run #{run_id}.",
"ru": "No jobs found for run #{run_id}.",
"zh": "No jobs found for run #{run_id}."
},
"No open PR found for branch '{branch}'.": {
"en": "No open PR found for branch '{branch}'.",
"bg": "No open PR found for branch '{branch}'.",
"de": "No open PR found for branch '{branch}'.",
"pl": "No open PR found for branch '{branch}'.",
"ru": "No open PR found for branch '{branch}'.",
"zh": "No open PR found for branch '{branch}'."
},
"No workflow runs found for SHA {sha}.": {
"en": "No workflow runs found for SHA {sha}.",
"bg": "No workflow runs found for SHA {sha}.",
"de": "No workflow runs found for SHA {sha}.",
"pl": "No workflow runs found for SHA {sha}.",
"ru": "No workflow runs found for SHA {sha}.",
"zh": "No workflow runs found for SHA {sha}."
},
"Repository name not set. Use DEVX_REPO_NAME, [tool.devx] repo_name, or GITHUB_REPOSITORY env var.": {
"en": "Repository name not set. Use DEVX_REPO_NAME, [tool.devx] repo_name, or GITHUB_REPOSITORY env var.",
"bg": "Repository name not set. Use DEVX_REPO_NAME, [tool.devx] repo_name, or GITHUB_REPOSITORY env var.",
"de": "Repository name not set. Use DEVX_REPO_NAME, [tool.devx] repo_name, or GITHUB_REPOSITORY env var.",
"pl": "Repository name not set. Use DEVX_REPO_NAME, [tool.devx] repo_name, or GITHUB_REPOSITORY env var.",
"ru": "Repository name not set. Use DEVX_REPO_NAME, [tool.devx] repo_name, or GITHUB_REPOSITORY env var.",
"zh": "Repository name not set. Use DEVX_REPO_NAME, [tool.devx] repo_name, or GITHUB_REPOSITORY env var."
},
"Timeout reached after {timeout}s.": {
"en": "Timeout reached after {timeout}s.",
"bg": "Timeout reached after {timeout}s.",
"de": "Timeout reached after {timeout}s.",
"pl": "Timeout reached after {timeout}s.",
"ru": "Timeout reached after {timeout}s.",
"zh": "Timeout reached after {timeout}s."
},
"Waiting for CI checks to complete (timeout: {timeout}s)...": {
"en": "Waiting for CI checks to complete (timeout: {timeout}s)...",
"bg": "Waiting for CI checks to complete (timeout: {timeout}s)...",
"de": "Waiting for CI checks to complete (timeout: {timeout}s)...",
"pl": "Waiting for CI checks to complete (timeout: {timeout}s)...",
"ru": "Waiting for CI checks to complete (timeout: {timeout}s)...",
"zh": "Waiting for CI checks to complete (timeout: {timeout}s)..."
}
}