diff --git a/.devin/skills/devx-workflow/SKILL.md b/.devin/skills/devx-workflow/SKILL.md index c64f118..6582320 100644 --- a/.devin/skills/devx-workflow/SKILL.md +++ b/.devin/skills/devx-workflow/SKILL.md @@ -12,7 +12,6 @@ Quick reference for devx tools when working on the devx repo itself. | 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` | @@ -24,6 +23,15 @@ When the `ready-to-merge` label is added and all CI checks pass: 3. The rebase triggers a new CI run; the next auto-merge attempt merges 4. No manual rebase needed unless the API rebase fails +## Spec-Driven CI Gates (Pre-merge) + +Every PR must pass these gates before merge: + +| Gate | Module | What it checks | +|------|--------|----------------| +| Spec validation | `devx.ci.validate_spec` | Spec file exists at `docs/specs/.md`, has REQ-IDs, all ACs checked | +| PR size | `devx.ci.check_pr_size` | Max 500 lines / 10 files (excludes CHANGELOG, badges, locks) | + ## Key Rules - Never manually merge via API — always use auto-merge with `ready-to-merge` label diff --git a/.devin/skills/pr-review/SKILL.md b/.devin/skills/pr-review/SKILL.md new file mode 100644 index 0000000..967ec1f --- /dev/null +++ b/.devin/skills/pr-review/SKILL.md @@ -0,0 +1,272 @@ +# pr-review + +Deep, critical PR review with auto-fix. This skill guides the agent +through a thorough review of a pull request, posting inline comments +for each issue found, auto-fixing them, resolving the discussion threads, +and marking the PR as ready-to-merge when no blocking issues remain. + +## When to Invoke + +Invoke this skill when asked to review a PR, or when a PR is open and +needs review before merge. Do NOT invoke automatically on every PR — +this is an on-demand deep review, not a CI gate. + +## Prerequisites + +- The PR must be open in a Gitea repo +- The agent needs Gitea MCP access (gitea server) +- The agent needs git push access to the PR's head branch +- The PR should have passed CI (validate job) before deep review + +## Review Categories + +Review every PR against these 8 categories. For each issue found, post +an inline comment on the specific line, then auto-fix it. + +### 1. Functional Correctness + +- Does the code actually do what the spec/PR title claims? +- Are edge cases handled? (empty input, null, boundary values, concurrent access) +- Are error paths tested? Not just happy path. +- Does the code handle all return values? (ignored errors, unchecked None) +- Are there off-by-one errors, wrong comparisons, inverted conditions? +- Do loops terminate correctly? (no infinite loops, correct break/continue) +- Are regex patterns correct? (anchored, escaped, non-greedy where needed) +- Are API responses validated before use? (status codes, response shape) + +### 2. Completeness + +- Are all requirements from the spec implemented? (check each REQ-ID) +- Are all acceptance criteria in the spec checked off? +- Are tests written for all new code paths? +- Are error messages user-facing (wrapped in `_()`)? +- Are new CLI commands documented in `docs/user/cli-commands.md`? +- Are new modules added to architecture docs? +- Are CHANGELOG entries added for user-facing changes? +- Are translations added for new user-facing strings? + +### 3. Architecture + +- Does the code follow the repo's layer separation? (no business logic in CLI, no direct subprocess in CLI) +- Are new dependencies justified? (no unnecessary new packages) +- Is configuration via env vars / config.py, not hardcoded? +- Are new modules placed in the correct directory? (ci/ vs tools/ vs molecule/) +- Does the code reuse existing utilities? (no reimplemented helpers) +- Are imports circular? (check import chains) +- Is the code testable? (injectable dependencies, no hidden global state) +- Does the code follow existing patterns in the codebase? + +### 4. Reliability + +- Are external API calls retried with backoff? +- Are timeouts set on all network operations? +- Are file operations atomic? (write to temp, rename) +- Are database operations transactional where needed? +- Are there race conditions? (check shared mutable state) +- Are resources cleaned up in all paths? (finally blocks, context managers) +- Can the code handle partial failures? (one service down, others up) +- Are idempotency guarantees maintained? (safe to retry) + +### 5. Robustness + +- Does the code fail gracefully? (meaningful error messages, not stack traces) +- Are unexpected inputs handled? (type checking, validation) +- Are there any crash-on-bad-input paths? +- Does the code degrade under load? (backpressure, queue limits) +- Are there resource leaks? (file handles, connections, memory) +- Does the code survive network partitions? (retry, circuit breaker) +- Are there any unhandled exceptions that could crash the process? +- Is logging sufficient to diagnose production issues? + +### 6. Security + +- Are there hardcoded secrets, tokens, or passwords? +- Is `shell=True` used with user input? (command injection) +- Is `eval()` or `exec()` used? (code injection) +- Are SQL queries parameterized? (no string concatenation) +- Are file paths validated? (no path traversal) +- Are user inputs sanitized before display? (XSS in web contexts) +- Are SSL/TLS verifications disabled without justification? +- Are secrets logged in error messages or debug output? +- Are permissions checked before privileged operations? +- Is sensitive data in memory longer than necessary? + +### 7. Technical Excellence + +- Are functions under 50 lines? (refactor if longer) +- Is cyclomatic complexity reasonable? (no deeply nested if/else chains) +- Are names meaningful? (no single-letter vars, no misleading names) +- Is dead code removed? (no commented-out blocks, no unused imports) +- Are comments explaining WHY, not WHAT? +- Is the code DRY? (no copy-pasted blocks that should be shared) +- Is the code SOLID? (single responsibility, open/closed) +- Are magic numbers extracted to named constants? +- Is the code formatted per the repo's linter config? +- Are type hints present on all function signatures? + +### 8. Test Quality + +- Do tests actually test the behavior? (not just that code runs) +- Are tests independent? (no shared mutable state, no order dependency) +- Are tests fast? (no real sleeps, no real network calls, mocked) +- Are edge cases tested? (empty, None, boundary, error paths) +- Are test names descriptive? (test_what_condition_expected_result) +- Are mocks set up correctly? (mocking the right object, not too broad) +- Is coverage 100% for new code? (every branch, every line) +- Are integration tests added for cross-module changes? +- Do tests clean up after themselves? (tmp_path, fixtures) + +## Review Procedure + +### Step 1: Gather Context + +``` +1. Read the PR spec (if exists): docs/specs/.md +2. Fetch PR details via Gitea MCP: pull_request_read (get_pr, list_pr_files) +3. Read the full diff: git diff origin/master...HEAD +4. Read the PR description and any existing review comments +5. Identify the repo's task prefix (OBL-INFRA, GRM, SSO, DEVX) +``` + +### Step 2: Review Each File + +For each changed file in the PR: + +1. Read the full file (not just the diff) to understand context +2. Go through all 8 review categories +3. For each issue found, note: file path, line number, category, severity, description, suggested fix + +### Step 3: Post Inline Comments + +For each issue found, post an inline review comment using the Gitea MCP: + +``` +mcp_call_tool: gitea / pull_request_review_write + method: create + owner: + repo: + pull_number: + state: PENDING (accumulate comments before submitting) + body: "" (empty for now, summary added on submit) + comments: [ + { + path: "", + new_line_num: , + body: "**[] []** \n\n**Suggested fix:**\n```\n\n```" + } + ] +``` + +Comment format: +``` +**[Security] [error]** `shell=True` used with user input — command injection risk. + +**Suggested fix:** +```python +subprocess.run(["git", "log", commit], check=True) +``` +``` + +Severity levels: +- `error` — must fix before merge (security, correctness, crash) +- `warning` — should fix before merge (reliability, best practice) +- `info` — consider fixing (style, minor improvement) + +### Step 4: Auto-Fix Issues + +For each issue that can be safely auto-fixed: + +1. Edit the file using the `edit` tool +2. Commit with message: `fix: address review comment — ` +3. Push to the PR's head branch: `git push origin HEAD` +4. Wait for CI to re-run on the push + +Auto-fix ALL issues unless: +- The fix requires an architectural decision (ask the user) +- The fix changes public API behavior (ask the user) +- The fix is ambiguous (multiple valid approaches, ask the user) + +### Step 5: Resolve Discussion Threads + +After auto-fixing an issue and CI passes: + +1. Find the review comment thread for that issue +2. Post a reply: `Fixed in . Closing this thread.` +3. Resolve the discussion (if Gitea supports it via API) +4. If resolving via API is not available, the reply comment serves as resolution + +### Step 6: Submit Final Review + +After all issues are addressed (fixed or discussed): + +``` +mcp_call_tool: gitea / pull_request_review_write + method: submit + owner: + repo: + pull_number: + review_id: + state: COMMENT (or APPROVED if no blocking issues remain) + body: +``` + +### Step 7: Post Summary + +Post a brief summary as a PR comment (via `issue_write / add_comment`): + +``` +## Deep Review Summary + +- **Files reviewed:** N +- **Issues found:** N (N auto-fixed, N require attention) +- **Categories:** security (N), correctness (N), architecture (N), ... + +**Outcome:** ✅ Ready to merge — all issues addressed. +**OR** +**Outcome:** ⚠️ N blocking issue(s) remain — see inline comments. +``` + +Keep the summary to 5-10 bullet points. Do not paste the full review. + +### Step 8: Mark PR Ready + +If all issues are addressed and no blocking issues remain: + +``` +mcp_call_tool: gitea / issue_write + method: add_labels + owner: + repo: + issue_number: + labels: [] +``` + +If blocking issues remain, do NOT add the label. Post a comment +explaining what needs to be resolved before the PR can merge. + +## Gitea MCP Tools Reference + +| Action | MCP tool | Method | +|--------|----------|--------| +| Get PR details | `pull_request_read` | `get_pr` | +| List PR files | `pull_request_read` | `list_pr_files` | +| Get PR diff | `pull_request_read` | `get_pr_diff` | +| Create review (pending) | `pull_request_review_write` | `create` (state: PENDING) | +| Submit review | `pull_request_review_write` | `submit` (state: APPROVED/COMMENT/REQUEST_CHANGES) | +| Post PR comment | `issue_write` | `add_comment` | +| Add label | `issue_write` | `add_labels` | +| List labels | `label_read` | `list_repo_labels` | +| Merge PR | `pull_request_write` | `merge` (do NOT use — auto-merge handles this) | + +## Important Rules + +- **Never merge the PR yourself.** Add the `ready-to-merge` label and let + the auto-merge workflow handle it. This ensures CI passes and the + commit message follows the `-N: ` format. +- **Never approve your own PR.** If the agent created the PR, post + COMMENT state, not APPROVED. +- **Always push fixes to the PR branch**, not directly to master. +- **Wait for CI after each push** before resolving the discussion thread. +- **Post one review with all comments**, not multiple reviews. +- **The summary must be brief** — 5-10 bullet points max. +- **Severity matters**: only `error` severity blocks the `ready-to-merge` label. diff --git a/.devin/skills/spec-driven-development/SKILL.md b/.devin/skills/spec-driven-development/SKILL.md new file mode 100644 index 0000000..8fef430 --- /dev/null +++ b/.devin/skills/spec-driven-development/SKILL.md @@ -0,0 +1,130 @@ +# Spec-Driven Development + +## Overview + +Every change starts with a spec. No spec, no code. No code, no PR. + +The spec is a markdown file at `docs/specs/.md` in the repo. +It contains structured requirements (REQ-IDs) and acceptance criteria +(AC checklist) that CI validates before merge. + +## Workflow + +1. **Create Vikunja task** — `make create-task -- --title "Title" --description "..."` +2. **Write spec** — Create `docs/specs/.md` (see template below) +3. **Create branch** — `git checkout -b -N-short-description` +4. **Implement** — Write code with `# Implements: REQ-N` comments +5. **Check ACs** — Tick all acceptance criteria checkboxes in the spec +6. **Push and create PR** — `make push-with-pr` +7. **CI validates** — Spec validation, PR size check, fast molecule, lint, tests +8. **Auto-merge** — Add `ready-to-merge` label after review +9. **Auto-deploy** — Post-merge deploys to staging (if nightly gate is green) + +## Spec Template + +```markdown +# : + +## Problem +<What is broken or missing? Why does this change exist?> + +## Approach +<How will you solve it? What are the key design decisions?> + +REQ-1: <First requirement description> +REQ-2: <Second requirement description> +REQ-3: <Third requirement description> + +## Test Plan +- <How will you verify each REQ is implemented correctly?> +- <Include unit tests, molecule scenarios, integration tests> + +## Deploy Plan +- <How will this change be deployed?> +- <What order do components need to deploy in?> +- <Are there migrations or one-time operations?> + +## Rollback Plan +- <How do you revert if something goes wrong?> +- <What data/state changes are irreversible?> + +## Acceptance Criteria +- [ ] REQ-1: <criterion that proves REQ-1 is done> +- [ ] REQ-2: <criterion that proves REQ-2 is done> +- [ ] REQ-3: <criterion that proves REQ-3 is done> +``` + +## CI Validation + +The `devx.ci.validate_spec` module checks: + +1. **Spec file exists** at `docs/specs/<TASK-ID>.md` (TASK-ID from branch name) +2. **Required sections present**: Problem, Approach, Test Plan, Deploy Plan, Rollback Plan, Acceptance Criteria +3. **At least one REQ-ID** line (format: `REQ-N: <description>`) +4. **All AC checkboxes checked** (`- [x]`, not `- [ ]`) + +If any check fails, CI blocks the PR before expensive jobs run. + +## PR Size Limits + +CI enforces max 500 lines / 10 files changed (excluding CHANGELOG.md, +README.md, badges, lock files). Oversized PRs are rejected. Split your +work into smaller PRs. + +## Code-to-Spec Linking + +Each function, task, or template that implements a requirement should +have a comment: + +```python +# Implements: REQ-1 +def install_sso_bridge(): + ... +``` + +```yaml +# Implements: REQ-2 +- name: Clone infra repo + git: + ... +``` + +## Fast Molecule (Pre-merge) + +CI runs molecule only for **changed roles** (detected via git diff), +with converge + verify only, single platform. This gives quick feedback +(~5-10 min) without the full molecule suite. + +## Full Molecule (Nightly) + +The complete molecule suite (all scenarios, all platforms) runs nightly +at 02:00 CET on master. If it fails: +- A Gitea issue is created with the `feedback` label +- The `NIGHTLY_STATUS` repo variable is set to `failed:<run_id>` +- All staging deploys are blocked until nightly passes again + +## Auto-Deploy on Merge + +Every merged PR auto-deploys to staging (if nightly gate is green). +No manual trigger needed. The deploy runs the full pipeline: +provision → deploy-observability → deploy-customer → configure-oidc. + +For grm/sso-bridge: post-merge publishes the package, then auto-creates +an infra PR to bump the pinned version. That infra PR auto-deploys when +merged. + +## Key Commands + +```bash +# Validate spec locally (before pushing) +python -m devx.ci.validate_spec --branch <PREFIX>-N-description + +# Check PR size locally +python -m devx.ci.check_pr_size --base origin/master --head HEAD + +# See which roles need fast molecule +python -m devx.ci.fast_molecule --base origin/master --head HEAD + +# Check nightly gate status +python -m devx.ci.nightly_gate --repo oblachno/infra --action check +``` diff --git a/.devin/skills/testing-and-debugging/SKILL.md b/.devin/skills/testing-and-debugging/SKILL.md index 21c5d85..a6ab9eb 100644 --- a/.devin/skills/testing-and-debugging/SKILL.md +++ b/.devin/skills/testing-and-debugging/SKILL.md @@ -45,6 +45,13 @@ 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. +### Spec-Driven Workflow + +Every PR requires a spec file at `docs/specs/<TASK-ID>.md`. See the +`spec-driven-development` skill for the full workflow and template. +CI validates the spec (via `devx.ci.validate_spec`) and checks PR size +(via `devx.ci.check_pr_size`) before running expensive jobs. + ## CI Failure Investigation When investigating a CI failure: diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index e1eee70..5461407 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -106,14 +106,30 @@ jobs: --pr-title "$PR_TITLE" \ --repo "$REPOSITORY" \ --pr-number "$PR_NUMBER" - - name: Run automated PR review + - name: Validate spec file if: github.event_name == 'pull_request' + env: + DEVX_TASK_PREFIX: DEVX + PYTHONPATH: ${{ env.PYTHONPATH }} + HEAD_REF: ${{ github.head_ref }} run: | . .venv/bin/activate 2>/dev/null || true - set -euo pipefail - python3 -m devx.ci.pr_review \ - "${{ github.event.number }}" \ - "${{ github.repository }}" + python3 -m devx.ci.validate_spec \ + --branch "$HEAD_REF" \ + --github-output + - name: Check PR size + if: github.event_name == 'pull_request' + env: + PYTHONPATH: ${{ env.PYTHONPATH }} + CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }} + run: | + . .venv/bin/activate 2>/dev/null || true + python3 -m devx.ci.check_pr_size \ + --base "origin/master" \ + --head "${{ github.event.pull_request.head.sha || github.sha }}" \ + --repo "${{ github.repository }}" \ + --pr-number "${{ github.event.number }}" \ + --github-output # --- release-dry-run step (conditional) --- - name: Release dry-run validation if: steps.detect.outputs.user-facing-changed == 'true' @@ -165,18 +181,18 @@ jobs: - 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 }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_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)." + # Post APPROVE review via Gitea API to satisfy branch protection + curl -s -X POST \ + "${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}/reviews" \ + -H "Authorization: token ${REVIEWER_GITEA_API_TOKEN}" \ + -H "Content-Type: application/json" \ + -d '{"event":"APPROVED","body":"Auto-approved: all CI checks passed (validate job)."}' \ + || echo "::warning::Failed to post approval review (best-effort)." - name: Squash merge with task ID env: CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }} diff --git a/AGENTS.md b/AGENTS.md index 600f50d..1086e35 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -83,7 +83,6 @@ src/devx/ │ ├── 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) @@ -158,8 +157,37 @@ src/devx/ - **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 + +## Spec-Driven Development + +Every change starts with a spec. No spec, no code. + +**Workflow:** +1. Create Vikunja task → get `<PREFIX>-N` task ID +2. Write spec at `docs/specs/<TASK-ID>.md` (see template in `.devin/skills/spec-driven-development/SKILL.md`) +3. Create branch, implement with `# Implements: REQ-N` comments +4. Tick all acceptance criteria checkboxes in spec +5. Push and create PR — CI validates spec before expensive jobs + +**CI gates (pre-merge):** +- `devx.ci.validate_spec` — checks spec exists, has required sections, REQ-IDs, all ACs checked +- `devx.ci.check_pr_size` — max 500 lines / 10 files (excludes CHANGELOG, badges, locks) +- `devx.ci.fast_molecule` — converge+verify only for changed roles, single platform + +**Nightly (infra only):** +- Full molecule suite (all scenarios, all platforms) + staging deploy + integration tests +- On failure: sets `NIGHTLY_STATUS=failed`, blocks staging deploys +- Post-merge auto-deploy to staging checks this gate before deploying + +**Post-merge:** +- Infra: auto-deploys to staging (if nightly gate is green) +- GRM/sso-bridge: auto-publishes package, auto-creates infra dependency PR to bump pinned version + +**Skill:** `.devin/skills/spec-driven-development/SKILL.md` — full template and workflow details. + ## PR Workflow (Mandatory) + Every change to master goes through this workflow. No exceptions. ### Branch Protection (Required Gitea Settings) @@ -210,7 +238,7 @@ docs: update README ### 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. +review via the `pr-review` skill (agent-invoked, not a CI step). This posts a review with `COMMENT` (no issues) or `REQUEST_CHANGES` (issues found): diff --git a/docs/specs/DEVX-155.md b/docs/specs/DEVX-155.md new file mode 100644 index 0000000..7c77e61 --- /dev/null +++ b/docs/specs/DEVX-155.md @@ -0,0 +1,46 @@ +# DEVX-155: Replace pr_review with spec-driven CI gates and pr-review skill + +## Problem +The `devx.ci.pr_review` module was a monolithic automated PR review tool that +ran in CI and posted COMMENT/REQUEST_CHANGES reviews. It duplicated logic now +better handled by an agent-invoked skill, and it blocked the introduction of +spec-driven development gates (validate_spec, check_pr_size) that should run +before expensive CI jobs. + +## Approach +Remove `pr_review` and replace it with lightweight, focused CI gates plus a +new `pr-review` skill for deep agent-invoked reviews. + +REQ-1: Add `devx.ci.validate_spec` — validates spec file exists, has required sections, REQ-IDs, all ACs checked +REQ-2: Add `devx.ci.check_pr_size` — enforces max 500 lines / 10 files (excludes CHANGELOG, badges, locks) +REQ-3: Add `devx.ci.fast_molecule` — detects changed roles, outputs fast molecule commands (converge+verify, single platform) +REQ-4: Add `devx.ci.nightly_gate` — checks/sets NIGHTLY_STATUS repo variable to block staging deploys on nightly failure +REQ-5: Add `devx.ci.create_dependency_pr` — auto-creates infra PR to bump pinned package version after grm/sso-bridge release +REQ-6: Remove `devx.ci.pr_review` module and `tests/unit/test_pr_review.py` +REQ-7: Update CI workflows to replace pr_review steps with validate_spec + check_pr_size + curl-based APPROVE +REQ-8: Add `spec-driven-development` and `pr-review` skills under `.devin/skills/` +REQ-9: Update AGENTS.md and skill docs to document the new spec-driven workflow + +## Test Plan +- Unit tests for each new module (test_validate_spec, test_check_pr_size, test_fast_molecule, test_nightly_gate, test_create_dependency_pr, test_spec_driven_workflows) +- Remove test_pr_review.py and pr_review references from test_cli.py (pr_review.py deleted from source) +- Verify CI workflow YAML passes actionlint + +## Deploy Plan +- Merge to master via auto-merge workflow +- devx post-merge publishes new version; downstream repos (grm, infra, sso-bridge) bump their devx pin + +## Rollback Plan +- Revert the merge commit; downstream repos keep their current devx pin +- pr_review.py can be restored from git history if needed + +## Acceptance Criteria +- [x] REQ-1: `devx.ci.validate_spec` module exists with `--branch` and `--github-output` options +- [x] REQ-2: `devx.ci.check_pr_size` module exists with `--base`, `--head`, `--github-output` options +- [x] REQ-3: `devx.ci.fast_molecule` module exists and outputs changed roles + commands +- [x] REQ-4: `devx.ci.nightly_gate` module exists with `--action check/set-passed/set-failed` +- [x] REQ-5: `devx.ci.create_dependency_pr` module exists with `--repo`, `--package`, `--new-version` options +- [x] REQ-6: The pr_review CI module and its test file are deleted from source tree +- [x] REQ-7: CI workflow uses validate_spec + check_pr_size + curl APPROVE instead of pr_review +- [x] REQ-8: `.devin/skills/spec-driven-development/SKILL.md` and `.devin/skills/pr-review/SKILL.md` exist +- [x] REQ-9: AGENTS.md documents spec-driven development workflow and pr-review skill diff --git a/src/devx/ci/check_pr_size.py b/src/devx/ci/check_pr_size.py new file mode 100644 index 0000000..cc20ccc --- /dev/null +++ b/src/devx/ci/check_pr_size.py @@ -0,0 +1,211 @@ +#!/usr/bin/env python3 +# Implements: REQ-2 +"""Check PR size and reject oversized PRs. + +Enforces max lines changed and max files changed to keep PRs small +and deployable. Generated/excluded files are not counted. + +PRs with the ``refactoring`` label bypass the size check — large but +legitimate refactoring PRs that touch many files in a coordinated way. + +Usage: + python -m devx.ci.check_pr_size --base origin/master --head HEAD + +In CI, pass ``--github-output`` to set ``pr-size-ok`` and ``pr-size-detail`` +for downstream steps. +""" + +from __future__ import annotations + +import subprocess # nosec B404 + +import click +from dotenv import load_dotenv + +from devx.api_clients import GiteaClient +from devx.ci._shared import write_github_output +from devx.config import GITEA_API_URL +from devx.i18n import _ +from devx.tokens import get_ci_token + +load_dotenv() + +# Files/patterns excluded from size counting (generated, badges, locks, etc.) +DEFAULT_EXCLUDED_PATTERNS = [ + "CHANGELOG.md", + "README.md", + "docs/index.md", + "*.svg", + "uv.lock", + "poetry.lock", + "Pipfile.lock", + "package-lock.json", + "yarn.lock", + "go.sum", +] + +DEFAULT_MAX_LINES = 500 +DEFAULT_MAX_FILES = 10 +REFACTORING_LABEL = "refactoring" + + +def has_refactoring_label(repo: str, pr_number: int) -> bool: + """Check if a PR has the 'refactoring' label (bypasses size check).""" + try: + token = get_ci_token() + owner, repo_name = repo.split("/", 1) + client = GiteaClient(GITEA_API_URL, token, owner, repo_name) + pr = client.get_pr(pr_number) + labels = pr.get("labels", []) + return any(label.get("name") == REFACTORING_LABEL for label in labels) + except Exception: + return False + + +def get_diff_stats(base: str, head: str) -> list[tuple[str, int, int]]: + """Get per-file diff stats (additions, deletions) between base and head. + + Returns a list of (filename, additions, deletions) tuples. + """ + result = subprocess.run( # nosec B603 B607 + ["git", "diff", "--numstat", base, head], + capture_output=True, + text=True, + check=False, + ) + if result.returncode != 0: + raise click.ClickException(_("git diff --numstat failed: {stderr}", stderr=result.stderr.strip())) + stats: list[tuple[str, int, int]] = [] + for line in result.stdout.strip().split("\n"): + if not line: + continue + parts = line.split("\t") + if len(parts) != 3: + continue + additions_s, deletions_s, filename = parts + # Binary files show "-" for additions/deletions + additions = int(additions_s) if additions_s.isdigit() else 0 + deletions = int(deletions_s) if deletions_s.isdigit() else 0 + stats.append((filename, additions, deletions)) + return stats + + +def is_excluded(filename: str, excluded_patterns: list[str]) -> bool: + """Check if a filename matches any excluded pattern.""" + from fnmatch import fnmatch + + return any(fnmatch(filename, pat) for pat in excluded_patterns) + + +def check_size( + stats: list[tuple[str, int, int]], + max_lines: int, + max_files: int, + excluded_patterns: list[str], +) -> tuple[bool, str]: + """Check diff stats against limits. + + Returns (is_ok, detail_message). + """ + included = [(f, a, d) for f, a, d in stats if not is_excluded(f, excluded_patterns)] + total_lines = sum(a + d for _, a, d in included) + total_files = len(included) + + if total_files == 0: + return True, "No non-excluded files changed" + + if total_files > max_files: + return False, _( + "PR has {file_count} files changed (max {max_files}). Excluded: {excluded_count} files.", + file_count=total_files, + max_files=max_files, + excluded_count=len(stats) - total_files, + ) + + if total_lines > max_lines: + return False, _( + "PR has {line_count} lines changed (max {max_lines}). Excluded: {excluded_count} files.", + line_count=total_lines, + max_lines=max_lines, + excluded_count=len(stats) - total_files, + ) + + return True, _( + "PR size OK: {file_count} files, {line_count} lines (max {max_files} files, {max_lines} lines).", + file_count=total_files, + line_count=total_lines, + max_files=max_files, + max_lines=max_lines, + ) + + +@click.command() +@click.option("--base", default="origin/master", help=_("Base ref for diff")) +@click.option("--head", default="HEAD", help=_("Head ref for diff")) +@click.option( + "--max-lines", + type=int, + default=DEFAULT_MAX_LINES, + help=_("Max lines changed (excluded files not counted)"), +) +@click.option( + "--max-files", + type=int, + default=DEFAULT_MAX_FILES, + help=_("Max files changed (excluded files not counted)"), +) +@click.option( + "--github-output", + "github_output", + is_flag=True, + default=False, + help=_("Write results to $GITHUB_OUTPUT"), +) +@click.option( + "--excluded", + "excluded", + multiple=True, + help=_("Additional excluded patterns (in addition to defaults)"), +) +@click.option("--repo", default=None, help=_("Repo (owner/name) for label check")) +@click.option("--pr-number", type=int, default=None, help=_("PR number for label check")) +def cli( + base: str, + head: str, + max_lines: int, + max_files: int, + github_output: bool, + excluded: tuple[str, ...], + repo: str | None, + pr_number: int | None, +) -> None: + """Check PR size and reject oversized PRs.""" + # Check for refactoring label bypass + if repo and pr_number and has_refactoring_label(repo, pr_number): + detail = _("PR has 'refactoring' label — size check bypassed.") + if github_output: + write_github_output("pr-size-ok", "true") + write_github_output("pr-size-detail", detail) + click.echo(f"[pr-size] {detail}") + return + + excluded_patterns = list(DEFAULT_EXCLUDED_PATTERNS) + list(excluded) + stats = get_diff_stats(base, head) + is_ok, detail = check_size(stats, max_lines, max_files, excluded_patterns) + + if github_output: + write_github_output("pr-size-ok", "true" if is_ok else "false") + write_github_output("pr-size-detail", detail) + + if is_ok: + click.echo(f"[pr-size] {detail}") + else: + click.echo(f"[pr-size] FAILED: {detail}", err=True) + click.echo("", err=True) + click.echo("Oversized PRs cannot be reliably reviewed or deployed independently.", err=True) + click.echo("Split your work into smaller PRs, each addressing one concern.", err=True) + raise click.ClickException(_("PR size check failed.")) + + +if __name__ == "__main__": # pragma: no cover + cli() diff --git a/src/devx/ci/create_dependency_pr.py b/src/devx/ci/create_dependency_pr.py new file mode 100644 index 0000000..9e1a196 --- /dev/null +++ b/src/devx/ci/create_dependency_pr.py @@ -0,0 +1,226 @@ +#!/usr/bin/env python3 +# Implements: REQ-5 +"""Auto-create an infra PR to bump a pinned dependency version. + +After grm or sso-bridge publishes a new package version, this module +creates a PR in the infra repo to bump the pinned version in +``pyproject.toml`` or ``ansible/group_vars/all/images.yml``. + +Reuses ``devx.tools.create_pr`` for PR creation and Vikunja task linking. + +Usage: + python -m devx.ci.create_dependency_pr \ + --repo oblachno/infra \ + --package grm \ + --new-version 0.5.2 \ + --source-repo oblachno/grm \ + --source-run-id 12345 +""" + +from __future__ import annotations + +import re +import subprocess # nosec B404 +from pathlib import Path + +import click +from dotenv import load_dotenv + +from devx.api_clients import GiteaClient +from devx.config import GITEA_API_URL, VIKUNJA_API_URL, VIKUNJA_PROJECT_ID +from devx.exceptions import APIError +from devx.i18n import _ +from devx.tokens import get_ci_token, get_vikunja_token +from devx.tools.create_pr import find_existing_pr + +load_dotenv() + +# Where infra pins dependency versions +PYPROJECT_PATH = "pyproject.toml" +IMAGES_YML_PATH = "ansible/group_vars/all/images.yml" + + +def find_pinned_version(package: str, file_path: str) -> str | None: + """Find the currently pinned version of a package in a file. + + Looks for patterns like: + - ``"grm @ git+...@v0.5.1"`` + - ``grm = "0.5.1"`` + - ``grm_version: "0.5.1"`` + - ``grm_image_version: "0.5.1"`` + """ + path = Path(file_path) + if not path.exists(): + return None + content = path.read_text(encoding="utf-8") + # Match various pinning patterns + patterns = [ + rf"{package}\s*@\s*git\+[^@]+@v?([\d.]+)", # pip: package @ git+url@vX.Y.Z + rf'{package}\s*=\s*"([\d.]+)"', # pyproject: package = "X.Y.Z" + rf'{package}_version:\s*"([\d.]+)"', # ansible vars: package_version: "X.Y.Z" + rf'{package}_image_version:\s*"([\d.]+)"', # ansible vars: package_image_version: "X.Y.Z" + ] + for pat in patterns: + match = re.search(pat, content) + if match: + return match.group(1) + return None + + +def update_pinned_version(file_path: str, package: str, old_version: str, new_version: str) -> bool: + """Update the pinned version in a file. Returns True if changed.""" + path = Path(file_path) + if not path.exists(): + return False + content = path.read_text(encoding="utf-8") + # Replace old version with new version in package-related lines + patterns = [ + (rf"({package}\s*@\s*git\+[^@]+@v?){old_version}", rf"\g<1>{new_version}"), + (rf'({package}\s*=\s*"){old_version}(")', rf"\g<1>{new_version}\g<2>"), + (rf'({package}_version:\s*"){old_version}(")', rf"\g<1>{new_version}\g<2>"), + (rf'({package}_image_version:\s*"){old_version}(")', rf"\g<1>{new_version}\g<2>"), + ] + new_content = content + changed = False + for pat, replacement in patterns: + new_content, n = re.subn(pat, replacement, new_content) + if n > 0: + changed = True + if changed: + path.write_text(new_content, encoding="utf-8") + return changed + + +def create_vikunja_task(title: str, description: str) -> str | None: + """Create a Vikunja task and return its identifier (e.g., OBL-INFRA-531).""" + try: + token = get_vikunja_token() + except click.ClickException: + return None + + from devx.api_clients import VikunjaClient + + client = VikunjaClient(VIKUNJA_API_URL, token) + task = client.create_task(VIKUNJA_PROJECT_ID, title=title, description=description) + return str(task.get("identifier", "")) + + +@click.command() +@click.option("--repo", default="oblachno/infra", help=_("Target repo (owner/name) to create PR in")) +@click.option("--package", required=True, help=_("Package name to bump (e.g., grm, sso-bridge)")) +@click.option("--new-version", required=True, help=_("New version to pin")) +@click.option("--source-repo", required=True, help=_("Source repo that published (owner/name)")) +@click.option("--source-run-id", default="", help=_("CI run ID that triggered the publish")) +@click.option("--dry-run", is_flag=True, default=False, help=_("Show what would be done without creating PR")) +def cli( + repo: str, + package: str, + new_version: str, + source_repo: str, + source_run_id: str, + dry_run: bool, +) -> None: + """Create an infra PR to bump a pinned dependency version.""" + token = get_ci_token() + if "/" not in repo: + raise click.ClickException(_("Invalid repo format: {repo}", repo=repo)) + owner, repo_name = repo.split("/", 1) + client = GiteaClient(GITEA_API_URL, token, owner, repo_name) + + # Find current pinned version + old_version = None + changed_file = None + for f in [PYPROJECT_PATH, IMAGES_YML_PATH]: + old_version = find_pinned_version(package, f) + if old_version: + changed_file = f + break + + if not old_version: + click.echo(_("[dep-pr] Could not find pinned version for {pkg} in infra repo.", pkg=package)) + if dry_run: + return + raise click.ClickException(_("Could not find pinned version for {pkg}", pkg=package)) + + if old_version == new_version: + click.echo(_("[dep-pr] {pkg} already at {version} — no PR needed.", pkg=package, version=new_version)) + return + + click.echo( + _( + "[dep-pr] Bumping {pkg} from {old} to {new} in {file}", + pkg=package, + old=old_version, + new=new_version, + file=changed_file, + ) + ) + + if dry_run: + click.echo(f"[dep-pr] DRY RUN: would update {changed_file} and create PR") + return + + # Create a branch + branch_name = f"deps/{package}-{new_version}" + base_branch = "master" + + # Check for existing PR (reuse from tools.create_pr) + existing = find_existing_pr(client, branch_name) + if existing: + click.echo(_("[dep-pr] PR already exists: #{number}", number=existing.get("number", "?"))) + return + + # Create branch via API + try: + master_ref = client._request("GET", "/git/refs/heads/master").json() + master_sha = master_ref.get("object", {}).get("sha", "") + if not master_sha: + raise click.ClickException("Could not get master SHA") + client._request("POST", "/git/refs", json={"ref": f"refs/heads/{branch_name}", "sha": master_sha}) + except APIError as e: + if "already exists" in str(e).lower(): + click.echo(f"[dep-pr] Branch {branch_name} already exists") + else: + raise click.ClickException(_("Failed to create branch: {error}", error=str(e))) from None + + # Clone, update file, commit, push + subprocess.run(["git", "fetch", "origin", f"{branch_name}"], check=False, capture_output=True) # nosec B603 B607 + subprocess.run(["git", "checkout", branch_name], check=False, capture_output=True) # nosec B603 B607 + + if not changed_file or not update_pinned_version(changed_file, package, old_version, new_version): + raise click.ClickException(_("Failed to update {file}", file=changed_file)) + + subprocess.run(["git", "add", changed_file], check=True) # nosec B603 B607 + commit_msg = f"deps: bump {package} from {old_version} to {new_version}" + subprocess.run(["git", "commit", "-m", commit_msg], check=True) # nosec B603 B607 + subprocess.run(["git", "push", "origin", branch_name], check=True) # nosec B603 B607 + + # Create Vikunja task for tracking + task_title = f"Bump {package} to {new_version}" + task_desc = ( + f"<p>Auto-created dependency bump PR.</p>" + f"<p>Package: {package}</p>" + f"<p>Version: {old_version} → {new_version}</p>" + f"<p>Source: {source_repo} (run #{source_run_id})</p>" + ) + task_id = create_vikunja_task(task_title, task_desc) + + # Create PR directly (dependency PRs have custom titles, not Vikunja-derived) + pr_title = f"{task_id}: {task_title}" if task_id else task_title + pr_body = ( + f"## Dependency Bump\n\n" + f"Bumps **{package}** from `{old_version}` to `{new_version}`.\n\n" + f"- **Source**: {source_repo}\n" + f"- **Triggered by**: CI run #{source_run_id}\n" + f"- **Changed file**: `{changed_file}`\n\n" + f"This PR was auto-created by `devx.ci.create_dependency_pr`.\n" + ) + if task_id: + pr_body += f"\nCloses {task_id}" + + pr = client.create_pr(title=pr_title, head=branch_name, base=base_branch, body=pr_body) + click.echo(_("[dep-pr] Created PR #{number}: {title}", number=pr.get("number", "?"), title=pr_title)) + + +if __name__ == "__main__": # pragma: no cover + cli() diff --git a/src/devx/ci/doc_coverage.py b/src/devx/ci/doc_coverage.py index 2063c7b..b11f6a9 100644 --- a/src/devx/ci/doc_coverage.py +++ b/src/devx/ci/doc_coverage.py @@ -44,7 +44,6 @@ REQUIRED_SCRIPTS = [ "auto_merge.py", "release.py", "publish.py", - "pr_review.py", "notify_failure.py", "post_merge.py", "classify_changes.py", diff --git a/src/devx/ci/fast_molecule.py b/src/devx/ci/fast_molecule.py new file mode 100644 index 0000000..19817ff --- /dev/null +++ b/src/devx/ci/fast_molecule.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 +# Implements: REQ-3 +"""Detect changed Ansible roles and output fast molecule test commands. + +Reuses ``devx.molecule.molecule_changed`` for role detection (which handles +playbook→role mapping and shared infrastructure paths). + +Fast molecule = converge + verify only, single platform, no idempotence +check. Used in pre-merge CI to get quick feedback on Ansible changes +without running the full molecule suite (which runs nightly). + +Usage: + python -m devx.ci.fast_molecule --base origin/master --head HEAD + +Outputs the list of changed roles and the molecule commands to run. +In CI, pass ``--github-output`` to set ``fast-molecule-roles`` (space- +separated) and ``fast-molecule-needed`` (true/false) for downstream steps. +""" + +from __future__ import annotations + +from pathlib import Path + +import click +from dotenv import load_dotenv + +from devx.ci._shared import write_github_output +from devx.i18n import _ +from devx.molecule.molecule_changed import detect_changed_roles, get_changed_files + +load_dotenv() + + +def get_molecule_scenarios(role_name: str, roles_dir: str = "ansible/roles") -> list[str]: + """Get list of molecule scenario names for a role.""" + mol_dir = Path(roles_dir) / role_name / "molecule" + if not mol_dir.is_dir(): + return [] + scenarios = [] + for p in mol_dir.iterdir(): + if p.is_dir() and (p / "molecule.yml").exists(): + scenarios.append(p.name) + return sorted(scenarios) + + +def build_molecule_commands( + roles: set[str], + roles_dir: str = "ansible/roles", + platform: str = "ubuntu-2604", +) -> list[str]: + """Build molecule test commands for changed roles. + + For each role, runs each scenario with converge + verify only + (skip create/destroy between scenarios, skip idempotence). + """ + commands: list[str] = [] + for role in sorted(roles): + scenarios = get_molecule_scenarios(role, roles_dir) + if not scenarios: + continue + for scenario in scenarios: + cmd = f"molecule test -s {scenario} --destroy=never --platform-name={platform}" + commands.append(cmd) + return commands + + +@click.command() +@click.option("--base", default="origin/master", help=_("Base ref for diff")) +@click.option("--head", default="HEAD", help=_("Head ref for diff")) +@click.option("--roles-dir", default="ansible/roles", help=_("Directory containing Ansible roles")) +@click.option("--platform", default="ubuntu-2604", help=_("Single platform to test against")) +@click.option( + "--github-output", + "github_output", + is_flag=True, + default=False, + help=_("Write results to $GITHUB_OUTPUT"), +) +def cli( + base: str, + head: str, + roles_dir: str, + platform: str, + github_output: bool, +) -> None: + """Detect changed roles and output fast molecule test commands.""" + # Use molecule_changed for role detection (handles playbooks, shared infra) + files = get_changed_files(base) + if not files: + click.echo("[fast-molecule] No files changed.") + if github_output: + write_github_output("fast-molecule-needed", "false") + write_github_output("fast-molecule-roles", "") + return + + roles = detect_changed_roles(files) + if not roles: + click.echo("[fast-molecule] No Ansible roles changed.") + if github_output: + write_github_output("fast-molecule-needed", "false") + write_github_output("fast-molecule-roles", "") + return + + commands = build_molecule_commands(roles, roles_dir, platform) + + if github_output: + write_github_output("fast-molecule-needed", "true" if commands else "false") + write_github_output("fast-molecule-roles", " ".join(sorted(roles))) + + click.echo(_("[fast-molecule] Changed roles: {roles}", roles=", ".join(sorted(roles)))) + if not commands: + click.echo("[fast-molecule] No molecule scenarios found for changed roles.") + return + + click.echo(f"[fast-molecule] {len(commands)} scenario(s) to run:") + for cmd in commands: + click.echo(f" {cmd}") + + +if __name__ == "__main__": # pragma: no cover + cli() diff --git a/src/devx/ci/nightly_gate.py b/src/devx/ci/nightly_gate.py new file mode 100644 index 0000000..fae7b02 --- /dev/null +++ b/src/devx/ci/nightly_gate.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +# Implements: REQ-4 +"""Check if the nightly CI gate has passed; block staging deploys if it failed. + +The nightly gate stores its status as a Gitea Actions repository variable +named ``NIGHTLY_STATUS`` on the infra repo. Values: + +- ``passed`` — nightly molecule + staging deploy + integration tests passed. +- ``failed:<run_id>`` — nightly failed. Staging deploys are blocked until + the nightly passes again. +- (not set) — nightly hasn't run yet. First deploy is allowed (bootstrap). + +Usage: + python -m devx.ci.nightly_gate --repo oblachno/infra --action check + python -m devx.ci.nightly_gate --repo oblachno/infra --action set-passed --run-id 12345 + python -m devx.ci.nightly_gate --repo oblachno/infra --action set-failed --run-id 12345 +""" + +from __future__ import annotations + +import click +from dotenv import load_dotenv + +from devx.api_clients import GiteaClient +from devx.ci._shared import write_github_output +from devx.config import GITEA_API_URL +from devx.i18n import _ +from devx.tokens import get_ci_token + +load_dotenv() + +NIGHTLY_STATUS_VAR = "NIGHTLY_STATUS" + + +def get_nightly_status(client: GiteaClient) -> str: + """Get the nightly status variable. Returns empty string if not set.""" + val = client.get_repo_variable(NIGHTLY_STATUS_VAR) + return val or "" + + +def set_nightly_status(client: GiteaClient, status: str) -> None: + """Set the nightly status variable.""" + client.set_repo_variable(NIGHTLY_STATUS_VAR, status) + + +@click.command() +@click.option("--repo", required=True, help=_("Repository in owner/name format")) +@click.option( + "--action", + type=click.Choice(["check", "set-passed", "set-failed"]), + required=True, + help=_("Action to perform"), +) +@click.option("--run-id", default="", help=_("CI run ID (for set-failed/set-passed)")) +@click.option( + "--github-output", + "github_output", + is_flag=True, + default=False, + help=_("Write results to $GITHUB_OUTPUT"), +) +def cli(repo: str, action: str, run_id: str, github_output: bool) -> None: + """Check or set the nightly CI gate status.""" + token = get_ci_token() + if "/" not in repo: + raise click.ClickException(_("Invalid repo format: {repo}. Expected owner/name.", repo=repo)) + owner, repo_name = repo.split("/", 1) + client = GiteaClient(GITEA_API_URL, token, owner, repo_name) + + if action == "check": + status = get_nightly_status(client) + if not status: + # Bootstrap: no nightly has run yet, allow deploy + click.echo("[nightly-gate] No nightly status set — allowing deploy (bootstrap).") + if github_output: + write_github_output("nightly-gate-passed", "true") + write_github_output("nightly-status", "") + return + + if status.startswith("passed"): + click.echo("[nightly-gate] Nightly passed. Deploy allowed.") + if github_output: + write_github_output("nightly-gate-passed", "true") + write_github_output("nightly-status", status) + elif status.startswith("failed"): + run_part = status.split(":", 1)[1] if ":" in status else "" + run_link = f" (run #{run_part})" if run_part else "" + click.echo( + _( + "[nightly-gate] Nightly FAILED{run}. Staging deploys are blocked until nightly passes.", + run=run_link, + ), + err=True, + ) + if github_output: + write_github_output("nightly-gate-passed", "false") + write_github_output("nightly-status", status) + raise click.ClickException(_("Nightly gate failed — staging deploy blocked.")) + else: + click.echo(f"[nightly-gate] Unknown nightly status: {status} — allowing deploy.") + if github_output: + write_github_output("nightly-gate-passed", "true") + write_github_output("nightly-status", status) + + elif action == "set-passed": + set_nightly_status(client, f"passed:{run_id}" if run_id else "passed") + click.echo(_("[nightly-gate] Set NIGHTLY_STATUS=passed{run}", run=f":{run_id}" if run_id else "")) + if github_output: + write_github_output("nightly-status", f"passed:{run_id}" if run_id else "passed") + + elif action == "set-failed": + set_nightly_status(client, f"failed:{run_id}" if run_id else "failed") + click.echo(_("[nightly-gate] Set NIGHTLY_STATUS=failed{run}", run=f":{run_id}" if run_id else "")) + if github_output: + write_github_output("nightly-status", f"failed:{run_id}" if run_id else "failed") + + +if __name__ == "__main__": # pragma: no cover + cli() diff --git a/src/devx/ci/pr_review.py b/src/devx/ci/pr_review.py deleted file mode 100644 index 1d79715..0000000 --- a/src/devx/ci/pr_review.py +++ /dev/null @@ -1,715 +0,0 @@ -#!/usr/bin/env python3 -"""Automated PR review: check architecture compliance, best practices, and quality. - -Fetches the PR diff via the Gitea API, runs a series of automated checks, -and posts a structured review using GiteaClient.create_review. - -Checks performed: - 1. Architecture compliance — no business logic in CLI, no direct subprocess - calls outside executor, no hardcoded config that should be in config.py - 2. Best practices — no bare except, no print() (use click.echo), no TODO/FIXME - left in merged code, no functions > 50 lines - 3. Security — no secrets in code, no shell=True, no eval/exec - 4. i18n — no raw English strings in click.echo() without _() wrapper - 5. Resource management — no open() without with statement, no subprocess without cleanup - 6. Documentation — new CLI commands documented, new modules in architecture.md - 7. Test coverage — 100% enforced by pytest-cov (checked in quality job) - 8. Commit conventions — conventional commit format on branch commits - -Usage: - CI_GITEA_API_TOKEN=<token> [REVIEWER_GITEA_API_TOKEN=<token>] python3 -m devx.ci.pr_review <pr_number> <owner/repo> -""" - -from __future__ import annotations - -import os -import re -from dataclasses import dataclass, field -from typing import Any - -import click -from dotenv import load_dotenv # pyright: ignore[reportMissingImports,reportUnknownVariableType] - -from devx.api_clients import GiteaClient -from devx.config import GITEA_API_URL -from devx.exceptions import APIError -from devx.i18n import _ -from devx.tokens import get_ci_token, get_reviewer_token - -load_dotenv() - -# Files that are exempt from certain checks -WORKFLOW_ONLY_SUFFIXES = (".yml", ".yaml", ".md", ".json", ".toml", ".cfg", ".ini", ".txt") -PYTHON_SUFFIX = ".py" - -# Architecture rules -CLI_FILE = "src/devx/cli.py" -EXECUTOR_FILE = "src/devx/executor.py" -CONFIG_FILE = "src/devx/config.py" - -# Patterns that indicate business logic in CLI (should be in runner_manager.py) -BUSINESS_LOGIC_IN_CLI = [ - (r"subprocess\.(run|call|Popen|check_output|check_call)", "subprocess call in CLI — delegate to executor.py"), - (r"\bos\.system\b", "os.system call in CLI — delegate to executor.py"), - (r"\bansible-playbook\b", "ansible-playbook reference in CLI — delegate to executor.py"), -] - -# Patterns that indicate bad practices -BAD_PRACTICES = [ - (r"\bprint\s*\(", "print() found — use click.echo() for user output"), - (r"\beval\s*\(", "eval() found — security risk, avoid dynamic code execution"), - (r"\bexec\s*\(", "exec() found — security risk, avoid dynamic code execution"), - (r"shell\s*=\s*True", "shell=True found — security risk, use shell=False with list args"), - (r"except\s*:", "bare except found — catch specific exceptions"), - (r"except\s+Exception\s*:", "broad Exception catch — catch specific exceptions"), - (r"#\s*(TODO|FIXME|HACK|XXX)", "TODO/FIXME found — resolve before merging"), -] - -# Patterns for hardcoded config values that should be in config.py -HARDCODED_CONFIG = [ - (r"https?://[a-z]+\.[a-z]+\.[a-z]+", "hardcoded URL — move to config.py with env var override"), -] - - -@dataclass -class ReviewResult: - """Result of automated review checks.""" - - issues: list[dict[str, Any]] = field(default_factory=list) - summary: list[str] = field(default_factory=list) - - @property - def has_issues(self) -> bool: - return bool(self.issues) - - def add_issue(self, file_path: str, line: int, message: str, severity: str = "warning") -> None: - self.issues.append( - { - "path": file_path, - "body": f"[{severity}] {message}", - "new_position": line, - } - ) - - def add_summary(self, text: str) -> None: - self.summary.append(text) - - -def is_python_file(path: str) -> bool: - """Check if a file is a Python source file.""" - return path.endswith(PYTHON_SUFFIX) and not path.startswith("tests/") - - -def is_workflow_only(path: str) -> bool: - """Check if a file is workflow/config/docs only (not Python source).""" - return path.endswith(WORKFLOW_ONLY_SUFFIXES) or path.startswith((".gitea/", "docs/", "ansible/")) - - -def check_architecture_compliance(files: list[dict[str, Any]], result: ReviewResult) -> None: - """Check that changes follow the documented architecture.""" - for f in files: - path = f.get("filename", "") - if not is_python_file(path): - continue - - patch = f.get("patch", "") - if not patch: - continue - - lines = patch.split("\n") - current_line = 0 - for line in lines: - if line.startswith("@@"): - match = re.search(r"\+(\d+)", line) - if match: - current_line = int(match.group(1)) - 1 - continue - if line.startswith("+") and not line.startswith("+++"): - current_line += 1 - content = line[1:] - - # Check for business logic in CLI - if path == CLI_FILE: - for pattern, msg in BUSINESS_LOGIC_IN_CLI: - if re.search(pattern, content): - result.add_issue(path, current_line, msg, "error") - - if not result.issues: - result.add_summary("- Architecture compliance: OK") - - -def check_best_practices(files: list[dict[str, Any]], result: ReviewResult) -> None: - """Check for common code quality issues.""" - for f in files: - path = f.get("filename", "") - if not is_python_file(path): - continue - - patch = f.get("patch", "") - if not patch: - continue - - lines = patch.split("\n") - current_line = 0 - for line in lines: - if line.startswith("@@"): - match = re.search(r"\+(\d+)", line) - if match: - current_line = int(match.group(1)) - 1 - continue - if line.startswith("+") and not line.startswith("+++"): - current_line += 1 - content = line[1:] - - for pattern, msg in BAD_PRACTICES: - if re.search(pattern, content): - result.add_issue(path, current_line, msg, "warning") - - if not any(i["body"].startswith("[warning]") for i in result.issues): - result.add_summary("- Best practices: OK") - - -def check_security(files: list[dict[str, Any]], result: ReviewResult) -> None: - """Check for security issues in changed files.""" - for f in files: - path = f.get("filename", "") - if not is_python_file(path): - continue - - patch = f.get("patch", "") - if not patch: - continue - - lines = patch.split("\n") - current_line = 0 - for line in lines: - if line.startswith("@@"): - match = re.search(r"\+(\d+)", line) - if match: - current_line = int(match.group(1)) - 1 - continue - if line.startswith("+") and not line.startswith("+++"): - current_line += 1 - content = line[1:] - - # Check for hardcoded secrets - secret_re = r'(token|password|secret|key)\s*=\s*["\'][^"\']{8,}["\']' # nosec B105 - is_secret = re.search(secret_re, content, re.IGNORECASE) - is_comment = content.strip().startswith("#") - is_example = "your-" in content or "example" in content - if is_secret and not is_comment and not is_example: - result.add_issue( - path, - current_line, - "potential hardcoded secret — use environment variable", - "error", - ) - - if not any(i["body"].startswith("[error]") and "secret" in i["body"] for i in result.issues): - result.add_summary("- Security: OK") - - -def check_i18n(files: list[dict[str, Any]], result: ReviewResult) -> None: - """Check that user-facing strings are wrapped in _(). - - Detects ``click.echo()`` calls with raw string literals that are not - wrapped in ``_()``. Only checks ``src/`` files, not tests or scripts. - """ - # Pattern: click.echo("...") or click.echo(f"...") without _() wrapper - raw_echo_re = re.compile(r'click\.echo\s*\(\s*["\']([^"\']+)["\']') - raw_fstring_re = re.compile(r'click\.echo\s*\(\s*f["\']') - # Also check click.ClickException and raise with string - raw_exception_re = re.compile(r'click\.ClickException\s*\(\s*["\']([^"\']+)["\']') - - for f in files: - path = f.get("filename", "") - if not is_python_file(path) or not path.startswith("src/"): - continue - - patch = f.get("patch", "") - if not patch: - continue - - lines = patch.split("\n") - current_line = 0 - for line in lines: - if line.startswith("@@"): - match = re.search(r"\+(\d+)", line) - if match: - current_line = int(match.group(1)) - 1 - continue - if line.startswith("+") and not line.startswith("+++"): - current_line += 1 - content = line[1:] - - # Skip comments and docstrings - stripped = content.strip() - if stripped.startswith("#") or stripped.startswith('"""') or stripped.startswith("'''"): - continue - - # Check for raw strings in click.echo without _() - for regex, msg in [ - (raw_echo_re, "click.echo() with raw string — wrap in _() for i18n"), - (raw_fstring_re, "click.echo() with f-string — wrap in _() for i18n"), - (raw_exception_re, "ClickException with raw string — wrap in _() for i18n"), - ]: - if regex.search(content): - result.add_issue(path, current_line, msg, "warning") - - if not any("i18n" in i["body"] for i in result.issues): - result.add_summary("- i18n: OK") - - -def check_resource_management(files: list[dict[str, Any]], result: ReviewResult) -> None: - """Check for resource leaks: open() without with, subprocess without cleanup. - - Detects: - - ``open()`` calls not in a ``with`` statement - - ``subprocess.Popen()`` without ``.wait()`` or ``.communicate()`` - """ - # Pattern: open("...") not preceded by "with" on the same line - open_re = re.compile(r"(?<!with\s)\bopen\s*\(") - popen_re = re.compile(r"subprocess\.Popen\s*\(") - - for f in files: - path = f.get("filename", "") - if not is_python_file(path): - continue - - patch = f.get("patch", "") - if not patch: - continue - - lines = patch.split("\n") - current_line = 0 - for line in lines: - if line.startswith("@@"): - match = re.search(r"\+(\d+)", line) - if match: - current_line = int(match.group(1)) - 1 - continue - if line.startswith("+") and not line.startswith("+++"): - current_line += 1 - content = line[1:] - - # Skip comments - if content.strip().startswith("#"): - continue - - # Check for open() without with - if open_re.search(content) and "with " not in content: - result.add_issue( - path, current_line, "open() without with statement — potential resource leak", "warning" - ) - - # Check for Popen without communicate/wait on same line - if popen_re.search(content) and ".communicate" not in content and ".wait" not in content: - result.add_issue( - path, - current_line, - "subprocess.Popen() without immediate .communicate() or .wait() — ensure cleanup", - "warning", - ) - - if not any("resource" in i["body"].lower() for i in result.issues): - result.add_summary("- Resource management: OK") - - -def check_function_length(files: list[dict[str, Any]], result: ReviewResult) -> None: - """Check that no new function is excessively long (> 50 lines).""" - for f in files: - path = f.get("filename", "") - if not is_python_file(path): - continue - - patch = f.get("patch", "") - if not patch: - continue - - # Count consecutive added lines within a function - lines = patch.split("\n") - current_line = 0 - func_start = 0 - func_name = "" - added_in_func = 0 - - for line in lines: - if line.startswith("@@"): - if func_name and added_in_func > 50: - result.add_issue( - path, - func_start, - f"function '{func_name}' adds {added_in_func} lines — consider splitting (> 50 lines)", - "warning", - ) - match = re.search(r"\+(\d+)", line) - if match: - current_line = int(match.group(1)) - 1 - func_name = "" - added_in_func = 0 - continue - if line.startswith("+") and not line.startswith("+++"): - current_line += 1 - content = line[1:] - func_match = re.match(r"\s*def\s+(\w+)\s*\(", content) - if func_match: - if func_name and added_in_func > 50: - result.add_issue( - path, - func_start, - f"function '{func_name}' adds {added_in_func} lines — consider splitting (> 50 lines)", - "warning", - ) - func_name = func_match.group(1) - func_start = current_line - added_in_func = 0 - else: - added_in_func += 1 - elif line.startswith(" ") or line.startswith("-"): - pass # context or removed line - - # Check last function - if func_name and added_in_func > 50: - result.add_issue( - path, - func_start, - f"function '{func_name}' adds {added_in_func} lines — consider splitting (> 50 lines)", - "warning", - ) - - -def check_documentation(files: list[dict[str, Any]], result: ReviewResult) -> None: - """Check that documentation is updated for relevant changes.""" - has_src_changes = any( - is_python_file(f.get("filename", "")) and f.get("filename", "").startswith("src/") for f in files - ) - has_doc_changes = any( - f.get("filename", "").startswith("docs/") or f.get("filename", "") in ("README.md", "AGENTS.md", "CHANGELOG.md") - for f in files - ) - has_ansible_changes = any(f.get("filename", "").startswith("ansible/") for f in files) - has_tofu_changes = any(f.get("filename", "").startswith("tofu/") for f in files) - has_workflow_changes = any(f.get("filename", "").startswith(".gitea/") for f in files) - - # Check for TODO/FIXME in changed docs - todo_issues: list[str] = [] - for f in files: - filename = f.get("filename", "") - if filename.endswith(".md") and filename.startswith(("docs/", "README", "AGENTS")): - # Can't check file content from PR API easily, but flag if patch adds TODO - patch = f.get("patch", "") - if patch and re.search(r"^\+.*\b(TODO|FIXME|HACK|XXX)\b", patch, re.IGNORECASE): - todo_issues.append(f"{filename}: new TODO/FIXME added in documentation") - - if has_src_changes and not has_doc_changes: - result.add_summary("- Documentation: WARNING — source files changed but no docs updated") - elif has_ansible_changes and not has_doc_changes: - result.add_summary("- Documentation: WARNING — Ansible role changed but no docs updated") - elif has_tofu_changes and not has_doc_changes: - result.add_summary("- Documentation: WARNING — OpenTofu changes but no docs updated") - elif has_workflow_changes and not has_doc_changes: - result.add_summary("- Documentation: INFO — workflow changes (consider updating CI docs if behavior changed)") - else: - result.add_summary("- Documentation: OK") - - if todo_issues: - for issue in todo_issues: - result.add_summary(f"- Documentation: WARNING — {issue}") - - -def check_test_coverage(files: list[dict[str, Any]], result: ReviewResult) -> None: - """Check that tests are updated for source changes.""" - has_src_changes = any( - is_python_file(f.get("filename", "")) and f.get("filename", "").startswith("src/") for f in files - ) - has_test_changes = any(f.get("filename", "").startswith("tests/") for f in files) - - if has_src_changes and not has_test_changes: - result.add_summary("- Tests: WARNING — source files changed but no test files updated") - else: - result.add_summary("- Tests: OK") - - -def check_commit_conventions(client: GiteaClient, pr_number: str, result: ReviewResult) -> None: - """Check that PR commits follow conventional commit format. - - Verifies that at least one commit on the PR branch matches the - conventional commit pattern (type: description). Merge commits - and revert commits are exempt. - """ - try: - commits = client.get_pr_commits(pr_number) - except APIError as e: - result.add_summary(f"- Commit conventions: ERROR — could not fetch commits: {e.message}") - return - - if not commits: - result.add_summary("- Commit conventions: OK (no commits to check)") - return - - from devx.config import CONVENTIONAL_RE - - has_conventional = False - non_conventional: list[str] = [] - - for commit in commits: - commit_info = commit.get("commit", {}) - message = str(commit_info.get("message", "") if isinstance(commit_info, dict) else "").split("\n")[0] - # Skip merge commits and revert commits - if message.startswith(("Merge", "Revert")): - continue - if CONVENTIONAL_RE.match(message): - has_conventional = True - else: - non_conventional.append(message[:60]) - - if has_conventional: - result.add_summary("- Commit conventions: OK") - elif non_conventional: - result.add_summary( - f"- Commit conventions: WARNING — no conventional commit found. " - f"Non-conventional commits: {', '.join(non_conventional[:3])}" - ) - else: - result.add_summary("- Commit conventions: OK (all commits are merges/reverts)") - - -def run_review(client: GiteaClient, pr_number: str) -> ReviewResult: - """Run all review checks and return the result.""" - result = ReviewResult() - - try: - files = client.get_pr_files(pr_number) - except APIError as e: - result.add_summary(f"- ERROR: Could not fetch PR files: {e.message}") - return result - - if not files: - result.add_summary("- No files changed in this PR") - return result - - # Run all checks - check_architecture_compliance(files, result) - check_best_practices(files, result) - check_security(files, result) - check_i18n(files, result) - check_resource_management(files, result) - check_function_length(files, result) - check_documentation(files, result) - check_test_coverage(files, result) - check_commit_conventions(client, pr_number, result) - - return result - - -def build_review_body(result: ReviewResult) -> str: - """Build the review body text from the review result.""" - lines = ["## Automated PR Review", ""] - - for item in result.summary: - lines.append(item) - - if result.issues: - lines.append("") - lines.append(f"**{len(result.issues)} issue(s) found:**") - lines.append("") - for issue in result.issues: - lines.append(f"- `{issue['path']}:{issue['new_position']}` — {issue['body']}") - else: - lines.append("") - lines.append("No issues found by automated checks.") - - lines.append("") - lines.append("---") - lines.append("**Auto-merge:** If all CI checks pass, this PR will be merged automatically.") - - return "\n".join(lines) - - -def post_review(client: GiteaClient, pr_number: str, result: ReviewResult) -> dict[str, Any]: - """Post the review to the PR. - - Uses REQUEST_CHANGES when issues are found, COMMENT otherwise. - Never uses APPROVE — the bot shares the PR author's token, so - Gitea rejects self-approval. The actual APPROVE must come from - the manual review step. - """ - body = build_review_body(result) - event = "REQUEST_CHANGES" if result.has_issues else "COMMENT" - comments = result.issues if result.has_issues else [] - - return client.create_review(pr_number, event=event, body=body, comments=comments) - - -def _post_manual_review( - client: GiteaClient, - pr_number: str, - event: str, - body: str | None, - checklist_confirmed: bool, - checklist_categories: str | None, - dry_run: bool, - owner: str | None = None, - repo_name: str | None = None, -) -> None: - """Post a manual review with validation for APPROVE events. - - When self-approval is rejected (reviewer token belongs to PR author), - falls back to the CI token (different user) if available. - """ - if not body or len(body) < 50: - raise click.ClickException(_("Review body must be at least 50 characters.")) - - if event == "APPROVE": - if not checklist_confirmed: - raise click.ClickException( - _("--checklist-confirmed is required for APPROVE events."), - ) - cats = [c.strip() for c in (checklist_categories or "").split(",") if c.strip()] - cat_nums: list[int] = [] - for c in cats: - try: - cat_nums.append(int(c)) - except ValueError: - raise click.ClickException( - _("Invalid checklist category: {cat}. Must be numbers.", cat=c), - ) from None - if len(cat_nums) < 8: - raise click.ClickException( - _("--checklist-categories must list at least 8 of 13 categories. Got {count}.", count=len(cat_nums)), - ) - - click.echo(f"Manual review event: {event}") - click.echo(f"Body: {body[:80]}...") - if checklist_confirmed: - click.echo(f"Checklist confirmed: {checklist_categories}") - - if dry_run: - click.echo("\n[dry-run] Review not posted.") - return - - try: - review = client.create_review(pr_number, event=event, body=body) - except APIError as e: - if "approve" in e.message.lower() or "422" in str(e.status): - # Self-approval not allowed (reviewer token belongs to PR author). - # Fall back to CI token (different user) if available. - ci_token = os.environ.get("CI_GITEA_API_TOKEN", "").strip() - if ci_token and owner and repo_name: - click.echo(_("Note: Self-approval not allowed with reviewer token. Retrying with CI token.")) - ci_client = GiteaClient(GITEA_API_URL, ci_token, owner, repo_name) - try: - review = ci_client.create_review(pr_number, event=event, body=body) - except APIError: - click.echo(_("Note: CI token also cannot approve. Posting COMMENT instead.")) - review = client.create_review(pr_number, event="COMMENT", body=body) - else: - click.echo(_("Note: Self-approval not allowed. Posting COMMENT instead.")) - review = client.create_review(pr_number, event="COMMENT", body=body) - else: - raise - review_id = review.get("id", "?") - click.echo( - _( - "\nReview #{review_id} posted on PR #{pr_number} with event '{event}'.", - review_id=review_id, - pr_number=pr_number, - event=event, - ) - ) - - -@click.command() -@click.argument("pr_number") -@click.argument("repo") -@click.option("--dry-run", is_flag=True, default=False, help="Print review without posting.") -@click.option( - "--event", - type=click.Choice(["APPROVE", "REQUEST_CHANGES", "COMMENT"], case_sensitive=False), - default=None, - help="Post a manual review with the given event (skips automated checks).", -) -@click.option("--body", default=None, help="Review body text (required with --event).") -@click.option( - "--checklist-confirmed", - is_flag=True, - default=False, - help="Attest that REVIEW_CHECKLIST.md categories were checked (required for APPROVE).", -) -@click.option( - "--checklist-categories", - default=None, - help="Comma-separated checklist category numbers (required for APPROVE, min 8 of 13).", -) -def main( - pr_number: str, - repo: str, - dry_run: bool, - event: str | None, - body: str | None, - checklist_confirmed: bool, - checklist_categories: str | None, -) -> None: - """Run automated PR review and post results to Gitea. - - Without --event: runs automated checks and posts COMMENT/REQUEST_CHANGES. - With --event: posts a manual review (skips automated checks). - """ - try: - token = get_reviewer_token() if (event and event.upper() == "APPROVE") else get_ci_token() - except click.ClickException: - raise click.ClickException(_("ERROR: CI_GITEA_TOKEN is not set.")) from None - - owner, repo_name = repo.split("/") - client = GiteaClient(GITEA_API_URL, token, owner, repo_name) - - if event is not None: - _post_manual_review( - client, - pr_number, - event.upper(), - body, - checklist_confirmed, - checklist_categories, - dry_run, - owner=owner, - repo_name=repo_name, - ) - return - - result = run_review(client, pr_number) - - body = build_review_body(result) - event = "REQUEST_CHANGES" if result.has_issues else "COMMENT" - - click.echo(f"Review event: {event}") - click.echo(f"Issues found: {len(result.issues)}") - click.echo("") - click.echo(body) - - if dry_run: - click.echo("\n[dry-run] Review not posted.") - return - - try: - review = post_review(client, pr_number, result) - except APIError as e: - if "approve" in e.message.lower() or "422" in str(e.status): - click.echo(_("Note: Self-approval not allowed. Posting COMMENT instead.")) - review = client.create_review(pr_number, event="COMMENT", body=body) - else: - raise - review_id = review.get("id", "?") - click.echo( - _( - "\nReview #{review_id} posted on PR #{pr_number} with event '{event}' ({num_comments} inline comments).", - review_id=review_id, - pr_number=pr_number, - event=event, - num_comments=len(result.issues), - ) - ) - - -if __name__ == "__main__": # pragma: no cover - main() diff --git a/src/devx/ci/validate_spec.py b/src/devx/ci/validate_spec.py new file mode 100644 index 0000000..96acaaa --- /dev/null +++ b/src/devx/ci/validate_spec.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python3 +# Implements: REQ-1 +"""Validate that a PR has a spec file with required sections and acceptance criteria. + +Spec-driven development gate. Runs in CI before expensive jobs. + +Validates: +1. A spec file exists at ``docs/specs/<TASK-ID>.md`` (TASK-ID extracted from branch). +2. The spec contains required sections: Problem, Approach, Test Plan, Deploy Plan, Rollback Plan. +3. The spec contains REQ-ID lines (``REQ-N: <description>``). +4. The spec contains an Acceptance Criteria checklist with at least one item. +5. All acceptance criteria checkboxes are checked (``- [x]``). + +Usage: + python -m devx.ci.validate_spec --branch OBL-INFRA-531-fix-foo + +In CI, also pass ``--github-output`` to set ``spec-valid`` and ``spec-path`` +for downstream steps. +""" + +from __future__ import annotations + +import re +from pathlib import Path + +import click +from dotenv import load_dotenv + +from devx.ci._shared import extract_task_id, write_github_output +from devx.i18n import _ + +load_dotenv() + +REQUIRED_SECTIONS = [ + "## Problem", + "## Approach", + "## Test Plan", + "## Deploy Plan", + "## Rollback Plan", + "## Acceptance Criteria", +] + +REQ_ID_RE = re.compile(r"^REQ-\d+:\s+.+", re.MULTILINE) +AC_CHECKED_RE = re.compile(r"^\s*- \[x\]\s+.+", re.MULTILINE) +AC_UNCHECKED_RE = re.compile(r"^\s*- \[ \]\s+.+", re.MULTILINE) + + +def find_spec_file(task_id: str, specs_dir: str = "docs/specs") -> Path | None: + """Find the spec file for the given task ID. + + Looks for ``docs/specs/<TASK-ID>.md`` (case-insensitive filename). + Returns the Path if found, None otherwise. + """ + base = Path(specs_dir) + if not base.is_dir(): + return None + # Exact match (case-insensitive) + for p in base.glob("*.md"): + if p.stem.upper() == task_id.upper(): + return p + return None + + +def validate_spec_content(content: str) -> list[str]: + """Validate spec content and return a list of error messages. + + Returns an empty list if the spec is valid. + """ + errors: list[str] = [] + + # Check required sections + for section in REQUIRED_SECTIONS: + if section not in content: + errors.append(_("Missing required section: {section}", section=section)) + + # Check for at least one REQ-ID + req_ids = REQ_ID_RE.findall(content) + if not req_ids: + errors.append(_("No REQ-ID lines found. Each requirement must be labeled (e.g., 'REQ-1: <description>').")) + + # Check acceptance criteria has at least one item + checked = AC_CHECKED_RE.findall(content) + unchecked = AC_UNCHECKED_RE.findall(content) + if not checked and not unchecked: + errors.append(_("Acceptance Criteria section has no checklist items. Add at least one '- [ ] item'.")) + elif unchecked: + errors.append( + _( + "Acceptance Criteria has {count} unchecked item(s). All AC items must be checked (- [x]) before merge.", + count=len(unchecked), + ) + ) + + return errors + + +@click.command() +@click.option("--branch", required=True, help=_("Branch name (e.g., OBL-INFRA-531-fix-foo)")) +@click.option("--specs-dir", default="docs/specs", help=_("Directory containing spec files")) +@click.option( + "--github-output", + "github_output", + is_flag=True, + default=False, + help=_("Write results to $GITHUB_OUTPUT"), +) +@click.option("--allow-missing", is_flag=True, default=False, help=_("Allow missing spec (warn only, don't fail)")) +def cli(branch: str, specs_dir: str, github_output: bool, allow_missing: bool) -> None: + """Validate that a spec file exists and has required content.""" + task_id = extract_task_id(branch) + if not task_id: + msg = _("No task ID found in branch name '{branch}'. Expected format: <PREFIX>-N-description.", branch=branch) + if allow_missing: + click.echo(f"WARNING: {msg}") + if github_output: + write_github_output("spec-valid", "false") + write_github_output("spec-path", "") + return + raise click.ClickException(msg) + + spec_path = find_spec_file(task_id, specs_dir) + if spec_path is None: + msg = _( + "No spec file found for task {task_id} in {dir}/. Expected: {dir}/{task_id}.md", + task_id=task_id, + dir=specs_dir, + ) + if allow_missing: + click.echo(f"WARNING: {msg}") + if github_output: + write_github_output("spec-valid", "false") + write_github_output("spec-path", "") + return + raise click.ClickException(msg) + + content = spec_path.read_text(encoding="utf-8") + errors = validate_spec_content(content) + + if github_output: + write_github_output("spec-valid", "true" if not errors else "false") + write_github_output("spec-path", str(spec_path)) + + if errors: + click.echo("", err=True) + click.echo("=" * 60, err=True) + click.echo(f"Spec validation FAILED for {spec_path}:", err=True) + click.echo("=" * 60, err=True) + for e in errors: + click.echo(f" - {e}", err=True) + raise click.ClickException(_("Spec validation failed.")) + + click.echo(_("[spec-check] Spec validated: {path}", path=spec_path)) + + +if __name__ == "__main__": # pragma: no cover + cli() diff --git a/src/devx/cli.py b/src/devx/cli.py index 758e33f..dd019a1 100644 --- a/src/devx/cli.py +++ b/src/devx/cli.py @@ -116,13 +116,6 @@ def ci_post_merge(args: tuple[str, ...]) -> None: _run_module("devx.ci.post_merge", list(args)) -@ci.command("pr-review") -@click.argument("args", nargs=-1) -def ci_pr_review(args: tuple[str, ...]) -> None: - """Run automated PR review.""" - _run_module("devx.ci.pr_review", list(args)) - - @ci.command("publish") @click.argument("args", nargs=-1) def ci_publish(args: tuple[str, ...]) -> None: diff --git a/src/devx/make/devx.mak b/src/devx/make/devx.mak index 828ac8e..8e66123 100644 --- a/src/devx/make/devx.mak +++ b/src/devx/make/devx.mak @@ -109,7 +109,7 @@ devx-ensure-venv: fi .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 devx-pr-review devx-rebase devx-pr-rebase +.PHONY: devx-pr-status devx-pr-logs devx-pr-label devx-rebase devx-pr-rebase .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 devx-venv devx-ensure-venv @@ -171,16 +171,6 @@ devx-pr-label: $(if $(PR),--pr $(PR)) \ --label $(or $(LABEL),ready-to-merge) -# Usage: make devx-pr-review PR=42 EVENT=APPROVE BODY="..." CHECKLIST=1,2,3,4,5,6,7,8,9,10,11,12,13 -# make devx-pr-review PR=42 EVENT=REQUEST_CHANGES BODY="..." -# make devx-pr-review PR=42 (auto review) -devx-pr-review: - @$(DEVX_PYTHON) -m devx.ci.pr_review \ - $(PR) $(DEVX_REPO_OWNER)/$(DEVX_REPO_NAME) \ - $(if $(EVENT),--event $(EVENT)) \ - $(if $(BODY),--body "$(BODY)") \ - $(if $(CHECKLIST),--checklist-confirmed --checklist-categories $(CHECKLIST)) - # Rebase current branch onto origin/master and force-push # Usage: make devx-rebase # make devx-rebase NO_PUSH=1 diff --git a/src/devx/tokens.py b/src/devx/tokens.py index 25110aa..6ea30db 100644 --- a/src/devx/tokens.py +++ b/src/devx/tokens.py @@ -2,19 +2,16 @@ Centralizes Gitea/Vikunja token discovery with role-based environment variable names and backwards compatibility with the legacy -``CI_GITEA_TOKEN`` / ``REVIEW_GITEA_TOKEN`` naming convention. +``CI_GITEA_TOKEN`` naming convention. Roles: - ``CI_GITEA_API_TOKEN``: CI workflows (read actions, post status, merge, etc.) -- ``REVIEWER_GITEA_API_TOKEN``: PR approval reviews (must be a different user - from the PR author for Gitea to accept the review as an approval) - ``DEVELOPER_GITEA_API_TOKEN``: local development tools (create-task, create-pr, setup, etc.) Fallbacks: - New role names are checked first. -- Legacy names (``CI_GITEA_TOKEN``, ``REVIEW_GITEA_TOKEN``) are accepted for - backwards compatibility. +- Legacy names (``CI_GITEA_TOKEN``) are accepted for backwards compatibility. - If no role-specific token is set, the generic CI tokens are tried last. """ @@ -28,12 +25,6 @@ from devx.i18n import _ # Token environment variable names, in lookup priority order. CI_TOKEN_NAMES = ["CI_GITEA_API_TOKEN", "CI_GITEA_TOKEN"] -REVIEWER_TOKEN_NAMES = [ - "REVIEWER_GITEA_API_TOKEN", - # Legacy name used before role-based tokens. - "REVIEW_GITEA_TOKEN", - *CI_TOKEN_NAMES, -] DEVELOPER_TOKEN_NAMES = ["DEVELOPER_GITEA_API_TOKEN", *CI_TOKEN_NAMES] VIKUNJA_TOKEN_NAMES = ["VIKUNJA_TOKEN"] @@ -61,11 +52,6 @@ def get_ci_token() -> str: return get_token(*CI_TOKEN_NAMES) -def get_reviewer_token() -> str: - """Resolve the reviewer Gitea API token used for PR approvals.""" - return get_token(*REVIEWER_TOKEN_NAMES) - - def get_developer_token() -> str: """Resolve the developer Gitea API token used for local tooling.""" return get_token(*DEVELOPER_TOKEN_NAMES) diff --git a/src/devx/translations.json b/src/devx/translations.json index dc3f151..06f356c 100644 --- a/src/devx/translations.json +++ b/src/devx/translations.json @@ -5,7 +5,9 @@ "en": "\n=== Summary ===", "pl": "\n=== Podsumowanie ===", "ru": "\n=== Summary ===", - "zh": "\n=== Summary ===" + "zh": "\n=== Summary ===", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" }, "\nAll documentation coverage checks passed!": { "bg": "\nAll documentation coverage checks passed!", @@ -13,7 +15,9 @@ "en": "\nAll documentation coverage checks passed!", "pl": "\nWszystkie kontrole pokrycia dokumentacji zakończone pomyślnie!", "ru": "\nAll documentation coverage checks passed!", - "zh": "\nAll documentation coverage checks passed!" + "zh": "\nAll documentation coverage checks passed!", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" }, "\nCHANGELOG version ordering:": { "bg": "\nCHANGELOG version ordering:", @@ -21,7 +25,9 @@ "en": "\nCHANGELOG version ordering:", "pl": "\nKolejność wersji w CHANGELOG:", "ru": "\nCHANGELOG version ordering:", - "zh": "\nCHANGELOG version ordering:" + "zh": "\nCHANGELOG version ordering:", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" }, "\nChecking CI script documentation in ci-cd-workflow.md...": { "bg": "\nChecking CI script documentation in ci-cd-workflow.md...", @@ -29,7 +35,9 @@ "en": "\nChecking CI script documentation in ci-cd-workflow.md...", "pl": "\nSprawdzanie dokumentacji skryptów CI w ci-cd-workflow.md...", "ru": "\nChecking CI script documentation in ci-cd-workflow.md...", - "zh": "\nChecking CI script documentation in ci-cd-workflow.md..." + "zh": "\nChecking CI script documentation in ci-cd-workflow.md...", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" }, "\nChecking module documentation in architecture.md...": { "bg": "\nChecking module documentation in architecture.md...", @@ -37,7 +45,9 @@ "en": "\nChecking module documentation in architecture.md...", "pl": "\nSprawdzanie dokumentacji modułów w architecture.md...", "ru": "\nChecking module documentation in architecture.md...", - "zh": "\nChecking module documentation in architecture.md..." + "zh": "\nChecking module documentation in architecture.md...", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" }, "\nDoc coverage: {covered}/{total} ({pct}%)": { "bg": "\nDoc coverage: {covered}/{total} ({pct}%)", @@ -45,7 +55,9 @@ "en": "\nDoc coverage: {covered}/{total} ({pct}%)", "pl": "\nPokrycie dokumentacji: {covered}/{total} ({pct}%)", "ru": "\nDoc coverage: {covered}/{total} ({pct}%)", - "zh": "\nDoc coverage: {covered}/{total} ({pct}%)" + "zh": "\nDoc coverage: {covered}/{total} ({pct}%)", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" }, "\nDone! Synced: {synced}, Pruned: {pruned}": { "bg": "", @@ -53,7 +65,9 @@ "en": "\nDone! Synced: {synced}, Pruned: {pruned}", "pl": "", "ru": "", - "zh": "" + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" }, "\nDone. Deleted {deleted}, kept {kept}, failed {failed}.": { "bg": "\nDone. Deleted {deleted}, kept {kept}, failed {failed}.", @@ -61,7 +75,9 @@ "en": "\nDone. Deleted {deleted}, kept {kept}, failed {failed}.", "pl": "\nDone. Deleted {deleted}, kept {kept}, failed {failed}.", "ru": "\nDone. Deleted {deleted}, kept {kept}, failed {failed}.", - "zh": "\nDone. Deleted {deleted}, kept {kept}, failed {failed}." + "zh": "\nDone. Deleted {deleted}, kept {kept}, failed {failed}.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" }, "\nERROR: Documentation coverage is not 100%. Use --fail-on-missing to enforce.": { "bg": "\nERROR: Documentation coverage is not 100%. Use --fail-on-missing to enforce.", @@ -69,7 +85,9 @@ "en": "\nERROR: Documentation coverage is not 100%. Use --fail-on-missing to enforce.", "pl": "\nBŁĄD: Pokrycie dokumentacji nie wynosi 100%. Użyj --fail-on-missing, aby to wymusić.", "ru": "\nERROR: Documentation coverage is not 100%. Use --fail-on-missing to enforce.", - "zh": "\nERROR: Documentation coverage is not 100%. Use --fail-on-missing to enforce." + "zh": "\nERROR: Documentation coverage is not 100%. Use --fail-on-missing to enforce.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" }, "\nFAIL: {n} stale version reference(s) found:": { "bg": "", @@ -77,7 +95,9 @@ "en": "\nFAIL: {n} stale version reference(s) found:", "pl": "", "ru": "", - "zh": "" + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" }, "\nFix the misaligned tags before creating new releases. Run 'python3 -m devx.ci.release --verify' for a full report.": { "bg": "\nFix the misaligned tags before creating new releases. Run 'python3 -m devx.ci.release --verify' for a full report.", @@ -85,7 +105,9 @@ "en": "\nFix the misaligned tags before creating new releases. Run 'python3 -m devx.ci.release --verify' for a full report.", "pl": "\nNapraw niezgodne tagi przed utworzeniem nowych wydań. Uruchom 'python3 -m devx.ci.release --verify', aby uzyskać pełny raport.", "ru": "\nFix the misaligned tags before creating new releases. Run 'python3 -m devx.ci.release --verify' for a full report.", - "zh": "\nFix the misaligned tags before creating new releases. Run 'python3 -m devx.ci.release --verify' for a full report." + "zh": "\nFix the misaligned tags before creating new releases. Run 'python3 -m devx.ci.release --verify' for a full report.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" }, "\nFixed {n} stale version reference(s).": { "bg": "", @@ -93,7 +115,9 @@ "en": "\nFixed {n} stale version reference(s).", "pl": "", "ru": "", - "zh": "" + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" }, "\nGenerated {count} badges:": { "bg": "\nGenerated {count} badges:", @@ -101,7 +125,9 @@ "en": "\nGenerated {count} badges:", "pl": "\nGenerated {count} badges:", "ru": "\nGenerated {count} badges:", - "zh": "\nGenerated {count} badges:" + "zh": "\nGenerated {count} badges:", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" }, "\nKeeping {kept}, would delete {count}": { "bg": "\nKeeping {kept}, would delete {count}", @@ -109,7 +135,9 @@ "en": "\nKeeping {kept}, would delete {count}", "pl": "\nKeeping {kept}, would delete {count}", "ru": "\nKeeping {kept}, would delete {count}", - "zh": "\nKeeping {kept}, would delete {count}" + "zh": "\nKeeping {kept}, would delete {count}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" }, "\nLatest tag: {tag}": { "bg": "\nLatest tag: {tag}", @@ -117,7 +145,9 @@ "en": "\nLatest tag: {tag}", "pl": "\nNajnowszy tag: {tag}", "ru": "\nLatest tag: {tag}", - "zh": "\nLatest tag: {tag}" + "zh": "\nLatest tag: {tag}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" }, "\nMissing documentation:": { "bg": "\nMissing documentation:", @@ -125,7 +155,9 @@ "en": "\nMissing documentation:", "pl": "\nBrakująca dokumentacja:", "ru": "\nMissing documentation:", - "zh": "\nMissing documentation:" + "zh": "\nMissing documentation:", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" }, "\nNo stale version references found.": { "bg": "", @@ -133,7 +165,9 @@ "en": "\nNo stale version references found.", "pl": "", "ru": "", - "zh": "" + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" }, "\nPASS: All version references are current.": { "bg": "", @@ -141,7 +175,9 @@ "en": "\nPASS: All version references are current.", "pl": "", "ru": "", - "zh": "" + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" }, "\nResult: {status}": { "bg": "\nResult: {status}", @@ -149,23 +185,9 @@ "en": "\nResult: {status}", "pl": "\nWynik: {status}", "ru": "\nResult: {status}", - "zh": "\nResult: {status}" - }, - "\nReview #{review_id} posted on PR #{pr_number} with event '{event}' ({num_comments} inline comments).": { - "bg": "\nReview #{review_id} posted on PR #{pr_number} with event '{event}' ({num_comments} inline comments).", - "de": "\nReview #{review_id} posted on PR #{pr_number} with event '{event}' ({num_comments} inline comments).", - "en": "\nReview #{review_id} posted on PR #{pr_number} with event '{event}' ({num_comments} inline comments).", - "pl": "\nRecenzja #{review_id} opublikowana na PR #{pr_number} ze zdarzeniem '{event}' ({num_comments} komentarzy w tekście).", - "ru": "\nReview #{review_id} posted on PR #{pr_number} with event '{event}' ({num_comments} inline comments).", - "zh": "\nReview #{review_id} posted on PR #{pr_number} with event '{event}' ({num_comments} inline comments)." - }, - "\nReview #{review_id} posted on PR #{pr_number} with event '{event}'.": { - "bg": "\nReview #{review_id} posted on PR #{pr_number} with event '{event}'.", - "de": "\nReview #{review_id} posted on PR #{pr_number} with event '{event}'.", - "en": "\nReview #{review_id} posted on PR #{pr_number} with event '{event}'.", - "pl": "\nReview #{review_id} posted on PR #{pr_number} with event '{event}'.", - "ru": "\nReview #{review_id} posted on PR #{pr_number} with event '{event}'.", - "zh": "\nReview #{review_id} posted on PR #{pr_number} with event '{event}'." + "zh": "\nResult: {status}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" }, "\nRun with --fix to auto-update version references.": { "bg": "", @@ -173,7 +195,9 @@ "en": "\nRun with --fix to auto-update version references.", "pl": "", "ru": "", - "zh": "" + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" }, "\nTag → Commit alignment:": { "bg": "\nTag → Commit alignment:", @@ -181,3455 +205,9 @@ "en": "\nTag → Commit alignment:", "pl": "\nTag → Commit: zgodność:", "ru": "\nTag → Commit alignment:", - "zh": "\nTag → Commit alignment:" - }, - "\nUntagged release commits:": { - "bg": "\nUntagged release commits:", - "de": "\nUntagged release commits:", - "en": "\nUntagged release commits:", - "pl": "\nCommity wydania bez tagu:", - "ru": "\nUntagged release commits:", - "zh": "\nUntagged release commits:" - }, - "\nUser-facing changes ({count}):": { - "bg": "\nUser-facing changes ({count}):", - "de": "\nUser-facing changes ({count}):", - "en": "\nUser-facing changes ({count}):", - "pl": "\nZmiany widoczne dla użytkownika ({count}):", - "ru": "\nUser-facing changes ({count}):", - "zh": "\nUser-facing changes ({count}):" - }, - "\nVerification passed — all wiki pages exist.": { - "bg": "", - "de": "", - "en": "\nVerification passed — all wiki pages exist.", - "pl": "", - "ru": "", - "zh": "" - }, - "\nVerifying wiki pages...": { - "bg": "", - "de": "", - "en": "\nVerifying wiki pages...", - "pl": "", - "ru": "", - "zh": "" - }, - "\nWorkflow-only changes ({count}):": { - "bg": "\nWorkflow-only changes ({count}):", - "de": "\nWorkflow-only changes ({count}):", - "en": "\nWorkflow-only changes ({count}):", - "pl": "\nZmiany tylko w workflow ({count}):", - "ru": "\nWorkflow-only changes ({count}):", - "zh": "\nWorkflow-only changes ({count}):" - }, - "\n[check_test_coverage] Fix: add the missing test file(s) before committing.": { - "bg": "\n[check_test_coverage] Fix: add the missing test file(s) before committing.", - "de": "\n[check_test_coverage] Fix: add the missing test file(s) before committing.", - "en": "\n[check_test_coverage] Fix: add the missing test file(s) before committing.", - "pl": "\n[check_test_coverage] Fix: add the missing test file(s) before committing.", - "ru": "\n[check_test_coverage] Fix: add the missing test file(s) before committing.", - "zh": "\n[check_test_coverage] Fix: add the missing test file(s) before committing." - }, - "\n[dry-run] Changelog:\n{changelog}": { - "bg": "\n[dry-run] Changelog:\n{changelog}", - "de": "\n[dry-run] Changelog:\n{changelog}", - "en": "\n[dry-run] Changelog:\n{changelog}", - "pl": "\n[dry-run] Changelog:\n{changelog}", - "ru": "\n[dry-run] Changelog:\n{changelog}", - "zh": "\n[dry-run] Changelog:\n{changelog}" - }, - "\n{label} files changed ({count}):": { - "bg": "\n{label} files changed ({count}):", - "de": "\n{label} files changed ({count}):", - "en": "\n{label} files changed ({count}):", - "pl": "\n{label} plików zmienionych ({count}):", - "ru": "\n{label} files changed ({count}):", - "zh": "\n{label} files changed ({count}):" - }, - "\n{separator}": { - "bg": "\n{separator}", - "de": "\n{separator}", - "en": "\n{separator}", - "pl": "\n{separator}", - "ru": "\n{separator}", - "zh": "\n{separator}" - }, - "\n{tag} files ({count}):": { - "bg": "\n{tag} files ({count}):", - "de": "\n{tag} files ({count}):", - "en": "\n{tag} files ({count}):", - "pl": "\nPliki {tag} ({count}):", - "ru": "\n{tag} files ({count}):", - "zh": "\n{tag} files ({count}):" - }, - " Could not fetch logs: {error}": { - "bg": " Could not fetch logs: {error}", - "de": " Could not fetch logs: {error}", - "en": " Could not fetch logs: {error}", - "pl": " Could not fetch logs: {error}", - "ru": " Could not fetch logs: {error}", - "zh": " Could not fetch logs: {error}" - }, - " pytest stderr (last 300 chars): {stderr}": { - "bg": " pytest stderr (last 300 chars): {stderr}", - "de": " pytest stderr (last 300 chars): {stderr}", - "en": " pytest stderr (last 300 chars): {stderr}", - "pl": " pytest stderr (last 300 chars): {stderr}", - "ru": " pytest stderr (last 300 chars): {stderr}", - "zh": " pytest stderr (last 300 chars): {stderr}" - }, - " pytest stdout (last 300 chars): {stdout}": { - "bg": " pytest stdout (last 300 chars): {stdout}", - "de": " pytest stdout (last 300 chars): {stdout}", - "en": " pytest stdout (last 300 chars): {stdout}", - "pl": " pytest stdout (last 300 chars): {stdout}", - "ru": " pytest stdout (last 300 chars): {stdout}", - "zh": " pytest stdout (last 300 chars): {stdout}" - }, - " stderr: {stderr}": { - "bg": " stderr: {stderr}", - "de": " stderr: {stderr}", - "en": " stderr: {stderr}", - "pl": " stderr: {stderr}", - "ru": " stderr: {stderr}", - "zh": " stderr: {stderr}" - }, - " - Auto-delete branch after merge: yes": { - "bg": " - Автоматично изтриване на клон след сливане: да", - "de": " - Branch nach Merge automatisch löschen: ja", - "en": " - Auto-delete branch after merge: yes", - "pl": " - Auto-usuwanie gałęzi po scaleniu: tak", - "ru": " - Автоудаление ветки после слияния: да", - "zh": " - 合并后自动删除分支: 是" - }, - " - Block admin merge override: yes": { - "bg": " - Блокиране на admin merge override: да", - "de": " - Admin-Merge-Override blockieren: ja", - "en": " - Block admin merge override: yes", - "pl": " - Blokuj admin merge override: tak", - "ru": " - Блокировать admin merge override: да", - "zh": " - 阻止管理员合并覆盖:是" - }, - " - Block outdated branches: yes": { - "bg": " - Блокиране на остарели клонове: да", - "de": " - Veraltete Branches blockieren: ja", - "en": " - Block outdated branches: yes", - "pl": " - Blokowanie nieaktualnych gałęzi: tak", - "ru": " - Блокировать устаревшие ветки: да", - "zh": " - 阻止过时分支: 是" - }, - " - Block rejected reviews: yes": { - "bg": " - Блокиране на отхвърлени рецензии: да", - "de": " - Abgelehnte Reviews blockieren: ja", - "en": " - Block rejected reviews: yes", - "pl": " - Blokowanie odrzuconych recenzji: tak", - "ru": " - Блокировать отклонённые ревью: да", - "zh": " - 阻止被拒绝的审查: 是" - }, - " - Direct pushes: BLOCKED (require PR, whitelisted users can push)": { - "bg": " - Direct pushes: BLOCKED (require PR, whitelisted users can push)", - "de": " - Direct pushes: BLOCKED (require PR, whitelisted users can push)", - "en": " - Direct pushes: BLOCKED (require PR, whitelisted users can push)", - "pl": " - Bezpośrednie push-e: ZABLOKOWANE (wymagają PR, użytkownicy z białej listy mogą pushować)", - "ru": " - Direct pushes: BLOCKED (require PR, whitelisted users can push)", - "zh": " - Direct pushes: BLOCKED (require PR, whitelisted users can push)" - }, - " - Dismiss stale approvals: yes": { - "bg": " - Анулиране на остарели одобрения: да", - "de": " - Veraltete Genehmigungen ablehnen: ja", - "en": " - Dismiss stale approvals: yes", - "pl": " - Odrzucanie nieaktualnych zatwierdzeń: tak", - "ru": " - Отклонять устаревшие одобрения: да", - "zh": " - 忽略过时审批: 是" - }, - " - Required approvals: {count}": { - "bg": " - Необходими одобрения: {count}", - "de": " - Erforderliche Genehmigungen: {count}", - "en": " - Required approvals: {count}", - "pl": " - Wymagane zatwierdzenia: {count}", - "ru": " - Требуемые одобрения: {count}", - "zh": " - 必需审批数: {count}" - }, - " - Required status checks: {checks}": { - "bg": " - Необходими проверки на състоянието: {checks}", - "de": " - Erforderliche Status-Checks: {checks}", - "en": " - Required status checks: {checks}", - "pl": " - Wymagane kontrole statusu: {checks}", - "ru": " - Требуемые проверки статуса: {checks}", - "zh": " - 必需状态检查: {checks}" - }, - " - {count} standard labels verified": { - "bg": " - {count} standard labels verified", - "de": " - {count} standard labels verified", - "en": " - {count} standard labels verified", - "pl": " - {count} standard labels verified", - "ru": " - {count} standard labels verified", - "zh": " - {count} standard labels verified" - }, - " -> {dir}": { - "bg": " -> {dir}", - "de": " -> {dir}", - "en": " -> {dir}", - "pl": " -> {dir}", - "ru": " -> {dir}", - "zh": " -> {dir}" - }, - " ... and {n} more": { - "bg": "", - "de": "", - "en": " ... and {n} more", - "pl": "", - "ru": "", - "zh": "" - }, - " Auto-fixed trailing whitespace in {n} files": { - "bg": " Auto-fixed trailing whitespace in {n} files", - "de": " Auto-fixed trailing whitespace in {n} files", - "en": " Auto-fixed trailing whitespace in {n} files", - "pl": " Auto-fixed trailing whitespace in {n} files", - "ru": " Auto-fixed trailing whitespace in {n} files", - "zh": " Auto-fixed trailing whitespace in {n} files" - }, - " Collecting code quality...": { - "bg": " Collecting code quality...", - "de": " Collecting code quality...", - "en": " Collecting code quality...", - "pl": " Collecting code quality...", - "ru": " Collecting code quality...", - "zh": " Collecting code quality..." - }, - " Collecting coverage and tests...": { - "bg": " Collecting coverage and tests...", - "de": " Collecting coverage and tests...", - "en": " Collecting coverage and tests...", - "pl": " Collecting coverage and tests...", - "ru": " Collecting coverage and tests...", - "zh": " Collecting coverage and tests..." - }, - " Collecting doc coverage...": { - "bg": " Collecting doc coverage...", - "de": " Collecting doc coverage...", - "en": " Collecting doc coverage...", - "pl": " Collecting doc coverage...", - "ru": " Collecting doc coverage...", - "zh": " Collecting doc coverage..." - }, - " Collecting version...": { - "bg": " Collecting version...", - "de": " Collecting version...", - "en": " Collecting version...", - "pl": " Collecting version...", - "ru": " Collecting version...", - "zh": " Collecting version..." - }, - " Deleted: {version}": { - "bg": " Deleted: {version}", - "de": " Deleted: {version}", - "en": " Deleted: {version}", - "pl": " Deleted: {version}", - "ru": " Deleted: {version}", - "zh": " Deleted: {version}" - }, - " FAIL: {title} — page not found in wiki!": { - "bg": "", - "de": "", - "en": " FAIL: {title} — page not found in wiki!", - "pl": "", - "ru": "", - "zh": "" - }, - " FAILED to delete: {version}": { - "bg": " FAILED to delete: {version}", - "de": " FAILED to delete: {version}", - "en": " FAILED to delete: {version}", - "pl": " FAILED to delete: {version}", - "ru": " FAILED to delete: {version}", - "zh": " FAILED to delete: {version}" - }, - " Fixed {fixes} version ref(s) in {file}": { - "bg": "", - "de": "", - "en": " Fixed {fixes} version ref(s) in {file}", - "pl": "", - "ru": "", - "zh": "" - }, - " Generated: {path}": { - "bg": " Generated: {path}", - "de": " Generated: {path}", - "en": " Generated: {path}", - "pl": " Generated: {path}", - "ru": " Generated: {path}", - "zh": " Generated: {path}" - }, - " MISSING: {cmd}": { - "bg": " MISSING: {cmd}", - "de": " MISSING: {cmd}", - "en": " MISSING: {cmd}", - "pl": " MISSING: {cmd}", - "ru": " MISSING: {cmd}", - "zh": " MISSING: {cmd}" - }, - " MISSING: {module}": { - "bg": " MISSING: {module}", - "de": " MISSING: {module}", - "en": " MISSING: {module}", - "pl": " BRAK: {module}", - "ru": " MISSING: {module}", - "zh": " MISSING: {module}" - }, - " MISSING: {script}": { - "bg": " MISSING: {script}", - "de": " MISSING: {script}", - "en": " MISSING: {script}", - "pl": " BRAK: {script}", - "ru": " MISSING: {script}", - "zh": " MISSING: {script}" - }, - " OK: {cmd}": { - "bg": " OK: {cmd}", - "de": " OK: {cmd}", - "en": " OK: {cmd}", - "pl": " OK: {cmd}", - "ru": " OK: {cmd}", - "zh": " OK: {cmd}" - }, - " OK: {module}": { - "bg": " OK: {module}", - "de": " OK: {module}", - "en": " OK: {module}", - "pl": " OK: {module}", - "ru": " OK: {module}", - "zh": " OK: {module}" - }, - " OK: {script}": { - "bg": " OK: {script}", - "de": " OK: {script}", - "en": " OK: {script}", - "pl": " OK: {script}", - "ru": " OK: {script}", - "zh": " OK: {script}" - }, - " OK: {title}": { - "bg": "", - "de": "", - "en": " OK: {title}", - "pl": "", - "ru": "", - "zh": "" - }, - " Package: {pkg}": { - "bg": " Package: {pkg}", - "de": " Package: {pkg}", - "en": " Package: {pkg}", - "pl": " Package: {pkg}", - "ru": " Package: {pkg}", - "zh": " Package: {pkg}" - }, - " Pruned: {file} (not in mapping)": { - "bg": "", - "de": "", - "en": " Pruned: {file} (not in mapping)", - "pl": "", - "ru": "", - "zh": "" - }, - " Quality checks: {checks}": { - "bg": " Quality checks: {checks}", - "de": " Quality checks: {checks}", - "en": " Quality checks: {checks}", - "pl": " Quality checks: {checks}", - "ru": " Quality checks: {checks}", - "zh": " Quality checks: {checks}" - }, - " Repo root: {root}": { - "bg": " Repo root: {root}", - "de": " Repo root: {root}", - "en": " Repo root: {root}", - "pl": " Repo root: {root}", - "ru": " Repo root: {root}", - "zh": " Repo root: {root}" - }, - " Run 'make install-checkmake' to install the Makefile linter.": { - "bg": " Изпълнете 'make install-checkmake' за инсталиране на Makefile линтера.", - "de": " Führen Sie 'make install-checkmake' aus, um den Makefile-Linter zu installieren.", - "en": " Run 'make install-checkmake' to install the Makefile linter.", - "pl": " Uruchom 'make install-checkmake', aby zainstalować linter Makefile.", - "ru": " Выполните 'make install-checkmake' для установки линтера Makefile.", - "zh": " 运行 'make install-checkmake' 来安装 Makefile 检查器。" - }, - " Synced: {title} → {file}": { - "bg": "", - "de": "", - "en": " Synced: {title} → {file}", - "pl": "", - "ru": "", - "zh": "" - }, - " Test paths: {testpaths}": { - "bg": " Test paths: {testpaths}", - "de": " Test paths: {testpaths}", - "en": " Test paths: {testpaths}", - "pl": " Test paths: {testpaths}", - "ru": " Test paths: {testpaths}", - "zh": " Test paths: {testpaths}" - }, - " WARN: Mapped file {file} is empty, skipping": { - "bg": "", - "de": "", - "en": " WARN: Mapped file {file} is empty, skipping", - "pl": "", - "ru": "", - "zh": "" - }, - " WARN: Mapped file {file} not found, skipping": { - "bg": "", - "de": "", - "en": " WARN: Mapped file {file} not found, skipping", - "pl": "", - "ru": "", - "zh": "" - }, - " WARNING: Could not extract coverage from pytest output (rc={rc})": { - "bg": " WARNING: Could not extract coverage from pytest output (rc={rc})", - "de": " WARNING: Could not extract coverage from pytest output (rc={rc})", - "en": " WARNING: Could not extract coverage from pytest output (rc={rc})", - "pl": " WARNING: Could not extract coverage from pytest output (rc={rc})", - "ru": " WARNING: Could not extract coverage from pytest output (rc={rc})", - "zh": " WARNING: Could not extract coverage from pytest output (rc={rc})" - }, - " WARNING: Could not extract doc coverage (rc={rc})": { - "bg": " WARNING: Could not extract doc coverage (rc={rc})", - "de": " WARNING: Could not extract doc coverage (rc={rc})", - "en": " WARNING: Could not extract doc coverage (rc={rc})", - "pl": " WARNING: Could not extract doc coverage (rc={rc})", - "ru": " WARNING: Could not extract doc coverage (rc={rc})", - "zh": " WARNING: Could not extract doc coverage (rc={rc})" - }, - " WARNING: Could not extract test count from pytest output (rc={rc})": { - "bg": " WARNING: Could not extract test count from pytest output (rc={rc})", - "de": " WARNING: Could not extract test count from pytest output (rc={rc})", - "en": " WARNING: Could not extract test count from pytest output (rc={rc})", - "pl": " WARNING: Could not extract test count from pytest output (rc={rc})", - "ru": " WARNING: Could not extract test count from pytest output (rc={rc})", - "zh": " WARNING: Could not extract test count from pytest output (rc={rc})" - }, - " WARNING: No Python package found under src/ — version badge will show 'unknown'": { - "bg": " WARNING: No Python package found under src/ — version badge will show 'unknown'", - "de": " WARNING: No Python package found under src/ — version badge will show 'unknown'", - "en": " WARNING: No Python package found under src/ — version badge will show 'unknown'", - "pl": " WARNING: No Python package found under src/ — version badge will show 'unknown'", - "ru": " WARNING: No Python package found under src/ — version badge will show 'unknown'", - "zh": " WARNING: No Python package found under src/ — version badge will show 'unknown'" - }, - " WARNING: No __version__ found in {init_file} — version badge will show 'unknown'": { - "bg": " WARNING: No __version__ found in {init_file} — version badge will show 'unknown'", - "de": " WARNING: No __version__ found in {init_file} — version badge will show 'unknown'", - "en": " WARNING: No __version__ found in {init_file} — version badge will show 'unknown'", - "pl": " WARNING: No __version__ found in {init_file} — version badge will show 'unknown'", - "ru": " WARNING: No __version__ found in {init_file} — version badge will show 'unknown'", - "zh": " WARNING: No __version__ found in {init_file} — version badge will show 'unknown'" - }, - " WARNING: No coverage target detected (no src/ package, no --cov in pyproject.toml)": { - "bg": " WARNING: No coverage target detected (no src/ package, no --cov in pyproject.toml)", - "de": " WARNING: No coverage target detected (no src/ package, no --cov in pyproject.toml)", - "en": " WARNING: No coverage target detected (no src/ package, no --cov in pyproject.toml)", - "pl": " WARNING: No coverage target detected (no src/ package, no --cov in pyproject.toml)", - "ru": " WARNING: No coverage target detected (no src/ package, no --cov in pyproject.toml)", - "zh": " WARNING: No coverage target detected (no src/ package, no --cov in pyproject.toml)" - }, - " WARNING: {init_file} not found — version badge will show 'unknown'": { - "bg": " WARNING: {init_file} not found — version badge will show 'unknown'", - "de": " WARNING: {init_file} not found — version badge will show 'unknown'", - "en": " WARNING: {init_file} not found — version badge will show 'unknown'", - "pl": " WARNING: {init_file} not found — version badge will show 'unknown'", - "ru": " WARNING: {init_file} not found — version badge will show 'unknown'", - "zh": " WARNING: {init_file} not found — version badge will show 'unknown'" - }, - " WARNING: {name} failed (rc={rc})": { - "bg": " WARNING: {name} failed (rc={rc})", - "de": " WARNING: {name} failed (rc={rc})", - "en": " WARNING: {name} failed (rc={rc})", - "pl": " WARNING: {name} failed (rc={rc})", - "ru": " WARNING: {name} failed (rc={rc})", - "zh": " WARNING: {name} failed (rc={rc})" - }, - " WARNING: {name} not installed — skipping (counted as pass)": { - "bg": " WARNING: {name} not installed — skipping (counted as pass)", - "de": " WARNING: {name} not installed — skipping (counted as pass)", - "en": " WARNING: {name} not installed — skipping (counted as pass)", - "pl": " WARNING: {name} not installed — skipping (counted as pass)", - "ru": " WARNING: {name} not installed — skipping (counted as pass)", - "zh": " WARNING: {name} not installed — skipping (counted as pass)" - }, - " [dry-run] Would delete: {version}": { - "bg": " [dry-run] Would delete: {version}", - "de": " [dry-run] Would delete: {version}", - "en": " [dry-run] Would delete: {version}", - "pl": " [dry-run] Would delete: {version}", - "ru": " [dry-run] Would delete: {version}", - "zh": " [dry-run] Would delete: {version}" - }, - " {name}: {label}={message} ({color})": { - "bg": " {name}: {label}={message} ({color})", - "de": " {name}: {label}={message} ({color})", - "en": " {name}: {label}={message} ({color})", - "pl": " {name}: {label}={message} ({color})", - "ru": " {name}: {label}={message} ({color})", - "zh": " {name}: {label}={message} ({color})" - }, - " {n} long lines found (warnings only)": { - "bg": "", - "de": "", - "en": " {n} long lines found (warnings only)", - "pl": "", - "ru": "", - "zh": "" - }, - " {n} orphan docs found (warnings only)": { - "bg": "", - "de": "", - "en": " {n} orphan docs found (warnings only)", - "pl": "", - "ru": "", - "zh": "" - }, - " {n} stale docs found (warnings only)": { - "bg": " {n} stale docs found (warnings only)", - "de": " {n} stale docs found (warnings only)", - "en": " {n} stale docs found (warnings only)", - "pl": " {n} stale docs found (warnings only)", - "ru": " {n} stale docs found (warnings only)", - "zh": " {n} stale docs found (warnings only)" - }, - " {tool}: found at {path}": { - "bg": " {tool}: намерен на {path}", - "de": " {tool}: gefunden unter {path}", - "en": " {tool}: found at {path}", - "pl": " {tool}: znaleziono w {path}", - "ru": " {tool}: найден в {path}", - "zh": " {tool}: 在 {path} 找到" - }, - " {version} (created: {created})": { - "bg": " {version} (created: {created})", - "de": " {version} (created: {created})", - "en": " {version} (created: {created})", - "pl": " {version} (created: {created})", - "ru": " {version} (created: {created})", - "zh": " {version} (created: {created})" - }, - "--checklist-categories must list at least 8 of 13 categories. Got {count}.": { - "bg": "--checklist-categories must list at least 8 of 13 categories. Got {count}.", - "de": "--checklist-categories must list at least 8 of 13 categories. Got {count}.", - "en": "--checklist-categories must list at least 8 of 13 categories. Got {count}.", - "pl": "--checklist-categories must list at least 8 of 13 categories. Got {count}.", - "ru": "--checklist-categories must list at least 8 of 13 categories. Got {count}.", - "zh": "--checklist-categories must list at least 8 of 13 categories. Got {count}." - }, - "--checklist-confirmed is required for APPROVE events.": { - "bg": "--checklist-confirmed is required for APPROVE events.", - "de": "--checklist-confirmed is required for APPROVE events.", - "en": "--checklist-confirmed is required for APPROVE events.", - "pl": "--checklist-confirmed is required for APPROVE events.", - "ru": "--checklist-confirmed is required for APPROVE events.", - "zh": "--checklist-confirmed is required for APPROVE events." - }, - "--push requires --registry": { - "bg": "--push requires --registry", - "de": "--push requires --registry", - "en": "--push requires --registry", - "pl": "--push requires --registry", - "ru": "--push requires --registry", - "zh": "--push requires --registry" - }, - "--skip-build: skipping package build and PyPI publish.": { - "bg": "--skip-build: skipping package build and PyPI publish.", - "de": "--skip-build: skipping package build and PyPI publish.", - "en": "--skip-build: skipping package build and PyPI publish.", - "pl": "--skip-build: pomijanie budowania pakietu i publikacji PyPI.", - "ru": "--skip-build: skipping package build and PyPI publish.", - "zh": "--skip-build: skipping package build and PyPI publish." - }, - "=== Release Alignment Verification ===\n": { - "bg": "=== Release Alignment Verification ===\n", - "de": "=== Release Alignment Verification ===\n", - "en": "=== Release Alignment Verification ===\n", - "pl": "=== Weryfikacja zgodności wydań ===\n", - "ru": "=== Release Alignment Verification ===\n", - "zh": "=== Release Alignment Verification ===\n" - }, - "API poll warning: {exc}": { - "bg": "API poll warning: {exc}", - "de": "API poll warning: {exc}", - "en": "API poll warning: {exc}", - "pl": "Ostrzeżenie sondowania API: {exc}", - "ru": "API poll warning: {exc}", - "zh": "API poll warning: {exc}" - }, - "Added label '{label}' to PR #{pr}.": { - "bg": "Added label '{label}' to PR #{pr}.", - "de": "Added label '{label}' to PR #{pr}.", - "en": "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}." - }, - "Additional directory to scan (default: scripts, tests). Can be repeated.": { - "bg": "Additional directory to scan (default: scripts, tests). Can be repeated.", - "de": "Additional directory to scan (default: scripts, tests). Can be repeated.", - "en": "Additional directory to scan (default: scripts, tests). Can be repeated.", - "pl": "Additional directory to scan (default: scripts, tests). Can be repeated.", - "ru": "Additional directory to scan (default: scripts, tests). Can be repeated.", - "zh": "Additional directory to scan (default: scripts, tests). Can be repeated." - }, - "Allow empty tag (PR mode where SHA is concrete).": { - "bg": "Позволи празен таг (PR режим, където SHA е конкретен).", - "de": "Leeren Tag zulassen (PR-Modus, in dem SHA konkret ist).", - "en": "Allow empty tag (PR mode where SHA is concrete).", - "pl": "Zezwalaj na pusty tag (tryb PR, w którym SHA jest konkretne).", - "ru": "Разрешить пустой тег (режим PR, где SHA конкретен).", - "zh": "允许空标签(SHA 为具体值的 PR 模式)。" - }, - "Another runner failed. Stopping this runner early.": { - "bg": "Друг runner се провали. Спиране на този runner по-рано.", - "de": "Ein anderer Runner ist fehlgeschlagen. Dieser Runner wird vorzeitig gestoppt.", - "en": "Another runner failed. Stopping this runner early.", - "pl": "Inny runner zakończył się niepowodzeniem. Wczesne zatrzymanie tego runnera.", - "ru": "Другой runner завершился с ошибкой. Останавливаю этот runner досрочно.", - "zh": "另一个 runner 失败。提前停止此 runner。" - }, - "Assigned {count} files to runner {runner_index}": { - "bg": "Assigned {count} files to runner {runner_index}", - "de": "Assigned {count} files to runner {runner_index}", - "en": "Assigned {count} files to runner {runner_index}", - "pl": "Assigned {count} files to runner {runner_index}", - "ru": "Assigned {count} files to runner {runner_index}", - "zh": "Assigned {count} files to runner {runner_index}" - }, - "Assigned {count} items to runner {runner_index}: {encoded}": { - "bg": "Assigned {count} items to runner {runner_index}: {encoded}", - "de": "Assigned {count} items to runner {runner_index}: {encoded}", - "en": "Assigned {count} items to runner {runner_index}: {encoded}", - "pl": "Assigned {count} items to runner {runner_index}: {encoded}", - "ru": "Assigned {count} items to runner {runner_index}: {encoded}", - "zh": "Assigned {count} items to runner {runner_index}: {encoded}" - }, - "Auto-rebase failed with HTTP {status}: {message}\nRebase manually:\n git fetch origin master && git rebase origin/master && git push --force-with-lease\nThen re-add the ready-to-merge label.": { - "bg": "Auto-rebase failed with HTTP {status}: {message}\nRebase manually:\n git fetch origin master && git rebase origin/master && git push --force-with-lease\nThen re-add the ready-to-merge label.", - "de": "Auto-rebase failed with HTTP {status}: {message}\nRebase manually:\n git fetch origin master && git rebase origin/master && git push --force-with-lease\nThen re-add the ready-to-merge label.", - "en": "Auto-rebase failed with HTTP {status}: {message}\nRebase manually:\n git fetch origin master && git rebase origin/master && git push --force-with-lease\nThen re-add the ready-to-merge label.", - "pl": "Auto-rebase failed with HTTP {status}: {message}\nRebase manually:\n git fetch origin master && git rebase origin/master && git push --force-with-lease\nThen re-add the ready-to-merge label.", - "ru": "Auto-rebase failed with HTTP {status}: {message}\nRebase manually:\n git fetch origin master && git rebase origin/master && git push --force-with-lease\nThen re-add the ready-to-merge label.", - "zh": "Auto-rebase failed with HTTP {status}: {message}\nRebase manually:\n git fetch origin master && git rebase origin/master && git push --force-with-lease\nThen re-add the ready-to-merge label." - }, - "Automated CI commit (badge) — skipping post-merge jobs.": { - "bg": "Automated CI commit (badge) — skipping post-merge jobs.", - "de": "Automated CI commit (badge) — skipping post-merge jobs.", - "en": "Automated CI commit (badge) — skipping post-merge jobs.", - "pl": "Automated CI commit (badge) — skipping post-merge jobs.", - "ru": "Automated CI commit (badge) — skipping post-merge jobs.", - "zh": "Automated CI commit (badge) — skipping post-merge jobs." - }, - "Badge push attempt {attempt}/{retries} failed — retrying: {error}": { - "bg": "Badge push attempt {attempt}/{retries} failed — retrying: {error}", - "de": "Badge push attempt {attempt}/{retries} failed — retrying: {error}", - "en": "Badge push attempt {attempt}/{retries} failed — retrying: {error}", - "pl": "Badge push attempt {attempt}/{retries} failed — retrying: {error}", - "ru": "Badge push attempt {attempt}/{retries} failed — retrying: {error}", - "zh": "Badge push attempt {attempt}/{retries} failed — retrying: {error}" - }, - "Badge push failed after {retries} attempts: {error}": { - "bg": "Badge push failed after {retries} attempts: {error}", - "de": "Badge push failed after {retries} attempts: {error}", - "en": "Badge push failed after {retries} attempts: {error}", - "pl": "Badge push failed after {retries} attempts: {error}", - "ru": "Badge push failed after {retries} attempts: {error}", - "zh": "Badge push failed after {retries} attempts: {error}" - }, - "Badges commit SHA: {sha}": { - "bg": "Badges commit SHA: {sha}", - "de": "Badges commit SHA: {sha}", - "en": "Badges commit SHA: {sha}", - "pl": "Badges commit SHA: {sha}", - "ru": "Badges commit SHA: {sha}", - "zh": "Badges commit SHA: {sha}" - }, - "Badges pushed to badges branch": { - "bg": "Badges pushed to badges branch", - "de": "Badges pushed to badges branch", - "en": "Badges pushed to badges branch", - "pl": "Badges pushed to badges branch", - "ru": "Badges pushed to badges branch", - "zh": "Badges pushed to badges branch" - }, - "Branch '{branch}' does not contain a task ID.\n Expected format: {prefix}-N-short-description": { - "bg": "Клон '{branch}' не съдържа ID на задача.\n Очакван формат: {prefix}-N-кратко-описание", - "de": "Branch '{branch}' enthält keine Task-ID.\n Erwartetes Format: {prefix}-N-kurz-beschreibung", - "en": "Branch '{branch}' does not contain a task ID.\n Expected format: {prefix}-N-short-description", - "pl": "Gałąź '{branch}' nie zawiera ID zadania.\n Oczekiwany format: {prefix}-N-krótki-opis", - "ru": "Ветка '{branch}' не содержит ID задачи.\n Ожидаемый формат: {prefix}-N-краткое-описание", - "zh": "分支 '{branch}' 不包含任务 ID。\n 预期格式: {prefix}-N-简短描述" - }, - "Branch '{branch}' does not contain a task ID.\n Expected format: {prefix}-N-short-description\n Example: {prefix}-42-add-feature\n Fix: rename the branch or create a Vikunja task first:\n python -m devx.tools.create_task --title \"Task title\"": { - "bg": "Клон '{branch}' не съдържа ID на задача.\n Очакван формат: {prefix}-N-кратко-описание\n Пример: {prefix}-42-add-feature\n Решение: преименувайте клона или създайте Vikunja задача:\n python -m devx.tools.create_task --title \"Заглавие на задача\"", - "de": "Branch '{branch}' enthält keine Task-ID.\n Erwartetes Format: {prefix}-N-kurz-beschreibung\n Beispiel: {prefix}-42-add-feature\n Fix: Branch umbenennen oder Vikunja-Task erstellen:\n python -m devx.tools.create_task --title \"Task-Titel\"", - "en": "Branch '{branch}' does not contain a task ID.\n Expected format: {prefix}-N-short-description\n Example: {prefix}-42-add-feature\n Fix: rename the branch or create a Vikunja task first:\n python -m devx.tools.create_task --title \"Task title\"", - "pl": "Gałąź '{branch}' nie zawiera ID zadania.\n Oczekiwany format: {prefix}-N-krótki-opis\n Przykład: {prefix}-42-add-feature\n Naprawa: zmień nazwę gałęzi lub utwórz zadanie Vikunja:\n python -m devx.tools.create_task --title \"Tytuł zadania\"", - "ru": "Ветка '{branch}' не содержит ID задачи.\n Ожидаемый формат: {prefix}-N-краткое-описание\n Пример: {prefix}-42-add-feature\n Исправление: переименуйте ветку или создайте задачу Vikunja:\n python -m devx.tools.create_task --title \"Заголовок задачи\"", - "zh": "分支 '{branch}' 不包含任务 ID。\n 预期格式: {prefix}-N-简短描述\n 示例: {prefix}-42-add-feature\n 修复: 重命名分支或先创建 Vikunja 任务:\n python -m devx.tools.create_task --title \"任务标题\"" - }, - "Branch is already up-to-date with origin/master.": { - "bg": "Branch is already up-to-date with origin/master.", - "de": "Branch is already up-to-date with origin/master.", - "en": "Branch is already up-to-date with origin/master.", - "pl": "Branch is already up-to-date with origin/master.", - "ru": "Branch is already up-to-date with origin/master.", - "zh": "Branch is already up-to-date with origin/master." - }, - "Branch is behind master. Auto-rebasing via Gitea API...\nA new CI run will start automatically after the rebase.\nThe next auto-merge attempt will merge this PR.": { - "bg": "Branch is behind master. Auto-rebasing via Gitea API...\nA new CI run will start automatically after the rebase.\nThe next auto-merge attempt will merge this PR.", - "de": "Branch is behind master. Auto-rebasing via Gitea API...\nA new CI run will start automatically after the rebase.\nThe next auto-merge attempt will merge this PR.", - "en": "Branch is behind master. Auto-rebasing via Gitea API...\nA new CI run will start automatically after the rebase.\nThe next auto-merge attempt will merge this PR.", - "pl": "Branch is behind master. Auto-rebasing via Gitea API...\nA new CI run will start automatically after the rebase.\nThe next auto-merge attempt will merge this PR.", - "ru": "Branch is behind master. Auto-rebasing via Gitea API...\nA new CI run will start automatically after the rebase.\nThe next auto-merge attempt will merge this PR.", - "zh": "Branch is behind master. Auto-rebasing via Gitea API...\nA new CI run will start automatically after the rebase.\nThe next auto-merge attempt will merge this PR." - }, - "Branch is behind origin/master. Rebase first: git fetch origin master && git rebase origin/master": { - "bg": "Branch is behind origin/master. Rebase first: git fetch origin master && git rebase origin/master", - "de": "Branch is behind origin/master. Rebase first: git fetch origin master && git rebase origin/master", - "en": "Branch is behind origin/master. Rebase first: git fetch origin master && git rebase origin/master", - "pl": "Branch is behind origin/master. Rebase first: git fetch origin master && git rebase origin/master", - "ru": "Branch is behind origin/master. Rebase first: git fetch origin master && git rebase origin/master", - "zh": "Branch is behind origin/master. Rebase first: git fetch origin master && git rebase origin/master" - }, - "Branch is {count} commit(s) behind master. Rebasing...": { - "bg": "Branch is {count} commit(s) behind master. Rebasing...", - "de": "Branch is {count} commit(s) behind master. Rebasing...", - "en": "Branch is {count} commit(s) behind master. Rebasing...", - "pl": "Branch is {count} commit(s) behind master. Rebasing...", - "ru": "Branch is {count} commit(s) behind master. Rebasing...", - "zh": "Branch is {count} commit(s) behind master. Rebasing..." - }, - "Branch name (e.g., DEVX-256-fix-foo)": { - "bg": "Branch name (e.g., DEVX-256-fix-foo)", - "de": "Branch name (e.g., DEVX-256-fix-foo)", - "en": "Branch name (e.g., DEVX-256-fix-foo)", - "pl": "Branch name (e.g., DEVX-256-fix-foo)", - "ru": "Branch name (e.g., DEVX-256-fix-foo)", - "zh": "Branch name (e.g., DEVX-256-fix-foo)" - }, - "Branch name must contain a task ID.": { - "bg": "Branch name must contain a task ID.", - "de": "Branch name must contain a task ID.", - "en": "Branch name must contain a task ID.", - "pl": "Branch name must contain a task ID.", - "ru": "Branch name must contain a task ID.", - "zh": "Branch name must contain a task ID." - }, - "Build failed for {name}": { - "bg": "Build failed for {name}", - "de": "Build failed for {name}", - "en": "Build failed for {name}", - "pl": "Build failed for {name}", - "ru": "Build failed for {name}", - "zh": "Build failed for {name}" - }, - "Bumping version: {current} -> v{new_version}": { - "bg": "Bumping version: {current} -> v{new_version}", - "de": "Bumping version: {current} -> v{new_version}", - "en": "Bumping version: {current} -> v{new_version}", - "pl": "Zmiana wersji: {current} -> v{new_version}", - "ru": "Bumping version: {current} -> v{new_version}", - "zh": "Bumping version: {current} -> v{new_version}" - }, - "CI checks did not complete within timeout.": { - "bg": "CI checks did not complete within timeout.", - "de": "CI checks did not complete within timeout.", - "en": "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.": { - "bg": "CI checks failed.", - "de": "CI checks failed.", - "en": "CI checks failed.", - "pl": "CI checks failed.", - "ru": "CI checks failed.", - "zh": "CI checks failed." - }, - "CI_GITEA_TOKEN environment variable required": { - "bg": "CI_GITEA_TOKEN environment variable required", - "de": "CI_GITEA_TOKEN environment variable required", - "en": "CI_GITEA_TOKEN environment variable required", - "pl": "CI_GITEA_TOKEN environment variable required", - "ru": "CI_GITEA_TOKEN environment variable required", - "zh": "CI_GITEA_TOKEN environment variable required" - }, - "CI_GITEA_TOKEN is not set.": { - "bg": "CI_GITEA_TOKEN is not set.", - "de": "CI_GITEA_TOKEN is not set.", - "en": "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." - }, - "CI_GITEA_TOKEN is not set. Add it to .env or export it.": { - "bg": "CI_GITEA_TOKEN is not set. Add it to .env or export it.", - "de": "CI_GITEA_TOKEN is not set. Add it to .env or export it.", - "en": "CI_GITEA_TOKEN is not set. Add it to .env or export it.", - "pl": "CI_GITEA_TOKEN is not set. Add it to .env or export it.", - "ru": "CI_GITEA_TOKEN is not set. Add it to .env or export it.", - "zh": "CI_GITEA_TOKEN is not set. Add it to .env or export it." - }, - "CI_GITEA_TOKEN is not set. Required to create a PR.": { - "bg": "CI_GITEA_TOKEN не е зададен. Необходим за създаване на PR.", - "de": "CI_GITEA_TOKEN nicht gesetzt. Erforderlich zum Erstellen eines PR.", - "en": "CI_GITEA_TOKEN is not set. Required to create a PR.", - "pl": "CI_GITEA_TOKEN nie jest ustawiony. Wymagany do utworzenia PR.", - "ru": "CI_GITEA_TOKEN не установлен. Требуется для создания PR.", - "zh": "CI_GITEA_TOKEN 未设置。创建 PR 所需。" - }, - "CI_GITEA_TOKEN not set — skipping login configuration.": { - "bg": "CI_GITEA_TOKEN not set — skipping login configuration.", - "de": "CI_GITEA_TOKEN not set — skipping login configuration.", - "en": "CI_GITEA_TOKEN not set — skipping login configuration.", - "pl": "CI_GITEA_TOKEN not set — skipping login configuration.", - "ru": "CI_GITEA_TOKEN not set — skipping login configuration.", - "zh": "CI_GITEA_TOKEN not set — skipping login configuration." - }, - "Cannot read __version__ from src/{pkg}/__init__.py — skipping.": { - "bg": "", - "de": "", - "en": "Cannot read __version__ from src/{pkg}/__init__.py — skipping.", - "pl": "", - "ru": "", - "zh": "" - }, - "Cannot rebase: not on a branch (detached HEAD).": { - "bg": "Cannot rebase: not on a branch (detached HEAD).", - "de": "Cannot rebase: not on a branch (detached HEAD).", - "en": "Cannot rebase: not on a branch (detached HEAD).", - "pl": "Cannot rebase: not on a branch (detached HEAD).", - "ru": "Cannot rebase: not on a branch (detached HEAD).", - "zh": "Cannot rebase: not on a branch (detached HEAD)." - }, - "Checking CLI command documentation...": { - "bg": "Checking CLI command documentation...", - "de": "Checking CLI command documentation...", - "en": "Checking CLI command documentation...", - "pl": "Sprawdzanie dokumentacji poleceń CLI...", - "ru": "Checking CLI command documentation...", - "zh": "Checking CLI command documentation..." - }, - "Checking code block languages...": { - "bg": "", - "de": "", - "en": "Checking code block languages...", - "pl": "", - "ru": "", - "zh": "" - }, - "Checking docs structure...": { - "bg": "Checking docs structure...", - "de": "Checking docs structure...", - "en": "Checking docs structure...", - "pl": "Checking docs structure...", - "ru": "Checking docs structure...", - "zh": "Checking docs structure..." - }, - "Checking duplicate headings...": { - "bg": "Checking duplicate headings...", - "de": "Checking duplicate headings...", - "en": "Checking duplicate headings...", - "pl": "Checking duplicate headings...", - "ru": "Checking duplicate headings...", - "zh": "Checking duplicate headings..." - }, - "Checking for TODO/FIXME markers...": { - "bg": "Checking for TODO/FIXME markers...", - "de": "Checking for TODO/FIXME markers...", - "en": "Checking for TODO/FIXME markers...", - "pl": "Checking for TODO/FIXME markers...", - "ru": "Checking for TODO/FIXME markers...", - "zh": "Checking for TODO/FIXME markers..." - }, - "Checking for orphan docs...": { - "bg": "", - "de": "", - "en": "Checking for orphan docs...", - "pl": "", - "ru": "", - "zh": "" - }, - "Checking for stale docs...": { - "bg": "Checking for stale docs...", - "de": "Checking for stale docs...", - "en": "Checking for stale docs...", - "pl": "Checking for stale docs...", - "ru": "Checking for stale docs...", - "zh": "Checking for stale docs..." - }, - "Checking heading hierarchy...": { - "bg": "Checking heading hierarchy...", - "de": "Checking heading hierarchy...", - "en": "Checking heading hierarchy...", - "pl": "Checking heading hierarchy...", - "ru": "Checking heading hierarchy...", - "zh": "Checking heading hierarchy..." - }, - "Checking internal links...": { - "bg": "Checking internal links...", - "de": "Checking internal links...", - "en": "Checking internal links...", - "pl": "Checking internal links...", - "ru": "Checking internal links...", - "zh": "Checking internal links..." - }, - "Checking line length...": { - "bg": "", - "de": "", - "en": "Checking line length...", - "pl": "", - "ru": "", - "zh": "" - }, - "Checking max heading depth...": { - "bg": "", - "de": "", - "en": "Checking max heading depth...", - "pl": "", - "ru": "", - "zh": "" - }, - "Checking required files...": { - "bg": "Checking required files...", - "de": "Checking required files...", - "en": "Checking required files...", - "pl": "Checking required files...", - "ru": "Checking required files...", - "zh": "Checking required files..." - }, - "Checking single H1 per file...": { - "bg": "", - "de": "", - "en": "Checking single H1 per file...", - "pl": "", - "ru": "", - "zh": "" - }, - "Checking status for PR #{pr_number}...": { - "bg": "Checking status for PR #{pr_number}...", - "de": "Checking status for PR #{pr_number}...", - "en": "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}..." - }, - "Checking trailing whitespace...": { - "bg": "Checking trailing whitespace...", - "de": "Checking trailing whitespace...", - "en": "Checking trailing whitespace...", - "pl": "Checking trailing whitespace...", - "ru": "Checking trailing whitespace...", - "zh": "Checking trailing whitespace..." - }, - "Checking version references for {pkg} (current: v{version})": { - "bg": "", - "de": "", - "en": "Checking version references for {pkg} (current: v{version})", - "pl": "", - "ru": "", - "zh": "" - }, - "Cloned existing wiki.": { - "bg": "", - "de": "", - "en": "Cloned existing wiki.", - "pl": "", - "ru": "", - "zh": "" - }, - "Cloning wiki repo...": { - "bg": "", - "de": "", - "en": "Cloning wiki repo...", - "pl": "", - "ru": "", - "zh": "" - }, - "Command failed ({cmd}): {stderr}": { - "bg": "Command failed ({cmd}): {stderr}", - "de": "Command failed ({cmd}): {stderr}", - "en": "Command failed ({cmd}): {stderr}", - "pl": "Polecenie nie powiodło się ({cmd}): {stderr}", - "ru": "Command failed ({cmd}): {stderr}", - "zh": "Command failed ({cmd}): {stderr}" - }, - "Commit message: {msg}": { - "bg": "Commit message: {msg}", - "de": "Commit message: {msg}", - "en": "Commit message: {msg}", - "pl": "Commit message: {msg}", - "ru": "Commit message: {msg}", - "zh": "Commit message: {msg}" - }, - "Commit: {sha}": { - "bg": "Commit: {sha}", - "de": "Commit: {sha}", - "en": "Commit: {sha}", - "pl": "Commit: {sha}", - "ru": "Commit: {sha}", - "zh": "Commit: {sha}" - }, - "Committing and pushing...": { - "bg": "", - "de": "", - "en": "Committing and pushing...", - "pl": "", - "ru": "", - "zh": "" - }, - "Comparing {base}..{head} ({count} files changed)": { - "bg": "Comparing {base}..{head} ({count} files changed)", - "de": "Comparing {base}..{head} ({count} files changed)", - "en": "Comparing {base}..{head} ({count} files changed)", - "pl": "Porównywanie {base}..{head} ({count} zmienionych plików)", - "ru": "Comparing {base}..{head} ({count} files changed)", - "zh": "Comparing {base}..{head} ({count} files changed)" - }, - "Configuration OK: [tool.devx] present, devx versions consistent.": { - "bg": "Конфигурацията е OK: [tool.devx] присъства, версиите на devx са консистентни.", - "de": "Konfiguration OK: [tool.devx] vorhanden, devx-Versionen konsistent.", - "en": "Configuration OK: [tool.devx] present, devx versions consistent.", - "pl": "Konfiguracja OK: [tool.devx] obecne, wersje devx spójne.", - "ru": "Конфигурация OK: [tool.devx] присутствует, версии devx согласованы.", - "zh": "配置正常: [tool.devx] 已存在, devx 版本一致。" - }, - "Configuration validation failed.": { - "bg": "Configuration validation failed.", - "de": "Configuration validation failed.", - "en": "Configuration validation failed.", - "pl": "Configuration validation failed.", - "ru": "Configuration validation failed.", - "zh": "Configuration validation failed." - }, - "Configuring branch protection for {branch}...": { - "bg": "Конфигуриране на защита на клона {branch}...", - "de": "Konfiguriere Branch-Schutz für {branch}...", - "en": "Configuring branch protection for {branch}...", - "pl": "Konfigurowanie ochrony gałęzi dla {branch}...", - "ru": "Настройка защиты ветки {branch}...", - "zh": "正在配置 {branch} 的分支保护..." - }, - "Configuring repository settings...": { - "bg": "Конфигуриране на настройките на хранилището...", - "de": "Repository-Einstellungen konfigurieren...", - "en": "Configuring repository settings...", - "pl": "Konfigurowanie ustawień repozytorium...", - "ru": "Настройка параметров репозитория...", - "zh": "正在配置仓库设置..." - }, - "Configuring tea login '{name}' for {url}...": { - "bg": "Configuring tea login '{name}' for {url}...", - "de": "Configuring tea login '{name}' for {url}...", - "en": "Configuring tea login '{name}' for {url}...", - "pl": "Configuring tea login '{name}' for {url}...", - "ru": "Configuring tea login '{name}' for {url}...", - "zh": "Configuring tea login '{name}' for {url}..." - }, - "Could not detect PR number. Use --pr to specify it explicitly,\nor run this command from a branch with an open PR.": { - "bg": "Could not detect PR number. Use --pr to specify it explicitly,\nor run this command from a branch with an open PR.", - "de": "Could not detect PR number. Use --pr to specify it explicitly,\nor run this command from a branch with an open PR.", - "en": "Could not detect PR number. Use --pr to specify it explicitly,\nor run this command from a branch with an open PR.", - "pl": "Could not detect PR number. Use --pr to specify it explicitly,\nor run this command from a branch with an open PR.", - "ru": "Could not detect PR number. Use --pr to specify it explicitly,\nor run this command from a branch with an open PR.", - "zh": "Could not detect PR number. Use --pr to specify it explicitly,\nor run this command from a branch with an open PR." - }, - "Could not detect current branch: {error}": { - "bg": "Не може да се определи текущия клон: {error}", - "de": "Aktueller Branch konnte nicht erkannt werden: {error}", - "en": "Could not detect current branch: {error}", - "pl": "Nie można wykryć bieżącej gałęzi: {error}", - "ru": "Не удалось определить текущую ветку: {error}", - "zh": "无法检测当前分支: {error}" - }, - "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}.", - "en": "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}." - }, - "Could not determine repository. Set DEVX_REPO_OWNER and DEVX_REPO_NAME\nor GITHUB_REPOSITORY environment variables.": { - "bg": "Could not determine repository. Set DEVX_REPO_OWNER and DEVX_REPO_NAME\nor GITHUB_REPOSITORY environment variables.", - "de": "Could not determine repository. Set DEVX_REPO_OWNER and DEVX_REPO_NAME\nor GITHUB_REPOSITORY environment variables.", - "en": "Could not determine repository. Set DEVX_REPO_OWNER and DEVX_REPO_NAME\nor GITHUB_REPOSITORY environment variables.", - "pl": "Could not determine repository. Set DEVX_REPO_OWNER and DEVX_REPO_NAME\nor GITHUB_REPOSITORY environment variables.", - "ru": "Could not determine repository. Set DEVX_REPO_OWNER and DEVX_REPO_NAME\nor GITHUB_REPOSITORY environment variables.", - "zh": "Could not determine repository. Set DEVX_REPO_OWNER and DEVX_REPO_NAME\nor GITHUB_REPOSITORY environment variables." - }, - "Could not extract conventional commit message from PR commits.": { - "bg": "Could not extract conventional commit message from PR commits.", - "de": "Could not extract conventional commit message from PR commits.", - "en": "Could not extract conventional commit message from PR commits.", - "pl": "Nie udało się wyodrębnić konwencjonalnej wiadomości commit z commitów PR.", - "ru": "Could not extract conventional commit message from PR commits.", - "zh": "Could not extract conventional commit message from PR commits." - }, - "Could not fetch PR title from Gitea (CI_GITEA_TOKEN not set or PR not found).": { - "bg": "Could not fetch PR title from Gitea (CI_GITEA_TOKEN not set or PR not found).", - "de": "Could not fetch PR title from Gitea (CI_GITEA_TOKEN not set or PR not found).", - "en": "Could not fetch PR title from Gitea (CI_GITEA_TOKEN not set or PR not found).", - "pl": "Could not fetch PR title from Gitea (CI_GITEA_TOKEN not set or PR not found).", - "ru": "Could not fetch PR title from Gitea (CI_GITEA_TOKEN not set or PR not found).", - "zh": "Could not fetch PR title from Gitea (CI_GITEA_TOKEN not set or PR not found)." - }, - "Could not find Vikunja task {task_id} in project {project_id}.": { - "bg": "Не е намерена Vikunja задача {task_id} в проект {project_id}.", - "de": "Vikunja-Task {task_id} in Projekt {project_id} nicht gefunden.", - "en": "Could not find Vikunja task {task_id} in project {project_id}.", - "pl": "Nie znaleziono zadania Vikunja {task_id} w projekcie {project_id}.", - "ru": "Не найдена задача Vikunja {task_id} в проекте {project_id}.", - "zh": "在项目 {project_id} 中找不到 Vikunja 任务 {task_id}。" - }, - "Could not find Vikunja task {task_id} in project {project_id}. Every PR must have a corresponding Vikunja task.": { - "bg": "Could not find Vikunja task {task_id} in project {project_id}. Every PR must have a corresponding Vikunja task.", - "de": "Could not find Vikunja task {task_id} in project {project_id}. Every PR must have a corresponding Vikunja task.", - "en": "Could not find Vikunja task {task_id} in project {project_id}. Every PR must have a corresponding Vikunja task.", - "pl": "Nie znaleziono zadania Vikunja {task_id} w projekcie {project_id}. Każdy PR musi mieć odpowiadające zadanie Vikunja.", - "ru": "Could not find Vikunja task {task_id} in project {project_id}. Every PR must have a corresponding Vikunja task.", - "zh": "Could not find Vikunja task {task_id} in project {project_id}. Every PR must have a corresponding Vikunja task." - }, - "Could not find __version__ in {file}": { - "bg": "Could not find __version__ in {file}", - "de": "Could not find __version__ in {file}", - "en": "Could not find __version__ in {file}", - "pl": "Nie znaleziono __version__ w {file}", - "ru": "Could not find __version__ in {file}", - "zh": "Could not find __version__ in {file}" - }, - "Could not parse test execution time from output.": { - "bg": "Could not parse test execution time from output.", - "de": "Could not parse test execution time from output.", - "en": "Could not parse test execution time from output.", - "pl": "Nie udało się przeanalizować czasu wykonania testu z wyjścia.", - "ru": "Could not parse test execution time from output.", - "zh": "Could not parse test execution time from output." - }, - "Created PR #{index}: {title}\n {url}": { - "bg": "Създаден PR #{index}: {title}\n {url}", - "de": "PR erstellt #{index}: {title}\n {url}", - "en": "Created PR #{index}: {title}\n {url}", - "pl": "Utworzono PR #{index}: {title}\n {url}", - "ru": "Создан PR #{index}: {title}\n {url}", - "zh": "已创建 PR #{index}: {title}\n {url}" - }, - "Created Vikunja task: {identifier} (id={task_id})": { - "bg": "Създадена Vikunja задача: {identifier} (id={task_id})", - "de": "Vikunja-Task erstellt: {identifier} (id={task_id})", - "en": "Created Vikunja task: {identifier} (id={task_id})", - "pl": "Utworzono zadanie Vikunja: {identifier} (id={task_id})", - "ru": "Создана задача Vikunja: {identifier} (id={task_id})", - "zh": "已创建 Vikunja 任务: {identifier} (id={task_id})" - }, - "Created issue #{issue_id}: {title}": { - "bg": "Created issue #{issue_id}: {title}", - "de": "Created issue #{issue_id}: {title}", - "en": "Created issue #{issue_id}: {title}", - "pl": "Utworzono zgłoszenie #{issue_id}: {title}", - "ru": "Created issue #{issue_id}: {title}", - "zh": "Created issue #{issue_id}: {title}" - }, - "Created release commit.": { - "bg": "Created release commit.", - "de": "Created release commit.", - "en": "Created release commit.", - "pl": "Utworzono commit wydania.", - "ru": "Created release commit.", - "zh": "Created release commit." - }, - "Dependencies must have documentation comments.": { - "bg": "Dependencies must have documentation comments.", - "de": "Dependencies must have documentation comments.", - "en": "Dependencies must have documentation comments.", - "pl": "Dependencies must have documentation comments.", - "ru": "Dependencies must have documentation comments.", - "zh": "Dependencies must have documentation comments." - }, - "Directory to scan (default: tests/integration). Can be repeated.": { - "bg": "Директория за сканиране (по подразбиране: tests/integration). Може да се повтаря.", - "de": "Zu scannendes Verzeichnis (Standard: tests/integration). Kann wiederholt werden.", - "en": "Directory to scan (default: tests/integration). Can be repeated.", - "pl": "Katalog do skanowania (domyślnie: tests/integration). Można powtarzać.", - "ru": "Директория для сканирования (по умолчанию: tests/integration). Можно повторять.", - "zh": "要扫描的目录(默认:tests/integration)。可重复。" - }, - "Docker daemon already running": { - "bg": "Докер демонът вече работи", - "de": "Docker-Daemon läuft bereits", - "en": "Docker daemon already running", - "pl": "Demon Docker już uruchomiony", - "ru": "Демон Docker уже работает", - "zh": "Docker 守护进程已在运行" - }, - "Docker daemon failed to start": { - "bg": "Docker daemon failed to start", - "de": "Docker-Daemon konnte nicht gestartet werden", - "en": "Docker daemon failed to start", - "pl": "Nie udało się uruchomić demona Docker", - "ru": "Не удалось запустить Docker-демон", - "zh": "Docker 守护进程启动失败" - }, - "Docker daemon started": { - "bg": "Docker daemon started", - "de": "Docker-Daemon gestartet", - "en": "Docker daemon started", - "pl": "Demon Docker uruchomiony", - "ru": "Docker-демон запущен", - "zh": "Docker 守护进程已启动" - }, - "Dockerfile not found: {path}": { - "bg": "Dockerfile not found: {path}", - "de": "Dockerfile not found: {path}", - "en": "Dockerfile not found: {path}", - "pl": "Dockerfile not found: {path}", - "ru": "Dockerfile not found: {path}", - "zh": "Dockerfile not found: {path}" - }, - "Dry-run mode: on branch '{branch}' (not master). Some checks may behave differently.": { - "bg": "Dry-run mode: on branch '{branch}' (not master). Some checks may behave differently.", - "de": "Dry-run mode: on branch '{branch}' (not master). Some checks may behave differently.", - "en": "Dry-run mode: on branch '{branch}' (not master). Some checks may behave differently.", - "pl": "Tryb dry-run: na gałęzi '{branch}' (nie master). Niektóre kontrole mogą zachowywać się inaczej.", - "ru": "Dry-run mode: on branch '{branch}' (not master). Some checks may behave differently.", - "zh": "Dry-run mode: on branch '{branch}' (not master). Some checks may behave differently." - }, - "ERROR: CI_GITEA_TOKEN is not set.": { - "bg": "ГРЕШКА: CI_GITEA_TOKEN не е зададен.", - "de": "FEHLER: CI_GITEA_TOKEN ist nicht gesetzt.", - "en": "ERROR: CI_GITEA_TOKEN is not set.", - "pl": "BŁĄD: CI_GITEA_TOKEN nie jest ustawiony.", - "ru": "ОШИБКА: CI_GITEA_TOKEN не задан.", - "zh": "错误:未设置 CI_GITEA_TOKEN。" - }, - "ERROR: Repository name not specified. Use --repo or set DEVX_REPO_NAME.": { - "bg": "ГРЕШКА: Името на хранилището не е указано. Използвайте --repo или задайте DEVX_REPO_NAME.", - "de": "FEHLER: Repository-Name nicht angegeben. Verwenden Sie --repo oder setzen Sie DEVX_REPO_NAME.", - "en": "ERROR: Repository name not specified. Use --repo or set DEVX_REPO_NAME.", - "pl": "BŁĄD: Nazwa repozytorium nie jest określona. Użyj --repo lub ustaw DEVX_REPO_NAME.", - "ru": "ОШИБКА: Имя репозитория не указано. Используйте --repo или задайте DEVX_REPO_NAME.", - "zh": "错误:未指定仓库名称。请使用 --repo 或设置 DEVX_REPO_NAME。" - }, - "ERROR: Tag consistency check failed. Existing tags are misaligned:": { - "bg": "ERROR: Tag consistency check failed. Existing tags are misaligned:", - "de": "ERROR: Tag consistency check failed. Existing tags are misaligned:", - "en": "ERROR: Tag consistency check failed. Existing tags are misaligned:", - "pl": "BŁĄD: Kontrola zgodności tagów nie powiodła się. Istniejące tagi są niezgodne:", - "ru": "ERROR: Tag consistency check failed. Existing tags are misaligned:", - "zh": "ERROR: Tag consistency check failed. Existing tags are misaligned:" - }, - "ERROR: VIKUNJA_TOKEN is not set.": { - "bg": "ГРЕШКА: VIKUNJA_TOKEN не е зададен.", - "de": "FEHLER: VIKUNJA_TOKEN ist nicht gesetzt.", - "en": "ERROR: VIKUNJA_TOKEN is not set.", - "pl": "BŁĄD: VIKUNJA_TOKEN nie jest ustawiony.", - "ru": "ОШИБКА: VIKUNJA_TOKEN не задан.", - "zh": "错误:未设置 VIKUNJA_TOKEN。" - }, - "ERROR: mapping.json not found at {path}": { - "bg": "ERROR: mapping.json not found at {path}", - "de": "ERROR: mapping.json not found at {path}", - "en": "ERROR: mapping.json not found at {path}", - "pl": "BŁĄD: mapping.json nie znaleziono w {path}", - "ru": "ERROR: mapping.json not found at {path}", - "zh": "ERROR: mapping.json not found at {path}" - }, - "Each item must be a string or an object with 'id', got {type}": { - "bg": "Всеки елемент трябва да е низ или обект с 'id', получено {type}", - "de": "Jedes Element muss ein String oder ein Objekt mit 'id' sein, erhalten {type}", - "en": "Each item must be a string or an object with 'id', got {type}", - "pl": "Każdy element musi być ciągiem lub obiektem z 'id', otrzymano {type}", - "ru": "Каждый элемент должен быть строкой или объектом с 'id', получено {type}", - "zh": "每个元素必须是字符串或带有 'id' 的对象,得到 {type}" - }, - "Ensuring standard labels...": { - "bg": "Ensuring standard labels...", - "de": "Ensuring standard labels...", - "en": "Ensuring standard labels...", - "pl": "Ensuring standard labels...", - "ru": "Ensuring standard labels...", - "zh": "Ensuring standard labels..." - }, - "FAIL: Could not clone wiki for verification.": { - "bg": "", - "de": "", - "en": "FAIL: Could not clone wiki for verification.", - "pl": "", - "ru": "", - "zh": "" - }, - "FAIL: {n} documentation issues found:": { - "bg": "FAIL: {n} documentation issues found:", - "de": "FAIL: {n} documentation issues found:", - "en": "FAIL: {n} documentation issues found:", - "pl": "FAIL: {n} documentation issues found:", - "ru": "FAIL: {n} documentation issues found:", - "zh": "FAIL: {n} documentation issues found:" - }, - "FAILED: {count} undocumented dependency/ies": { - "bg": "FAILED: {count} undocumented dependency/ies", - "de": "FAILED: {count} undocumented dependency/ies", - "en": "FAILED: {count} undocumented dependency/ies", - "pl": "FAILED: {count} undocumented dependency/ies", - "ru": "FAILED: {count} undocumented dependency/ies", - "zh": "FAILED: {count} undocumented dependency/ies" - }, - "Failed images: {names}": { - "bg": "Failed images: {names}", - "de": "Failed images: {names}", - "en": "Failed images: {names}", - "pl": "Failed images: {names}", - "ru": "Failed images: {names}", - "zh": "Failed images: {names}" - }, - "Failed to create issue via tea: {error}": { - "bg": "Failed to create issue via tea: {error}", - "de": "Failed to create issue via tea: {error}", - "en": "Failed to create issue via tea: {error}", - "pl": "Nie udało się utworzyć zgłoszenia przez tea: {error}", - "ru": "Failed to create issue via tea: {error}", - "zh": "Failed to create issue via tea: {error}" - }, - "Failed to delete {count} image version(s)": { - "bg": "Failed to delete {count} image version(s)", - "de": "Failed to delete {count} image version(s)", - "en": "Failed to delete {count} image version(s)", - "pl": "Failed to delete {count} image version(s)", - "ru": "Failed to delete {count} image version(s)", - "zh": "Failed to delete {count} image version(s)" - }, - "Failed to list versions for {name}: {error}": { - "bg": "Failed to list versions for {name}: {error}", - "de": "Failed to list versions for {name}: {error}", - "en": "Failed to list versions for {name}: {error}", - "pl": "Failed to list versions for {name}: {error}", - "ru": "Failed to list versions for {name}: {error}", - "zh": "Failed to list versions for {name}: {error}" - }, - "Failed to push release commit after 3 attempts. Manual intervention required.": { - "bg": "Failed to push release commit after 3 attempts. Manual intervention required.", - "de": "Failed to push release commit after 3 attempts. Manual intervention required.", - "en": "Failed to push release commit after 3 attempts. Manual intervention required.", - "pl": "Failed to push release commit after 3 attempts. Manual intervention required.", - "ru": "Failed to push release commit after 3 attempts. Manual intervention required.", - "zh": "Failed to push release commit after 3 attempts. Manual intervention required." - }, - "Failed to start ssh-agent: {error}": { - "bg": "Неуспешно стартиране на ssh-agent: {error}", - "de": "Starten von ssh-agent fehlgeschlagen: {error}", - "en": "Failed to start ssh-agent: {error}", - "pl": "Nie udało się uruchomić ssh-agent: {error}", - "ru": "Не удалось запустить ssh-agent: {error}", - "zh": "启动 ssh-agent 失败: {error}" - }, - "Fetch failed: {error}": { - "bg": "Fetch failed: {error}", - "de": "Fetch failed: {error}", - "en": "Fetch failed: {error}", - "pl": "Fetch failed: {error}", - "ru": "Fetch failed: {error}", - "zh": "Fetch failed: {error}" - }, - "Fetching logs for PR #{pr_number}...": { - "bg": "Fetching logs for PR #{pr_number}...", - "de": "Fetching logs for PR #{pr_number}...", - "en": "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}..." - }, - "Fetching origin/master...": { - "bg": "Fetching origin/master...", - "de": "Fetching origin/master...", - "en": "Fetching origin/master...", - "pl": "Fetching origin/master...", - "ru": "Fetching origin/master...", - "zh": "Fetching origin/master..." - }, - "Force-push failed:\n{error}\nThe remote may have unexpected commits. Fetch and try again.": { - "bg": "Force-push failed:\n{error}\nThe remote may have unexpected commits. Fetch and try again.", - "de": "Force-push failed:\n{error}\nThe remote may have unexpected commits. Fetch and try again.", - "en": "Force-push failed:\n{error}\nThe remote may have unexpected commits. Fetch and try again.", - "pl": "Force-push failed:\n{error}\nThe remote may have unexpected commits. Fetch and try again.", - "ru": "Force-push failed:\n{error}\nThe remote may have unexpected commits. Fetch and try again.", - "zh": "Force-push failed:\n{error}\nThe remote may have unexpected commits. Fetch and try again." - }, - "Force-pushing...": { - "bg": "Force-pushing...", - "de": "Force-pushing...", - "en": "Force-pushing...", - "pl": "Force-pushing...", - "ru": "Force-pushing...", - "zh": "Force-pushing..." - }, - "Found {count} mutable global(s) — use factory functions or pytest fixtures.": { - "bg": "Found {count} mutable global(s) — use factory functions or pytest fixtures.", - "de": "Found {count} mutable global(s) — use factory functions or pytest fixtures.", - "en": "Found {count} mutable global(s) — use factory functions or pytest fixtures.", - "pl": "Found {count} mutable global(s) — use factory functions or pytest fixtures.", - "ru": "Found {count} mutable global(s) — use factory functions or pytest fixtures.", - "zh": "Found {count} mutable global(s) — use factory functions or pytest fixtures." - }, - "Found {count} stale documentation reference(s)": { - "bg": "Found {count} stale documentation reference(s)", - "de": "Found {count} stale documentation reference(s)", - "en": "Found {count} stale documentation reference(s)", - "pl": "Found {count} stale documentation reference(s)", - "ru": "Found {count} stale documentation reference(s)", - "zh": "Found {count} stale documentation reference(s)" - }, - "Found {count} unsafe identity check(s) in integration tests.": { - "bg": "Намерени са {count} небрежни проверки за идентичност в интеграционните тестове.", - "de": "{count} unsichere Identitätsprüfung(en) in Integrationstests gefunden.", - "en": "Found {count} unsafe identity check(s) in integration tests.", - "pl": "Znaleziono {count} niebezpiecznych sprawdzeń tożsamości w testach integracyjnych.", - "ru": "Найдено {count} небезопасных проверок идентичности в интеграционных тестах.", - "zh": "在集成测试中发现 {count} 个不安全的身份检查。" - }, - "Found {count} version(s):": { - "bg": "Found {count} version(s):", - "de": "Found {count} version(s):", - "en": "Found {count} version(s):", - "pl": "Found {count} version(s):", - "ru": "Found {count} version(s):", - "zh": "Found {count} version(s):" - }, - "GITEA_URL/CI_GITEA_TOKEN/RUN_ID not set; running without cross-runner cancellation.": { - "bg": "GITEA_URL/CI_GITEA_TOKEN/RUN_ID not set; running without cross-runner cancellation.", - "de": "GITEA_URL/CI_GITEA_TOKEN/RUN_ID not set; running without cross-runner cancellation.", - "en": "GITEA_URL/CI_GITEA_TOKEN/RUN_ID not set; running without cross-runner cancellation.", - "pl": "GITEA_URL/CI_GITEA_TOKEN/RUN_ID nie ustawione; uruchamianie bez anulowania między runnerami.", - "ru": "GITEA_URL/CI_GITEA_TOKEN/RUN_ID not set; running without cross-runner cancellation.", - "zh": "GITEA_URL/CI_GITEA_TOKEN/RUN_ID not set; running without cross-runner cancellation." - }, - "Generated {count} badge files": { - "bg": "Generated {count} badge files", - "de": "Generated {count} badge files", - "en": "Generated {count} badge files", - "pl": "Generated {count} badge files", - "ru": "Generated {count} badge files", - "zh": "Generated {count} badge files" - }, - "Generated {file} with prefix '{prefix}'.": { - "bg": "Generated {file} with prefix '{prefix}'.", - "de": "Generated {file} with prefix '{prefix}'.", - "en": "Generated {file} with prefix '{prefix}'.", - "pl": "Wygenerowano {file} z prefiksem '{prefix}'.", - "ru": "Generated {file} with prefix '{prefix}'.", - "zh": "Generated {file} with prefix '{prefix}'." - }, - "Generating badges in {out}...": { - "bg": "Generating badges in {out}...", - "de": "Generating badges in {out}...", - "en": "Generating badges in {out}...", - "pl": "Generating badges in {out}...", - "ru": "Generating badges in {out}...", - "zh": "Generating badges in {out}..." - }, - "Git tag or ref that was deployed": { - "bg": "Git таг или референция, която беше разгърната", - "de": "Git-Tag oder Ref, der bereitgestellt wurde", - "en": "Git tag or ref that was deployed", - "pl": "Tag Git lub ref, który został wdrożony", - "ru": "Git-тег или ссылка, которые были развёрнуты", - "zh": "已部署的 Git 标签或引用" - }, - "Git tag to deploy (e.g. v0.28.1).": { - "bg": "Git таг за разгръщане (напр. v0.28.1).", - "de": "Git-Tag für Bereitstellung (z.B. v0.28.1).", - "en": "Git tag to deploy (e.g. v0.28.1).", - "pl": "Tag Git do wdrożenia (np. v0.28.1).", - "ru": "Git-тег для развёртывания (напр. v0.28.1).", - "zh": "要部署的 Git 标签(例如 v0.28.1)。" - }, - "Gitea API token not set. Set one of: {names}": { - "bg": "Gitea API token not set. Set one of: {names}", - "de": "Gitea API token not set. Set one of: {names}", - "en": "Gitea API token not set. Set one of: {names}", - "pl": "Gitea API token not set. Set one of: {names}", - "ru": "Gitea API token not set. Set one of: {names}", - "zh": "Gitea API token not set. Set one of: {names}" - }, - "Gitea PyPI registry: {tag} already published — continuing.": { - "bg": "Gitea PyPI registry: {tag} вече е публикуван — продължава.", - "de": "Gitea PyPI-Registry: {tag} bereits veröffentlicht — wird fortgesetzt.", - "en": "Gitea PyPI registry: {tag} already published — continuing.", - "pl": "Gitea PyPI registry: {tag} już opublikowano — kontynuacja.", - "ru": "Gitea PyPI registry: {tag} уже опубликован — продолжаем.", - "zh": "Gitea PyPI registry: {tag} 已发布 — 继续。" - }, - "Gitea release {tag} already exists — skipping creation.": { - "bg": "Gitea release {tag} вече съществува — прескачане на създаването.", - "de": "Gitea-Release {tag} existiert bereits — Erstellung übersprungen.", - "en": "Gitea release {tag} already exists — skipping creation.", - "pl": "Wydanie Gitea {tag} już istnieje — pomijanie tworzenia.", - "ru": "Gitea release {tag} уже существует — пропуск создания.", - "zh": "Gitea release {tag} 已存在 — 跳过创建。" - }, - "HEAD is a release commit ('{msg}') but tag {tag} is missing. Recovering by creating tag.": { - "bg": "HEAD is a release commit ('{msg}') but tag {tag} is missing. Recovering by creating tag.", - "de": "HEAD is a release commit ('{msg}') but tag {tag} is missing. Recovering by creating tag.", - "en": "HEAD is a release commit ('{msg}') but tag {tag} is missing. Recovering by creating tag.", - "pl": "HEAD jest commitem wydania ('{msg}') ale tag {tag} brakuje. Naprawa przez utworzenie tagu.", - "ru": "HEAD is a release commit ('{msg}') but tag {tag} is missing. Recovering by creating tag.", - "zh": "HEAD is a release commit ('{msg}') but tag {tag} is missing. Recovering by creating tag." - }, - "HEAD is a release commit for v{version} but tag {tag} points to a different commit ({tag_commit} vs HEAD {head_commit}). This indicates a tag/commit misalignment.": { - "bg": "HEAD is a release commit for v{version} but tag {tag} points to a different commit ({tag_commit} vs HEAD {head_commit}). This indicates a tag/commit misalignment.", - "de": "HEAD is a release commit for v{version} but tag {tag} points to a different commit ({tag_commit} vs HEAD {head_commit}). This indicates a tag/commit misalignment.", - "en": "HEAD is a release commit for v{version} but tag {tag} points to a different commit ({tag_commit} vs HEAD {head_commit}). This indicates a tag/commit misalignment.", - "pl": "HEAD jest commitem wydania dla v{version} ale tag {tag} wskazuje na inny commit ({tag_commit} vs HEAD {head_commit}). Wskazuje to na niezgodność tag/commit.", - "ru": "HEAD is a release commit for v{version} but tag {tag} points to a different commit ({tag_commit} vs HEAD {head_commit}). This indicates a tag/commit misalignment.", - "zh": "HEAD is a release commit for v{version} but tag {tag} points to a different commit ({tag_commit} vs HEAD {head_commit}). This indicates a tag/commit misalignment." - }, - "HEAD is already a release commit ('{msg}') and tag {tag} points to HEAD. Skipping.": { - "bg": "HEAD is already a release commit ('{msg}') and tag {tag} points to HEAD. Skipping.", - "de": "HEAD is already a release commit ('{msg}') and tag {tag} points to HEAD. Skipping.", - "en": "HEAD is already a release commit ('{msg}') and tag {tag} points to HEAD. Skipping.", - "pl": "HEAD jest już commitem wydania ('{msg}') a tag {tag} wskazuje na HEAD. Pomijanie.", - "ru": "HEAD is already a release commit ('{msg}') and tag {tag} points to HEAD. Skipping.", - "zh": "HEAD is already a release commit ('{msg}') and tag {tag} points to HEAD. Skipping." - }, - "HEAD is not a release commit for {tag} — skipping publish.": { - "bg": "HEAD is not a release commit for {tag} — skipping publish.", - "de": "HEAD is not a release commit for {tag} — skipping publish.", - "en": "HEAD is not a release commit for {tag} — skipping publish.", - "pl": "HEAD nie jest commitem wydania dla {tag} — pomijanie publikacji.", - "ru": "HEAD is not a release commit for {tag} — skipping publish.", - "zh": "HEAD is not a release commit for {tag} — skipping publish." - }, - "HTTP error: {status} — {message}": { - "bg": "HTTP грешка: {status} — {message}", - "de": "HTTP-Fehler: {status} — {message}", - "en": "HTTP error: {status} — {message}", - "pl": "Błąd HTTP: {status} — {message}", - "ru": "Ошибка HTTP: {status} — {message}", - "zh": "HTTP 错误: {status} — {message}" - }, - "HTTP {status} Forbidden — your token lacks admin rights.\nMake sure the token belongs to a repo owner or organisation admin.\nAlternatively, configure branch protection manually in Settings → Branches.": { - "bg": "HTTP {status} Забранено — вашият токен няма администраторски права.\nУверете се, че токенът принадлежи на собственик на хранилище или администратор на организация.\nАлтернативно, конфигурирайте защитата на клона ръчно в Настройки → Клонове.", - "de": "HTTP {status} Verboten — Ihr Token hat keine Admin-Rechte.\nStellen Sie sicher, dass das Token einem Repository-Besitzer oder Organisations-Admin gehört.\nAlternativ können Sie den Branch-Schutz manuell unter Einstellungen → Branches konfigurieren.", - "en": "HTTP {status} Forbidden — your token lacks admin rights.\nMake sure the token belongs to a repo owner or organisation admin.\nAlternatively, configure branch protection manually in Settings → Branches.", - "pl": "HTTP {status} Forbidden — twój token nie ma uprawnień administratora.\nUpewnij się, że token należy do właściciela repozytorium lub administratora organizacji.\nAlternatywnie skonfiguruj ochronę gałęzi ręcznie w Ustawienia → Gałęzie.", - "ru": "HTTP {status} Запрещено — у вашего токена нет прав администратора.\nУбедитесь, что токен принадлежит владельцу репозитория или администратору организации.\nЛибо настройте защиту ветки вручную в разделе Настройки → Ветки.", - "zh": "HTTP {status} 禁止访问 — 您的令牌缺少管理员权限。\n请确保令牌属于仓库所有者或组织管理员。\n或者,您可以在 设置 → 分支 中手动配置分支保护。" - }, - "Host Docker not available, starting local dockerd...": { - "bg": "Хост Docker не е наличен, стартиране на локален dockerd...", - "de": "Host-Docker nicht verfügbar, lokaler dockerd wird gestartet...", - "en": "Host Docker not available, starting local dockerd...", - "pl": "Host Docker niedostępny, uruchamianie lokalnego dockerd...", - "ru": "Хост Docker недоступен, запускается локальный dockerd...", - "zh": "主机 Docker 不可用,正在启动本地 dockerd..." - }, - "Image 'tags' must be a list": { - "bg": "Image 'tags' must be a list", - "de": "Image 'tags' must be a list", - "en": "Image 'tags' must be a list", - "pl": "Image 'tags' must be a list", - "ru": "Image 'tags' must be a list", - "zh": "Image 'tags' must be a list" - }, - "Image manifest entry missing 'dockerfile'": { - "bg": "Image manifest entry missing 'dockerfile'", - "de": "Image manifest entry missing 'dockerfile'", - "en": "Image manifest entry missing 'dockerfile'", - "pl": "Image manifest entry missing 'dockerfile'", - "ru": "Image manifest entry missing 'dockerfile'", - "zh": "Image manifest entry missing 'dockerfile'" - }, - "Image manifest entry missing 'name'": { - "bg": "Image manifest entry missing 'name'", - "de": "Image manifest entry missing 'name'", - "en": "Image manifest entry missing 'name'", - "pl": "Image manifest entry missing 'name'", - "ru": "Image manifest entry missing 'name'", - "zh": "Image manifest entry missing 'name'" - }, - "Infrastructure commit (no DEVX-N task ID), skipping Vikunja update: {msg}": { - "bg": "Инфраструктурен commit (без идентификатор на задача DEVX-N), пропускаме обновяването на Vikunja: {msg}", - "de": "Infrastruktur-Commit (keine DEVX-N Task-ID), Vikunja-Update wird übersprungen: {msg}", - "en": "Infrastructure commit (no DEVX-N task ID), skipping Vikunja update: {msg}", - "pl": "Commit infrastruktury (bez ID zadania DEVX-N), pomijanie aktualizacji Vikunja: {msg}", - "ru": "Инфраструктурный коммит (без ID задачи DEVX-N), пропуск обновления Vikunja: {msg}", - "zh": "基础设施提交(无 DEVX-N 任务 ID),跳过 Vikunja 更新: {msg}" - }, - "Integration tests cancelled — another runner failed.": { - "bg": "Integration tests cancelled — another runner failed.", - "de": "Integration tests cancelled — another runner failed.", - "en": "Integration tests cancelled — another runner failed.", - "pl": "Testy integracyjne anulowane — inny runner zakończył się niepowodzeniem.", - "ru": "Integration tests cancelled — another runner failed.", - "zh": "Integration tests cancelled — another runner failed." - }, - "Integration tests failed with exit code {code}": { - "bg": "Integration tests failed with exit code {code}", - "de": "Integration tests failed with exit code {code}", - "en": "Integration tests failed with exit code {code}", - "pl": "Testy integracyjne zakończone niepowodzeniem z kodem {code}", - "ru": "Integration tests failed with exit code {code}", - "zh": "Integration tests failed with exit code {code}" - }, - "Integration tests passed.": { - "bg": "Integration tests passed.", - "de": "Integration tests passed.", - "en": "Integration tests passed.", - "pl": "Testy integracyjne zakończone pomyślnie.", - "ru": "Integration tests passed.", - "zh": "Integration tests passed." - }, - "Invalid checklist category: {cat}. Must be numbers.": { - "bg": "Invalid checklist category: {cat}. Must be numbers.", - "de": "Invalid checklist category: {cat}. Must be numbers.", - "en": "Invalid checklist category: {cat}. Must be numbers.", - "pl": "Invalid checklist category: {cat}. Must be numbers.", - "ru": "Invalid checklist category: {cat}. Must be numbers.", - "zh": "Invalid checklist category: {cat}. Must be numbers." - }, - "Items input must be a JSON array, got {type}": { - "bg": "Входните данни трябва да са JSON масив, получено {type}", - "de": "Eingabe muss ein JSON-Array sein, erhalten {type}", - "en": "Items input must be a JSON array, got {type}", - "pl": "Dane wejściowe muszą być tablicą JSON, otrzymano {type}", - "ru": "Входные данные должны быть JSON-массивом, получено {type}", - "zh": "输入必须是 JSON 数组,得到 {type}" - }, - "Label '{label}' already on PR #{pr}.": { - "bg": "Label '{label}' already on PR #{pr}.", - "de": "Label '{label}' already on PR #{pr}.", - "en": "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})": { - "bg": "Latest run: #{run_id} (status: {status})", - "de": "Latest run: #{run_id} (status: {status})", - "en": "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})" - }, - "Lint failed — refusing to release. Fix lint errors first.\n{stderr}": { - "bg": "Lint failed — refusing to release. Fix lint errors first.\n{stderr}", - "de": "Lint failed — refusing to release. Fix lint errors first.\n{stderr}", - "en": "Lint failed — refusing to release. Fix lint errors first.\n{stderr}", - "pl": "Lint nie powiódł się — odmowa wydania. Najpierw napraw błędy lint.\n{stderr}", - "ru": "Lint failed — refusing to release. Fix lint errors first.\n{stderr}", - "zh": "Lint failed — refusing to release. Fix lint errors first.\n{stderr}" - }, - "Lint passed.": { - "bg": "Lint passed.", - "de": "Lint passed.", - "en": "Lint passed.", - "pl": "Lint zakończony pomyślnie.", - "ru": "Lint passed.", - "zh": "Lint passed." - }, - "Linting documentation in {root}...": { - "bg": "Linting documentation in {root}...", - "de": "Linting documentation in {root}...", - "en": "Linting documentation in {root}...", - "pl": "Linting documentation in {root}...", - "ru": "Linting documentation in {root}...", - "zh": "Linting documentation in {root}..." - }, - "Login to {registry} failed: {error}": { - "bg": "Влизането в {registry} не успя: {error}", - "de": "Anmeldung bei {registry} fehlgeschlagen: {error}", - "en": "Login to {registry} failed: {error}", - "pl": "Logowanie do {registry} nie powiodło się: {error}", - "ru": "Ошибка входа в {registry}: {error}", - "zh": "登录 {registry} 失败: {error}" - }, - "Loop with {count} iterations in test '{test}' — consider property-based testing (hypothesis) or reduce to <= {max} iterations.": { - "bg": "Цикъл с {count} итерации в тест '{test}' — използвайте property-based тестове (hypothesis) или намалете до <= {max} итерации.", - "de": "Schleife mit {count} Iterationen in Test '{test}' — property-based testing (hypothesis) verwenden oder auf <= {max} Iterationen reduzieren.", - "en": "Loop with {count} iterations in test '{test}' — consider property-based testing (hypothesis) or reduce to <= {max} iterations.", - "pl": "Pętla z {count} iteracjami w teście '{test}' — rozważ testy oparte na właściwościach (hypothesis) lub zmniejsz do <= {max} iteracji.", - "ru": "Цикл с {count} итерациями в тесте '{test}' — используйте property-based тестирование (hypothesis) или уменьшите до <= {max} итераций.", - "zh": "测试 '{test}' 中有 {count} 次迭代的循环 — 考虑使用基于属性的测试 (hypothesis) 或减少到 <= {max} 次迭代。" - }, - "Manifest file not found: {path}": { - "bg": "Manifest file not found: {path}", - "de": "Manifest file not found: {path}", - "en": "Manifest file not found: {path}", - "pl": "Manifest file not found: {path}", - "ru": "Manifest file not found: {path}", - "zh": "Manifest file not found: {path}" - }, - "Manifest must be a JSON list": { - "bg": "Manifest must be a JSON list", - "de": "Manifest must be a JSON list", - "en": "Manifest must be a JSON list", - "pl": "Manifest must be a JSON list", - "ru": "Manifest must be a JSON list", - "zh": "Manifest must be a JSON list" - }, - "Merge failed with HTTP {status}: {message}\nPlease check the PR is ready and you have merge rights.": { - "bg": "Сливането неуспешно с HTTP {status}: {message}\nПроверете дали PR е готов и имате права за сливане.", - "de": "Merge fehlgeschlagen mit HTTP {status}: {message}\nBitte prüfen Sie, ob der PR bereit ist und Sie Merge-Rechte haben.", - "en": "Merge failed with HTTP {status}: {message}\nPlease check the PR is ready and you have merge rights.", - "pl": "Scalanie nie powiodło się z HTTP {status}: {message}\nSprawdź czy PR jest gotowy i masz uprawnienia do scalania.", - "ru": "Слияние не удалось: HTTP {status}: {message}\nПроверьте, что PR готов и у вас есть права на слияние.", - "zh": "合并失败: HTTP {status}: {message}\n请检查 PR 是否准备就绪且您具有合并权限。" - }, - "Missing tests for changed files.": { - "bg": "Missing tests for changed files.", - "de": "Missing tests for changed files.", - "en": "Missing tests for changed files.", - "pl": "Missing tests for changed files.", - "ru": "Missing tests for changed files.", - "zh": "Missing tests for changed files." - }, - "Module {mod} has no main() function": { - "bg": "Модул {mod} няма функция main()", - "de": "Modul {mod} hat keine main()-Funktion", - "en": "Module {mod} has no main() function", - "pl": "Moduł {mod} nie ma funkcji main()", - "ru": "Модуль {mod} не имеет функции main()", - "zh": "模块 {mod} 没有 main() 函数" - }, - "Molecule directory not found: {path}": { - "bg": "Директорията на molecule не е намерена: {path}", - "de": "Molecule-Verzeichnis nicht gefunden: {path}", - "en": "Molecule directory not found: {path}", - "pl": "Katalog molecule nie znaleziony: {path}", - "ru": "Директория molecule не найдена: {path}", - "zh": "未找到 molecule 目录: {path}" - }, - "Next steps:\n 1. git checkout master && git pull\n 2. git checkout -b {prefix}-{num}-short-description\n 3. Implement changes, commit with conventional commit format\n 4. git push -u origin HEAD\n 5. make create-pr (creates PR with title: {identifier}: {title})": { - "bg": "Следващи стъпки:\n 1. git checkout master && git pull\n 2. git checkout -b {prefix}-{num}-кратко-описание\n 3. Имплементирайте промените, commit с conventional commit формат\n 4. git push -u origin HEAD\n 5. make create-pr (създава PR с заглавие: {identifier}: {title})", - "de": "Nächste Schritte:\n 1. git checkout master && git pull\n 2. git checkout -b {prefix}-{num}-kurz-beschreibung\n 3. Änderungen implementieren, mit Conventional-Commit-Format committen\n 4. git push -u origin HEAD\n 5. make create-pr (erstellt PR mit Titel: {identifier}: {title})", - "en": "Next steps:\n 1. git checkout master && git pull\n 2. git checkout -b {prefix}-{num}-short-description\n 3. Implement changes, commit with conventional commit format\n 4. git push -u origin HEAD\n 5. make create-pr (creates PR with title: {identifier}: {title})", - "pl": "Następne kroki:\n 1. git checkout master && git pull\n 2. git checkout -b {prefix}-{num}-krótki-opis\n 3. Wprowadź zmiany, commituj w formacie conventional commit\n 4. git push -u origin HEAD\n 5. make create-pr (tworzy PR z tytułem: {identifier}: {title})", - "ru": "Следующие шаги:\n 1. git checkout master && git pull\n 2. git checkout -b {prefix}-{num}-краткое-описание\n 3. Реализуйте изменения, коммитьте в conventional commit формате\n 4. git push -u origin HEAD\n 5. make create-pr (создаёт PR с заголовком: {identifier}: {title})", - "zh": "后续步骤:\n 1. git checkout master && git pull\n 2. git checkout -b {prefix}-{num}-简短描述\n 3. 实现更改,使用 conventional commit 格式提交\n 4. git push -u origin HEAD\n 5. make create-pr (创建 PR,标题: {identifier}: {title})" - }, - "Nice! Gitea release {tag} created.": { - "bg": "Отлично! Gitea release {tag} е създаден.", - "de": "Prima! Gitea-Release {tag} erstellt.", - "en": "Nice! Gitea release {tag} created.", - "pl": "Świetnie! Wydanie Gitea {tag} utworzone.", - "ru": "Отлично! Gitea release {tag} создан.", - "zh": "不错!Gitea release {tag} 已创建。" - }, - "Nice! PR #{pr_number} squash-merged with title: {merge_title}": { - "bg": "Отлично! PR #{pr_number} е squash-merge-нат със заглавие: {merge_title}", - "de": "Prima! PR #{pr_number} wurde mit Titel {merge_title} squash-gemergt.", - "en": "Nice! PR #{pr_number} squash-merged with title: {merge_title}", - "pl": "Świetnie! PR #{pr_number} squash-merged z tytułem: {merge_title}", - "ru": "Отлично! PR #{pr_number} squash-merge с заголовком: {merge_title}", - "zh": "不错!PR #{pr_number} 已 squash 合并,标题: {merge_title}" - }, - "Nice! Release v{version} tagged and pushed. The publish workflow will be triggered.": { - "bg": "Nice! Release v{version} tagged and pushed. The publish workflow will be triggered.", - "de": "Nice! Release v{version} tagged and pushed. The publish workflow will be triggered.", - "en": "Nice! Release v{version} tagged and pushed. The publish workflow will be triggered.", - "pl": "Świetnie! Wydanie v{version} otagowane i wypchnięte. Workflow publikacji zostanie uruchomiony.", - "ru": "Nice! Release v{version} tagged and pushed. The publish workflow will be triggered.", - "zh": "Nice! Release v{version} tagged and pushed. The publish workflow will be triggered." - }, - "Nice! Vikunja task {task_id} (ID {vikunja_id}) updated and marked done.": { - "bg": "Отлично! Задача Vikunja {task_id} (ID {vikunja_id}) е обновена и маркирана като готова.", - "de": "Prima! Vikunja-Aufgabe {task_id} (ID {vikunja_id}) aktualisiert und als erledigt markiert.", - "en": "Nice! Vikunja task {task_id} (ID {vikunja_id}) updated and marked done.", - "pl": "Świetnie! Zadanie Vikunja {task_id} (ID {vikunja_id}) zaktualizowane i oznaczone jako ukończone.", - "ru": "Отлично! Задача Vikunja {task_id} (ID {vikunja_id}) обновлена и отмечена как выполненная.", - "zh": "不错!Vikunja 任务 {task_id} (ID {vikunja_id}) 已更新并标记为完成。" - }, - "No CI checks found for commit {sha}.": { - "bg": "No CI checks found for commit {sha}.", - "de": "No CI checks found for commit {sha}.", - "en": "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 Python package found under src/ — skipping version check.": { - "bg": "", - "de": "", - "en": "No Python package found under src/ — skipping version check.", - "pl": "", - "ru": "", - "zh": "" - }, - "No badge SVG files generated": { - "bg": "No badge SVG files generated", - "de": "No badge SVG files generated", - "en": "No badge SVG files generated", - "pl": "No badge SVG files generated", - "ru": "No badge SVG files generated", - "zh": "No badge SVG files generated" - }, - "No badge URLs found to update — README already up to date": { - "bg": "No badge URLs found to update — README already up to date", - "de": "No badge URLs found to update — README already up to date", - "en": "No badge URLs found to update — README already up to date", - "pl": "No badge URLs found to update — README already up to date", - "ru": "No badge URLs found to update — README already up to date", - "zh": "No badge URLs found to update — README already up to date" - }, - "No badge changes — skipping commit": { - "bg": "", - "de": "", - "en": "No badge changes — skipping commit", - "pl": "", - "ru": "", - "zh": "" - }, - "No changes between {base} and {head}.": { - "bg": "No changes between {base} and {head}.", - "de": "No changes between {base} and {head}.", - "en": "No changes between {base} and {head}.", - "pl": "Brak zmian między {base} i {head}.", - "ru": "No changes between {base} and {head}.", - "zh": "No changes between {base} and {head}." - }, - "No changes to sync — wiki is up to date.": { - "bg": "", - "de": "", - "en": "No changes to sync — wiki is up to date.", - "pl": "", - "ru": "", - "zh": "" - }, - "No failed jobs.": { - "bg": "No failed jobs.", - "de": "No failed jobs.", - "en": "No failed jobs.", - "pl": "No failed jobs.", - "ru": "No failed jobs.", - "zh": "No failed jobs." - }, - "No job matching '{job}' found.": { - "bg": "No job matching '{job}' found.", - "de": "No job matching '{job}' found.", - "en": "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}.": { - "bg": "No jobs found for run #{run_id}.", - "de": "No jobs found for run #{run_id}.", - "en": "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}'.": { - "bg": "No open PR found for branch '{branch}'.", - "de": "No open PR found for branch '{branch}'.", - "en": "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 push needed (no changes or push failed).": { - "bg": "", - "de": "", - "en": "No push needed (no changes or push failed).", - "pl": "", - "ru": "", - "zh": "" - }, - "No staged changes — version and changelog already up to date.": { - "bg": "No staged changes — version and changelog already up to date.", - "de": "No staged changes — version and changelog already up to date.", - "en": "No staged changes — version and changelog already up to date.", - "pl": "Brak zmian w staging — wersja i changelog są już aktualne.", - "ru": "No staged changes — version and changelog already up to date.", - "zh": "No staged changes — version and changelog already up to date." - }, - "No tag found — skipping publish.": { - "bg": "No tag found — skipping publish.", - "de": "No tag found — skipping publish.", - "en": "No tag found — skipping publish.", - "pl": "Nie znaleziono tagu — pomijanie publikacji.", - "ru": "No tag found — skipping publish.", - "zh": "No tag found — skipping publish." - }, - "No tags found — treating all changes as user-facing.": { - "bg": "No tags found — treating all changes as user-facing.", - "de": "No tags found — treating all changes as user-facing.", - "en": "No tags found — treating all changes as user-facing.", - "pl": "Nie znaleziono tagów — traktowanie wszystkich zmian jako widocznych dla użytkownika.", - "ru": "No tags found — treating all changes as user-facing.", - "zh": "No tags found — treating all changes as user-facing." - }, - "No task ID ({prefix}-N) found in commit message: {msg}. Every non-infrastructure commit must have a task ID.": { - "bg": "No task ID ({prefix}-N) found in commit message: {msg}. Every non-infrastructure commit must have a task ID.", - "de": "No task ID ({prefix}-N) found in commit message: {msg}. Every non-infrastructure commit must have a task ID.", - "en": "No task ID ({prefix}-N) found in commit message: {msg}. Every non-infrastructure commit must have a task ID.", - "pl": "Nie znaleziono ID zadania ({prefix}-N) w wiadomości commit: {msg}. Każdy commit nie-infrastrukturalny musi mieć ID zadania.", - "ru": "No task ID ({prefix}-N) found in commit message: {msg}. Every non-infrastructure commit must have a task ID.", - "zh": "No task ID ({prefix}-N) found in commit message: {msg}. Every non-infrastructure commit must have a task ID." - }, - "No task ID found in branch name '{branch}'. Expected format: {prefix}-N-description.": { - "bg": "No task ID found in branch name '{branch}'. Expected format: {prefix}-N-description.", - "de": "No task ID found in branch name '{branch}'. Expected format: {prefix}-N-description.", - "en": "No task ID found in branch name '{branch}'. Expected format: {prefix}-N-description.", - "pl": "No task ID found in branch name '{branch}'. Expected format: {prefix}-N-description.", - "ru": "No task ID found in branch name '{branch}'. Expected format: {prefix}-N-description.", - "zh": "No task ID found in branch name '{branch}'. Expected format: {prefix}-N-description." - }, - "No unreleased changes found. Nothing to release.": { - "bg": "No unreleased changes found. Nothing to release.", - "de": "No unreleased changes found. Nothing to release.", - "en": "No unreleased changes found. Nothing to release.", - "pl": "Nie znaleziono nieopublikowanych zmian. Nic do wydania.", - "ru": "No unreleased changes found. Nothing to release.", - "zh": "No unreleased changes found. Nothing to release." - }, - "No user-facing changes since {tag} — only workflow/infrastructure files changed. Skipping release.": { - "bg": "No user-facing changes since {tag} — only workflow/infrastructure files changed. Skipping release.", - "de": "No user-facing changes since {tag} — only workflow/infrastructure files changed. Skipping release.", - "en": "No user-facing changes since {tag} — only workflow/infrastructure files changed. Skipping release.", - "pl": "Brak zmian widocznych dla użytkownika od {tag} — tylko pliki workflow/infrastruktury uległy zmianie. Pomijanie wydania.", - "ru": "No user-facing changes since {tag} — only workflow/infrastructure files changed. Skipping release.", - "zh": "No user-facing changes since {tag} — only workflow/infrastructure files changed. Skipping release." - }, - "No versions found.": { - "bg": "No versions found.", - "de": "No versions found.", - "en": "No versions found.", - "pl": "No versions found.", - "ru": "No versions found.", - "zh": "No versions found." - }, - "No workflow runs found for SHA {sha}.": { - "bg": "No workflow runs found for SHA {sha}.", - "de": "No workflow runs found for SHA {sha}.", - "en": "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}." - }, - "Note: CI token also cannot approve. Posting COMMENT instead.": { - "bg": "Забележка: CI тоукънът също не може да одобри. Публикуване на COMMENT вместо това.", - "de": "Hinweis: CI-Token kann ebenfalls nicht genehmigen. COMMENT wird stattdessen gesendet.", - "en": "Note: CI token also cannot approve. Posting COMMENT instead.", - "pl": "Uwaga: Token CI również nie może zatwierdzić. Publikowanie COMMENT zamiast tego.", - "ru": "Примечание: CI токен также не может одобрить. Публикация COMMENT вместо этого.", - "zh": "注意:CI 令牌也无法批准。改为发布 COMMENT。" - }, - "Note: Self-approval not allowed with reviewer token. Retrying with CI token.": { - "bg": "Забележка: Само-одобрението не е разрешено с тоукън на рецензента. Повторен опит с CI тоукън.", - "de": "Hinweis: Selbstgenehmigung mit Reviewer-Token nicht erlaubt. Wiederholung mit CI-Token.", - "en": "Note: Self-approval not allowed with reviewer token. Retrying with CI token.", - "pl": "Uwaga: Samo-zatwierdzenie niedozwolone tokenem recenzenta. Ponawianie tokenem CI.", - "ru": "Примечание: Самоодобрение токеном ревьюера не разрешено. Повторная попытка с CI токеном.", - "zh": "注意:不允许使用审阅者令牌进行自我批准。正在使用 CI 令牌重试。" - }, - "Note: Self-approval not allowed. Posting COMMENT instead.": { - "bg": "Забележка: Само-одобрението не е разрешено. Публикуване на COMMENT вместо това.", - "de": "Hinweis: Selbstgenehmigung nicht erlaubt. COMMENT wird stattdessen gesendet.", - "en": "Note: Self-approval not allowed. Posting COMMENT instead.", - "pl": "Uwaga: Samo-zatwierdzenie niedozwolone. Publikowanie COMMENT zamiast tego.", - "ru": "Примечание: Самоодобрение не разрешено. Публикация COMMENT вместо этого.", - "zh": "注意:不允许自我批准。改为发布 COMMENT。" - }, - "Nothing to push.": { - "bg": "Nothing to push.", - "de": "Nothing to push.", - "en": "Nothing to push.", - "pl": "Nothing to push.", - "ru": "Nothing to push.", - "zh": "Nothing to push." - }, - "Only check staged files (for pre-commit)": { - "bg": "Only check staged files (for pre-commit)", - "de": "Only check staged files (for pre-commit)", - "en": "Only check staged files (for pre-commit)", - "pl": "Only check staged files (for pre-commit)", - "ru": "Only check staged files (for pre-commit)", - "zh": "Only check staged files (for pre-commit)" - }, - "Oops! Commit message must follow conventional commit format.\n Expected: <type>: <description>\n Got: {subject}\n Allowed types: feat, fix, chore, docs, style, refactor,\n perf, test, ci, build, revert, BREAKING CHANGE": { - "bg": "Опа! Съобщението за commit трябва да следва конвенционален формат.\n Очаква се: <type>: <description>\n Получено: {subject}\n Разрешени типове: feat, fix, chore, docs, style, refactor,\n perf, test, ci, build, revert, BREAKING CHANGE", - "de": "Ups! Commit-Nachricht muss dem konventionellen Commit-Format folgen.\n Erwartet: <type>: <description>\n Erhalten: {subject}\n Erlaubte Typen: feat, fix, chore, docs, style, refactor,\n perf, test, ci, build, revert, BREAKING CHANGE", - "en": "Oops! Commit message must follow conventional commit format.\n Expected: <type>: <description>\n Got: {subject}\n Allowed types: feat, fix, chore, docs, style, refactor,\n perf, test, ci, build, revert, BREAKING CHANGE", - "pl": "Ups! Wiadomość commit musi być w formacie conventional commit.\n Oczekiwano: <typ>: <opis>\n Otrzymano: {subject}\n Dozwolone typy: feat, fix, chore, docs, style, refactor,\n perf, test, ci, build, revert, BREAKING CHANGE", - "ru": "Ой! Сообщение коммита должно соответствовать формату conventional commit.\n Ожидается: <type>: <description>\n Получено: {subject}\n Допустимые типы: feat, fix, chore, docs, style, refactor,\n perf, test, ci, build, revert, BREAKING CHANGE", - "zh": "哎呀!提交消息必须遵循 conventional commit 格式。\n 预期格式: <type>: <description>\n 实际: {subject}\n 允许的类型: feat, fix, chore, docs, style, refactor,\n perf, test, ci, build, revert, BREAKING CHANGE" - }, - "Oops! Do not include task ID ({prefix}-N) in feature branch commits.\n The task ID will be added automatically on merge via CI.": { - "bg": "Oops! Do not include task ID ({prefix}-N) in feature branch commits.\n The task ID will be added automatically on merge via CI.", - "de": "Oops! Do not include task ID ({prefix}-N) in feature branch commits.\n The task ID will be added automatically on merge via CI.", - "en": "Oops! Do not include task ID ({prefix}-N) in feature branch commits.\n The task ID will be added automatically on merge via CI.", - "pl": "Ups! Nie dołączaj ID zadania ({prefix}-N) w commitach gałęzi feature.\n ID zadania zostanie dodane automatycznie przy scaleniu przez CI.", - "ru": "Oops! Do not include task ID ({prefix}-N) in feature branch commits.\n The task ID will be added automatically on merge via CI.", - "zh": "Oops! Do not include task ID ({prefix}-N) in feature branch commits.\n The task ID will be added automatically on merge via CI." - }, - "Oops! Gitea PyPI registry publish failed:\n{stderr}": { - "bg": "Опа! Публикуването в Gitea PyPI registry неуспешно:\n{stderr}", - "de": "Ups! Veröffentlichung in der Gitea PyPI-Registry fehlgeschlagen:\n{stderr}", - "en": "Oops! Gitea PyPI registry publish failed:\n{stderr}", - "pl": "Ups! Publikacja w rejestrze Gitea PyPI nie powiodła się:\n{stderr}", - "ru": "Ой! Публикация в Gitea PyPI registry не удалась:\n{stderr}", - "zh": "哎呀!Gitea PyPI registry 发布失败:\n{stderr}" - }, - "Oops! Master branch commit must follow conventional format after task ID.\n Expected: {prefix}-N: <type>: <description>\n Got: {subject}": { - "bg": "Oops! Master branch commit must follow conventional format after task ID.\n Expected: {prefix}-N: <type>: <description>\n Got: {subject}", - "de": "Oops! Master branch commit must follow conventional format after task ID.\n Expected: {prefix}-N: <type>: <description>\n Got: {subject}", - "en": "Oops! Master branch commit must follow conventional format after task ID.\n Expected: {prefix}-N: <type>: <description>\n Got: {subject}", - "pl": "Ups! Commit gałęzi master musi być w formacie conventional po ID zadania.\n Oczekiwano: {prefix}-N: <typ>: <opis>\n Otrzymano: {subject}", - "ru": "Oops! Master branch commit must follow conventional format after task ID.\n Expected: {prefix}-N: <type>: <description>\n Got: {subject}", - "zh": "Oops! Master branch commit must follow conventional format after task ID.\n Expected: {prefix}-N: <type>: <description>\n Got: {subject}" - }, - "Oops! Master branch commits must start with a task ID.\n Expected: {prefix}-N: <conventional commit message>\n Got: {subject}": { - "bg": "Oops! Master branch commits must start with a task ID.\n Expected: {prefix}-N: <conventional commit message>\n Got: {subject}", - "de": "Oops! Master branch commits must start with a task ID.\n Expected: {prefix}-N: <conventional commit message>\n Got: {subject}", - "en": "Oops! Master branch commits must start with a task ID.\n Expected: {prefix}-N: <conventional commit message>\n Got: {subject}", - "pl": "Ups! Commity gałęzi master muszą zaczynać się od ID zadania.\n Oczekiwano: {prefix}-N: <conwencjonalna wiadomość commit>\n Otrzymano: {subject}", - "ru": "Oops! Master branch commits must start with a task ID.\n Expected: {prefix}-N: <conventional commit message>\n Got: {subject}", - "zh": "Oops! Master branch commits must start with a task ID.\n Expected: {prefix}-N: <conventional commit message>\n Got: {subject}" - }, - "Oops! No task ID found in branch name '{branch}'. Branch names must include the task ID prefix (e.g., DEVX-31-fix-bug).": { - "bg": "Ой! Не е намерен ID на задача в името на клона '{branch}'. Имената на клонове трябва да включват префикса за ID на задача (напр. DEVX-31-fix-bug).", - "de": "Hoppla! Keine Task-ID im Branch-Namen '{branch}' gefunden. Branch-Namen müssen das Task-ID-Präfix enthalten (z.B. DEVX-31-fix-bug).", - "en": "Oops! No task ID found in branch name '{branch}'. Branch names must include the task ID prefix (e.g., DEVX-31-fix-bug).", - "pl": "Ups! Nie znaleziono ID zadania w nazwie gałęzi '{branch}'. Nazwy gałęzi muszą zawierać prefiks ID zadania (np., DEVX-31-fix-bug).", - "ru": "Ой! ID задачи не найден в имени ветки '{branch}'. Имена веток должны включать префикс ID задачи (например, DEVX-31-fix-bug).", - "zh": "哎呀!在分支名称 '{branch}' 中未找到任务 ID。分支名称必须包含任务 ID 前缀(例如 DEVX-31-fix-bug)。" - }, - "Oops! PR title must follow format '{prefix}-N: <task title>'.\n Expected: {task_id}: <task title>\n Got: {pr_title}": { - "bg": "Oops! PR title must follow format '{prefix}-N: <task title>'.\n Expected: {task_id}: <task title>\n Got: {pr_title}", - "de": "Oops! PR title must follow format '{prefix}-N: <task title>'.\n Expected: {task_id}: <task title>\n Got: {pr_title}", - "en": "Oops! PR title must follow format '{prefix}-N: <task title>'.\n Expected: {task_id}: <task title>\n Got: {pr_title}", - "pl": "Ups! Tytuł PR musi być w formacie '{prefix}-N: <tytuł zadania>'.\n Oczekiwano: {task_id}: <tytuł zadania>\n Otrzymano: {pr_title}", - "ru": "Oops! PR title must follow format '{prefix}-N: <task title>'.\n Expected: {task_id}: <task title>\n Got: {pr_title}", - "zh": "Oops! PR title must follow format '{prefix}-N: <task title>'.\n Expected: {task_id}: <task title>\n Got: {pr_title}" - }, - "Oops! PR title task ID mismatch.\n Branch task ID: {task_id}\n PR title: {pr_title}": { - "bg": "Oops! PR title task ID mismatch.\n Branch task ID: {task_id}\n PR title: {pr_title}", - "de": "Oops! PR title task ID mismatch.\n Branch task ID: {task_id}\n PR title: {pr_title}", - "en": "Oops! PR title task ID mismatch.\n Branch task ID: {task_id}\n PR title: {pr_title}", - "pl": "Ups! Niezgodność ID zadania w tytule PR.\n ID zadania z gałęzi: {task_id}\n Tytuł PR: {pr_title}", - "ru": "Oops! PR title task ID mismatch.\n Branch task ID: {task_id}\n PR title: {pr_title}", - "zh": "Oops! PR title task ID mismatch.\n Branch task ID: {task_id}\n PR title: {pr_title}" - }, - "Oops! Package build failed:\n{stderr}": { - "bg": "Опа! Сборката на пакета неуспешна:\n{stderr}", - "de": "Ups! Paket-Build fehlgeschlagen:\n{stderr}", - "en": "Oops! Package build failed:\n{stderr}", - "pl": "Ups! Budowanie pakietu nie powiodło się:\n{stderr}", - "ru": "Ой! Сборка пакета не удалась:\n{stderr}", - "zh": "哎呀!包构建失败:\n{stderr}" - }, - "Oops! PyPI publish failed:\n{stderr}": { - "bg": "Опа! Публикуването в PyPI неуспешно:\n{stderr}", - "de": "Ups! PyPI-Veröffentlichung fehlgeschlagen:\n{stderr}", - "en": "Oops! PyPI publish failed:\n{stderr}", - "pl": "Ups! Publikacja PyPI nie powiodła się:\n{stderr}", - "ru": "Ой! Публикация в PyPI не удалась:\n{stderr}", - "zh": "哎呀!PyPI 发布失败:\n{stderr}" - }, - "PASS: All documentation checks passed!": { - "bg": "PASS: All documentation checks passed!", - "de": "PASS: All documentation checks passed!", - "en": "PASS: All documentation checks passed!", - "pl": "PASS: All documentation checks passed!", - "ru": "PASS: All documentation checks passed!", - "zh": "PASS: All documentation checks passed!" - }, - "PR #{pr} rebased successfully. A new CI run will start automatically.\nIf auto-merge is enabled (ready-to-merge label), the next CI run\nwill attempt to merge this PR.": { - "bg": "PR #{pr} rebased successfully. A new CI run will start automatically.\nIf auto-merge is enabled (ready-to-merge label), the next CI run\nwill attempt to merge this PR.", - "de": "PR #{pr} rebased successfully. A new CI run will start automatically.\nIf auto-merge is enabled (ready-to-merge label), the next CI run\nwill attempt to merge this PR.", - "en": "PR #{pr} rebased successfully. A new CI run will start automatically.\nIf auto-merge is enabled (ready-to-merge label), the next CI run\nwill attempt to merge this PR.", - "pl": "PR #{pr} rebased successfully. A new CI run will start automatically.\nIf auto-merge is enabled (ready-to-merge label), the next CI run\nwill attempt to merge this PR.", - "ru": "PR #{pr} rebased successfully. A new CI run will start automatically.\nIf auto-merge is enabled (ready-to-merge label), the next CI run\nwill attempt to merge this PR.", - "zh": "PR #{pr} rebased successfully. A new CI run will start automatically.\nIf auto-merge is enabled (ready-to-merge label), the next CI run\nwill attempt to merge this PR." - }, - "PR already exists: #{index} — {url}": { - "bg": "PR вече съществува: #{index} — {url}", - "de": "PR existiert bereits: #{index} — {url}", - "en": "PR already exists: #{index} — {url}", - "pl": "PR już istnieje: #{index} — {url}", - "ru": "PR уже существует: #{index} — {url}", - "zh": "PR 已存在: #{index} — {url}" - }, - "PR number (to fetch title from Gitea)": { - "bg": "PR number (to fetch title from Gitea)", - "de": "PR number (to fetch title from Gitea)", - "en": "PR number (to fetch title from Gitea)", - "pl": "PR number (to fetch title from Gitea)", - "ru": "PR number (to fetch title from Gitea)", - "zh": "PR number (to fetch title from Gitea)" - }, - "PR number must be an integer, got: {pr_number}": { - "bg": "PR number must be an integer, got: {pr_number}", - "de": "PR number must be an integer, got: {pr_number}", - "en": "PR number must be an integer, got: {pr_number}", - "pl": "Numer PR musi być liczbą całkowitą, otrzymano: {pr_number}", - "ru": "PR number must be an integer, got: {pr_number}", - "zh": "PR number must be an integer, got: {pr_number}" - }, - "PR title (auto-fetched if --pr-number given)": { - "bg": "PR title (auto-fetched if --pr-number given)", - "de": "PR title (auto-fetched if --pr-number given)", - "en": "PR title (auto-fetched if --pr-number given)", - "pl": "PR title (auto-fetched if --pr-number given)", - "ru": "PR title (auto-fetched if --pr-number given)", - "zh": "PR title (auto-fetched if --pr-number given)" - }, - "PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {pr_title}": { - "bg": "PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {pr_title}", - "de": "PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {pr_title}", - "en": "PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {pr_title}", - "pl": "Tytuł PR nie pasuje do tytułu zadania Vikunja.\n Oczekiwano: {expected}\n Otrzymano: {pr_title}", - "ru": "PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {pr_title}", - "zh": "PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {pr_title}" - }, - "PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {title}": { - "bg": "PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {title}", - "de": "PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {title}", - "en": "PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {title}", - "pl": "PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {title}", - "ru": "PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {title}", - "zh": "PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {title}" - }, - "PR title must follow format '{prefix}-N: <task title>'.\n Got: {title}": { - "bg": "PR title must follow format '{prefix}-N: <task title>'.\n Got: {title}", - "de": "PR title must follow format '{prefix}-N: <task title>'.\n Got: {title}", - "en": "PR title must follow format '{prefix}-N: <task title>'.\n Got: {title}", - "pl": "PR title must follow format '{prefix}-N: <task title>'.\n Got: {title}", - "ru": "PR title must follow format '{prefix}-N: <task title>'.\n Got: {title}", - "zh": "PR title must follow format '{prefix}-N: <task title>'.\n Got: {title}" - }, - "PR title task ID mismatch.\n Branch task ID: {task_id}\n PR title: {title}": { - "bg": "PR title task ID mismatch.\n Branch task ID: {task_id}\n PR title: {title}", - "de": "PR title task ID mismatch.\n Branch task ID: {task_id}\n PR title: {title}", - "en": "PR title task ID mismatch.\n Branch task ID: {task_id}\n PR title: {title}", - "pl": "PR title task ID mismatch.\n Branch task ID: {task_id}\n PR title: {title}", - "ru": "PR title task ID mismatch.\n Branch task ID: {task_id}\n PR title: {title}", - "zh": "PR title task ID mismatch.\n Branch task ID: {task_id}\n PR title: {title}" - }, - "PYPI_TOKEN not set and no registry URL configured — skipping PyPI publish. No worries, we'll just create the Gitea release.": { - "bg": "PYPI_TOKEN не е зададен и няма конфигуриран URL на registry — пропускаме публикуването в PyPI. Без притеснения, просто ще създадем Gitea release.", - "de": "PYPI_TOKEN nicht gesetzt und keine Registry-URL konfiguriert — PyPI-Veröffentlichung wird übersprungen. Keine Sorge, wir erstellen einfach das Gitea-Release.", - "en": "PYPI_TOKEN not set and no registry URL configured — skipping PyPI publish. No worries, we'll just create the Gitea release.", - "pl": "PYPI_TOKEN nie ustawiony i brak URL rejestru — pomijanie publikacji PyPI. Bez obaw, utworzymy tylko wydanie Gitea.", - "ru": "PYPI_TOKEN не задан и URL registry не настроен — пропускаем публикацию в PyPI. Не беспокойтесь, мы просто создадим Gitea release.", - "zh": "未设置 PYPI_TOKEN 且未配置 registry URL — 跳过 PyPI 发布。别担心,我们直接创建 Gitea release。" - }, - "Package owner not specified. Use --owner or set [tool.devx] repo_owner.": { - "bg": "Package owner not specified. Use --owner or set [tool.devx] repo_owner.", - "de": "Package owner not specified. Use --owner or set [tool.devx] repo_owner.", - "en": "Package owner not specified. Use --owner or set [tool.devx] repo_owner.", - "pl": "Package owner not specified. Use --owner or set [tool.devx] repo_owner.", - "ru": "Package owner not specified. Use --owner or set [tool.devx] repo_owner.", - "zh": "Package owner not specified. Use --owner or set [tool.devx] repo_owner." - }, - "Package: {owner}/{name}": { - "bg": "Package: {owner}/{name}", - "de": "Package: {owner}/{name}", - "en": "Package: {owner}/{name}", - "pl": "Package: {owner}/{name}", - "ru": "Package: {owner}/{name}", - "zh": "Package: {owner}/{name}" - }, - "Parsed owner={owner}, repo={repo} from DEVX_REPO_NAME": { - "bg": "Разбор на owner={owner}, repo={repo} от DEVX_REPO_NAME", - "de": "Owner={owner}, repo={repo} aus DEVX_REPO_NAME analysiert", - "en": "Parsed owner={owner}, repo={repo} from DEVX_REPO_NAME", - "pl": "Przeanalizowano owner={owner}, repo={repo} z DEVX_REPO_NAME", - "ru": "Извлечён owner={owner}, repo={repo} из DEVX_REPO_NAME", - "zh": "从 DEVX_REPO_NAME 解析 owner={owner}, repo={repo}" - }, - "Path to pyproject.toml (default: pyproject.toml in CWD).": { - "bg": "Path to pyproject.toml (default: pyproject.toml in CWD).", - "de": "Path to pyproject.toml (default: pyproject.toml in CWD).", - "en": "Path to pyproject.toml (default: pyproject.toml in CWD).", - "pl": "Path to pyproject.toml (default: pyproject.toml in CWD).", - "ru": "Path to pyproject.toml (default: pyproject.toml in CWD).", - "zh": "Path to pyproject.toml (default: pyproject.toml in CWD)." - }, - "Per-test speed check FAILED: {count} test(s) exceed {limit}s limit.": { - "bg": "Per-test speed check FAILED: {count} test(s) exceed {limit}s limit.", - "de": "Per-test speed check FAILED: {count} test(s) exceed {limit}s limit.", - "en": "Per-test speed check FAILED: {count} test(s) exceed {limit}s limit.", - "pl": "Kontrola szybkości pojedynczego testu NIEUDANA: {count} test(ów) przekracza limit {limit}s.", - "ru": "Per-test speed check FAILED: {count} test(s) exceed {limit}s limit.", - "zh": "Per-test speed check FAILED: {count} test(s) exceed {limit}s limit." - }, - "Pre-merge validation failed.": { - "bg": "Pre-merge validation failed.", - "de": "Pre-merge validation failed.", - "en": "Pre-merge validation failed.", - "pl": "Pre-merge validation failed.", - "ru": "Pre-merge validation failed.", - "zh": "Pre-merge validation failed." - }, - "Pre-push check passed: task {task_id} exists.": { - "bg": "Pre-push проверката премина: задача {task_id} съществува.", - "de": "Pre-push-Prüfung bestanden: Task {task_id} existiert.", - "en": "Pre-push check passed: task {task_id} exists.", - "pl": "Sprawdzanie pre-push zakończone: zadanie {task_id} istnieje.", - "ru": "Pre-push проверка пройдена: задача {task_id} существует.", - "zh": "Pre-push 检查通过: 任务 {task_id} 存在。" - }, - "Print warnings but always exit 0": { - "bg": "Print warnings but always exit 0", - "de": "Print warnings but always exit 0", - "en": "Print warnings but always exit 0", - "pl": "Print warnings but always exit 0", - "ru": "Print warnings but always exit 0", - "zh": "Print warnings but always exit 0" - }, - "Provide --manifest or both --dockerfile and --name": { - "bg": "Provide --manifest or both --dockerfile and --name", - "de": "Provide --manifest or both --dockerfile and --name", - "en": "Provide --manifest or both --dockerfile and --name", - "pl": "Provide --manifest or both --dockerfile and --name", - "ru": "Provide --manifest or both --dockerfile and --name", - "zh": "Provide --manifest or both --dockerfile and --name" - }, - "Provide a commit message file or use --git.": { - "bg": "Provide a commit message file or use --git.", - "de": "Provide a commit message file or use --git.", - "en": "Provide a commit message file or use --git.", - "pl": "Podaj plik komunikatu commitu lub użyj --git.", - "ru": "Provide a commit message file or use --git.", - "zh": "Provide a commit message file or use --git." - }, - "Published to Gitea PyPI registry.": { - "bg": "Публикувано в Gitea PyPI registry.", - "de": "In der Gitea PyPI-Registry veröffentlicht.", - "en": "Published to Gitea PyPI registry.", - "pl": "Opublikowano w rejestrze Gitea PyPI.", - "ru": "Опубликовано в Gitea PyPI registry.", - "zh": "已发布到 Gitea PyPI registry。" - }, - "Published to PyPI.": { - "bg": "Публикувано в PyPI.", - "de": "In PyPI veröffentlicht.", - "en": "Published to PyPI.", - "pl": "Opublikowano w PyPI.", - "ru": "Опубликовано в PyPI.", - "zh": "已发布到 PyPI。" - }, - "Publishing release {tag}...": { - "bg": "Publishing release {tag}...", - "de": "Publishing release {tag}...", - "en": "Publishing release {tag}...", - "pl": "Publikowanie wydania {tag}...", - "ru": "Publishing release {tag}...", - "zh": "Publishing release {tag}..." - }, - "Push attempt {n}/3 failed: {err}": { - "bg": "Push attempt {n}/3 failed: {err}", - "de": "Push attempt {n}/3 failed: {err}", - "en": "Push attempt {n}/3 failed: {err}", - "pl": "Push attempt {n}/3 failed: {err}", - "ru": "Push attempt {n}/3 failed: {err}", - "zh": "Push attempt {n}/3 failed: {err}" - }, - "Push failed for {tag}: {error}": { - "bg": "Push failed for {tag}: {error}", - "de": "Push failed for {tag}: {error}", - "en": "Push failed for {tag}: {error}", - "pl": "Push failed for {tag}: {error}", - "ru": "Push failed for {tag}: {error}", - "zh": "Push failed for {tag}: {error}" - }, - "Push failed: {error}": { - "bg": "", - "de": "", - "en": "Push failed: {error}", - "pl": "", - "ru": "", - "zh": "" - }, - "Pushed README update with badge SHA {sha}": { - "bg": "Pushed README update with badge SHA {sha}", - "de": "Pushed README update with badge SHA {sha}", - "en": "Pushed README update with badge SHA {sha}", - "pl": "Pushed README update with badge SHA {sha}", - "ru": "Pushed README update with badge SHA {sha}", - "zh": "Pushed README update with badge SHA {sha}" - }, - "Pushed release commit to master.": { - "bg": "Pushed release commit to master.", - "de": "Pushed release commit to master.", - "en": "Pushed release commit to master.", - "pl": "Wypchnięto commit wydania do master.", - "ru": "Pushed release commit to master.", - "zh": "Pushed release commit to master." - }, - "Pushed {branch} to origin.": { - "bg": "Pushed {branch} to origin.", - "de": "Pushed {branch} to origin.", - "en": "Pushed {branch} to origin.", - "pl": "Pushed {branch} to origin.", - "ru": "Pushed {branch} to origin.", - "zh": "Pushed {branch} to origin." - }, - "PyPI publish failed (non-fatal — continuing to Gitea release):\n{error}": { - "bg": "Публикуването в PyPI неуспешно (некритично — продължава към Gitea release):\n{error}", - "de": "PyPI-Veröffentlichung fehlgeschlagen (nicht fatal — Gitea-Release wird fortgesetzt):\n{error}", - "en": "PyPI publish failed (non-fatal — continuing to Gitea release):\n{error}", - "pl": "Publikacja PyPI nie powiodła się (niekrytyczne — kontynuacja Gitea release):\n{error}", - "ru": "Публикация в PyPI не удалась (некритично — продолжаем создание Gitea release):\n{error}", - "zh": "PyPI 发布失败(非致命 — 继续创建 Gitea release):\n{error}" - }, - "REPO argument is required (or set GITHUB_REPOSITORY env var).": { - "bg": "REPO argument is required (or set GITHUB_REPOSITORY env var).", - "de": "REPO argument is required (or set GITHUB_REPOSITORY env var).", - "en": "REPO argument is required (or set GITHUB_REPOSITORY env var).", - "pl": "Argument REPO jest wymagany (lub ustaw zmienną GITHUB_REPOSITORY).", - "ru": "REPO argument is required (or set GITHUB_REPOSITORY env var).", - "zh": "REPO argument is required (or set GITHUB_REPOSITORY env var)." - }, - "Rebase attempt {n}/3 failed: {err}": { - "bg": "Rebase attempt {n}/3 failed: {err}", - "de": "Rebase attempt {n}/3 failed: {err}", - "en": "Rebase attempt {n}/3 failed: {err}", - "pl": "Rebase attempt {n}/3 failed: {err}", - "ru": "Rebase attempt {n}/3 failed: {err}", - "zh": "Rebase attempt {n}/3 failed: {err}" - }, - "Rebase failed (conflicts or other error):\n{error}\nResolve conflicts and run: git rebase --continue": { - "bg": "Rebase failed (conflicts or other error):\n{error}\nResolve conflicts and run: git rebase --continue", - "de": "Rebase failed (conflicts or other error):\n{error}\nResolve conflicts and run: git rebase --continue", - "en": "Rebase failed (conflicts or other error):\n{error}\nResolve conflicts and run: git rebase --continue", - "pl": "Rebase failed (conflicts or other error):\n{error}\nResolve conflicts and run: git rebase --continue", - "ru": "Rebase failed (conflicts or other error):\n{error}\nResolve conflicts and run: git rebase --continue", - "zh": "Rebase failed (conflicts or other error):\n{error}\nResolve conflicts and run: git rebase --continue" - }, - "Rebase failed with HTTP {status}: {message}": { - "bg": "Rebase failed with HTTP {status}: {message}", - "de": "Rebase failed with HTTP {status}: {message}", - "en": "Rebase failed with HTTP {status}: {message}", - "pl": "Rebase failed with HTTP {status}: {message}", - "ru": "Rebase failed with HTTP {status}: {message}", - "zh": "Rebase failed with HTTP {status}: {message}" - }, - "Rebase successful.": { - "bg": "Rebase successful.", - "de": "Rebase successful.", - "en": "Rebase successful.", - "pl": "Rebase successful.", - "ru": "Rebase successful.", - "zh": "Rebase successful." - }, - "Rebasing PR #{pr} via Gitea API...": { - "bg": "Rebasing PR #{pr} via Gitea API...", - "de": "Rebasing PR #{pr} via Gitea API...", - "en": "Rebasing PR #{pr} via Gitea API...", - "pl": "Rebasing PR #{pr} via Gitea API...", - "ru": "Rebasing PR #{pr} via Gitea API...", - "zh": "Rebasing PR #{pr} via Gitea API..." - }, - "Registry credentials required: set CI_GITEA_TOKEN and CI_GITEA_USERNAME env vars": { - "bg": "Registry credentials required: set CI_GITEA_TOKEN and CI_GITEA_USERNAME env vars", - "de": "Registry credentials required: set CI_GITEA_TOKEN and CI_GITEA_USERNAME env vars", - "en": "Registry credentials required: set CI_GITEA_TOKEN and CI_GITEA_USERNAME env vars", - "pl": "Registry credentials required: set CI_GITEA_TOKEN and CI_GITEA_USERNAME env vars", - "ru": "Registry credentials required: set CI_GITEA_TOKEN and CI_GITEA_USERNAME env vars", - "zh": "Registry credentials required: set CI_GITEA_TOKEN and CI_GITEA_USERNAME env vars" - }, - "Registry login failed": { - "bg": "Registry login failed", - "de": "Registry login failed", - "en": "Registry login failed", - "pl": "Registry login failed", - "ru": "Registry login failed", - "zh": "Registry login failed" - }, - "Registry login failed: {error}": { - "bg": "Registry login failed: {error}", - "de": "Registry login failed: {error}", - "en": "Registry login failed: {error}", - "pl": "Registry login failed: {error}", - "ru": "Registry login failed: {error}", - "zh": "Registry login failed: {error}" - }, - "Regular merge commit — running all post-merge jobs.": { - "bg": "Regular merge commit — running all post-merge jobs.", - "de": "Regular merge commit — running all post-merge jobs.", - "en": "Regular merge commit — running all post-merge jobs.", - "pl": "Regular merge commit — running all post-merge jobs.", - "ru": "Regular merge commit — running all post-merge jobs.", - "zh": "Regular merge commit — running all post-merge jobs." - }, - "Release commit — skipping all post-merge jobs.": { - "bg": "Release commit — skipping all post-merge jobs.", - "de": "Release commit — skipping all post-merge jobs.", - "en": "Release commit — skipping all post-merge jobs.", - "pl": "Release commit — skipping all post-merge jobs.", - "ru": "Release commit — skipping all post-merge jobs.", - "zh": "Release commit — skipping all post-merge jobs." - }, - "Release creation failed: {error}": { - "bg": "Release creation failed: {error}", - "de": "Release creation failed: {error}", - "en": "Release creation failed: {error}", - "pl": "Tworzenie wydania nie powiodło się: {error}", - "ru": "Release creation failed: {error}", - "zh": "Release creation failed: {error}" - }, - "Release must be run on master, currently on '{branch}'.": { - "bg": "Release must be run on master, currently on '{branch}'.", - "de": "Release must be run on master, currently on '{branch}'.", - "en": "Release must be run on master, currently on '{branch}'.", - "pl": "Wydanie musi być uruchomione na master, obecnie na '{branch}'.", - "ru": "Release must be run on master, currently on '{branch}'.", - "zh": "Release must be run on master, currently on '{branch}'." - }, - "Repo must be in 'owner/name' format, got: {repo}": { - "bg": "Repo must be in 'owner/name' format, got: {repo}", - "de": "Repo must be in 'owner/name' format, got: {repo}", - "en": "Repo must be in 'owner/name' format, got: {repo}", - "pl": "Repo musi być w formacie 'owner/name', otrzymano: {repo}", - "ru": "Repo must be in 'owner/name' format, got: {repo}", - "zh": "Repo must be in 'owner/name' format, got: {repo}" - }, - "Repository configuration complete.": { - "bg": "Конфигурирането на хранилището е завършено.", - "de": "Repository-Konfiguration abgeschlossen.", - "en": "Repository configuration complete.", - "pl": "Konfiguracja repozytorium zakończona.", - "ru": "Конфигурация репозитория завершена.", - "zh": "仓库配置完成。" - }, - "Repository in owner/name format": { - "bg": "Repository in owner/name format", - "de": "Repository in owner/name format", - "en": "Repository in owner/name format", - "pl": "Repository in owner/name format", - "ru": "Repository in owner/name format", - "zh": "Repository in owner/name format" - }, - "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.", - "en": "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." - }, - "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.", - "en": "Repository owner not set. Use --owner or DEVX_REPO_OWNER env var.", - "pl": "Właściciel repozytorium nie jest ustawiony. Użyj --owner lub DEVX_REPO_OWNER env var.", - "ru": "Владелец репозитория не установлен. Используйте --owner или DEVX_REPO_OWNER env var.", - "zh": "仓库所有者未设置。使用 --owner 或 DEVX_REPO_OWNER 环境变量。" - }, - "Required tools missing.": { - "bg": "Липсват задължителни инструменти.", - "de": "Erforderliche Werkzeuge fehlen.", - "en": "Required tools missing.", - "pl": "Brak wymaganych narzędzi.", - "ru": "Отсутствуют обязательные инструменты.", - "zh": "缺少必需的工具。" - }, - "Review body must be at least 50 characters.": { - "bg": "Review body must be at least 50 characters.", - "de": "Review body must be at least 50 characters.", - "en": "Review body must be at least 50 characters.", - "pl": "Review body must be at least 50 characters.", - "ru": "Review body must be at least 50 characters.", - "zh": "Review body must be at least 50 characters." - }, - "Roles directory not found: {path}": { - "bg": "Roles directory not found: {path}", - "de": "Roles directory not found: {path}", - "en": "Roles directory not found: {path}", - "pl": "Katalog ról nie znaleziony: {path}", - "ru": "Roles directory not found: {path}", - "zh": "Roles directory not found: {path}" - }, - "Runner count: {count}": { - "bg": "Runner count: {count}", - "de": "Runner count: {count}", - "en": "Runner count: {count}", - "pl": "Runner count: {count}", - "ru": "Runner count: {count}", - "zh": "Runner count: {count}" - }, - "Runner index {index} out of range (0..{max})": { - "bg": "Индексът на runner {index} е извън диапазона (0..{max})", - "de": "Runner-Index {index} außerhalb des Bereichs (0..{max})", - "en": "Runner index {index} out of range (0..{max})", - "pl": "Indeks runnera {index} poza zakresem (0..{max})", - "ru": "Индекс runner {index} вне диапазона (0..{max})", - "zh": "Runner 索引 {index} 超出范围 (0..{max})" - }, - "Runner index {runner_index} is out of range (must be >= 1)": { - "bg": "Runner index {runner_index} is out of range (must be >= 1)", - "de": "Runner index {runner_index} is out of range (must be >= 1)", - "en": "Runner index {runner_index} is out of range (must be >= 1)", - "pl": "Runner index {runner_index} is out of range (must be >= 1)", - "ru": "Runner index {runner_index} is out of range (must be >= 1)", - "zh": "Runner index {runner_index} is out of range (must be >= 1)" - }, - "Runner indices: {indices}": { - "bg": "Runner indices: {indices}", - "de": "Runner indices: {indices}", - "en": "Runner indices: {indices}", - "pl": "Runner indices: {indices}", - "ru": "Runner indices: {indices}", - "zh": "Runner indices: {indices}" - }, - "Runner {i}: {labels}": { - "bg": "Runner {i}: {labels}", - "de": "Runner {i}: {labels}", - "en": "Runner {i}: {labels}", - "pl": "Runner {i}: {labels}", - "ru": "Runner {i}: {labels}", - "zh": "Runner {i}: {labels}" - }, - "Running lint checks...": { - "bg": "Running lint checks...", - "de": "Running lint checks...", - "en": "Running lint checks...", - "pl": "Uruchamianie kontroli lint...", - "ru": "Running lint checks...", - "zh": "Running lint checks..." - }, - "Running tests...": { - "bg": "Running tests...", - "de": "Running tests...", - "en": "Running tests...", - "pl": "Uruchamianie testów...", - "ru": "Running tests...", - "zh": "Running tests..." - }, - "Running: {cmd}": { - "bg": "Running: {cmd}", - "de": "Running: {cmd}", - "en": "Running: {cmd}", - "pl": "Running: {cmd}", - "ru": "Running: {cmd}", - "zh": "Running: {cmd}" - }, - "SSH key set up successfully": { - "bg": "SSH ключът е настроен успешно", - "de": "SSH-Schlüssel erfolgreich eingerichtet", - "en": "SSH key set up successfully", - "pl": "Klucz SSH skonfigurowany pomyślnie", - "ru": "SSH-ключ успешно настроен", - "zh": "SSH 密钥设置成功" - }, - "SSH key setup skipped (no key provided)": { - "bg": "Настройката на SSH ключ е пропусната (не е предоставен ключ)", - "de": "SSH-Schlüssel-Setup übersprungen (kein Schlüssel bereitgestellt)", - "en": "SSH key setup skipped (no key provided)", - "pl": "Pominięto konfigurację klucza SSH (brak klucza)", - "ru": "Настройка SSH-ключа пропущена (ключ не предоставлен)", - "zh": "SSH 密钥设置已跳过(未提供密钥)" - }, - "SSH_PRIVATE_KEY not set — skipping SSH key setup": { - "bg": "SSH_PRIVATE_KEY не е зададен — пропускане на SSH ключ настройката", - "de": "SSH_PRIVATE_KEY nicht gesetzt — SSH-Schlüssel-Setup übersprungen", - "en": "SSH_PRIVATE_KEY not set — skipping SSH key setup", - "pl": "SSH_PRIVATE_KEY nie ustawione — pomijanie konfiguracji klucza SSH", - "ru": "SSH_PRIVATE_KEY не задан — пропуск настройки SSH-ключа", - "zh": "SSH_PRIVATE_KEY 未设置 — 跳过 SSH 密钥设置" - }, - "Skip Vikunja title match check": { - "bg": "Skip Vikunja title match check", - "de": "Skip Vikunja title match check", - "en": "Skip Vikunja title match check", - "pl": "Skip Vikunja title match check", - "ru": "Skip Vikunja title match check", - "zh": "Skip Vikunja title match check" - }, - "Skip branch-behind-master check": { - "bg": "Skip branch-behind-master check", - "de": "Skip branch-behind-master check", - "en": "Skip branch-behind-master check", - "pl": "Skip branch-behind-master check", - "ru": "Skip branch-behind-master check", - "zh": "Skip branch-behind-master check" - }, - "Skipping commit push — no staged changes.": { - "bg": "Skipping commit push — no staged changes.", - "de": "Skipping commit push — no staged changes.", - "en": "Skipping commit push — no staged changes.", - "pl": "Pomijanie wypchnięcia commit — brak zmian w staging.", - "ru": "Skipping commit push — no staged changes.", - "zh": "Skipping commit push — no staged changes." - }, - "Skipping — runner index {runner_index} > max runners {max_runners}": { - "bg": "Skipping — runner index {runner_index} > max runners {max_runners}", - "de": "Skipping — runner index {runner_index} > max runners {max_runners}", - "en": "Skipping — runner index {runner_index} > max runners {max_runners}", - "pl": "Skipping — runner index {runner_index} > max runners {max_runners}", - "ru": "Skipping — runner index {runner_index} > max runners {max_runners}", - "zh": "Skipping — runner index {runner_index} > max runners {max_runners}" - }, - "Synced to latest origin/{branch}": { - "bg": "Synced to latest origin/{branch}", - "de": "Synced to latest origin/{branch}", - "en": "Synced to latest origin/{branch}", - "pl": "Synced to latest origin/{branch}", - "ru": "Synced to latest origin/{branch}", - "zh": "Synced to latest origin/{branch}" - }, - "Syncing files...": { - "bg": "", - "de": "", - "en": "Syncing files...", - "pl": "", - "ru": "", - "zh": "" - }, - "Syncing {count} documentation pages to wiki via Git...": { - "bg": "", - "de": "", - "en": "Syncing {count} documentation pages to wiki via Git...", - "pl": "", - "ru": "", - "zh": "" - }, - "Tag consistency check failed.": { - "bg": "Tag consistency check failed.", - "de": "Tag consistency check failed.", - "en": "Tag consistency check failed.", - "pl": "Kontrola zgodności tagów nie powiodła się.", - "ru": "Tag consistency check failed.", - "zh": "Tag consistency check failed." - }, - "Tag is required (or use --from-tag).": { - "bg": "Tag is required (or use --from-tag).", - "de": "Tag is required (or use --from-tag).", - "en": "Tag is required (or use --from-tag).", - "pl": "Tag jest wymagany (lub użyj --from-tag).", - "ru": "Tag is required (or use --from-tag).", - "zh": "Tag is required (or use --from-tag)." - }, - "Tag v{version} already existed. Publish workflow should already have been triggered.": { - "bg": "Tag v{version} already existed. Publish workflow should already have been triggered.", - "de": "Tag v{version} already existed. Publish workflow should already have been triggered.", - "en": "Tag v{version} already existed. Publish workflow should already have been triggered.", - "pl": "Tag v{version} już istniał. Workflow publikacji powinien już być uruchomiony.", - "ru": "Tag v{version} already existed. Publish workflow should already have been triggered.", - "zh": "Tag v{version} already existed. Publish workflow should already have been triggered." - }, - "Tag {tag} already exists and points to HEAD. Skipping creation.": { - "bg": "Tag {tag} already exists and points to HEAD. Skipping creation.", - "de": "Tag {tag} already exists and points to HEAD. Skipping creation.", - "en": "Tag {tag} already exists and points to HEAD. Skipping creation.", - "pl": "Tag {tag} już istnieje i wskazuje na HEAD. Pomijanie tworzenia.", - "ru": "Tag {tag} already exists and points to HEAD. Skipping creation.", - "zh": "Tag {tag} already exists and points to HEAD. Skipping creation." - }, - "Tag {tag} already exists but points to {tag_commit} (expected HEAD {head_commit}). This indicates a tag/commit misalignment. Run 'python3 -m devx.ci.release --verify' for details.": { - "bg": "Tag {tag} already exists but points to {tag_commit} (expected HEAD {head_commit}). This indicates a tag/commit misalignment. Run 'python3 -m devx.ci.release --verify' for details.", - "de": "Tag {tag} already exists but points to {tag_commit} (expected HEAD {head_commit}). This indicates a tag/commit misalignment. Run 'python3 -m devx.ci.release --verify' for details.", - "en": "Tag {tag} already exists but points to {tag_commit} (expected HEAD {head_commit}). This indicates a tag/commit misalignment. Run 'python3 -m devx.ci.release --verify' for details.", - "pl": "Tag {tag} już istnieje ale wskazuje na {tag_commit} (oczekiwano HEAD {head_commit}). Wskazuje to na niezgodność tag/commit. Uruchom 'python3 -m devx.ci.release --verify', aby uzyskać szczegóły.", - "ru": "Tag {tag} already exists but points to {tag_commit} (expected HEAD {head_commit}). This indicates a tag/commit misalignment. Run 'python3 -m devx.ci.release --verify' for details.", - "zh": "Tag {tag} already exists but points to {tag_commit} (expected HEAD {head_commit}). This indicates a tag/commit misalignment. Run 'python3 -m devx.ci.release --verify' for details." - }, - "Task ID: {task_id}": { - "bg": "Task ID: {task_id}", - "de": "Task ID: {task_id}", - "en": "Task ID: {task_id}", - "pl": "ID zadania: {task_id}", - "ru": "Task ID: {task_id}", - "zh": "Task ID: {task_id}" - }, - "Test '{name}' took {elapsed:.2f}s (limit: {limit}s). Optimise: use lighter fixtures, reduce I/O, or mock external calls.": { - "bg": "Test '{name}' took {elapsed:.2f}s (limit: {limit}s). Optimise: use lighter fixtures, reduce I/O, or mock external calls.", - "de": "Test '{name}' took {elapsed:.2f}s (limit: {limit}s). Optimise: use lighter fixtures, reduce I/O, or mock external calls.", - "en": "Test '{name}' took {elapsed:.2f}s (limit: {limit}s). Optimise: use lighter fixtures, reduce I/O, or mock external calls.", - "pl": "Test '{name}' trwał {elapsed:.2f}s (limit: {limit}s). Optymalizuj: użyj lżejszych fixtures, zmniejsz I/O, lub mockuj zewnętrzne wywołania.", - "ru": "Test '{name}' took {elapsed:.2f}s (limit: {limit}s). Optimise: use lighter fixtures, reduce I/O, or mock external calls.", - "zh": "Test '{name}' took {elapsed:.2f}s (limit: {limit}s). Optimise: use lighter fixtures, reduce I/O, or mock external calls." - }, - "Test isolation check passed: {count} test files analyzed, no violations found.": { - "bg": "Проверката за изолация на тестове премина: анализирани са {count} тестови файла, няма нарушения.", - "de": "Testisolationsprüfung bestanden: {count} Testdateien analysiert, keine Verstöße gefunden.", - "en": "Test isolation check passed: {count} test files analyzed, no violations found.", - "pl": "Sprawdzenie izolacji testów zaliczone: przeanalizowano {count} plików testowych, brak naruszeń.", - "ru": "Проверка изоляции тестов пройдена: проанализировано {count} тестовых файлов, нарушений не найдено.", - "zh": "测试隔离检查通过:已分析 {count} 个测试文件,未发现违规。" - }, - "Tests failed — refusing to release. Fix test failures first.\n{stderr}": { - "bg": "Tests failed — refusing to release. Fix test failures first.\n{stderr}", - "de": "Tests failed — refusing to release. Fix test failures first.\n{stderr}", - "en": "Tests failed — refusing to release. Fix test failures first.\n{stderr}", - "pl": "Testy nie powiodły się — odmowa wydania. Najpierw napraw niepowodzenia testów.\n{stderr}", - "ru": "Tests failed — refusing to release. Fix test failures first.\n{stderr}", - "zh": "Tests failed — refusing to release. Fix test failures first.\n{stderr}" - }, - "Tests passed.": { - "bg": "Tests passed.", - "de": "Tests passed.", - "en": "Tests passed.", - "pl": "Testy zakończone pomyślnie.", - "ru": "Tests passed.", - "zh": "Tests passed." - }, - "Timeout reached after {timeout}s.": { - "bg": "Timeout reached after {timeout}s.", - "de": "Timeout reached after {timeout}s.", - "en": "Timeout reached after {timeout}s.", - "pl": "Timeout reached after {timeout}s.", - "ru": "Timeout reached after {timeout}s.", - "zh": "Timeout reached after {timeout}s." - }, - "Unit tests passed in {duration:.2f}s (under {max}s limit, all tests under {single}s per-test limit).": { - "bg": "Unit tests passed in {duration:.2f}s (under {max}s limit, all tests under {single}s per-test limit).", - "de": "Unit tests passed in {duration:.2f}s (under {max}s limit, all tests under {single}s per-test limit).", - "en": "Unit tests passed in {duration:.2f}s (under {max}s limit, all tests under {single}s per-test limit).", - "pl": "Testy jednostkowe zakończone pomyślnie w {duration:.2f}s (poniżej limitu {max}s, wszystkie testy poniżej limitu {single}s na test).", - "ru": "Unit tests passed in {duration:.2f}s (under {max}s limit, all tests under {single}s per-test limit).", - "zh": "Unit tests passed in {duration:.2f}s (under {max}s limit, all tests under {single}s per-test limit)." - }, - "Unit tests too slow: {duration:.2f}s (max allowed: {max}s).\n Fix: run 'make pytest-cov' to profile, then optimise slow tests.\n Hint: avoid unnecessary imports, use lighter mocks, or cache fixtures.": { - "bg": "Unit tests too slow: {duration:.2f}s (max allowed: {max}s).\n Fix: run 'make pytest-cov' to profile, then optimise slow tests.\n Hint: avoid unnecessary imports, use lighter mocks, or cache fixtures.", - "de": "Unit tests too slow: {duration:.2f}s (max allowed: {max}s).\n Fix: run 'make pytest-cov' to profile, then optimise slow tests.\n Hint: avoid unnecessary imports, use lighter mocks, or cache fixtures.", - "en": "Unit tests too slow: {duration:.2f}s (max allowed: {max}s).\n Fix: run 'make pytest-cov' to profile, then optimise slow tests.\n Hint: avoid unnecessary imports, use lighter mocks, or cache fixtures.", - "pl": "Testy jednostkowe zbyt wolne: {duration:.2f}s (maks. dozwolone: {max}s).\n Naprawa: uruchom 'make pytest-cov' do profilowania, następnie zoptymalizuj wolne testy.\n Wskazówka: unikaj niepotrzebnych importów, użyj lżejszych mocków, lub buforuj fixtures.", - "ru": "Unit tests too slow: {duration:.2f}s (max allowed: {max}s).\n Fix: run 'make pytest-cov' to profile, then optimise slow tests.\n Hint: avoid unnecessary imports, use lighter mocks, or cache fixtures.", - "zh": "Unit tests too slow: {duration:.2f}s (max allowed: {max}s).\n Fix: run 'make pytest-cov' to profile, then optimise slow tests.\n Hint: avoid unnecessary imports, use lighter mocks, or cache fixtures." - }, - "Unknown check category '{check}'. Available: all, user-facing{tags}": { - "bg": "Unknown check category '{check}'. Available: all, user-facing{tags}", - "de": "Unknown check category '{check}'. Available: all, user-facing{tags}", - "en": "Unknown check category '{check}'. Available: all, user-facing{tags}", - "pl": "Nieznana kategoria kontroli '{check}'. Dostępne: all, user-facing{tags}", - "ru": "Unknown check category '{check}'. Available: all, user-facing{tags}", - "zh": "Unknown check category '{check}'. Available: all, user-facing{tags}" - }, - "Updated badge URLs in {filename}": { - "bg": "Updated badge URLs in {filename}", - "de": "Updated badge URLs in {filename}", - "en": "Updated badge URLs in {filename}", - "pl": "Updated badge URLs in {filename}", - "ru": "Updated badge URLs in {filename}", - "zh": "Updated badge URLs in {filename}" - }, - "Updated documentation version references to v{version}": { - "bg": "", - "de": "", - "en": "Updated documentation version references to v{version}", - "pl": "", - "ru": "", - "zh": "" - }, - "Updated version in {init}": { - "bg": "Updated version in {init}", - "de": "Updated version in {init}", - "en": "Updated version in {init}", - "pl": "Zaktualizowano wersję w {init}", - "ru": "Updated version in {init}", - "zh": "Updated version in {init}" - }, - "Updated {changelog_file}": { - "bg": "Updated {changelog_file}", - "de": "Updated {changelog_file}", - "en": "Updated {changelog_file}", - "pl": "Zaktualizowano {changelog_file}", - "ru": "Updated {changelog_file}", - "zh": "Updated {changelog_file}" - }, - "Use string comparison or _is_truthy()/_is_falsy() helpers instead. Add '{marker}' to suppress individual lines.": { - "bg": "Използвайте сравнение на низове или _is_truthy()/_is_falsy() помощници. Добавете '{marker}' за потискане на отделни редове.", - "de": "Verwenden Sie String-Vergleich oder _is_truthy()/_is_falsy() Hilfsfunktionen. Fügen Sie '{marker}' hinzu, um einzelne Zeilen zu unterdrücken.", - "en": "Use string comparison or _is_truthy()/_is_falsy() helpers instead. Add '{marker}' to suppress individual lines.", - "pl": "Użyj porównania ciągów lub pomocników _is_truthy()/_is_falsy(). Dodaj '{marker}', aby pominąć pojedyncze linie.", - "ru": "Используйте строковое сравнение или помощники _is_truthy()/_is_falsy(). Добавьте '{marker}' для подавления отдельных строк.", - "zh": "使用字符串比较或 _is_truthy()/_is_falsy() 辅助函数。添加 '{marker}' 以抑制个别行。" - }, - "VIKUNJA_TOKEN is not set. Required to derive PR title.": { - "bg": "VIKUNJA_TOKEN не е зададен. Необходим за извличане на PR заглавие.", - "de": "VIKUNJA_TOKEN nicht gesetzt. Erforderlich zum Ableiten des PR-Titels.", - "en": "VIKUNJA_TOKEN is not set. Required to derive PR title.", - "pl": "VIKUNJA_TOKEN nie jest ustawiony. Wymagany do pobrania tytułu PR.", - "ru": "VIKUNJA_TOKEN не установлен. Требуется для получения заголовка PR.", - "zh": "VIKUNJA_TOKEN 未设置。推导 PR 标题所需。" - }, - "VIKUNJA_TOKEN is not set. Set it in .env or environment.": { - "bg": "VIKUNJA_TOKEN не е зададен. Задайте го в .env или средата.", - "de": "VIKUNJA_TOKEN nicht gesetzt. In .env oder Umgebung setzen.", - "en": "VIKUNJA_TOKEN is not set. Set it in .env or environment.", - "pl": "VIKUNJA_TOKEN nie jest ustawiony. Ustaw go w .env lub środowisku.", - "ru": "VIKUNJA_TOKEN не установлен. Установите его в .env или среде.", - "zh": "VIKUNJA_TOKEN 未设置。在 .env 或环境中设置它。" - }, - "VIKUNJA_TOKEN is not set. This is required in CI to validate PR titles.": { - "bg": "VIKUNJA_TOKEN is not set. This is required in CI to validate PR titles.", - "de": "VIKUNJA_TOKEN is not set. This is required in CI to validate PR titles.", - "en": "VIKUNJA_TOKEN is not set. This is required in CI to validate PR titles.", - "pl": "VIKUNJA_TOKEN nie jest ustawiony. Jest to wymagane w CI do walidacji tytułów PR.", - "ru": "VIKUNJA_TOKEN is not set. This is required in CI to validate PR titles.", - "zh": "VIKUNJA_TOKEN is not set. This is required in CI to validate PR titles." - }, - "Version file: {file}": { - "bg": "Version file: {file}", - "de": "Version file: {file}", - "en": "Version file: {file}", - "pl": "Plik wersji: {file}", - "ru": "Version file: {file}", - "zh": "Version file: {file}" - }, - "Version stays at v{version} — no version bump from git-cliff. Commits since last tag don't warrant a new release. Skipping.": { - "bg": "", - "de": "", - "en": "Version stays at v{version} — no version bump from git-cliff. Commits since last tag don't warrant a new release. Skipping.", - "pl": "", - "ru": "", - "zh": "" - }, - "Vikunja API error (HTTP {status}): {message}. Task {task_id} was NOT updated. The merge succeeded but the Vikunja task needs manual update.": { - "bg": "Vikunja API error (HTTP {status}): {message}. Task {task_id} was NOT updated. The merge succeeded but the Vikunja task needs manual update.", - "de": "Vikunja API error (HTTP {status}): {message}. Task {task_id} was NOT updated. The merge succeeded but the Vikunja task needs manual update.", - "en": "Vikunja API error (HTTP {status}): {message}. Task {task_id} was NOT updated. The merge succeeded but the Vikunja task needs manual update.", - "pl": "Błąd API Vikunja (HTTP {status}): {message}. Zadanie {task_id} NIE zostało zaktualizowane. Scalenie powiodło się ale zadanie Vikunja wymaga ręcznej aktualizacji.", - "ru": "Vikunja API error (HTTP {status}): {message}. Task {task_id} was NOT updated. The merge succeeded but the Vikunja task needs manual update.", - "zh": "Vikunja API error (HTTP {status}): {message}. Task {task_id} was NOT updated. The merge succeeded but the Vikunja task needs manual update." - }, - "Vikunja task title '{title}' starts with '{prefix}:'. The task title should NOT include the '{prefix}' prefix — it is automatically added to the PR title. Update the Vikunja task title to remove the prefix.": { - "bg": "Заглавието на задачата във Vikunja '{title}' започва с '{prefix}:'. Заглавието на задачата НЕ трябва да съдържа префикса '{prefix}' — той се добавя автоматично към заглавието на PR. Актуализирайте заглавието на задачата във Vikunja, за да премахнете префикса.", - "de": "Der Vikunja-Aufgabentitel '{title}' beginnt mit '{prefix}:'. Der Aufgabentitel darf NICHT den Präfix '{prefix}' enthalten — er wird automatisch zum PR-Titel hinzugefügt. Aktualisieren Sie den Vikunja-Aufgabentitel, um den Präfix zu entfernen.", - "en": "Vikunja task title '{title}' starts with '{prefix}:'. The task title should NOT include the '{prefix}' prefix — it is automatically added to the PR title. Update the Vikunja task title to remove the prefix.", - "pl": "Tytuł zadania Vikunja '{title}' zaczyna się od '{prefix}:'. Tytuł zadania nie powinien zawierać prefiksu '{prefix}' — jest on automatycznie dodawany do tytułu PR. Zaktualizuj tytuł zadania Vikunja, aby usunąć prefiks.", - "ru": "Заголовок задачи Vikunja '{title}' начинается с '{prefix}:'. Заголовок задачи НЕ должен включать префикс '{prefix}' — он автоматически добавляется к заголовку PR. Обновите заголовок задачи Vikunja, чтобы удалить префикс.", - "zh": "Vikunja 任务标题 '{title}' 以 '{prefix}:' 开头。任务标题不应包含 '{prefix}' 前缀 — 它会自动添加到 PR 标题中。请更新 Vikunja 任务标题以删除前缀。" - }, - "Vikunja task {task_id} not found in project {project_id}.\n Create it first:\n python -m devx.tools.create_task --title \"Task title\"\n Or check that the task ID in the branch name is correct.": { - "bg": "Vikunja задача {task_id} не е намерена в проект {project_id}.\n Създайте я първо:\n python -m devx.tools.create_task --title \"Заглавие на задача\"\n Или проверете че ID на задачата в името на клона е правилно.", - "de": "Vikunja-Task {task_id} in Projekt {project_id} nicht gefunden.\n Zuerst erstellen:\n python -m devx.tools.create_task --title \"Task-Titel\"\n Oder prüfen, ob die Task-ID im Branch-Namen korrekt ist.", - "en": "Vikunja task {task_id} not found in project {project_id}.\n Create it first:\n python -m devx.tools.create_task --title \"Task title\"\n Or check that the task ID in the branch name is correct.", - "pl": "Zadanie Vikunja {task_id} nie znalezione w projekcie {project_id}.\n Utwórz je najpierw:\n python -m devx.tools.create_task --title \"Tytuł zadania\"\n Lub sprawdź, czy ID zadania w nazwie gałęzi jest poprawne.", - "ru": "Задача Vikunja {task_id} не найдена в проекте {project_id}.\n Сначала создайте её:\n python -m devx.tools.create_task --title \"Заголовок задачи\"\n Или проверьте, что ID задачи в имени ветки корректен.", - "zh": "在项目 {project_id} 中找不到 Vikunja 任务 {task_id}。\n 请先创建:\n python -m devx.tools.create_task --title \"任务标题\"\n 或检查分支名称中的任务 ID 是否正确。" - }, - "WARN: .venv has Python {version}, but >={req} is required.": { - "bg": "ПРЕДУПРЕЖДЕНИЕ: .venv има Python {version}, но се изисква >={req}.", - "de": "WARNUNG: .venv hat Python {version}, aber >={req} ist erforderlich.", - "en": "WARN: .venv has Python {version}, but >={req} is required.", - "pl": "OSTRZEŻENIE: .venv ma Python {version}, ale wymagane jest >={req}.", - "ru": "ПРЕДУПРЕЖДЕНИЕ: в .venv установлен Python {version}, но требуется >={req}.", - "zh": "警告: .venv 的 Python 版本为 {version},但要求 >={req}。" - }, - "WARN: .venv not found. Run 'make setup-venv' to create it.": { - "bg": "ПРЕДУПРЕЖДЕНИЕ: .venv не е намерен. Изпълнете 'make setup-venv' за създаване.", - "de": "WARNUNG: .venv nicht gefunden. Führen Sie 'make setup-venv' aus, um es zu erstellen.", - "en": "WARN: .venv not found. Run 'make setup-venv' to create it.", - "pl": "OSTRZEŻENIE: Nie znaleziono .venv. Uruchom 'make setup-venv', aby utworzyć.", - "ru": "ПРЕДУПРЕЖДЕНИЕ: .venv не найден. Выполните 'make setup-venv' для создания.", - "zh": "警告: 未找到 .venv。运行 'make setup-venv' 来创建。" - }, - "WARN: Could not determine Python version in .venv.": { - "bg": "ПРЕДУПРЕЖДЕНИЕ: Не може да се определи версията на Python в .venv.", - "de": "WARNUNG: Python-Version in .venv konnte nicht bestimmt werden.", - "en": "WARN: Could not determine Python version in .venv.", - "pl": "OSTRZEŻENIE: Nie można określić wersji Python w .venv.", - "ru": "ПРЕДУПРЕЖДЕНИЕ: Не удалось определить версию Python в .venv.", - "zh": "警告: 无法确定 .venv 中的 Python 版本。" - }, - "WARN: Could not parse Python version '{version}'.": { - "bg": "ПРЕДУПРЕЖДЕНИЕ: Не може да се анализира версията на Python '{version}'.", - "de": "WARNUNG: Python-Version '{version}' konnte nicht analysiert werden.", - "en": "WARN: Could not parse Python version '{version}'.", - "pl": "OSTRZEŻENIE: Nie można przeanalizować wersji Python '{version}'.", - "ru": "ПРЕДУПРЕЖДЕНИЕ: Не удалось разобрать версию Python '{version}'.", - "zh": "警告: 无法解析 Python 版本 '{version}'。" - }, - "WARNING: --skip-tests passed — skipping test verification.": { - "bg": "WARNING: --skip-tests passed — skipping test verification.", - "de": "WARNING: --skip-tests passed — skipping test verification.", - "en": "WARNING: --skip-tests passed — skipping test verification.", - "pl": "OSTRZEŻENIE: --skip-tests przekazane — pomijanie weryfikacji testów.", - "ru": "WARNING: --skip-tests passed — skipping test verification.", - "zh": "WARNING: --skip-tests passed — skipping test verification." - }, - "WARNING: .taskid file ({file_id}) is deprecated and disagrees with branch name ({branch_id}). Delete .taskid from the repo — branch name is the sole source of truth.": { - "bg": "ВНИМАНИЕ: Файлът .taskid ({file_id}) е остарял и не съвпада с името на клона ({branch_id}). Изтрийте .taskid от хранилището — името на клона е единственият източник на истината.", - "de": "WARNUNG: Die Datei .taskid ({file_id}) ist veraltet und stimmt nicht mit dem Branch-Namen ({branch_id}) überein. Löschen Sie .taskid aus dem Repo — der Branch-Name ist die einzige Wahrheitsquelle.", - "en": "WARNING: .taskid file ({file_id}) is deprecated and disagrees with branch name ({branch_id}). Delete .taskid from the repo — branch name is the sole source of truth.", - "pl": "OSTRZEŻENIE: plik .taskid ({file_id}) jest przestarzały i niezgodny z nazwą gałęzi ({branch_id}). Usuń .taskid z repozytorium — nazwa gałęzi jest jedynym źródłem prawdy.", - "ru": "ВНИМАНИЕ: Файл .taskid ({file_id}) устарел и не совпадает с именем ветки ({branch_id}). Удалите .taskid из репозитория — имя ветки — единственный источник истины.", - "zh": "警告:.taskid 文件 ({file_id}) 已弃用,与分支名称 ({branch_id}) 不一致。请从仓库中删除 .taskid — 分支名称是唯一的真实来源。" - }, - "WARNING: VIKUNJA_TOKEN not set — skipping task existence check. Set it in .env to enable full validation.": { - "bg": "ПРЕДУПРЕЖДЕНИЕ: VIKUNJA_TOKEN не е зададен — пропускане на проверката за съществуване на задача. Задайте го в .env за пълна валидация.", - "de": "WARNUNG: VIKUNJA_TOKEN nicht gesetzt — Task-Existenzprüfung übersprungen. In .env setzen für volle Validierung.", - "en": "WARNING: VIKUNJA_TOKEN not set — skipping task existence check. Set it in .env to enable full validation.", - "pl": "OSTRZEŻENIE: VIKUNJA_TOKEN nie jest ustawiony — pomijanie sprawdzania istnienia zadania. Ustaw w .env, aby włączyć pełną walidację.", - "ru": "ПРЕДУПРЕЖДЕНИЕ: VIKUNJA_TOKEN не установлен — пропуск проверки существования задачи. Установите в .env для полной проверки.", - "zh": "警告: VIKUNJA_TOKEN 未设置 — 跳过任务存在性检查。在 .env 中设置以启用完整验证。" - }, - "WARNING: Version badge shows stale version (expected v{version}) — regenerating": { - "bg": "", - "de": "", - "en": "WARNING: Version badge shows stale version (expected v{version}) — regenerating", - "pl": "", - "ru": "", - "zh": "" - }, - "WARNING: check_doc_versions --fix failed (rc={rc}): {err}": { - "bg": "", - "de": "", - "en": "WARNING: check_doc_versions --fix failed (rc={rc}): {err}", - "pl": "", - "ru": "", - "zh": "" - }, - "Waiting 5s for Gitea to process pushed commits...": { - "bg": "", - "de": "", - "en": "Waiting 5s for Gitea to process pushed commits...", - "pl": "", - "ru": "", - "zh": "" - }, - "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)...", - "en": "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)..." - }, - "Warning: could not fetch tags from origin.": { - "bg": "Warning: could not fetch tags from origin.", - "de": "Warning: could not fetch tags from origin.", - "en": "Warning: could not fetch tags from origin.", - "pl": "Ostrzeżenie: nie udało się pobrać tagów z origin.", - "ru": "Warning: could not fetch tags from origin.", - "zh": "Warning: could not fetch tags from origin." - }, - "Warning: instance-level runners query failed: {error}": { - "bg": "Warning: instance-level runners query failed: {error}", - "de": "Warning: instance-level runners query failed: {error}", - "en": "Warning: instance-level runners query failed: {error}", - "pl": "Warning: instance-level runners query failed: {error}", - "ru": "Warning: instance-level runners query failed: {error}", - "zh": "Warning: instance-level runners query failed: {error}" - }, - "Warning: instance-level runners query returned HTTP {status}": { - "bg": "Warning: instance-level runners query returned HTTP {status}", - "de": "Warning: instance-level runners query returned HTTP {status}", - "en": "Warning: instance-level runners query returned HTTP {status}", - "pl": "Warning: instance-level runners query returned HTTP {status}", - "ru": "Warning: instance-level runners query returned HTTP {status}", - "zh": "Warning: instance-level runners query returned HTTP {status}" - }, - "Warning: org-level runners query failed: {error}": { - "bg": "Warning: org-level runners query failed: {error}", - "de": "Warning: org-level runners query failed: {error}", - "en": "Warning: org-level runners query failed: {error}", - "pl": "Warning: org-level runners query failed: {error}", - "ru": "Warning: org-level runners query failed: {error}", - "zh": "Warning: org-level runners query failed: {error}" - }, - "Warning: org-level runners query returned HTTP {status}": { - "bg": "Warning: org-level runners query returned HTTP {status}", - "de": "Warning: org-level runners query returned HTTP {status}", - "en": "Warning: org-level runners query returned HTTP {status}", - "pl": "Warning: org-level runners query returned HTTP {status}", - "ru": "Warning: org-level runners query returned HTTP {status}", - "zh": "Warning: org-level runners query returned HTTP {status}" - }, - "Warning: repo-level runners query failed: {error}": { - "bg": "Warning: repo-level runners query failed: {error}", - "de": "Warning: repo-level runners query failed: {error}", - "en": "Warning: repo-level runners query failed: {error}", - "pl": "Warning: repo-level runners query failed: {error}", - "ru": "Warning: repo-level runners query failed: {error}", - "zh": "Warning: repo-level runners query failed: {error}" - }, - "Warning: repo-level runners query returned HTTP {status}": { - "bg": "Warning: repo-level runners query returned HTTP {status}", - "de": "Warning: repo-level runners query returned HTTP {status}", - "en": "Warning: repo-level runners query returned HTTP {status}", - "pl": "Warning: repo-level runners query returned HTTP {status}", - "ru": "Warning: repo-level runners query returned HTTP {status}", - "zh": "Warning: repo-level runners query returned HTTP {status}" - }, - "Wiki repo not found or empty — initializing fresh.": { - "bg": "", - "de": "", - "en": "Wiki repo not found or empty — initializing fresh.", - "pl": "", - "ru": "", - "zh": "" - }, - "Wiki synced successfully.": { - "bg": "", - "de": "", - "en": "Wiki synced successfully.", - "pl": "", - "ru": "", - "zh": "" - }, - "Wiki verification failed — could not clone wiki": { - "bg": "", - "de": "", - "en": "Wiki verification failed — could not clone wiki", - "pl": "", - "ru": "", - "zh": "" - }, - "Wiki verification failed — {failures} page(s) missing": { - "bg": "", - "de": "", - "en": "Wiki verification failed — {failures} page(s) missing", - "pl": "", - "ru": "", - "zh": "" - }, - "Write deploy-ref to $GITHUB_OUTPUT file.": { - "bg": "Запиши deploy-ref в $GITHUB_OUTPUT файла.", - "de": "Deploy-ref in $GITHUB_OUTPUT-Datei schreiben.", - "en": "Write deploy-ref to $GITHUB_OUTPUT file.", - "pl": "Zapisz deploy-ref do pliku $GITHUB_OUTPUT.", - "ru": "Записать deploy-ref в файл $GITHUB_OUTPUT.", - "zh": "将 deploy-ref 写入 $GITHUB_OUTPUT 文件。" - }, - "Wrote tag {tag} to GITHUB_OUTPUT.": { - "bg": "Wrote tag {tag} to GITHUB_OUTPUT.", - "de": "Wrote tag {tag} to GITHUB_OUTPUT.", - "en": "Wrote tag {tag} to GITHUB_OUTPUT.", - "pl": "Wrote tag {tag} to GITHUB_OUTPUT.", - "ru": "Wrote tag {tag} to GITHUB_OUTPUT.", - "zh": "Wrote tag {tag} to GITHUB_OUTPUT." - }, - "[check-api-identity-checks] Passed: no unsafe identity checks found": { - "bg": "[check-api-identity-checks] Мина: не са намерени небрежни проверки за идентичност", - "de": "[check-api-identity-checks] Bestanden: keine unsicheren Identitätsprüfungen gefunden", - "en": "[check-api-identity-checks] Passed: no unsafe identity checks found", - "pl": "[check-api-identity-checks] Passed: nie znaleziono niebezpiecznych sprawdzeń tożsamości", - "ru": "[check-api-identity-checks] Пройдено: небезопасных проверок идентичности не найдено", - "zh": "[check-api-identity-checks] 通过:未发现不安全的身份检查" - }, - "[check-dep-docs] Passed: all dependencies are documented": { - "bg": "[check-dep-docs] Passed: all dependencies are documented", - "de": "[check-dep-docs] Passed: all dependencies are documented", - "en": "[check-dep-docs] Passed: all dependencies are documented", - "pl": "[check-dep-docs] Passed: all dependencies are documented", - "ru": "[check-dep-docs] Passed: all dependencies are documented", - "zh": "[check-dep-docs] Passed: all dependencies are documented" - }, - "[check-deps] All core tools present.": { - "bg": "[check-deps] Всички основни инструменти са налични.", - "de": "[check-deps] Alle Kernwerkzeuge vorhanden.", - "en": "[check-deps] All core tools present.", - "pl": "[check-deps] Wszystkie podstawowe narzędzia są dostępne.", - "ru": "[check-deps] Все основные инструменты доступны.", - "zh": "[check-deps] 所有核心工具均已就绪。" - }, - "[check-deps] Verifying tools...": { - "bg": "[check-deps] Проверка на инструментите...", - "de": "[check-deps] Werkzeuge werden überprüft...", - "en": "[check-deps] Verifying tools...", - "pl": "[check-deps] Sprawdzanie narzędzi...", - "ru": "[check-deps] Проверка инструментов...", - "zh": "[check-deps] 正在验证工具..." - }, - "[check-deps] Virtualenv .venv ready (Python {version}).": { - "bg": "[check-deps] Виртуална среда .venv готова (Python {version}).", - "de": "[check-deps] Virtuelle Umgebung .venv bereit (Python {version}).", - "en": "[check-deps] Virtualenv .venv ready (Python {version}).", - "pl": "[check-deps] Środowisko wirtualne .venv gotowe (Python {version}).", - "ru": "[check-deps] Виртуальное окружение .venv готово (Python {version}).", - "zh": "[check-deps] 虚拟环境 .venv 已就绪 (Python {version})。" - }, - "[check-mutable-globals] Passed: no mutable path globals found": { - "bg": "[check-mutable-globals] Passed: no mutable path globals found", - "de": "[check-mutable-globals] Passed: no mutable path globals found", - "en": "[check-mutable-globals] Passed: no mutable path globals found", - "pl": "[check-mutable-globals] Passed: no mutable path globals found", - "ru": "[check-mutable-globals] Passed: no mutable path globals found", - "zh": "[check-mutable-globals] Passed: no mutable path globals found" - }, - "[check_agent_docs] Passed: scanned {count} file(s), no stale references": { - "bg": "[check_agent_docs] Passed: scanned {count} file(s), no stale references", - "de": "[check_agent_docs] Passed: scanned {count} file(s), no stale references", - "en": "[check_agent_docs] Passed: scanned {count} file(s), no stale references", - "pl": "[check_agent_docs] Passed: scanned {count} file(s), no stale references", - "ru": "[check_agent_docs] Passed: scanned {count} file(s), no stale references", - "zh": "[check_agent_docs] Passed: scanned {count} file(s), no stale references" - }, - "[check_test_coverage] No changed files to check.": { - "bg": "[check_test_coverage] No changed files to check.", - "de": "[check_test_coverage] No changed files to check.", - "en": "[check_test_coverage] No changed files to check.", - "pl": "[check_test_coverage] No changed files to check.", - "ru": "[check_test_coverage] No changed files to check.", - "zh": "[check_test_coverage] No changed files to check." - }, - "[docker-login] Logged in to {registry}.": { - "bg": "[docker-login] Влязъл в {registry}.", - "de": "[docker-login] Angemeldet bei {registry}.", - "en": "[docker-login] Logged in to {registry}.", - "pl": "[docker-login] Zalogowano do {registry}.", - "ru": "[docker-login] Выполнен вход в {registry}.", - "zh": "[docker-login] 已登录到 {registry}。" - }, - "[docker-login] Login to {registry} failed (continuing).": { - "bg": "[docker-login] Влизането в {registry} не успя (продължава).", - "de": "[docker-login] Anmeldung bei {registry} fehlgeschlagen (wird fortgesetzt).", - "en": "[docker-login] Login to {registry} failed (continuing).", - "pl": "[docker-login] Logowanie do {registry} nie powiodło się (kontynuowanie).", - "ru": "[docker-login] Ошибка входа в {registry} (продолжаем).", - "zh": "[docker-login] 登录 {registry} 失败(继续)。" - }, - "[docker-login] Skipping {registry} (token {env} not set).": { - "bg": "[docker-login] Пропускане на {registry} (токен {env} не е зададен).", - "de": "[docker-login] {registry} übersprungen (Token {env} nicht gesetzt).", - "en": "[docker-login] Skipping {registry} (token {env} not set).", - "pl": "[docker-login] Pomijanie {registry} (token {env} nie ustawiony).", - "ru": "[docker-login] Пропуск {registry} (токен {env} не задан).", - "zh": "[docker-login] 跳过 {registry}(未设置令牌 {env})。" - }, - "[dry-run] No changes pushed.": { - "bg": "", - "de": "", - "en": "[dry-run] No changes pushed.", - "pl": "", - "ru": "", - "zh": "" - }, - "[dry-run] Would commit and push wiki changes": { - "bg": "", - "de": "", - "en": "[dry-run] Would commit and push wiki changes", - "pl": "", - "ru": "", - "zh": "" - }, - "[dry-run] Would commit: release: v{version} [skip ci]": { - "bg": "[dry-run] Would commit: release: v{version} [skip ci]", - "de": "[dry-run] Would commit: release: v{version} [skip ci]", - "en": "[dry-run] Would commit: release: v{version} [skip ci]", - "pl": "[dry-run] Utworzono by commit: release: v{version} [skip ci]", - "ru": "[dry-run] Would commit: release: v{version} [skip ci]", - "zh": "[dry-run] Would commit: release: v{version} [skip ci]" - }, - "[dry-run] Would create tag: v{version}": { - "bg": "[dry-run] Would create tag: v{version}", - "de": "[dry-run] Would create tag: v{version}", - "en": "[dry-run] Would create tag: v{version}", - "pl": "[dry-run] Utworzono by tag: v{version}", - "ru": "[dry-run] Would create tag: v{version}", - "zh": "[dry-run] Would create tag: v{version}" - }, - "[dry-run] Would create tag: {tag}": { - "bg": "[dry-run] Would create tag: {tag}", - "de": "[dry-run] Would create tag: {tag}", - "en": "[dry-run] Would create tag: {tag}", - "pl": "[dry-run] Utworzono by tag: {tag}", - "ru": "[dry-run] Would create tag: {tag}", - "zh": "[dry-run] Would create tag: {tag}" - }, - "[dry-run] Would push commit to master": { - "bg": "[dry-run] Would push commit to master", - "de": "[dry-run] Would push commit to master", - "en": "[dry-run] Would push commit to master", - "pl": "[dry-run] Wypchnięto by commit do master", - "ru": "[dry-run] Would push commit to master", - "zh": "[dry-run] Would push commit to master" - }, - "[dry-run] Would update doc version references via check_doc_versions --fix": { - "bg": "", - "de": "", - "en": "[dry-run] Would update doc version references via check_doc_versions --fix", - "pl": "", - "ru": "", - "zh": "" - }, - "[dry-run] Would update {changelog_file}": { - "bg": "[dry-run] Would update {changelog_file}", - "de": "[dry-run] Would update {changelog_file}", - "en": "[dry-run] Would update {changelog_file}", - "pl": "[dry-run] Zaktualizowano by {changelog_file}", - "ru": "[dry-run] Would update {changelog_file}", - "zh": "[dry-run] Would update {changelog_file}" - }, - "[dry-run] Would update {init}": { - "bg": "[dry-run] Would update {init}", - "de": "[dry-run] Would update {init}", - "en": "[dry-run] Would update {init}", - "pl": "[dry-run] Zaktualizowano by {init}", - "ru": "[dry-run] Would update {init}", - "zh": "[dry-run] Would update {init}" - }, - "[tofu-init] Done.": { - "bg": "[tofu-init] Готово.", - "de": "[tofu-init] Fertig.", - "en": "[tofu-init] Done.", - "pl": "[tofu-init] Gotowe.", - "ru": "[tofu-init] Готово.", - "zh": "[tofu-init] 完成。" - }, - "[tofu-init] Initializing {dir}...": { - "bg": "[tofu-init] Инициализиране на {dir}...", - "de": "[tofu-init] Initialisiere {dir}...", - "en": "[tofu-init] Initializing {dir}...", - "pl": "[tofu-init] Inicjalizacja {dir}...", - "ru": "[tofu-init] Инициализация {dir}...", - "zh": "[tofu-init] 正在初始化 {dir}..." - }, - "[tofu-{mode}] All configurations valid.": { - "bg": "[tofu-{mode}] Всички конфигурации са валидни.", - "de": "[tofu-{mode}] Alle Konfigurationen gültig.", - "en": "[tofu-{mode}] All configurations valid.", - "pl": "[tofu-{mode}] Wszystkie konfiguracje są poprawne.", - "ru": "[tofu-{mode}] Все конфигурации валидны.", - "zh": "[tofu-{mode}] 所有配置有效。" - }, - "[tofu-{mode}] Validating OpenTofu configurations...": { - "bg": "[tofu-{mode}] Проверка на OpenTofu конфигурациите...", - "de": "[tofu-{mode}] Validiere OpenTofu-Konfigurationen...", - "en": "[tofu-{mode}] Validating OpenTofu configurations...", - "pl": "[tofu-{mode}] Sprawdzanie konfiguracji OpenTofu...", - "ru": "[tofu-{mode}] Проверка конфигураций OpenTofu...", - "zh": "[tofu-{mode}] 正在验证 OpenTofu 配置..." - }, - "[tool.devx] missing required keys: {keys}": { - "bg": "[tool.devx] липсват задължителни ключове: {keys}", - "de": "[tool.devx] fehlt erforderliche Schlüssel: {keys}", - "en": "[tool.devx] missing required keys: {keys}", - "pl": "[tool.devx] brak wymaganych kluczy: {keys}", - "ru": "[tool.devx] отсутствуют обязательные ключи: {keys}", - "zh": "[tool.devx] 缺少必需的键: {keys}" - }, - "active": { - "bg": "активен", - "de": "aktiv", - "en": "active", - "pl": "aktywny", - "ru": "активен", - "zh": "活跃" - }, - "completed": { - "bg": "завършен", - "de": "abgeschlossen", - "en": "completed", - "pl": "ukończony", - "ru": "завершён", - "zh": "已完成" - }, - "count={count}": { - "bg": "count={count}", - "de": "count={count}", - "en": "count={count}", - "pl": "count={count}", - "ru": "count={count}", - "zh": "count={count}" - }, - "devx version mismatch across extras: {detail}": { - "bg": "несъответствие на версията на devx между extras: {detail}", - "de": "devx-Versionskonflikt zwischen Extras: {detail}", - "en": "devx version mismatch across extras: {detail}", - "pl": "niezgodność wersji devx między extras: {detail}", - "ru": "несоответствие версии devx между extras: {detail}", - "zh": "devx 版本在 extras 之间不一致: {detail}" - }, - "failed": { - "bg": "неуспешен", - "de": "fehlgeschlagen", - "en": "failed", - "pl": "nieudany", - "ru": "неудачный", - "zh": "失败" - }, - "git command failed ({cmd}): {stderr}": { - "bg": "git command failed ({cmd}): {stderr}", - "de": "git command failed ({cmd}): {stderr}", - "en": "git command failed ({cmd}): {stderr}", - "pl": "polecenie git nie powiodło się ({cmd}): {stderr}", - "ru": "git command failed ({cmd}): {stderr}", - "zh": "git command failed ({cmd}): {stderr}" - }, - "git-cliff generated empty changelog for v{version}. Check cliff.toml and commit history.": { - "bg": "git-cliff generated empty changelog for v{version}. Check cliff.toml and commit history.", - "de": "git-cliff generated empty changelog for v{version}. Check cliff.toml and commit history.", - "en": "git-cliff generated empty changelog for v{version}. Check cliff.toml and commit history.", - "pl": "git-cliff wygenerował pusty changelog dla v{version}. Sprawdź cliff.toml i historię commitów.", - "ru": "git-cliff generated empty changelog for v{version}. Check cliff.toml and commit history.", - "zh": "git-cliff generated empty changelog for v{version}. Check cliff.toml and commit history." - }, - "git-cliff returned empty version.": { - "bg": "git-cliff returned empty version.", - "de": "git-cliff returned empty version.", - "en": "git-cliff returned empty version.", - "pl": "git-cliff zwrócił pustą wersję.", - "ru": "git-cliff returned empty version.", - "zh": "git-cliff returned empty version." - }, - "git-cliff returned invalid version format: {version}. Expected semver (e.g., 0.4.1).": { - "bg": "git-cliff returned invalid version format: {version}. Expected semver (e.g., 0.4.1).", - "de": "git-cliff returned invalid version format: {version}. Expected semver (e.g., 0.4.1).", - "en": "git-cliff returned invalid version format: {version}. Expected semver (e.g., 0.4.1).", - "pl": "git-cliff zwrócił nieprawidłowy format wersji: {version}. Oczekiwano semver (np., 0.4.1).", - "ru": "git-cliff returned invalid version format: {version}. Expected semver (e.g., 0.4.1).", - "zh": "git-cliff returned invalid version format: {version}. Expected semver (e.g., 0.4.1)." - }, - "in_progress": { - "bg": "в процес", - "de": "in Bearbeitung", - "en": "in progress", - "pl": "w toku", - "ru": "в процессе", - "zh": "进行中" - }, - "inactive": { - "bg": "неактивен", - "de": "inaktiv", - "en": "inactive", - "pl": "nieaktywny", - "ru": "неактивен", - "zh": "未激活" - }, - "indices={indices}": { - "bg": "indices={indices}", - "de": "indices={indices}", - "en": "indices={indices}", - "pl": "indices={indices}", - "ru": "indices={indices}", - "zh": "indices={indices}" - }, - "mapping.json keys and values must be strings, got {k}={v}": { - "bg": "mapping.json keys and values must be strings, got {k}={v}", - "de": "mapping.json keys and values must be strings, got {k}={v}", - "en": "mapping.json keys and values must be strings, got {k}={v}", - "pl": "klucze i wartości mapping.json muszą być ciągami znaków, otrzymano {k}={v}", - "ru": "mapping.json keys and values must be strings, got {k}={v}", - "zh": "mapping.json keys and values must be strings, got {k}={v}" - }, - "mapping.json must be a dict of file-path -> page-title, got {type}": { - "bg": "mapping.json must be a dict of file-path -> page-title, got {type}", - "de": "mapping.json must be a dict of file-path -> page-title, got {type}", - "en": "mapping.json must be a dict of file-path -> page-title, got {type}", - "pl": "mapping.json musi być słownikiem ścieżka-pliku -> tytuł-strony, otrzymano {type}", - "ru": "mapping.json must be a dict of file-path -> page-title, got {type}", - "zh": "mapping.json must be a dict of file-path -> page-title, got {type}" - }, - "pending": { - "bg": "в очакване", - "de": "ausstehend", - "en": "pending", - "pl": "oczekujący", - "ru": "ожидает", - "zh": "待处理" - }, - "pyproject.toml not found in current directory.": { - "bg": "pyproject.toml не е намерен в текущата директория.", - "de": "pyproject.toml im aktuellen Verzeichnis nicht gefunden.", - "en": "pyproject.toml not found in current directory.", - "pl": "nie znaleziono pyproject.toml w bieżącym katalogu.", - "ru": "pyproject.toml не найден в текущей директории.", - "zh": "在当前目录中未找到 pyproject.toml。" - }, - "tea login '{name}' already configured.": { - "bg": "tea login '{name}' already configured.", - "de": "tea login '{name}' already configured.", - "en": "tea login '{name}' already configured.", - "pl": "tea login '{name}' already configured.", - "ru": "tea login '{name}' already configured.", - "zh": "tea login '{name}' already configured." - }, - "tea not installed — skipping login configuration.": { - "bg": "tea not installed — skipping login configuration.", - "de": "tea not installed — skipping login configuration.", - "en": "tea not installed — skipping login configuration.", - "pl": "tea not installed — skipping login configuration.", - "ru": "tea not installed — skipping login configuration.", - "zh": "tea not installed — skipping login configuration." - }, - "time.sleep called in test '{test}' without @patch — this causes real wall-clock delays. Add @patch(\"<module>.time.sleep\").": { - "bg": "time.sleep извикано в тест '{test}' без @patch — това причинява реални забавяния. Добавете @patch(\"<module>.time.sleep\").", - "de": "time.sleep in Test '{test}' ohne @patch aufgerufen — dies verursacht echte Wanduhr-Verzögerungen. @patch(\"<module>.time.sleep\") hinzufügen.", - "en": "time.sleep called in test '{test}' without @patch — this causes real wall-clock delays. Add @patch(\"<module>.time.sleep\").", - "pl": "time.sleep wywołane w teście '{test}' bez @patch — to powoduje rzeczywiste opóźnienia. Dodaj @patch(\"<module>.time.sleep\").", - "ru": "time.sleep вызвано в тесте '{test}' без @patch — это вызывает реальные задержки. Добавьте @patch(\"<module>.time.sleep\").", - "zh": "time.sleep 在测试 '{test}' 中被调用但没有 @patch — 这会导致真实的挂钟延迟。请添加 @patch(\"<module>.time.sleep\")。" - }, - "tofu command failed in {dir}: {error}": { - "bg": "командата tofu не успя в {dir}: {error}", - "de": "tofu-Befehl fehlgeschlagen in {dir}: {error}", - "en": "tofu command failed in {dir}: {error}", - "pl": "polecenie tofu nie powiodło się w {dir}: {error}", - "ru": "команда tofu не удалась в {dir}: {error}", - "zh": "tofu 命令在 {dir} 中失败: {error}" - }, - "unknown": { - "bg": "неизвестен", - "de": "unbekannt", - "en": "unknown", - "pl": "nieznany", - "ru": "неизвестно", - "zh": "未知" - }, - "{call} called in test '{test}' without @patch — this spawns a real subprocess. Add @patch(\"<module>.subprocess.run\") or patch the calling function.": { - "bg": "{call} извикано в тест '{test}' без @patch — това стартира реален subprocess. Добавете @patch(\"<module>.subprocess.run\") или patch-нете извикващата функция.", - "de": "{call} in Test '{test}' ohne @patch aufgerufen — dies startet einen echten subprocess. @patch(\"<module>.subprocess.run\") hinzufügen oder die aufrufende Funktion patchen.", - "en": "{call} called in test '{test}' without @patch — this spawns a real subprocess. Add @patch(\"<module>.subprocess.run\") or patch the calling function.", - "pl": "{call} wywołane w teście '{test}' bez @patch — to uruchamia rzeczywisty subprocess. Dodaj @patch(\"<module>.subprocess.run\") lub patchuj wywołującą funkcję.", - "ru": "{call} вызвано в тесте '{test}' без @patch — это запускает реальный subprocess. Добавьте @patch(\"<module>.subprocess.run\") или patch вызывающую функцию.", - "zh": "{call} 在测试 '{test}' 中被调用但没有 @patch — 这会启动真实的子进程。请添加 @patch(\"<module>.subprocess.run\") 或 patch 调用函数。" - }, - "{env} is not set. Set it in your .env file or pass it as an environment variable.": { - "bg": "{env} не е зададен. Задайте го във вашия .env файл или го подайте като променлива на средата.", - "de": "{env} ist nicht gesetzt. Setzen Sie es in Ihrer .env-Datei oder übergeben Sie es als Umgebungsvariable.", - "en": "{env} is not set. Set it in your .env file or pass it as an environment variable.", - "pl": "{env} nie jest ustawiony. Ustaw go w pliku .env lub przekaż jako zmienną środowiskową.", - "ru": "{env} не задан. Установите его в файле .env или передайте как переменную окружения.", - "zh": "{env} 未设置。请在 .env 文件中设置或作为环境变量传递。" - }, - "{env} is not set. Set it in your .env file.": { - "bg": "{env} не е зададен. Задайте го във вашия .env файл.", - "de": "{env} ist nicht gesetzt. Setzen Sie es in Ihrer .env-Datei.", - "en": "{env} is not set. Set it in your .env file.", - "pl": "{env} nie jest ustawiony. Ustaw go w pliku .env.", - "ru": "{env} не задан. Установите его в файле .env.", - "zh": "{env} 未设置。请在 .env 文件中设置。" - }, - "{file} already exists. Use --force to overwrite.": { - "bg": "{file} already exists. Use --force to overwrite.", - "de": "{file} already exists. Use --force to overwrite.", - "en": "{file} already exists. Use --force to overwrite.", - "pl": "{file} już istnieje. Użyj --force, aby nadpisać.", - "ru": "{file} already exists. Use --force to overwrite.", - "zh": "{file} already exists. Use --force to overwrite." - }, - "{func} called in test '{test}' without @patch — this function {desc}. Add @patch(\"<module>.{func}\").": { - "bg": "{func} извикано в тест '{test}' без @patch — тази функция {desc}. Добавете @patch(\"<module>.{func}\").", - "de": "{func} in Test '{test}' ohne @patch aufgerufen — diese Funktion {desc}. @patch(\"<module>.{func}\") hinzufügen.", - "en": "{func} called in test '{test}' without @patch — this function {desc}. Add @patch(\"<module>.{func}\").", - "pl": "{func} wywołane w teście '{test}' bez @patch — ta funkcja {desc}. Dodaj @patch(\"<module>.{func}\").", - "ru": "{func} вызвано в тесте '{test}' без @patch — эта функция {desc}. Добавьте @patch(\"<module>.{func}\").", - "zh": "{func} 在测试 '{test}' 中被调用但没有 @patch — 此函数 {desc}。请添加 @patch(\"<module>.{func}\")。" - }, - "{level}: {tool} not found.{hint}": { - "bg": "{level}: {tool} не е намерен.{hint}", - "de": "{level}: {tool} nicht gefunden.{hint}", - "en": "{level}: {tool} not found.{hint}", - "pl": "{level}: {tool} nie znaleziono.{hint}", - "ru": "{level}: {tool} не найден.{hint}", - "zh": "{level}: 未找到 {tool}。{hint}" - }, - "{separator}": { - "bg": "{separator}", - "de": "{separator}", - "en": "{separator}", - "pl": "{separator}", - "ru": "{separator}", - "zh": "{separator}" + "zh": "\nTag → Commit alignment:", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" }, "\nTest isolation check FAILED: {count} violation(s) in {files} file(s).\n": { "bg": "\nTest isolation check FAILED: {count} violation(s) in {files} file(s).\n", @@ -3637,7 +215,339 @@ "en": "\nTest isolation check FAILED: {count} violation(s) in {files} file(s).\n", "pl": "\nTest isolation check FAILED: {count} violation(s) in {files} file(s).\n", "ru": "\nTest isolation check FAILED: {count} violation(s) in {files} file(s).\n", - "zh": "\nTest isolation check FAILED: {count} violation(s) in {files} file(s).\n" + "zh": "\nTest isolation check FAILED: {count} violation(s) in {files} file(s).\n", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "\nUntagged release commits:": { + "bg": "\nUntagged release commits:", + "de": "\nUntagged release commits:", + "en": "\nUntagged release commits:", + "pl": "\nCommity wydania bez tagu:", + "ru": "\nUntagged release commits:", + "zh": "\nUntagged release commits:", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "\nUser-facing changes ({count}):": { + "bg": "\nUser-facing changes ({count}):", + "de": "\nUser-facing changes ({count}):", + "en": "\nUser-facing changes ({count}):", + "pl": "\nZmiany widoczne dla użytkownika ({count}):", + "ru": "\nUser-facing changes ({count}):", + "zh": "\nUser-facing changes ({count}):", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "\nVerification passed — all wiki pages exist.": { + "bg": "", + "de": "", + "en": "\nVerification passed — all wiki pages exist.", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "\nVerifying wiki pages...": { + "bg": "", + "de": "", + "en": "\nVerifying wiki pages...", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "\nWorkflow-only changes ({count}):": { + "bg": "\nWorkflow-only changes ({count}):", + "de": "\nWorkflow-only changes ({count}):", + "en": "\nWorkflow-only changes ({count}):", + "pl": "\nZmiany tylko w workflow ({count}):", + "ru": "\nWorkflow-only changes ({count}):", + "zh": "\nWorkflow-only changes ({count}):", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "\n[check_test_coverage] Fix: add the missing test file(s) before committing.": { + "bg": "\n[check_test_coverage] Fix: add the missing test file(s) before committing.", + "de": "\n[check_test_coverage] Fix: add the missing test file(s) before committing.", + "en": "\n[check_test_coverage] Fix: add the missing test file(s) before committing.", + "pl": "\n[check_test_coverage] Fix: add the missing test file(s) before committing.", + "ru": "\n[check_test_coverage] Fix: add the missing test file(s) before committing.", + "zh": "\n[check_test_coverage] Fix: add the missing test file(s) before committing.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "\n[dry-run] Changelog:\n{changelog}": { + "bg": "\n[dry-run] Changelog:\n{changelog}", + "de": "\n[dry-run] Changelog:\n{changelog}", + "en": "\n[dry-run] Changelog:\n{changelog}", + "pl": "\n[dry-run] Changelog:\n{changelog}", + "ru": "\n[dry-run] Changelog:\n{changelog}", + "zh": "\n[dry-run] Changelog:\n{changelog}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "\n{label} files changed ({count}):": { + "bg": "\n{label} files changed ({count}):", + "de": "\n{label} files changed ({count}):", + "en": "\n{label} files changed ({count}):", + "pl": "\n{label} plików zmienionych ({count}):", + "ru": "\n{label} files changed ({count}):", + "zh": "\n{label} files changed ({count}):", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "\n{separator}": { + "bg": "\n{separator}", + "de": "\n{separator}", + "en": "\n{separator}", + "pl": "\n{separator}", + "ru": "\n{separator}", + "zh": "\n{separator}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "\n{tag} files ({count}):": { + "bg": "\n{tag} files ({count}):", + "de": "\n{tag} files ({count}):", + "en": "\n{tag} files ({count}):", + "pl": "\nPliki {tag} ({count}):", + "ru": "\n{tag} files ({count}):", + "zh": "\n{tag} files ({count}):", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " Could not fetch logs: {error}": { + "bg": " Could not fetch logs: {error}", + "de": " Could not fetch logs: {error}", + "en": " Could not fetch logs: {error}", + "pl": " Could not fetch logs: {error}", + "ru": " Could not fetch logs: {error}", + "zh": " Could not fetch logs: {error}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " pytest stderr (last 300 chars): {stderr}": { + "bg": " pytest stderr (last 300 chars): {stderr}", + "de": " pytest stderr (last 300 chars): {stderr}", + "en": " pytest stderr (last 300 chars): {stderr}", + "pl": " pytest stderr (last 300 chars): {stderr}", + "ru": " pytest stderr (last 300 chars): {stderr}", + "zh": " pytest stderr (last 300 chars): {stderr}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " pytest stdout (last 300 chars): {stdout}": { + "bg": " pytest stdout (last 300 chars): {stdout}", + "de": " pytest stdout (last 300 chars): {stdout}", + "en": " pytest stdout (last 300 chars): {stdout}", + "pl": " pytest stdout (last 300 chars): {stdout}", + "ru": " pytest stdout (last 300 chars): {stdout}", + "zh": " pytest stdout (last 300 chars): {stdout}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " stderr: {stderr}": { + "bg": " stderr: {stderr}", + "de": " stderr: {stderr}", + "en": " stderr: {stderr}", + "pl": " stderr: {stderr}", + "ru": " stderr: {stderr}", + "zh": " stderr: {stderr}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " - Auto-delete branch after merge: yes": { + "bg": " - Автоматично изтриване на клон след сливане: да", + "de": " - Branch nach Merge automatisch löschen: ja", + "en": " - Auto-delete branch after merge: yes", + "pl": " - Auto-usuwanie gałęzi po scaleniu: tak", + "ru": " - Автоудаление ветки после слияния: да", + "zh": " - 合并后自动删除分支: 是", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " - Block admin merge override: yes": { + "bg": " - Блокиране на admin merge override: да", + "de": " - Admin-Merge-Override blockieren: ja", + "en": " - Block admin merge override: yes", + "pl": " - Blokuj admin merge override: tak", + "ru": " - Блокировать admin merge override: да", + "zh": " - 阻止管理员合并覆盖:是", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " - Block outdated branches: yes": { + "bg": " - Блокиране на остарели клонове: да", + "de": " - Veraltete Branches blockieren: ja", + "en": " - Block outdated branches: yes", + "pl": " - Blokowanie nieaktualnych gałęzi: tak", + "ru": " - Блокировать устаревшие ветки: да", + "zh": " - 阻止过时分支: 是", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " - Block rejected reviews: yes": { + "bg": " - Блокиране на отхвърлени рецензии: да", + "de": " - Abgelehnte Reviews blockieren: ja", + "en": " - Block rejected reviews: yes", + "pl": " - Blokowanie odrzuconych recenzji: tak", + "ru": " - Блокировать отклонённые ревью: да", + "zh": " - 阻止被拒绝的审查: 是", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " - Direct pushes: BLOCKED (require PR, whitelisted users can push)": { + "bg": " - Direct pushes: BLOCKED (require PR, whitelisted users can push)", + "de": " - Direct pushes: BLOCKED (require PR, whitelisted users can push)", + "en": " - Direct pushes: BLOCKED (require PR, whitelisted users can push)", + "pl": " - Bezpośrednie push-e: ZABLOKOWANE (wymagają PR, użytkownicy z białej listy mogą pushować)", + "ru": " - Direct pushes: BLOCKED (require PR, whitelisted users can push)", + "zh": " - Direct pushes: BLOCKED (require PR, whitelisted users can push)", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " - Dismiss stale approvals: yes": { + "bg": " - Анулиране на остарели одобрения: да", + "de": " - Veraltete Genehmigungen ablehnen: ja", + "en": " - Dismiss stale approvals: yes", + "pl": " - Odrzucanie nieaktualnych zatwierdzeń: tak", + "ru": " - Отклонять устаревшие одобрения: да", + "zh": " - 忽略过时审批: 是", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " - Required approvals: {count}": { + "bg": " - Необходими одобрения: {count}", + "de": " - Erforderliche Genehmigungen: {count}", + "en": " - Required approvals: {count}", + "pl": " - Wymagane zatwierdzenia: {count}", + "ru": " - Требуемые одобрения: {count}", + "zh": " - 必需审批数: {count}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " - Required status checks: {checks}": { + "bg": " - Необходими проверки на състоянието: {checks}", + "de": " - Erforderliche Status-Checks: {checks}", + "en": " - Required status checks: {checks}", + "pl": " - Wymagane kontrole statusu: {checks}", + "ru": " - Требуемые проверки статуса: {checks}", + "zh": " - 必需状态检查: {checks}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " - {count} standard labels verified": { + "bg": " - {count} standard labels verified", + "de": " - {count} standard labels verified", + "en": " - {count} standard labels verified", + "pl": " - {count} standard labels verified", + "ru": " - {count} standard labels verified", + "zh": " - {count} standard labels verified", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " -> {dir}": { + "bg": " -> {dir}", + "de": " -> {dir}", + "en": " -> {dir}", + "pl": " -> {dir}", + "ru": " -> {dir}", + "zh": " -> {dir}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " ... and {n} more": { + "bg": "", + "de": "", + "en": " ... and {n} more", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " Auto-fixed trailing whitespace in {n} files": { + "bg": " Auto-fixed trailing whitespace in {n} files", + "de": " Auto-fixed trailing whitespace in {n} files", + "en": " Auto-fixed trailing whitespace in {n} files", + "pl": " Auto-fixed trailing whitespace in {n} files", + "ru": " Auto-fixed trailing whitespace in {n} files", + "zh": " Auto-fixed trailing whitespace in {n} files", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " Collecting code quality...": { + "bg": " Collecting code quality...", + "de": " Collecting code quality...", + "en": " Collecting code quality...", + "pl": " Collecting code quality...", + "ru": " Collecting code quality...", + "zh": " Collecting code quality...", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " Collecting coverage and tests...": { + "bg": " Collecting coverage and tests...", + "de": " Collecting coverage and tests...", + "en": " Collecting coverage and tests...", + "pl": " Collecting coverage and tests...", + "ru": " Collecting coverage and tests...", + "zh": " Collecting coverage and tests...", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " Collecting doc coverage...": { + "bg": " Collecting doc coverage...", + "de": " Collecting doc coverage...", + "en": " Collecting doc coverage...", + "pl": " Collecting doc coverage...", + "ru": " Collecting doc coverage...", + "zh": " Collecting doc coverage...", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " Collecting version...": { + "bg": " Collecting version...", + "de": " Collecting version...", + "en": " Collecting version...", + "pl": " Collecting version...", + "ru": " Collecting version...", + "zh": " Collecting version...", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " Deleted: {version}": { + "bg": " Deleted: {version}", + "de": " Deleted: {version}", + "en": " Deleted: {version}", + "pl": " Deleted: {version}", + "ru": " Deleted: {version}", + "zh": " Deleted: {version}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " FAIL: {title} — page not found in wiki!": { + "bg": "", + "de": "", + "en": " FAIL: {title} — page not found in wiki!", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " FAILED to delete: {version}": { + "bg": " FAILED to delete: {version}", + "de": " FAILED to delete: {version}", + "en": " FAILED to delete: {version}", + "pl": " FAILED to delete: {version}", + "ru": " FAILED to delete: {version}", + "zh": " FAILED to delete: {version}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" }, " Fix the PR title with:\n python3 -m devx.ci.fix_pr_title --repo {repo} --pr-number {pr}\n Or manually set the PR title to: '{expected}'": { "bg": " Fix the PR title with:\n python3 -m devx.ci.fix_pr_title --repo {repo} --pr-number {pr}\n Or manually set the PR title to: '{expected}'", @@ -3645,7 +555,419 @@ "en": " Fix the PR title with:\n python3 -m devx.ci.fix_pr_title --repo {repo} --pr-number {pr}\n Or manually set the PR title to: '{expected}'", "pl": " Fix the PR title with:\n python3 -m devx.ci.fix_pr_title --repo {repo} --pr-number {pr}\n Or manually set the PR title to: '{expected}'", "ru": " Fix the PR title with:\n python3 -m devx.ci.fix_pr_title --repo {repo} --pr-number {pr}\n Or manually set the PR title to: '{expected}'", - "zh": " Fix the PR title with:\n python3 -m devx.ci.fix_pr_title --repo {repo} --pr-number {pr}\n Or manually set the PR title to: '{expected}'" + "zh": " Fix the PR title with:\n python3 -m devx.ci.fix_pr_title --repo {repo} --pr-number {pr}\n Or manually set the PR title to: '{expected}'", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " Fixed {fixes} version ref(s) in {file}": { + "bg": "", + "de": "", + "en": " Fixed {fixes} version ref(s) in {file}", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " Generated: {path}": { + "bg": " Generated: {path}", + "de": " Generated: {path}", + "en": " Generated: {path}", + "pl": " Generated: {path}", + "ru": " Generated: {path}", + "zh": " Generated: {path}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " MISSING: {cmd}": { + "bg": " MISSING: {cmd}", + "de": " MISSING: {cmd}", + "en": " MISSING: {cmd}", + "pl": " MISSING: {cmd}", + "ru": " MISSING: {cmd}", + "zh": " MISSING: {cmd}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " MISSING: {module}": { + "bg": " MISSING: {module}", + "de": " MISSING: {module}", + "en": " MISSING: {module}", + "pl": " BRAK: {module}", + "ru": " MISSING: {module}", + "zh": " MISSING: {module}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " MISSING: {script}": { + "bg": " MISSING: {script}", + "de": " MISSING: {script}", + "en": " MISSING: {script}", + "pl": " BRAK: {script}", + "ru": " MISSING: {script}", + "zh": " MISSING: {script}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " OK: {cmd}": { + "bg": " OK: {cmd}", + "de": " OK: {cmd}", + "en": " OK: {cmd}", + "pl": " OK: {cmd}", + "ru": " OK: {cmd}", + "zh": " OK: {cmd}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " OK: {module}": { + "bg": " OK: {module}", + "de": " OK: {module}", + "en": " OK: {module}", + "pl": " OK: {module}", + "ru": " OK: {module}", + "zh": " OK: {module}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " OK: {script}": { + "bg": " OK: {script}", + "de": " OK: {script}", + "en": " OK: {script}", + "pl": " OK: {script}", + "ru": " OK: {script}", + "zh": " OK: {script}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " OK: {title}": { + "bg": "", + "de": "", + "en": " OK: {title}", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " Package: {pkg}": { + "bg": " Package: {pkg}", + "de": " Package: {pkg}", + "en": " Package: {pkg}", + "pl": " Package: {pkg}", + "ru": " Package: {pkg}", + "zh": " Package: {pkg}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " Pruned: {file} (not in mapping)": { + "bg": "", + "de": "", + "en": " Pruned: {file} (not in mapping)", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " Quality checks: {checks}": { + "bg": " Quality checks: {checks}", + "de": " Quality checks: {checks}", + "en": " Quality checks: {checks}", + "pl": " Quality checks: {checks}", + "ru": " Quality checks: {checks}", + "zh": " Quality checks: {checks}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " Repo root: {root}": { + "bg": " Repo root: {root}", + "de": " Repo root: {root}", + "en": " Repo root: {root}", + "pl": " Repo root: {root}", + "ru": " Repo root: {root}", + "zh": " Repo root: {root}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " Run 'make install-checkmake' to install the Makefile linter.": { + "bg": " Изпълнете 'make install-checkmake' за инсталиране на Makefile линтера.", + "de": " Führen Sie 'make install-checkmake' aus, um den Makefile-Linter zu installieren.", + "en": " Run 'make install-checkmake' to install the Makefile linter.", + "pl": " Uruchom 'make install-checkmake', aby zainstalować linter Makefile.", + "ru": " Выполните 'make install-checkmake' для установки линтера Makefile.", + "zh": " 运行 'make install-checkmake' 来安装 Makefile 检查器。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " Synced: {title} → {file}": { + "bg": "", + "de": "", + "en": " Synced: {title} → {file}", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " Test paths: {testpaths}": { + "bg": " Test paths: {testpaths}", + "de": " Test paths: {testpaths}", + "en": " Test paths: {testpaths}", + "pl": " Test paths: {testpaths}", + "ru": " Test paths: {testpaths}", + "zh": " Test paths: {testpaths}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " WARN: Mapped file {file} is empty, skipping": { + "bg": "", + "de": "", + "en": " WARN: Mapped file {file} is empty, skipping", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " WARN: Mapped file {file} not found, skipping": { + "bg": "", + "de": "", + "en": " WARN: Mapped file {file} not found, skipping", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " WARNING: Could not extract coverage from pytest output (rc={rc})": { + "bg": " WARNING: Could not extract coverage from pytest output (rc={rc})", + "de": " WARNING: Could not extract coverage from pytest output (rc={rc})", + "en": " WARNING: Could not extract coverage from pytest output (rc={rc})", + "pl": " WARNING: Could not extract coverage from pytest output (rc={rc})", + "ru": " WARNING: Could not extract coverage from pytest output (rc={rc})", + "zh": " WARNING: Could not extract coverage from pytest output (rc={rc})", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " WARNING: Could not extract doc coverage (rc={rc})": { + "bg": " WARNING: Could not extract doc coverage (rc={rc})", + "de": " WARNING: Could not extract doc coverage (rc={rc})", + "en": " WARNING: Could not extract doc coverage (rc={rc})", + "pl": " WARNING: Could not extract doc coverage (rc={rc})", + "ru": " WARNING: Could not extract doc coverage (rc={rc})", + "zh": " WARNING: Could not extract doc coverage (rc={rc})", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " WARNING: Could not extract test count from pytest output (rc={rc})": { + "bg": " WARNING: Could not extract test count from pytest output (rc={rc})", + "de": " WARNING: Could not extract test count from pytest output (rc={rc})", + "en": " WARNING: Could not extract test count from pytest output (rc={rc})", + "pl": " WARNING: Could not extract test count from pytest output (rc={rc})", + "ru": " WARNING: Could not extract test count from pytest output (rc={rc})", + "zh": " WARNING: Could not extract test count from pytest output (rc={rc})", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " WARNING: No Python package found under src/ — version badge will show 'unknown'": { + "bg": " WARNING: No Python package found under src/ — version badge will show 'unknown'", + "de": " WARNING: No Python package found under src/ — version badge will show 'unknown'", + "en": " WARNING: No Python package found under src/ — version badge will show 'unknown'", + "pl": " WARNING: No Python package found under src/ — version badge will show 'unknown'", + "ru": " WARNING: No Python package found under src/ — version badge will show 'unknown'", + "zh": " WARNING: No Python package found under src/ — version badge will show 'unknown'", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " WARNING: No __version__ found in {init_file} — version badge will show 'unknown'": { + "bg": " WARNING: No __version__ found in {init_file} — version badge will show 'unknown'", + "de": " WARNING: No __version__ found in {init_file} — version badge will show 'unknown'", + "en": " WARNING: No __version__ found in {init_file} — version badge will show 'unknown'", + "pl": " WARNING: No __version__ found in {init_file} — version badge will show 'unknown'", + "ru": " WARNING: No __version__ found in {init_file} — version badge will show 'unknown'", + "zh": " WARNING: No __version__ found in {init_file} — version badge will show 'unknown'", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " WARNING: No coverage target detected (no src/ package, no --cov in pyproject.toml)": { + "bg": " WARNING: No coverage target detected (no src/ package, no --cov in pyproject.toml)", + "de": " WARNING: No coverage target detected (no src/ package, no --cov in pyproject.toml)", + "en": " WARNING: No coverage target detected (no src/ package, no --cov in pyproject.toml)", + "pl": " WARNING: No coverage target detected (no src/ package, no --cov in pyproject.toml)", + "ru": " WARNING: No coverage target detected (no src/ package, no --cov in pyproject.toml)", + "zh": " WARNING: No coverage target detected (no src/ package, no --cov in pyproject.toml)", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " WARNING: {init_file} not found — version badge will show 'unknown'": { + "bg": " WARNING: {init_file} not found — version badge will show 'unknown'", + "de": " WARNING: {init_file} not found — version badge will show 'unknown'", + "en": " WARNING: {init_file} not found — version badge will show 'unknown'", + "pl": " WARNING: {init_file} not found — version badge will show 'unknown'", + "ru": " WARNING: {init_file} not found — version badge will show 'unknown'", + "zh": " WARNING: {init_file} not found — version badge will show 'unknown'", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " WARNING: {name} failed (rc={rc})": { + "bg": " WARNING: {name} failed (rc={rc})", + "de": " WARNING: {name} failed (rc={rc})", + "en": " WARNING: {name} failed (rc={rc})", + "pl": " WARNING: {name} failed (rc={rc})", + "ru": " WARNING: {name} failed (rc={rc})", + "zh": " WARNING: {name} failed (rc={rc})", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " WARNING: {name} not installed — skipping (counted as pass)": { + "bg": " WARNING: {name} not installed — skipping (counted as pass)", + "de": " WARNING: {name} not installed — skipping (counted as pass)", + "en": " WARNING: {name} not installed — skipping (counted as pass)", + "pl": " WARNING: {name} not installed — skipping (counted as pass)", + "ru": " WARNING: {name} not installed — skipping (counted as pass)", + "zh": " WARNING: {name} not installed — skipping (counted as pass)", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " [dry-run] Would delete: {version}": { + "bg": " [dry-run] Would delete: {version}", + "de": " [dry-run] Would delete: {version}", + "en": " [dry-run] Would delete: {version}", + "pl": " [dry-run] Would delete: {version}", + "ru": " [dry-run] Would delete: {version}", + "zh": " [dry-run] Would delete: {version}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " {name}: {label}={message} ({color})": { + "bg": " {name}: {label}={message} ({color})", + "de": " {name}: {label}={message} ({color})", + "en": " {name}: {label}={message} ({color})", + "pl": " {name}: {label}={message} ({color})", + "ru": " {name}: {label}={message} ({color})", + "zh": " {name}: {label}={message} ({color})", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " {n} long lines found (warnings only)": { + "bg": "", + "de": "", + "en": " {n} long lines found (warnings only)", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " {n} orphan docs found (warnings only)": { + "bg": "", + "de": "", + "en": " {n} orphan docs found (warnings only)", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " {n} stale docs found (warnings only)": { + "bg": " {n} stale docs found (warnings only)", + "de": " {n} stale docs found (warnings only)", + "en": " {n} stale docs found (warnings only)", + "pl": " {n} stale docs found (warnings only)", + "ru": " {n} stale docs found (warnings only)", + "zh": " {n} stale docs found (warnings only)", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " {tool}: found at {path}": { + "bg": " {tool}: намерен на {path}", + "de": " {tool}: gefunden unter {path}", + "en": " {tool}: found at {path}", + "pl": " {tool}: znaleziono w {path}", + "ru": " {tool}: найден в {path}", + "zh": " {tool}: 在 {path} 找到", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + " {version} (created: {created})": { + "bg": " {version} (created: {created})", + "de": " {version} (created: {created})", + "en": " {version} (created: {created})", + "pl": " {version} (created: {created})", + "ru": " {version} (created: {created})", + "zh": " {version} (created: {created})", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "--push requires --registry": { + "bg": "--push requires --registry", + "de": "--push requires --registry", + "en": "--push requires --registry", + "pl": "--push requires --registry", + "ru": "--push requires --registry", + "zh": "--push requires --registry", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "--skip-build: skipping package build and PyPI publish.": { + "bg": "--skip-build: skipping package build and PyPI publish.", + "de": "--skip-build: skipping package build and PyPI publish.", + "en": "--skip-build: skipping package build and PyPI publish.", + "pl": "--skip-build: pomijanie budowania pakietu i publikacji PyPI.", + "ru": "--skip-build: skipping package build and PyPI publish.", + "zh": "--skip-build: skipping package build and PyPI publish.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "=== Release Alignment Verification ===\n": { + "bg": "=== Release Alignment Verification ===\n", + "de": "=== Release Alignment Verification ===\n", + "en": "=== Release Alignment Verification ===\n", + "pl": "=== Weryfikacja zgodności wydań ===\n", + "ru": "=== Release Alignment Verification ===\n", + "zh": "=== Release Alignment Verification ===\n", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "API poll warning: {exc}": { + "bg": "API poll warning: {exc}", + "de": "API poll warning: {exc}", + "en": "API poll warning: {exc}", + "pl": "Ostrzeżenie sondowania API: {exc}", + "ru": "API poll warning: {exc}", + "zh": "API poll warning: {exc}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Acceptance Criteria has {count} unchecked item(s). All AC items must be checked (- [x]) before merge.": { + "bg": "Acceptance Criteria has {count} unchecked item(s). All AC items must be checked (- [x]) before merge.", + "de": "Acceptance Criteria has {count} unchecked item(s). All AC items must be checked (- [x]) before merge.", + "en": "Acceptance Criteria has {count} unchecked item(s). All AC items must be checked (- [x]) before merge.", + "pl": "Acceptance Criteria has {count} unchecked item(s). All AC items must be checked (- [x]) before merge.", + "ru": "Acceptance Criteria has {count} unchecked item(s). All AC items must be checked (- [x]) before merge.", + "zh": "Acceptance Criteria has {count} unchecked item(s). All AC items must be checked (- [x]) before merge.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Acceptance Criteria section has no checklist items. Add at least one '- [ ] item'.": { + "bg": "Acceptance Criteria section has no checklist items. Add at least one '- [ ] item'.", + "de": "Acceptance Criteria section has no checklist items. Add at least one '- [ ] item'.", + "en": "Acceptance Criteria section has no checklist items. Add at least one '- [ ] item'.", + "pl": "Acceptance Criteria section has no checklist items. Add at least one '- [ ] item'.", + "ru": "Acceptance Criteria section has no checklist items. Add at least one '- [ ] item'.", + "zh": "Acceptance Criteria section has no checklist items. Add at least one '- [ ] item'.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Action to perform": { + "bg": "Action to perform", + "de": "Action to perform", + "en": "Action to perform", + "pl": "Action to perform", + "ru": "Action to perform", + "zh": "Action to perform", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" }, "Add @patch(\"subprocess.run\") or patch the calling function to fix this.": { "bg": "Add @patch(\"subprocess.run\") or patch the calling function to fix this.", @@ -3653,7 +975,219 @@ "en": "Add @patch(\"subprocess.run\") or patch the calling function to fix this.", "pl": "Add @patch(\"subprocess.run\") or patch the calling function to fix this.", "ru": "Add @patch(\"subprocess.run\") or patch the calling function to fix this.", - "zh": "Add @patch(\"subprocess.run\") or patch the calling function to fix this." + "zh": "Add @patch(\"subprocess.run\") or patch the calling function to fix this.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Added label '{label}' to PR #{pr}.": { + "bg": "Added label '{label}' to PR #{pr}.", + "de": "Added label '{label}' to PR #{pr}.", + "en": "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}.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Additional directory to scan (default: scripts, tests). Can be repeated.": { + "bg": "Additional directory to scan (default: scripts, tests). Can be repeated.", + "de": "Additional directory to scan (default: scripts, tests). Can be repeated.", + "en": "Additional directory to scan (default: scripts, tests). Can be repeated.", + "pl": "Additional directory to scan (default: scripts, tests). Can be repeated.", + "ru": "Additional directory to scan (default: scripts, tests). Can be repeated.", + "zh": "Additional directory to scan (default: scripts, tests). Can be repeated.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Additional excluded patterns (in addition to defaults)": { + "bg": "Additional excluded patterns (in addition to defaults)", + "de": "Additional excluded patterns (in addition to defaults)", + "en": "Additional excluded patterns (in addition to defaults)", + "pl": "Additional excluded patterns (in addition to defaults)", + "ru": "Additional excluded patterns (in addition to defaults)", + "zh": "Additional excluded patterns (in addition to defaults)", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Allow empty tag (PR mode where SHA is concrete).": { + "bg": "Позволи празен таг (PR режим, където SHA е конкретен).", + "de": "Leeren Tag zulassen (PR-Modus, in dem SHA konkret ist).", + "en": "Allow empty tag (PR mode where SHA is concrete).", + "pl": "Zezwalaj na pusty tag (tryb PR, w którym SHA jest konkretne).", + "ru": "Разрешить пустой тег (режим PR, где SHA конкретен).", + "zh": "允许空标签(SHA 为具体值的 PR 模式)。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Allow missing spec (warn only, don't fail)": { + "bg": "Allow missing spec (warn only, don't fail)", + "de": "Allow missing spec (warn only, don't fail)", + "en": "Allow missing spec (warn only, don't fail)", + "pl": "Allow missing spec (warn only, don't fail)", + "ru": "Allow missing spec (warn only, don't fail)", + "zh": "Allow missing spec (warn only, don't fail)", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Another runner failed. Stopping this runner early.": { + "bg": "Друг runner се провали. Спиране на този runner по-рано.", + "de": "Ein anderer Runner ist fehlgeschlagen. Dieser Runner wird vorzeitig gestoppt.", + "en": "Another runner failed. Stopping this runner early.", + "pl": "Inny runner zakończył się niepowodzeniem. Wczesne zatrzymanie tego runnera.", + "ru": "Другой runner завершился с ошибкой. Останавливаю этот runner досрочно.", + "zh": "另一个 runner 失败。提前停止此 runner。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Assigned {count} files to runner {runner_index}": { + "bg": "Assigned {count} files to runner {runner_index}", + "de": "Assigned {count} files to runner {runner_index}", + "en": "Assigned {count} files to runner {runner_index}", + "pl": "Assigned {count} files to runner {runner_index}", + "ru": "Assigned {count} files to runner {runner_index}", + "zh": "Assigned {count} files to runner {runner_index}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Assigned {count} items to runner {runner_index}: {encoded}": { + "bg": "Assigned {count} items to runner {runner_index}: {encoded}", + "de": "Assigned {count} items to runner {runner_index}: {encoded}", + "en": "Assigned {count} items to runner {runner_index}: {encoded}", + "pl": "Assigned {count} items to runner {runner_index}: {encoded}", + "ru": "Assigned {count} items to runner {runner_index}: {encoded}", + "zh": "Assigned {count} items to runner {runner_index}: {encoded}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Auto-rebase failed with HTTP {status}: {message}\nRebase manually:\n git fetch origin master && git rebase origin/master && git push --force-with-lease\nThen re-add the ready-to-merge label.": { + "bg": "Auto-rebase failed with HTTP {status}: {message}\nRebase manually:\n git fetch origin master && git rebase origin/master && git push --force-with-lease\nThen re-add the ready-to-merge label.", + "de": "Auto-rebase failed with HTTP {status}: {message}\nRebase manually:\n git fetch origin master && git rebase origin/master && git push --force-with-lease\nThen re-add the ready-to-merge label.", + "en": "Auto-rebase failed with HTTP {status}: {message}\nRebase manually:\n git fetch origin master && git rebase origin/master && git push --force-with-lease\nThen re-add the ready-to-merge label.", + "pl": "Auto-rebase failed with HTTP {status}: {message}\nRebase manually:\n git fetch origin master && git rebase origin/master && git push --force-with-lease\nThen re-add the ready-to-merge label.", + "ru": "Auto-rebase failed with HTTP {status}: {message}\nRebase manually:\n git fetch origin master && git rebase origin/master && git push --force-with-lease\nThen re-add the ready-to-merge label.", + "zh": "Auto-rebase failed with HTTP {status}: {message}\nRebase manually:\n git fetch origin master && git rebase origin/master && git push --force-with-lease\nThen re-add the ready-to-merge label.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Automated CI commit (badge) — skipping post-merge jobs.": { + "bg": "Automated CI commit (badge) — skipping post-merge jobs.", + "de": "Automated CI commit (badge) — skipping post-merge jobs.", + "en": "Automated CI commit (badge) — skipping post-merge jobs.", + "pl": "Automated CI commit (badge) — skipping post-merge jobs.", + "ru": "Automated CI commit (badge) — skipping post-merge jobs.", + "zh": "Automated CI commit (badge) — skipping post-merge jobs.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Badge push attempt {attempt}/{retries} failed — retrying: {error}": { + "bg": "Badge push attempt {attempt}/{retries} failed — retrying: {error}", + "de": "Badge push attempt {attempt}/{retries} failed — retrying: {error}", + "en": "Badge push attempt {attempt}/{retries} failed — retrying: {error}", + "pl": "Badge push attempt {attempt}/{retries} failed — retrying: {error}", + "ru": "Badge push attempt {attempt}/{retries} failed — retrying: {error}", + "zh": "Badge push attempt {attempt}/{retries} failed — retrying: {error}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Badge push failed after {retries} attempts: {error}": { + "bg": "Badge push failed after {retries} attempts: {error}", + "de": "Badge push failed after {retries} attempts: {error}", + "en": "Badge push failed after {retries} attempts: {error}", + "pl": "Badge push failed after {retries} attempts: {error}", + "ru": "Badge push failed after {retries} attempts: {error}", + "zh": "Badge push failed after {retries} attempts: {error}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Badges commit SHA: {sha}": { + "bg": "Badges commit SHA: {sha}", + "de": "Badges commit SHA: {sha}", + "en": "Badges commit SHA: {sha}", + "pl": "Badges commit SHA: {sha}", + "ru": "Badges commit SHA: {sha}", + "zh": "Badges commit SHA: {sha}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Badges pushed to badges branch": { + "bg": "Badges pushed to badges branch", + "de": "Badges pushed to badges branch", + "en": "Badges pushed to badges branch", + "pl": "Badges pushed to badges branch", + "ru": "Badges pushed to badges branch", + "zh": "Badges pushed to badges branch", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Base ref for diff": { + "bg": "Base ref for diff", + "de": "Base ref for diff", + "en": "Base ref for diff", + "pl": "Base ref for diff", + "ru": "Base ref for diff", + "zh": "Base ref for diff", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Branch '{branch}' does not contain a task ID.\n Expected format: {prefix}-N-short-description": { + "bg": "Клон '{branch}' не съдържа ID на задача.\n Очакван формат: {prefix}-N-кратко-описание", + "de": "Branch '{branch}' enthält keine Task-ID.\n Erwartetes Format: {prefix}-N-kurz-beschreibung", + "en": "Branch '{branch}' does not contain a task ID.\n Expected format: {prefix}-N-short-description", + "pl": "Gałąź '{branch}' nie zawiera ID zadania.\n Oczekiwany format: {prefix}-N-krótki-opis", + "ru": "Ветка '{branch}' не содержит ID задачи.\n Ожидаемый формат: {prefix}-N-краткое-описание", + "zh": "分支 '{branch}' 不包含任务 ID。\n 预期格式: {prefix}-N-简短描述", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Branch '{branch}' does not contain a task ID.\n Expected format: {prefix}-N-short-description\n Example: {prefix}-42-add-feature\n Fix: rename the branch or create a Vikunja task first:\n python -m devx.tools.create_task --title \"Task title\"": { + "bg": "Клон '{branch}' не съдържа ID на задача.\n Очакван формат: {prefix}-N-кратко-описание\n Пример: {prefix}-42-add-feature\n Решение: преименувайте клона или създайте Vikunja задача:\n python -m devx.tools.create_task --title \"Заглавие на задача\"", + "de": "Branch '{branch}' enthält keine Task-ID.\n Erwartetes Format: {prefix}-N-kurz-beschreibung\n Beispiel: {prefix}-42-add-feature\n Fix: Branch umbenennen oder Vikunja-Task erstellen:\n python -m devx.tools.create_task --title \"Task-Titel\"", + "en": "Branch '{branch}' does not contain a task ID.\n Expected format: {prefix}-N-short-description\n Example: {prefix}-42-add-feature\n Fix: rename the branch or create a Vikunja task first:\n python -m devx.tools.create_task --title \"Task title\"", + "pl": "Gałąź '{branch}' nie zawiera ID zadania.\n Oczekiwany format: {prefix}-N-krótki-opis\n Przykład: {prefix}-42-add-feature\n Naprawa: zmień nazwę gałęzi lub utwórz zadanie Vikunja:\n python -m devx.tools.create_task --title \"Tytuł zadania\"", + "ru": "Ветка '{branch}' не содержит ID задачи.\n Ожидаемый формат: {prefix}-N-краткое-описание\n Пример: {prefix}-42-add-feature\n Исправление: переименуйте ветку или создайте задачу Vikunja:\n python -m devx.tools.create_task --title \"Заголовок задачи\"", + "zh": "分支 '{branch}' 不包含任务 ID。\n 预期格式: {prefix}-N-简短描述\n 示例: {prefix}-42-add-feature\n 修复: 重命名分支或先创建 Vikunja 任务:\n python -m devx.tools.create_task --title \"任务标题\"", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Branch is already up-to-date with origin/master.": { + "bg": "Branch is already up-to-date with origin/master.", + "de": "Branch is already up-to-date with origin/master.", + "en": "Branch is already up-to-date with origin/master.", + "pl": "Branch is already up-to-date with origin/master.", + "ru": "Branch is already up-to-date with origin/master.", + "zh": "Branch is already up-to-date with origin/master.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Branch is behind master. Auto-rebasing via Gitea API...\nA new CI run will start automatically after the rebase.\nThe next auto-merge attempt will merge this PR.": { + "bg": "Branch is behind master. Auto-rebasing via Gitea API...\nA new CI run will start automatically after the rebase.\nThe next auto-merge attempt will merge this PR.", + "de": "Branch is behind master. Auto-rebasing via Gitea API...\nA new CI run will start automatically after the rebase.\nThe next auto-merge attempt will merge this PR.", + "en": "Branch is behind master. Auto-rebasing via Gitea API...\nA new CI run will start automatically after the rebase.\nThe next auto-merge attempt will merge this PR.", + "pl": "Branch is behind master. Auto-rebasing via Gitea API...\nA new CI run will start automatically after the rebase.\nThe next auto-merge attempt will merge this PR.", + "ru": "Branch is behind master. Auto-rebasing via Gitea API...\nA new CI run will start automatically after the rebase.\nThe next auto-merge attempt will merge this PR.", + "zh": "Branch is behind master. Auto-rebasing via Gitea API...\nA new CI run will start automatically after the rebase.\nThe next auto-merge attempt will merge this PR.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Branch is behind origin/master. Rebase first: git fetch origin master && git rebase origin/master": { + "bg": "Branch is behind origin/master. Rebase first: git fetch origin master && git rebase origin/master", + "de": "Branch is behind origin/master. Rebase first: git fetch origin master && git rebase origin/master", + "en": "Branch is behind origin/master. Rebase first: git fetch origin master && git rebase origin/master", + "pl": "Branch is behind origin/master. Rebase first: git fetch origin master && git rebase origin/master", + "ru": "Branch is behind origin/master. Rebase first: git fetch origin master && git rebase origin/master", + "zh": "Branch is behind origin/master. Rebase first: git fetch origin master && git rebase origin/master", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Branch is {count} commit(s) behind master. Rebasing...": { + "bg": "Branch is {count} commit(s) behind master. Rebasing...", + "de": "Branch is {count} commit(s) behind master. Rebasing...", + "en": "Branch is {count} commit(s) behind master. Rebasing...", + "pl": "Branch is {count} commit(s) behind master. Rebasing...", + "ru": "Branch is {count} commit(s) behind master. Rebasing...", + "zh": "Branch is {count} commit(s) behind master. Rebasing...", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" }, "Branch name (auto-fetched from PR if not given)": { "bg": "Branch name (auto-fetched from PR if not given)", @@ -3661,7 +1195,99 @@ "en": "Branch name (auto-fetched from PR if not given)", "pl": "Branch name (auto-fetched from PR if not given)", "ru": "Branch name (auto-fetched from PR if not given)", - "zh": "Branch name (auto-fetched from PR if not given)" + "zh": "Branch name (auto-fetched from PR if not given)", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Branch name (e.g., DEVX-256-fix-foo)": { + "bg": "Branch name (e.g., DEVX-256-fix-foo)", + "de": "Branch name (e.g., DEVX-256-fix-foo)", + "en": "Branch name (e.g., DEVX-256-fix-foo)", + "pl": "Branch name (e.g., DEVX-256-fix-foo)", + "ru": "Branch name (e.g., DEVX-256-fix-foo)", + "zh": "Branch name (e.g., DEVX-256-fix-foo)", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Branch name (e.g., OBL-INFRA-531-fix-foo)": { + "bg": "Branch name (e.g., OBL-INFRA-531-fix-foo)", + "de": "Branch name (e.g., OBL-INFRA-531-fix-foo)", + "en": "Branch name (e.g., OBL-INFRA-531-fix-foo)", + "pl": "Branch name (e.g., OBL-INFRA-531-fix-foo)", + "ru": "Branch name (e.g., OBL-INFRA-531-fix-foo)", + "zh": "Branch name (e.g., OBL-INFRA-531-fix-foo)", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Branch name must contain a task ID.": { + "bg": "Branch name must contain a task ID.", + "de": "Branch name must contain a task ID.", + "en": "Branch name must contain a task ID.", + "pl": "Branch name must contain a task ID.", + "ru": "Branch name must contain a task ID.", + "zh": "Branch name must contain a task ID.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Build failed for {name}": { + "bg": "Build failed for {name}", + "de": "Build failed for {name}", + "en": "Build failed for {name}", + "pl": "Build failed for {name}", + "ru": "Build failed for {name}", + "zh": "Build failed for {name}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Bumping version: {current} -> v{new_version}": { + "bg": "Bumping version: {current} -> v{new_version}", + "de": "Bumping version: {current} -> v{new_version}", + "en": "Bumping version: {current} -> v{new_version}", + "pl": "Zmiana wersji: {current} -> v{new_version}", + "ru": "Bumping version: {current} -> v{new_version}", + "zh": "Bumping version: {current} -> v{new_version}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "CI checks did not complete within timeout.": { + "bg": "CI checks did not complete within timeout.", + "de": "CI checks did not complete within timeout.", + "en": "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.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "CI checks failed.": { + "bg": "CI checks failed.", + "de": "CI checks failed.", + "en": "CI checks failed.", + "pl": "CI checks failed.", + "ru": "CI checks failed.", + "zh": "CI checks failed.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "CI run ID (for set-failed/set-passed)": { + "bg": "CI run ID (for set-failed/set-passed)", + "de": "CI run ID (for set-failed/set-passed)", + "en": "CI run ID (for set-failed/set-passed)", + "pl": "CI run ID (for set-failed/set-passed)", + "ru": "CI run ID (for set-failed/set-passed)", + "zh": "CI run ID (for set-failed/set-passed)", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "CI run ID that triggered the publish": { + "bg": "CI run ID that triggered the publish", + "de": "CI run ID that triggered the publish", + "en": "CI run ID that triggered the publish", + "pl": "CI run ID that triggered the publish", + "ru": "CI run ID that triggered the publish", + "zh": "CI run ID that triggered the publish", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" }, "CI_GITEA_API_TOKEN not set: {error}": { "bg": "CI_GITEA_API_TOKEN not set: {error}", @@ -3669,7 +1295,239 @@ "en": "CI_GITEA_API_TOKEN not set: {error}", "pl": "CI_GITEA_API_TOKEN not set: {error}", "ru": "CI_GITEA_API_TOKEN not set: {error}", - "zh": "CI_GITEA_API_TOKEN not set: {error}" + "zh": "CI_GITEA_API_TOKEN not set: {error}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "CI_GITEA_TOKEN environment variable required": { + "bg": "CI_GITEA_TOKEN environment variable required", + "de": "CI_GITEA_TOKEN environment variable required", + "en": "CI_GITEA_TOKEN environment variable required", + "pl": "CI_GITEA_TOKEN environment variable required", + "ru": "CI_GITEA_TOKEN environment variable required", + "zh": "CI_GITEA_TOKEN environment variable required", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "CI_GITEA_TOKEN is not set.": { + "bg": "CI_GITEA_TOKEN is not set.", + "de": "CI_GITEA_TOKEN is not set.", + "en": "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.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "CI_GITEA_TOKEN is not set. Add it to .env or export it.": { + "bg": "CI_GITEA_TOKEN is not set. Add it to .env or export it.", + "de": "CI_GITEA_TOKEN is not set. Add it to .env or export it.", + "en": "CI_GITEA_TOKEN is not set. Add it to .env or export it.", + "pl": "CI_GITEA_TOKEN is not set. Add it to .env or export it.", + "ru": "CI_GITEA_TOKEN is not set. Add it to .env or export it.", + "zh": "CI_GITEA_TOKEN is not set. Add it to .env or export it.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "CI_GITEA_TOKEN is not set. Required to create a PR.": { + "bg": "CI_GITEA_TOKEN не е зададен. Необходим за създаване на PR.", + "de": "CI_GITEA_TOKEN nicht gesetzt. Erforderlich zum Erstellen eines PR.", + "en": "CI_GITEA_TOKEN is not set. Required to create a PR.", + "pl": "CI_GITEA_TOKEN nie jest ustawiony. Wymagany do utworzenia PR.", + "ru": "CI_GITEA_TOKEN не установлен. Требуется для создания PR.", + "zh": "CI_GITEA_TOKEN 未设置。创建 PR 所需。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "CI_GITEA_TOKEN not set — skipping login configuration.": { + "bg": "CI_GITEA_TOKEN not set — skipping login configuration.", + "de": "CI_GITEA_TOKEN not set — skipping login configuration.", + "en": "CI_GITEA_TOKEN not set — skipping login configuration.", + "pl": "CI_GITEA_TOKEN not set — skipping login configuration.", + "ru": "CI_GITEA_TOKEN not set — skipping login configuration.", + "zh": "CI_GITEA_TOKEN not set — skipping login configuration.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Cannot read __version__ from src/{pkg}/__init__.py — skipping.": { + "bg": "", + "de": "", + "en": "Cannot read __version__ from src/{pkg}/__init__.py — skipping.", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Cannot rebase: not on a branch (detached HEAD).": { + "bg": "Cannot rebase: not on a branch (detached HEAD).", + "de": "Cannot rebase: not on a branch (detached HEAD).", + "en": "Cannot rebase: not on a branch (detached HEAD).", + "pl": "Cannot rebase: not on a branch (detached HEAD).", + "ru": "Cannot rebase: not on a branch (detached HEAD).", + "zh": "Cannot rebase: not on a branch (detached HEAD).", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Checking CLI command documentation...": { + "bg": "Checking CLI command documentation...", + "de": "Checking CLI command documentation...", + "en": "Checking CLI command documentation...", + "pl": "Sprawdzanie dokumentacji poleceń CLI...", + "ru": "Checking CLI command documentation...", + "zh": "Checking CLI command documentation...", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Checking code block languages...": { + "bg": "", + "de": "", + "en": "Checking code block languages...", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Checking docs structure...": { + "bg": "Checking docs structure...", + "de": "Checking docs structure...", + "en": "Checking docs structure...", + "pl": "Checking docs structure...", + "ru": "Checking docs structure...", + "zh": "Checking docs structure...", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Checking duplicate headings...": { + "bg": "Checking duplicate headings...", + "de": "Checking duplicate headings...", + "en": "Checking duplicate headings...", + "pl": "Checking duplicate headings...", + "ru": "Checking duplicate headings...", + "zh": "Checking duplicate headings...", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Checking for TODO/FIXME markers...": { + "bg": "Checking for TODO/FIXME markers...", + "de": "Checking for TODO/FIXME markers...", + "en": "Checking for TODO/FIXME markers...", + "pl": "Checking for TODO/FIXME markers...", + "ru": "Checking for TODO/FIXME markers...", + "zh": "Checking for TODO/FIXME markers...", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Checking for orphan docs...": { + "bg": "", + "de": "", + "en": "Checking for orphan docs...", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Checking for stale docs...": { + "bg": "Checking for stale docs...", + "de": "Checking for stale docs...", + "en": "Checking for stale docs...", + "pl": "Checking for stale docs...", + "ru": "Checking for stale docs...", + "zh": "Checking for stale docs...", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Checking heading hierarchy...": { + "bg": "Checking heading hierarchy...", + "de": "Checking heading hierarchy...", + "en": "Checking heading hierarchy...", + "pl": "Checking heading hierarchy...", + "ru": "Checking heading hierarchy...", + "zh": "Checking heading hierarchy...", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Checking internal links...": { + "bg": "Checking internal links...", + "de": "Checking internal links...", + "en": "Checking internal links...", + "pl": "Checking internal links...", + "ru": "Checking internal links...", + "zh": "Checking internal links...", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Checking line length...": { + "bg": "", + "de": "", + "en": "Checking line length...", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Checking max heading depth...": { + "bg": "", + "de": "", + "en": "Checking max heading depth...", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Checking required files...": { + "bg": "Checking required files...", + "de": "Checking required files...", + "en": "Checking required files...", + "pl": "Checking required files...", + "ru": "Checking required files...", + "zh": "Checking required files...", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Checking single H1 per file...": { + "bg": "", + "de": "", + "en": "Checking single H1 per file...", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Checking status for PR #{pr_number}...": { + "bg": "Checking status for PR #{pr_number}...", + "de": "Checking status for PR #{pr_number}...", + "en": "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}...", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Checking trailing whitespace...": { + "bg": "Checking trailing whitespace...", + "de": "Checking trailing whitespace...", + "en": "Checking trailing whitespace...", + "pl": "Checking trailing whitespace...", + "ru": "Checking trailing whitespace...", + "zh": "Checking trailing whitespace...", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Checking version references for {pkg} (current: v{version})": { + "bg": "", + "de": "", + "en": "Checking version references for {pkg} (current: v{version})", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" }, "CliRunner.invoke({target}) in test '{test}' reaches unpatched dangerous functions: {funcs}. Add @patch for each or patch the calling function.": { "bg": "CliRunner.invoke({target}) in test '{test}' reaches unpatched dangerous functions: {funcs}. Add @patch for each or patch the calling function.", @@ -3677,7 +1535,149 @@ "en": "CliRunner.invoke({target}) in test '{test}' reaches unpatched dangerous functions: {funcs}. Add @patch for each or patch the calling function.", "pl": "CliRunner.invoke({target}) in test '{test}' reaches unpatched dangerous functions: {funcs}. Add @patch for each or patch the calling function.", "ru": "CliRunner.invoke({target}) in test '{test}' reaches unpatched dangerous functions: {funcs}. Add @patch for each or patch the calling function.", - "zh": "CliRunner.invoke({target}) in test '{test}' reaches unpatched dangerous functions: {funcs}. Add @patch for each or patch the calling function." + "zh": "CliRunner.invoke({target}) in test '{test}' reaches unpatched dangerous functions: {funcs}. Add @patch for each or patch the calling function.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Cloned existing wiki.": { + "bg": "", + "de": "", + "en": "Cloned existing wiki.", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Cloning wiki repo...": { + "bg": "", + "de": "", + "en": "Cloning wiki repo...", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Command failed ({cmd}): {stderr}": { + "bg": "Command failed ({cmd}): {stderr}", + "de": "Command failed ({cmd}): {stderr}", + "en": "Command failed ({cmd}): {stderr}", + "pl": "Polecenie nie powiodło się ({cmd}): {stderr}", + "ru": "Command failed ({cmd}): {stderr}", + "zh": "Command failed ({cmd}): {stderr}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Commit message: {msg}": { + "bg": "Commit message: {msg}", + "de": "Commit message: {msg}", + "en": "Commit message: {msg}", + "pl": "Commit message: {msg}", + "ru": "Commit message: {msg}", + "zh": "Commit message: {msg}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Commit: {sha}": { + "bg": "Commit: {sha}", + "de": "Commit: {sha}", + "en": "Commit: {sha}", + "pl": "Commit: {sha}", + "ru": "Commit: {sha}", + "zh": "Commit: {sha}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Committing and pushing...": { + "bg": "", + "de": "", + "en": "Committing and pushing...", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Comparing {base}..{head} ({count} files changed)": { + "bg": "Comparing {base}..{head} ({count} files changed)", + "de": "Comparing {base}..{head} ({count} files changed)", + "en": "Comparing {base}..{head} ({count} files changed)", + "pl": "Porównywanie {base}..{head} ({count} zmienionych plików)", + "ru": "Comparing {base}..{head} ({count} files changed)", + "zh": "Comparing {base}..{head} ({count} files changed)", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Configuration OK: [tool.devx] present, devx versions consistent.": { + "bg": "Конфигурацията е OK: [tool.devx] присъства, версиите на devx са консистентни.", + "de": "Konfiguration OK: [tool.devx] vorhanden, devx-Versionen konsistent.", + "en": "Configuration OK: [tool.devx] present, devx versions consistent.", + "pl": "Konfiguracja OK: [tool.devx] obecne, wersje devx spójne.", + "ru": "Конфигурация OK: [tool.devx] присутствует, версии devx согласованы.", + "zh": "配置正常: [tool.devx] 已存在, devx 版本一致。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Configuration validation failed.": { + "bg": "Configuration validation failed.", + "de": "Configuration validation failed.", + "en": "Configuration validation failed.", + "pl": "Configuration validation failed.", + "ru": "Configuration validation failed.", + "zh": "Configuration validation failed.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Configuring branch protection for {branch}...": { + "bg": "Конфигуриране на защита на клона {branch}...", + "de": "Konfiguriere Branch-Schutz für {branch}...", + "en": "Configuring branch protection for {branch}...", + "pl": "Konfigurowanie ochrony gałęzi dla {branch}...", + "ru": "Настройка защиты ветки {branch}...", + "zh": "正在配置 {branch} 的分支保护...", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Configuring repository settings...": { + "bg": "Конфигуриране на настройките на хранилището...", + "de": "Repository-Einstellungen konfigurieren...", + "en": "Configuring repository settings...", + "pl": "Konfigurowanie ustawień repozytorium...", + "ru": "Настройка параметров репозитория...", + "zh": "正在配置仓库设置...", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Configuring tea login '{name}' for {url}...": { + "bg": "Configuring tea login '{name}' for {url}...", + "de": "Configuring tea login '{name}' for {url}...", + "en": "Configuring tea login '{name}' for {url}...", + "pl": "Configuring tea login '{name}' for {url}...", + "ru": "Configuring tea login '{name}' for {url}...", + "zh": "Configuring tea login '{name}' for {url}...", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Could not detect PR number. Use --pr to specify it explicitly,\nor run this command from a branch with an open PR.": { + "bg": "Could not detect PR number. Use --pr to specify it explicitly,\nor run this command from a branch with an open PR.", + "de": "Could not detect PR number. Use --pr to specify it explicitly,\nor run this command from a branch with an open PR.", + "en": "Could not detect PR number. Use --pr to specify it explicitly,\nor run this command from a branch with an open PR.", + "pl": "Could not detect PR number. Use --pr to specify it explicitly,\nor run this command from a branch with an open PR.", + "ru": "Could not detect PR number. Use --pr to specify it explicitly,\nor run this command from a branch with an open PR.", + "zh": "Could not detect PR number. Use --pr to specify it explicitly,\nor run this command from a branch with an open PR.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Could not detect current branch: {error}": { + "bg": "Не може да се определи текущия клон: {error}", + "de": "Aktueller Branch konnte nicht erkannt werden: {error}", + "en": "Could not detect current branch: {error}", + "pl": "Nie można wykryć bieżącej gałęzi: {error}", + "ru": "Не удалось определить текущую ветку: {error}", + "zh": "无法检测当前分支: {error}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" }, "Could not determine branch name from PR #{pr}": { "bg": "Could not determine branch name from PR #{pr}", @@ -3685,7 +1685,369 @@ "en": "Could not determine branch name from PR #{pr}", "pl": "Could not determine branch name from PR #{pr}", "ru": "Could not determine branch name from PR #{pr}", - "zh": "Could not determine branch name from PR #{pr}" + "zh": "Could not determine branch name from PR #{pr}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "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}.", + "en": "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}.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Could not determine repository. Set DEVX_REPO_OWNER and DEVX_REPO_NAME\nor GITHUB_REPOSITORY environment variables.": { + "bg": "Could not determine repository. Set DEVX_REPO_OWNER and DEVX_REPO_NAME\nor GITHUB_REPOSITORY environment variables.", + "de": "Could not determine repository. Set DEVX_REPO_OWNER and DEVX_REPO_NAME\nor GITHUB_REPOSITORY environment variables.", + "en": "Could not determine repository. Set DEVX_REPO_OWNER and DEVX_REPO_NAME\nor GITHUB_REPOSITORY environment variables.", + "pl": "Could not determine repository. Set DEVX_REPO_OWNER and DEVX_REPO_NAME\nor GITHUB_REPOSITORY environment variables.", + "ru": "Could not determine repository. Set DEVX_REPO_OWNER and DEVX_REPO_NAME\nor GITHUB_REPOSITORY environment variables.", + "zh": "Could not determine repository. Set DEVX_REPO_OWNER and DEVX_REPO_NAME\nor GITHUB_REPOSITORY environment variables.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Could not extract conventional commit message from PR commits.": { + "bg": "Could not extract conventional commit message from PR commits.", + "de": "Could not extract conventional commit message from PR commits.", + "en": "Could not extract conventional commit message from PR commits.", + "pl": "Nie udało się wyodrębnić konwencjonalnej wiadomości commit z commitów PR.", + "ru": "Could not extract conventional commit message from PR commits.", + "zh": "Could not extract conventional commit message from PR commits.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Could not fetch PR title from Gitea (CI_GITEA_TOKEN not set or PR not found).": { + "bg": "Could not fetch PR title from Gitea (CI_GITEA_TOKEN not set or PR not found).", + "de": "Could not fetch PR title from Gitea (CI_GITEA_TOKEN not set or PR not found).", + "en": "Could not fetch PR title from Gitea (CI_GITEA_TOKEN not set or PR not found).", + "pl": "Could not fetch PR title from Gitea (CI_GITEA_TOKEN not set or PR not found).", + "ru": "Could not fetch PR title from Gitea (CI_GITEA_TOKEN not set or PR not found).", + "zh": "Could not fetch PR title from Gitea (CI_GITEA_TOKEN not set or PR not found).", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Could not find Vikunja task {task_id} in project {project_id}.": { + "bg": "Не е намерена Vikunja задача {task_id} в проект {project_id}.", + "de": "Vikunja-Task {task_id} in Projekt {project_id} nicht gefunden.", + "en": "Could not find Vikunja task {task_id} in project {project_id}.", + "pl": "Nie znaleziono zadania Vikunja {task_id} w projekcie {project_id}.", + "ru": "Не найдена задача Vikunja {task_id} в проекте {project_id}.", + "zh": "在项目 {project_id} 中找不到 Vikunja 任务 {task_id}。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Could not find Vikunja task {task_id} in project {project_id}. Every PR must have a corresponding Vikunja task.": { + "bg": "Could not find Vikunja task {task_id} in project {project_id}. Every PR must have a corresponding Vikunja task.", + "de": "Could not find Vikunja task {task_id} in project {project_id}. Every PR must have a corresponding Vikunja task.", + "en": "Could not find Vikunja task {task_id} in project {project_id}. Every PR must have a corresponding Vikunja task.", + "pl": "Nie znaleziono zadania Vikunja {task_id} w projekcie {project_id}. Każdy PR musi mieć odpowiadające zadanie Vikunja.", + "ru": "Could not find Vikunja task {task_id} in project {project_id}. Every PR must have a corresponding Vikunja task.", + "zh": "Could not find Vikunja task {task_id} in project {project_id}. Every PR must have a corresponding Vikunja task.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Could not find __version__ in {file}": { + "bg": "Could not find __version__ in {file}", + "de": "Could not find __version__ in {file}", + "en": "Could not find __version__ in {file}", + "pl": "Nie znaleziono __version__ w {file}", + "ru": "Could not find __version__ in {file}", + "zh": "Could not find __version__ in {file}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Could not find pinned version for {pkg}": { + "bg": "Could not find pinned version for {pkg}", + "de": "Could not find pinned version for {pkg}", + "en": "Could not find pinned version for {pkg}", + "pl": "Could not find pinned version for {pkg}", + "ru": "Could not find pinned version for {pkg}", + "zh": "Could not find pinned version for {pkg}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Could not parse test execution time from output.": { + "bg": "Could not parse test execution time from output.", + "de": "Could not parse test execution time from output.", + "en": "Could not parse test execution time from output.", + "pl": "Nie udało się przeanalizować czasu wykonania testu z wyjścia.", + "ru": "Could not parse test execution time from output.", + "zh": "Could not parse test execution time from output.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Created PR #{index}: {title}\n {url}": { + "bg": "Създаден PR #{index}: {title}\n {url}", + "de": "PR erstellt #{index}: {title}\n {url}", + "en": "Created PR #{index}: {title}\n {url}", + "pl": "Utworzono PR #{index}: {title}\n {url}", + "ru": "Создан PR #{index}: {title}\n {url}", + "zh": "已创建 PR #{index}: {title}\n {url}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Created Vikunja task: {identifier} (id={task_id})": { + "bg": "Създадена Vikunja задача: {identifier} (id={task_id})", + "de": "Vikunja-Task erstellt: {identifier} (id={task_id})", + "en": "Created Vikunja task: {identifier} (id={task_id})", + "pl": "Utworzono zadanie Vikunja: {identifier} (id={task_id})", + "ru": "Создана задача Vikunja: {identifier} (id={task_id})", + "zh": "已创建 Vikunja 任务: {identifier} (id={task_id})", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Created issue #{issue_id}: {title}": { + "bg": "Created issue #{issue_id}: {title}", + "de": "Created issue #{issue_id}: {title}", + "en": "Created issue #{issue_id}: {title}", + "pl": "Utworzono zgłoszenie #{issue_id}: {title}", + "ru": "Created issue #{issue_id}: {title}", + "zh": "Created issue #{issue_id}: {title}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Created release commit.": { + "bg": "Created release commit.", + "de": "Created release commit.", + "en": "Created release commit.", + "pl": "Utworzono commit wydania.", + "ru": "Created release commit.", + "zh": "Created release commit.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Dependencies must have documentation comments.": { + "bg": "Dependencies must have documentation comments.", + "de": "Dependencies must have documentation comments.", + "en": "Dependencies must have documentation comments.", + "pl": "Dependencies must have documentation comments.", + "ru": "Dependencies must have documentation comments.", + "zh": "Dependencies must have documentation comments.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Directory containing Ansible roles": { + "bg": "Directory containing Ansible roles", + "de": "Directory containing Ansible roles", + "en": "Directory containing Ansible roles", + "pl": "Directory containing Ansible roles", + "ru": "Directory containing Ansible roles", + "zh": "Directory containing Ansible roles", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Directory containing spec files": { + "bg": "Directory containing spec files", + "de": "Directory containing spec files", + "en": "Directory containing spec files", + "pl": "Directory containing spec files", + "ru": "Directory containing spec files", + "zh": "Directory containing spec files", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Directory to scan (default: tests/integration). Can be repeated.": { + "bg": "Директория за сканиране (по подразбиране: tests/integration). Може да се повтаря.", + "de": "Zu scannendes Verzeichnis (Standard: tests/integration). Kann wiederholt werden.", + "en": "Directory to scan (default: tests/integration). Can be repeated.", + "pl": "Katalog do skanowania (domyślnie: tests/integration). Można powtarzać.", + "ru": "Директория для сканирования (по умолчанию: tests/integration). Можно повторять.", + "zh": "要扫描的目录(默认:tests/integration)。可重复。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Docker daemon already running": { + "bg": "Докер демонът вече работи", + "de": "Docker-Daemon läuft bereits", + "en": "Docker daemon already running", + "pl": "Demon Docker już uruchomiony", + "ru": "Демон Docker уже работает", + "zh": "Docker 守护进程已在运行", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Docker daemon failed to start": { + "bg": "Docker daemon failed to start", + "de": "Docker-Daemon konnte nicht gestartet werden", + "en": "Docker daemon failed to start", + "pl": "Nie udało się uruchomić demona Docker", + "ru": "Не удалось запустить Docker-демон", + "zh": "Docker 守护进程启动失败", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Docker daemon started": { + "bg": "Docker daemon started", + "de": "Docker-Daemon gestartet", + "en": "Docker daemon started", + "pl": "Demon Docker uruchomiony", + "ru": "Docker-демон запущен", + "zh": "Docker 守护进程已启动", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Dockerfile not found: {path}": { + "bg": "Dockerfile not found: {path}", + "de": "Dockerfile not found: {path}", + "en": "Dockerfile not found: {path}", + "pl": "Dockerfile not found: {path}", + "ru": "Dockerfile not found: {path}", + "zh": "Dockerfile not found: {path}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Dry-run mode: on branch '{branch}' (not master). Some checks may behave differently.": { + "bg": "Dry-run mode: on branch '{branch}' (not master). Some checks may behave differently.", + "de": "Dry-run mode: on branch '{branch}' (not master). Some checks may behave differently.", + "en": "Dry-run mode: on branch '{branch}' (not master). Some checks may behave differently.", + "pl": "Tryb dry-run: na gałęzi '{branch}' (nie master). Niektóre kontrole mogą zachowywać się inaczej.", + "ru": "Dry-run mode: on branch '{branch}' (not master). Some checks may behave differently.", + "zh": "Dry-run mode: on branch '{branch}' (not master). Some checks may behave differently.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "ERROR: CI_GITEA_TOKEN is not set.": { + "bg": "ГРЕШКА: CI_GITEA_TOKEN не е зададен.", + "de": "FEHLER: CI_GITEA_TOKEN ist nicht gesetzt.", + "en": "ERROR: CI_GITEA_TOKEN is not set.", + "pl": "BŁĄD: CI_GITEA_TOKEN nie jest ustawiony.", + "ru": "ОШИБКА: CI_GITEA_TOKEN не задан.", + "zh": "错误:未设置 CI_GITEA_TOKEN。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "ERROR: Repository name not specified. Use --repo or set DEVX_REPO_NAME.": { + "bg": "ГРЕШКА: Името на хранилището не е указано. Използвайте --repo или задайте DEVX_REPO_NAME.", + "de": "FEHLER: Repository-Name nicht angegeben. Verwenden Sie --repo oder setzen Sie DEVX_REPO_NAME.", + "en": "ERROR: Repository name not specified. Use --repo or set DEVX_REPO_NAME.", + "pl": "BŁĄD: Nazwa repozytorium nie jest określona. Użyj --repo lub ustaw DEVX_REPO_NAME.", + "ru": "ОШИБКА: Имя репозитория не указано. Используйте --repo или задайте DEVX_REPO_NAME.", + "zh": "错误:未指定仓库名称。请使用 --repo 或设置 DEVX_REPO_NAME。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "ERROR: Tag consistency check failed. Existing tags are misaligned:": { + "bg": "ERROR: Tag consistency check failed. Existing tags are misaligned:", + "de": "ERROR: Tag consistency check failed. Existing tags are misaligned:", + "en": "ERROR: Tag consistency check failed. Existing tags are misaligned:", + "pl": "BŁĄD: Kontrola zgodności tagów nie powiodła się. Istniejące tagi są niezgodne:", + "ru": "ERROR: Tag consistency check failed. Existing tags are misaligned:", + "zh": "ERROR: Tag consistency check failed. Existing tags are misaligned:", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "ERROR: VIKUNJA_TOKEN is not set.": { + "bg": "ГРЕШКА: VIKUNJA_TOKEN не е зададен.", + "de": "FEHLER: VIKUNJA_TOKEN ist nicht gesetzt.", + "en": "ERROR: VIKUNJA_TOKEN is not set.", + "pl": "BŁĄD: VIKUNJA_TOKEN nie jest ustawiony.", + "ru": "ОШИБКА: VIKUNJA_TOKEN не задан.", + "zh": "错误:未设置 VIKUNJA_TOKEN。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "ERROR: mapping.json not found at {path}": { + "bg": "ERROR: mapping.json not found at {path}", + "de": "ERROR: mapping.json not found at {path}", + "en": "ERROR: mapping.json not found at {path}", + "pl": "BŁĄD: mapping.json nie znaleziono w {path}", + "ru": "ERROR: mapping.json not found at {path}", + "zh": "ERROR: mapping.json not found at {path}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Each item must be a string or an object with 'id', got {type}": { + "bg": "Всеки елемент трябва да е низ или обект с 'id', получено {type}", + "de": "Jedes Element muss ein String oder ein Objekt mit 'id' sein, erhalten {type}", + "en": "Each item must be a string or an object with 'id', got {type}", + "pl": "Każdy element musi być ciągiem lub obiektem z 'id', otrzymano {type}", + "ru": "Каждый элемент должен быть строкой или объектом с 'id', получено {type}", + "zh": "每个元素必须是字符串或带有 'id' 的对象,得到 {type}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Ensuring standard labels...": { + "bg": "Ensuring standard labels...", + "de": "Ensuring standard labels...", + "en": "Ensuring standard labels...", + "pl": "Ensuring standard labels...", + "ru": "Ensuring standard labels...", + "zh": "Ensuring standard labels...", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "FAIL: Could not clone wiki for verification.": { + "bg": "", + "de": "", + "en": "FAIL: Could not clone wiki for verification.", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "FAIL: {n} documentation issues found:": { + "bg": "FAIL: {n} documentation issues found:", + "de": "FAIL: {n} documentation issues found:", + "en": "FAIL: {n} documentation issues found:", + "pl": "FAIL: {n} documentation issues found:", + "ru": "FAIL: {n} documentation issues found:", + "zh": "FAIL: {n} documentation issues found:", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "FAILED: {count} undocumented dependency/ies": { + "bg": "FAILED: {count} undocumented dependency/ies", + "de": "FAILED: {count} undocumented dependency/ies", + "en": "FAILED: {count} undocumented dependency/ies", + "pl": "FAILED: {count} undocumented dependency/ies", + "ru": "FAILED: {count} undocumented dependency/ies", + "zh": "FAILED: {count} undocumented dependency/ies", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Failed images: {names}": { + "bg": "Failed images: {names}", + "de": "Failed images: {names}", + "en": "Failed images: {names}", + "pl": "Failed images: {names}", + "ru": "Failed images: {names}", + "zh": "Failed images: {names}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Failed to create branch: {error}": { + "bg": "Failed to create branch: {error}", + "de": "Failed to create branch: {error}", + "en": "Failed to create branch: {error}", + "pl": "Failed to create branch: {error}", + "ru": "Failed to create branch: {error}", + "zh": "Failed to create branch: {error}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Failed to create issue via tea: {error}": { + "bg": "Failed to create issue via tea: {error}", + "de": "Failed to create issue via tea: {error}", + "en": "Failed to create issue via tea: {error}", + "pl": "Nie udało się utworzyć zgłoszenia przez tea: {error}", + "ru": "Failed to create issue via tea: {error}", + "zh": "Failed to create issue via tea: {error}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Failed to delete {count} image version(s)": { + "bg": "Failed to delete {count} image version(s)", + "de": "Failed to delete {count} image version(s)", + "en": "Failed to delete {count} image version(s)", + "pl": "Failed to delete {count} image version(s)", + "ru": "Failed to delete {count} image version(s)", + "zh": "Failed to delete {count} image version(s)", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" }, "Failed to fetch PR #{pr}: {error}": { "bg": "Failed to fetch PR #{pr}: {error}", @@ -3693,7 +2055,39 @@ "en": "Failed to fetch PR #{pr}: {error}", "pl": "Failed to fetch PR #{pr}: {error}", "ru": "Failed to fetch PR #{pr}: {error}", - "zh": "Failed to fetch PR #{pr}: {error}" + "zh": "Failed to fetch PR #{pr}: {error}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Failed to list versions for {name}: {error}": { + "bg": "Failed to list versions for {name}: {error}", + "de": "Failed to list versions for {name}: {error}", + "en": "Failed to list versions for {name}: {error}", + "pl": "Failed to list versions for {name}: {error}", + "ru": "Failed to list versions for {name}: {error}", + "zh": "Failed to list versions for {name}: {error}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Failed to push release commit after 3 attempts. Manual intervention required.": { + "bg": "Failed to push release commit after 3 attempts. Manual intervention required.", + "de": "Failed to push release commit after 3 attempts. Manual intervention required.", + "en": "Failed to push release commit after 3 attempts. Manual intervention required.", + "pl": "Failed to push release commit after 3 attempts. Manual intervention required.", + "ru": "Failed to push release commit after 3 attempts. Manual intervention required.", + "zh": "Failed to push release commit after 3 attempts. Manual intervention required.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Failed to start ssh-agent: {error}": { + "bg": "Неуспешно стартиране на ssh-agent: {error}", + "de": "Starten von ssh-agent fehlgeschlagen: {error}", + "en": "Failed to start ssh-agent: {error}", + "pl": "Nie udało się uruchomić ssh-agent: {error}", + "ru": "Не удалось запустить ssh-agent: {error}", + "zh": "启动 ssh-agent 失败: {error}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" }, "Failed to update PR #{pr}: {error}": { "bg": "Failed to update PR #{pr}: {error}", @@ -3701,7 +2095,49 @@ "en": "Failed to update PR #{pr}: {error}", "pl": "Failed to update PR #{pr}: {error}", "ru": "Failed to update PR #{pr}: {error}", - "zh": "Failed to update PR #{pr}: {error}" + "zh": "Failed to update PR #{pr}: {error}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Failed to update {file}": { + "bg": "Failed to update {file}", + "de": "Failed to update {file}", + "en": "Failed to update {file}", + "pl": "Failed to update {file}", + "ru": "Failed to update {file}", + "zh": "Failed to update {file}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Fetch failed: {error}": { + "bg": "Fetch failed: {error}", + "de": "Fetch failed: {error}", + "en": "Fetch failed: {error}", + "pl": "Fetch failed: {error}", + "ru": "Fetch failed: {error}", + "zh": "Fetch failed: {error}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Fetching logs for PR #{pr_number}...": { + "bg": "Fetching logs for PR #{pr_number}...", + "de": "Fetching logs for PR #{pr_number}...", + "en": "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}...", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Fetching origin/master...": { + "bg": "Fetching origin/master...", + "de": "Fetching origin/master...", + "en": "Fetching origin/master...", + "pl": "Fetching origin/master...", + "ru": "Fetching origin/master...", + "zh": "Fetching origin/master...", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" }, "Fix: add @patch decorators or with patch() context managers for subprocess/time.sleep calls, or patch the calling function.": { "bg": "Fix: add @patch decorators or with patch() context managers for subprocess/time.sleep calls, or patch the calling function.", @@ -3709,7 +2145,9 @@ "en": "Fix: add @patch decorators or with patch() context managers for subprocess/time.sleep calls, or patch the calling function.", "pl": "Fix: add @patch decorators or with patch() context managers for subprocess/time.sleep calls, or patch the calling function.", "ru": "Fix: add @patch decorators or with patch() context managers for subprocess/time.sleep calls, or patch the calling function.", - "zh": "Fix: add @patch decorators or with patch() context managers for subprocess/time.sleep calls, or patch the calling function." + "zh": "Fix: add @patch decorators or with patch() context managers for subprocess/time.sleep calls, or patch the calling function.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" }, "Fix: add @patch decorators or with patch() context managers for subprocess/time.sleep calls, or patch the calling function.\n": { "bg": "Fix: add @patch decorators or with patch() context managers for subprocess/time.sleep calls, or patch the calling function.\n", @@ -3717,7 +2155,229 @@ "en": "Fix: add @patch decorators or with patch() context managers for subprocess/time.sleep calls, or patch the calling function.\n", "pl": "Fix: add @patch decorators or with patch() context managers for subprocess/time.sleep calls, or patch the calling function.\n", "ru": "Fix: add @patch decorators or with patch() context managers for subprocess/time.sleep calls, or patch the calling function.\n", - "zh": "Fix: add @patch decorators or with patch() context managers for subprocess/time.sleep calls, or patch the calling function.\n" + "zh": "Fix: add @patch decorators or with patch() context managers for subprocess/time.sleep calls, or patch the calling function.\n", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Force-push failed:\n{error}\nThe remote may have unexpected commits. Fetch and try again.": { + "bg": "Force-push failed:\n{error}\nThe remote may have unexpected commits. Fetch and try again.", + "de": "Force-push failed:\n{error}\nThe remote may have unexpected commits. Fetch and try again.", + "en": "Force-push failed:\n{error}\nThe remote may have unexpected commits. Fetch and try again.", + "pl": "Force-push failed:\n{error}\nThe remote may have unexpected commits. Fetch and try again.", + "ru": "Force-push failed:\n{error}\nThe remote may have unexpected commits. Fetch and try again.", + "zh": "Force-push failed:\n{error}\nThe remote may have unexpected commits. Fetch and try again.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Force-pushing...": { + "bg": "Force-pushing...", + "de": "Force-pushing...", + "en": "Force-pushing...", + "pl": "Force-pushing...", + "ru": "Force-pushing...", + "zh": "Force-pushing...", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Found {count} mutable global(s) — use factory functions or pytest fixtures.": { + "bg": "Found {count} mutable global(s) — use factory functions or pytest fixtures.", + "de": "Found {count} mutable global(s) — use factory functions or pytest fixtures.", + "en": "Found {count} mutable global(s) — use factory functions or pytest fixtures.", + "pl": "Found {count} mutable global(s) — use factory functions or pytest fixtures.", + "ru": "Found {count} mutable global(s) — use factory functions or pytest fixtures.", + "zh": "Found {count} mutable global(s) — use factory functions or pytest fixtures.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Found {count} stale documentation reference(s)": { + "bg": "Found {count} stale documentation reference(s)", + "de": "Found {count} stale documentation reference(s)", + "en": "Found {count} stale documentation reference(s)", + "pl": "Found {count} stale documentation reference(s)", + "ru": "Found {count} stale documentation reference(s)", + "zh": "Found {count} stale documentation reference(s)", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Found {count} unsafe identity check(s) in integration tests.": { + "bg": "Намерени са {count} небрежни проверки за идентичност в интеграционните тестове.", + "de": "{count} unsichere Identitätsprüfung(en) in Integrationstests gefunden.", + "en": "Found {count} unsafe identity check(s) in integration tests.", + "pl": "Znaleziono {count} niebezpiecznych sprawdzeń tożsamości w testach integracyjnych.", + "ru": "Найдено {count} небезопасных проверок идентичности в интеграционных тестах.", + "zh": "在集成测试中发现 {count} 个不安全的身份检查。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Found {count} version(s):": { + "bg": "Found {count} version(s):", + "de": "Found {count} version(s):", + "en": "Found {count} version(s):", + "pl": "Found {count} version(s):", + "ru": "Found {count} version(s):", + "zh": "Found {count} version(s):", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "GITEA_URL/CI_GITEA_TOKEN/RUN_ID not set; running without cross-runner cancellation.": { + "bg": "GITEA_URL/CI_GITEA_TOKEN/RUN_ID not set; running without cross-runner cancellation.", + "de": "GITEA_URL/CI_GITEA_TOKEN/RUN_ID not set; running without cross-runner cancellation.", + "en": "GITEA_URL/CI_GITEA_TOKEN/RUN_ID not set; running without cross-runner cancellation.", + "pl": "GITEA_URL/CI_GITEA_TOKEN/RUN_ID nie ustawione; uruchamianie bez anulowania między runnerami.", + "ru": "GITEA_URL/CI_GITEA_TOKEN/RUN_ID not set; running without cross-runner cancellation.", + "zh": "GITEA_URL/CI_GITEA_TOKEN/RUN_ID not set; running without cross-runner cancellation.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Generated {count} badge files": { + "bg": "Generated {count} badge files", + "de": "Generated {count} badge files", + "en": "Generated {count} badge files", + "pl": "Generated {count} badge files", + "ru": "Generated {count} badge files", + "zh": "Generated {count} badge files", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Generated {file} with prefix '{prefix}'.": { + "bg": "Generated {file} with prefix '{prefix}'.", + "de": "Generated {file} with prefix '{prefix}'.", + "en": "Generated {file} with prefix '{prefix}'.", + "pl": "Wygenerowano {file} z prefiksem '{prefix}'.", + "ru": "Generated {file} with prefix '{prefix}'.", + "zh": "Generated {file} with prefix '{prefix}'.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Generating badges in {out}...": { + "bg": "Generating badges in {out}...", + "de": "Generating badges in {out}...", + "en": "Generating badges in {out}...", + "pl": "Generating badges in {out}...", + "ru": "Generating badges in {out}...", + "zh": "Generating badges in {out}...", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Git tag or ref that was deployed": { + "bg": "Git таг или референция, която беше разгърната", + "de": "Git-Tag oder Ref, der bereitgestellt wurde", + "en": "Git tag or ref that was deployed", + "pl": "Tag Git lub ref, który został wdrożony", + "ru": "Git-тег или ссылка, которые были развёрнуты", + "zh": "已部署的 Git 标签或引用", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Git tag to deploy (e.g. v0.28.1).": { + "bg": "Git таг за разгръщане (напр. v0.28.1).", + "de": "Git-Tag für Bereitstellung (z.B. v0.28.1).", + "en": "Git tag to deploy (e.g. v0.28.1).", + "pl": "Tag Git do wdrożenia (np. v0.28.1).", + "ru": "Git-тег для развёртывания (напр. v0.28.1).", + "zh": "要部署的 Git 标签(例如 v0.28.1)。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Gitea API token not set. Set one of: {names}": { + "bg": "Gitea API token not set. Set one of: {names}", + "de": "Gitea API token not set. Set one of: {names}", + "en": "Gitea API token not set. Set one of: {names}", + "pl": "Gitea API token not set. Set one of: {names}", + "ru": "Gitea API token not set. Set one of: {names}", + "zh": "Gitea API token not set. Set one of: {names}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Gitea PyPI registry: {tag} already published — continuing.": { + "bg": "Gitea PyPI registry: {tag} вече е публикуван — продължава.", + "de": "Gitea PyPI-Registry: {tag} bereits veröffentlicht — wird fortgesetzt.", + "en": "Gitea PyPI registry: {tag} already published — continuing.", + "pl": "Gitea PyPI registry: {tag} już opublikowano — kontynuacja.", + "ru": "Gitea PyPI registry: {tag} уже опубликован — продолжаем.", + "zh": "Gitea PyPI registry: {tag} 已发布 — 继续。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Gitea release {tag} already exists — skipping creation.": { + "bg": "Gitea release {tag} вече съществува — прескачане на създаването.", + "de": "Gitea-Release {tag} existiert bereits — Erstellung übersprungen.", + "en": "Gitea release {tag} already exists — skipping creation.", + "pl": "Wydanie Gitea {tag} już istnieje — pomijanie tworzenia.", + "ru": "Gitea release {tag} уже существует — пропуск создания.", + "zh": "Gitea release {tag} 已存在 — 跳过创建。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "HEAD is a release commit ('{msg}') but tag {tag} is missing. Recovering by creating tag.": { + "bg": "HEAD is a release commit ('{msg}') but tag {tag} is missing. Recovering by creating tag.", + "de": "HEAD is a release commit ('{msg}') but tag {tag} is missing. Recovering by creating tag.", + "en": "HEAD is a release commit ('{msg}') but tag {tag} is missing. Recovering by creating tag.", + "pl": "HEAD jest commitem wydania ('{msg}') ale tag {tag} brakuje. Naprawa przez utworzenie tagu.", + "ru": "HEAD is a release commit ('{msg}') but tag {tag} is missing. Recovering by creating tag.", + "zh": "HEAD is a release commit ('{msg}') but tag {tag} is missing. Recovering by creating tag.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "HEAD is a release commit for v{version} but tag {tag} points to a different commit ({tag_commit} vs HEAD {head_commit}). This indicates a tag/commit misalignment.": { + "bg": "HEAD is a release commit for v{version} but tag {tag} points to a different commit ({tag_commit} vs HEAD {head_commit}). This indicates a tag/commit misalignment.", + "de": "HEAD is a release commit for v{version} but tag {tag} points to a different commit ({tag_commit} vs HEAD {head_commit}). This indicates a tag/commit misalignment.", + "en": "HEAD is a release commit for v{version} but tag {tag} points to a different commit ({tag_commit} vs HEAD {head_commit}). This indicates a tag/commit misalignment.", + "pl": "HEAD jest commitem wydania dla v{version} ale tag {tag} wskazuje na inny commit ({tag_commit} vs HEAD {head_commit}). Wskazuje to na niezgodność tag/commit.", + "ru": "HEAD is a release commit for v{version} but tag {tag} points to a different commit ({tag_commit} vs HEAD {head_commit}). This indicates a tag/commit misalignment.", + "zh": "HEAD is a release commit for v{version} but tag {tag} points to a different commit ({tag_commit} vs HEAD {head_commit}). This indicates a tag/commit misalignment.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "HEAD is already a release commit ('{msg}') and tag {tag} points to HEAD. Skipping.": { + "bg": "HEAD is already a release commit ('{msg}') and tag {tag} points to HEAD. Skipping.", + "de": "HEAD is already a release commit ('{msg}') and tag {tag} points to HEAD. Skipping.", + "en": "HEAD is already a release commit ('{msg}') and tag {tag} points to HEAD. Skipping.", + "pl": "HEAD jest już commitem wydania ('{msg}') a tag {tag} wskazuje na HEAD. Pomijanie.", + "ru": "HEAD is already a release commit ('{msg}') and tag {tag} points to HEAD. Skipping.", + "zh": "HEAD is already a release commit ('{msg}') and tag {tag} points to HEAD. Skipping.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "HEAD is not a release commit for {tag} — skipping publish.": { + "bg": "HEAD is not a release commit for {tag} — skipping publish.", + "de": "HEAD is not a release commit for {tag} — skipping publish.", + "en": "HEAD is not a release commit for {tag} — skipping publish.", + "pl": "HEAD nie jest commitem wydania dla {tag} — pomijanie publikacji.", + "ru": "HEAD is not a release commit for {tag} — skipping publish.", + "zh": "HEAD is not a release commit for {tag} — skipping publish.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "HTTP error: {status} — {message}": { + "bg": "HTTP грешка: {status} — {message}", + "de": "HTTP-Fehler: {status} — {message}", + "en": "HTTP error: {status} — {message}", + "pl": "Błąd HTTP: {status} — {message}", + "ru": "Ошибка HTTP: {status} — {message}", + "zh": "HTTP 错误: {status} — {message}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "HTTP {status} Forbidden — your token lacks admin rights.\nMake sure the token belongs to a repo owner or organisation admin.\nAlternatively, configure branch protection manually in Settings → Branches.": { + "bg": "HTTP {status} Забранено — вашият токен няма администраторски права.\nУверете се, че токенът принадлежи на собственик на хранилище или администратор на организация.\nАлтернативно, конфигурирайте защитата на клона ръчно в Настройки → Клонове.", + "de": "HTTP {status} Verboten — Ihr Token hat keine Admin-Rechte.\nStellen Sie sicher, dass das Token einem Repository-Besitzer oder Organisations-Admin gehört.\nAlternativ können Sie den Branch-Schutz manuell unter Einstellungen → Branches konfigurieren.", + "en": "HTTP {status} Forbidden — your token lacks admin rights.\nMake sure the token belongs to a repo owner or organisation admin.\nAlternatively, configure branch protection manually in Settings → Branches.", + "pl": "HTTP {status} Forbidden — twój token nie ma uprawnień administratora.\nUpewnij się, że token należy do właściciela repozytorium lub administratora organizacji.\nAlternatywnie skonfiguruj ochronę gałęzi ręcznie w Ustawienia → Gałęzie.", + "ru": "HTTP {status} Запрещено — у вашего токена нет прав администратора.\nУбедитесь, что токен принадлежит владельцу репозитория или администратору организации.\nЛибо настройте защиту ветки вручную в разделе Настройки → Ветки.", + "zh": "HTTP {status} 禁止访问 — 您的令牌缺少管理员权限。\n请确保令牌属于仓库所有者或组织管理员。\n或者,您可以在 设置 → 分支 中手动配置分支保护。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Head ref for diff": { + "bg": "Head ref for diff", + "de": "Head ref for diff", + "en": "Head ref for diff", + "pl": "Head ref for diff", + "ru": "Head ref for diff", + "zh": "Head ref for diff", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" }, "Heavy import '{mod}' (~{ms:.0f}ms) at module level — this slows test collection for all tests. Move inside test functions or use lazy import.": { "bg": "Heavy import '{mod}' (~{ms:.0f}ms) at module level — this slows test collection for all tests. Move inside test functions or use lazy import.", @@ -3725,7 +2385,529 @@ "en": "Heavy import '{mod}' (~{ms:.0f}ms) at module level — this slows test collection for all tests. Move inside test functions or use lazy import.", "pl": "Heavy import '{mod}' (~{ms:.0f}ms) at module level — this slows test collection for all tests. Move inside test functions or use lazy import.", "ru": "Heavy import '{mod}' (~{ms:.0f}ms) at module level — this slows test collection for all tests. Move inside test functions or use lazy import.", - "zh": "Heavy import '{mod}' (~{ms:.0f}ms) at module level — this slows test collection for all tests. Move inside test functions or use lazy import." + "zh": "Heavy import '{mod}' (~{ms:.0f}ms) at module level — this slows test collection for all tests. Move inside test functions or use lazy import.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Host Docker not available, starting local dockerd...": { + "bg": "Хост Docker не е наличен, стартиране на локален dockerd...", + "de": "Host-Docker nicht verfügbar, lokaler dockerd wird gestartet...", + "en": "Host Docker not available, starting local dockerd...", + "pl": "Host Docker niedostępny, uruchamianie lokalnego dockerd...", + "ru": "Хост Docker недоступен, запускается локальный dockerd...", + "zh": "主机 Docker 不可用,正在启动本地 dockerd...", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Image 'tags' must be a list": { + "bg": "Image 'tags' must be a list", + "de": "Image 'tags' must be a list", + "en": "Image 'tags' must be a list", + "pl": "Image 'tags' must be a list", + "ru": "Image 'tags' must be a list", + "zh": "Image 'tags' must be a list", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Image manifest entry missing 'dockerfile'": { + "bg": "Image manifest entry missing 'dockerfile'", + "de": "Image manifest entry missing 'dockerfile'", + "en": "Image manifest entry missing 'dockerfile'", + "pl": "Image manifest entry missing 'dockerfile'", + "ru": "Image manifest entry missing 'dockerfile'", + "zh": "Image manifest entry missing 'dockerfile'", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Image manifest entry missing 'name'": { + "bg": "Image manifest entry missing 'name'", + "de": "Image manifest entry missing 'name'", + "en": "Image manifest entry missing 'name'", + "pl": "Image manifest entry missing 'name'", + "ru": "Image manifest entry missing 'name'", + "zh": "Image manifest entry missing 'name'", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Infrastructure commit (no DEVX-N task ID), skipping Vikunja update: {msg}": { + "bg": "Инфраструктурен commit (без идентификатор на задача DEVX-N), пропускаме обновяването на Vikunja: {msg}", + "de": "Infrastruktur-Commit (keine DEVX-N Task-ID), Vikunja-Update wird übersprungen: {msg}", + "en": "Infrastructure commit (no DEVX-N task ID), skipping Vikunja update: {msg}", + "pl": "Commit infrastruktury (bez ID zadania DEVX-N), pomijanie aktualizacji Vikunja: {msg}", + "ru": "Инфраструктурный коммит (без ID задачи DEVX-N), пропуск обновления Vikunja: {msg}", + "zh": "基础设施提交(无 DEVX-N 任务 ID),跳过 Vikunja 更新: {msg}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Integration tests cancelled — another runner failed.": { + "bg": "Integration tests cancelled — another runner failed.", + "de": "Integration tests cancelled — another runner failed.", + "en": "Integration tests cancelled — another runner failed.", + "pl": "Testy integracyjne anulowane — inny runner zakończył się niepowodzeniem.", + "ru": "Integration tests cancelled — another runner failed.", + "zh": "Integration tests cancelled — another runner failed.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Integration tests failed with exit code {code}": { + "bg": "Integration tests failed with exit code {code}", + "de": "Integration tests failed with exit code {code}", + "en": "Integration tests failed with exit code {code}", + "pl": "Testy integracyjne zakończone niepowodzeniem z kodem {code}", + "ru": "Integration tests failed with exit code {code}", + "zh": "Integration tests failed with exit code {code}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Integration tests passed.": { + "bg": "Integration tests passed.", + "de": "Integration tests passed.", + "en": "Integration tests passed.", + "pl": "Testy integracyjne zakończone pomyślnie.", + "ru": "Integration tests passed.", + "zh": "Integration tests passed.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Invalid repo format: {repo}": { + "bg": "Invalid repo format: {repo}", + "de": "Invalid repo format: {repo}", + "en": "Invalid repo format: {repo}", + "pl": "Invalid repo format: {repo}", + "ru": "Invalid repo format: {repo}", + "zh": "Invalid repo format: {repo}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Invalid repo format: {repo}. Expected owner/name.": { + "bg": "Invalid repo format: {repo}. Expected owner/name.", + "de": "Invalid repo format: {repo}. Expected owner/name.", + "en": "Invalid repo format: {repo}. Expected owner/name.", + "pl": "Invalid repo format: {repo}. Expected owner/name.", + "ru": "Invalid repo format: {repo}. Expected owner/name.", + "zh": "Invalid repo format: {repo}. Expected owner/name.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Items input must be a JSON array, got {type}": { + "bg": "Входните данни трябва да са JSON масив, получено {type}", + "de": "Eingabe muss ein JSON-Array sein, erhalten {type}", + "en": "Items input must be a JSON array, got {type}", + "pl": "Dane wejściowe muszą być tablicą JSON, otrzymano {type}", + "ru": "Входные данные должны быть JSON-массивом, получено {type}", + "zh": "输入必须是 JSON 数组,得到 {type}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Label '{label}' already on PR #{pr}.": { + "bg": "Label '{label}' already on PR #{pr}.", + "de": "Label '{label}' already on PR #{pr}.", + "en": "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}.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Latest run: #{run_id} (status: {status})": { + "bg": "Latest run: #{run_id} (status: {status})", + "de": "Latest run: #{run_id} (status: {status})", + "en": "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})", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Lint failed — refusing to release. Fix lint errors first.\n{stderr}": { + "bg": "Lint failed — refusing to release. Fix lint errors first.\n{stderr}", + "de": "Lint failed — refusing to release. Fix lint errors first.\n{stderr}", + "en": "Lint failed — refusing to release. Fix lint errors first.\n{stderr}", + "pl": "Lint nie powiódł się — odmowa wydania. Najpierw napraw błędy lint.\n{stderr}", + "ru": "Lint failed — refusing to release. Fix lint errors first.\n{stderr}", + "zh": "Lint failed — refusing to release. Fix lint errors first.\n{stderr}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Lint passed.": { + "bg": "Lint passed.", + "de": "Lint passed.", + "en": "Lint passed.", + "pl": "Lint zakończony pomyślnie.", + "ru": "Lint passed.", + "zh": "Lint passed.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Linting documentation in {root}...": { + "bg": "Linting documentation in {root}...", + "de": "Linting documentation in {root}...", + "en": "Linting documentation in {root}...", + "pl": "Linting documentation in {root}...", + "ru": "Linting documentation in {root}...", + "zh": "Linting documentation in {root}...", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Login to {registry} failed: {error}": { + "bg": "Влизането в {registry} не успя: {error}", + "de": "Anmeldung bei {registry} fehlgeschlagen: {error}", + "en": "Login to {registry} failed: {error}", + "pl": "Logowanie do {registry} nie powiodło się: {error}", + "ru": "Ошибка входа в {registry}: {error}", + "zh": "登录 {registry} 失败: {error}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Loop with {count} iterations in test '{test}' — consider property-based testing (hypothesis) or reduce to <= {max} iterations.": { + "bg": "Цикъл с {count} итерации в тест '{test}' — използвайте property-based тестове (hypothesis) или намалете до <= {max} итерации.", + "de": "Schleife mit {count} Iterationen in Test '{test}' — property-based testing (hypothesis) verwenden oder auf <= {max} Iterationen reduzieren.", + "en": "Loop with {count} iterations in test '{test}' — consider property-based testing (hypothesis) or reduce to <= {max} iterations.", + "pl": "Pętla z {count} iteracjami w teście '{test}' — rozważ testy oparte na właściwościach (hypothesis) lub zmniejsz do <= {max} iteracji.", + "ru": "Цикл с {count} итерациями в тесте '{test}' — используйте property-based тестирование (hypothesis) или уменьшите до <= {max} итераций.", + "zh": "测试 '{test}' 中有 {count} 次迭代的循环 — 考虑使用基于属性的测试 (hypothesis) 或减少到 <= {max} 次迭代。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Manifest file not found: {path}": { + "bg": "Manifest file not found: {path}", + "de": "Manifest file not found: {path}", + "en": "Manifest file not found: {path}", + "pl": "Manifest file not found: {path}", + "ru": "Manifest file not found: {path}", + "zh": "Manifest file not found: {path}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Manifest must be a JSON list": { + "bg": "Manifest must be a JSON list", + "de": "Manifest must be a JSON list", + "en": "Manifest must be a JSON list", + "pl": "Manifest must be a JSON list", + "ru": "Manifest must be a JSON list", + "zh": "Manifest must be a JSON list", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Max files changed (excluded files not counted)": { + "bg": "Max files changed (excluded files not counted)", + "de": "Max files changed (excluded files not counted)", + "en": "Max files changed (excluded files not counted)", + "pl": "Max files changed (excluded files not counted)", + "ru": "Max files changed (excluded files not counted)", + "zh": "Max files changed (excluded files not counted)", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Max lines changed (excluded files not counted)": { + "bg": "Max lines changed (excluded files not counted)", + "de": "Max lines changed (excluded files not counted)", + "en": "Max lines changed (excluded files not counted)", + "pl": "Max lines changed (excluded files not counted)", + "ru": "Max lines changed (excluded files not counted)", + "zh": "Max lines changed (excluded files not counted)", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Merge failed with HTTP {status}: {message}\nPlease check the PR is ready and you have merge rights.": { + "bg": "Сливането неуспешно с HTTP {status}: {message}\nПроверете дали PR е готов и имате права за сливане.", + "de": "Merge fehlgeschlagen mit HTTP {status}: {message}\nBitte prüfen Sie, ob der PR bereit ist und Sie Merge-Rechte haben.", + "en": "Merge failed with HTTP {status}: {message}\nPlease check the PR is ready and you have merge rights.", + "pl": "Scalanie nie powiodło się z HTTP {status}: {message}\nSprawdź czy PR jest gotowy i masz uprawnienia do scalania.", + "ru": "Слияние не удалось: HTTP {status}: {message}\nПроверьте, что PR готов и у вас есть права на слияние.", + "zh": "合并失败: HTTP {status}: {message}\n请检查 PR 是否准备就绪且您具有合并权限。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Missing required section: {section}": { + "bg": "Missing required section: {section}", + "de": "Missing required section: {section}", + "en": "Missing required section: {section}", + "pl": "Missing required section: {section}", + "ru": "Missing required section: {section}", + "zh": "Missing required section: {section}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Missing tests for changed files.": { + "bg": "Missing tests for changed files.", + "de": "Missing tests for changed files.", + "en": "Missing tests for changed files.", + "pl": "Missing tests for changed files.", + "ru": "Missing tests for changed files.", + "zh": "Missing tests for changed files.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Module {mod} has no main() function": { + "bg": "Модул {mod} няма функция main()", + "de": "Modul {mod} hat keine main()-Funktion", + "en": "Module {mod} has no main() function", + "pl": "Moduł {mod} nie ma funkcji main()", + "ru": "Модуль {mod} не имеет функции main()", + "zh": "模块 {mod} 没有 main() 函数", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Molecule directory not found: {path}": { + "bg": "Директорията на molecule не е намерена: {path}", + "de": "Molecule-Verzeichnis nicht gefunden: {path}", + "en": "Molecule directory not found: {path}", + "pl": "Katalog molecule nie znaleziony: {path}", + "ru": "Директория molecule не найдена: {path}", + "zh": "未找到 molecule 目录: {path}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "New version to pin": { + "bg": "New version to pin", + "de": "New version to pin", + "en": "New version to pin", + "pl": "New version to pin", + "ru": "New version to pin", + "zh": "New version to pin", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Next steps:\n 1. git checkout master && git pull\n 2. git checkout -b {prefix}-{num}-short-description\n 3. Implement changes, commit with conventional commit format\n 4. git push -u origin HEAD\n 5. make create-pr (creates PR with title: {identifier}: {title})": { + "bg": "Следващи стъпки:\n 1. git checkout master && git pull\n 2. git checkout -b {prefix}-{num}-кратко-описание\n 3. Имплементирайте промените, commit с conventional commit формат\n 4. git push -u origin HEAD\n 5. make create-pr (създава PR с заглавие: {identifier}: {title})", + "de": "Nächste Schritte:\n 1. git checkout master && git pull\n 2. git checkout -b {prefix}-{num}-kurz-beschreibung\n 3. Änderungen implementieren, mit Conventional-Commit-Format committen\n 4. git push -u origin HEAD\n 5. make create-pr (erstellt PR mit Titel: {identifier}: {title})", + "en": "Next steps:\n 1. git checkout master && git pull\n 2. git checkout -b {prefix}-{num}-short-description\n 3. Implement changes, commit with conventional commit format\n 4. git push -u origin HEAD\n 5. make create-pr (creates PR with title: {identifier}: {title})", + "pl": "Następne kroki:\n 1. git checkout master && git pull\n 2. git checkout -b {prefix}-{num}-krótki-opis\n 3. Wprowadź zmiany, commituj w formacie conventional commit\n 4. git push -u origin HEAD\n 5. make create-pr (tworzy PR z tytułem: {identifier}: {title})", + "ru": "Следующие шаги:\n 1. git checkout master && git pull\n 2. git checkout -b {prefix}-{num}-краткое-описание\n 3. Реализуйте изменения, коммитьте в conventional commit формате\n 4. git push -u origin HEAD\n 5. make create-pr (создаёт PR с заголовком: {identifier}: {title})", + "zh": "后续步骤:\n 1. git checkout master && git pull\n 2. git checkout -b {prefix}-{num}-简短描述\n 3. 实现更改,使用 conventional commit 格式提交\n 4. git push -u origin HEAD\n 5. make create-pr (创建 PR,标题: {identifier}: {title})", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Nice! Gitea release {tag} created.": { + "bg": "Отлично! Gitea release {tag} е създаден.", + "de": "Prima! Gitea-Release {tag} erstellt.", + "en": "Nice! Gitea release {tag} created.", + "pl": "Świetnie! Wydanie Gitea {tag} utworzone.", + "ru": "Отлично! Gitea release {tag} создан.", + "zh": "不错!Gitea release {tag} 已创建。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Nice! PR #{pr_number} squash-merged with title: {merge_title}": { + "bg": "Отлично! PR #{pr_number} е squash-merge-нат със заглавие: {merge_title}", + "de": "Prima! PR #{pr_number} wurde mit Titel {merge_title} squash-gemergt.", + "en": "Nice! PR #{pr_number} squash-merged with title: {merge_title}", + "pl": "Świetnie! PR #{pr_number} squash-merged z tytułem: {merge_title}", + "ru": "Отлично! PR #{pr_number} squash-merge с заголовком: {merge_title}", + "zh": "不错!PR #{pr_number} 已 squash 合并,标题: {merge_title}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Nice! Release v{version} tagged and pushed. The publish workflow will be triggered.": { + "bg": "Nice! Release v{version} tagged and pushed. The publish workflow will be triggered.", + "de": "Nice! Release v{version} tagged and pushed. The publish workflow will be triggered.", + "en": "Nice! Release v{version} tagged and pushed. The publish workflow will be triggered.", + "pl": "Świetnie! Wydanie v{version} otagowane i wypchnięte. Workflow publikacji zostanie uruchomiony.", + "ru": "Nice! Release v{version} tagged and pushed. The publish workflow will be triggered.", + "zh": "Nice! Release v{version} tagged and pushed. The publish workflow will be triggered.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Nice! Vikunja task {task_id} (ID {vikunja_id}) updated and marked done.": { + "bg": "Отлично! Задача Vikunja {task_id} (ID {vikunja_id}) е обновена и маркирана като готова.", + "de": "Prima! Vikunja-Aufgabe {task_id} (ID {vikunja_id}) aktualisiert und als erledigt markiert.", + "en": "Nice! Vikunja task {task_id} (ID {vikunja_id}) updated and marked done.", + "pl": "Świetnie! Zadanie Vikunja {task_id} (ID {vikunja_id}) zaktualizowane i oznaczone jako ukończone.", + "ru": "Отлично! Задача Vikunja {task_id} (ID {vikunja_id}) обновлена и отмечена как выполненная.", + "zh": "不错!Vikunja 任务 {task_id} (ID {vikunja_id}) 已更新并标记为完成。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Nightly gate failed — staging deploy blocked.": { + "bg": "Nightly gate failed — staging deploy blocked.", + "de": "Nightly gate failed — staging deploy blocked.", + "en": "Nightly gate failed — staging deploy blocked.", + "pl": "Nightly gate failed — staging deploy blocked.", + "ru": "Nightly gate failed — staging deploy blocked.", + "zh": "Nightly gate failed — staging deploy blocked.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "No CI checks found for commit {sha}.": { + "bg": "No CI checks found for commit {sha}.", + "de": "No CI checks found for commit {sha}.", + "en": "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}.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "No Python package found under src/ — skipping version check.": { + "bg": "", + "de": "", + "en": "No Python package found under src/ — skipping version check.", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "No REQ-ID lines found. Each requirement must be labeled (e.g., 'REQ-1: <description>').": { + "bg": "No REQ-ID lines found. Each requirement must be labeled (e.g., 'REQ-1: <description>').", + "de": "No REQ-ID lines found. Each requirement must be labeled (e.g., 'REQ-1: <description>').", + "en": "No REQ-ID lines found. Each requirement must be labeled (e.g., 'REQ-1: <description>').", + "pl": "No REQ-ID lines found. Each requirement must be labeled (e.g., 'REQ-1: <description>').", + "ru": "No REQ-ID lines found. Each requirement must be labeled (e.g., 'REQ-1: <description>').", + "zh": "No REQ-ID lines found. Each requirement must be labeled (e.g., 'REQ-1: <description>').", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "No badge SVG files generated": { + "bg": "No badge SVG files generated", + "de": "No badge SVG files generated", + "en": "No badge SVG files generated", + "pl": "No badge SVG files generated", + "ru": "No badge SVG files generated", + "zh": "No badge SVG files generated", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "No badge URLs found to update — README already up to date": { + "bg": "No badge URLs found to update — README already up to date", + "de": "No badge URLs found to update — README already up to date", + "en": "No badge URLs found to update — README already up to date", + "pl": "No badge URLs found to update — README already up to date", + "ru": "No badge URLs found to update — README already up to date", + "zh": "No badge URLs found to update — README already up to date", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "No badge changes — skipping commit": { + "bg": "", + "de": "", + "en": "No badge changes — skipping commit", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "No changes between {base} and {head}.": { + "bg": "No changes between {base} and {head}.", + "de": "No changes between {base} and {head}.", + "en": "No changes between {base} and {head}.", + "pl": "Brak zmian między {base} i {head}.", + "ru": "No changes between {base} and {head}.", + "zh": "No changes between {base} and {head}.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "No changes to sync — wiki is up to date.": { + "bg": "", + "de": "", + "en": "No changes to sync — wiki is up to date.", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "No failed jobs.": { + "bg": "No failed jobs.", + "de": "No failed jobs.", + "en": "No failed jobs.", + "pl": "No failed jobs.", + "ru": "No failed jobs.", + "zh": "No failed jobs.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "No job matching '{job}' found.": { + "bg": "No job matching '{job}' found.", + "de": "No job matching '{job}' found.", + "en": "No job matching '{job}' found.", + "pl": "No job matching '{job}' found.", + "ru": "No job matching '{job}' found.", + "zh": "No job matching '{job}' found.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "No jobs found for run #{run_id}.": { + "bg": "No jobs found for run #{run_id}.", + "de": "No jobs found for run #{run_id}.", + "en": "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}.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "No open PR found for branch '{branch}'.": { + "bg": "No open PR found for branch '{branch}'.", + "de": "No open PR found for branch '{branch}'.", + "en": "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}'.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "No push needed (no changes or push failed).": { + "bg": "", + "de": "", + "en": "No push needed (no changes or push failed).", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "No spec file found for task {task_id} in {dir}/. Expected: {dir}/{task_id}.md": { + "bg": "No spec file found for task {task_id} in {dir}/. Expected: {dir}/{task_id}.md", + "de": "No spec file found for task {task_id} in {dir}/. Expected: {dir}/{task_id}.md", + "en": "No spec file found for task {task_id} in {dir}/. Expected: {dir}/{task_id}.md", + "pl": "No spec file found for task {task_id} in {dir}/. Expected: {dir}/{task_id}.md", + "ru": "No spec file found for task {task_id} in {dir}/. Expected: {dir}/{task_id}.md", + "zh": "No spec file found for task {task_id} in {dir}/. Expected: {dir}/{task_id}.md", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "No staged changes — version and changelog already up to date.": { + "bg": "No staged changes — version and changelog already up to date.", + "de": "No staged changes — version and changelog already up to date.", + "en": "No staged changes — version and changelog already up to date.", + "pl": "Brak zmian w staging — wersja i changelog są już aktualne.", + "ru": "No staged changes — version and changelog already up to date.", + "zh": "No staged changes — version and changelog already up to date.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "No tag found — skipping publish.": { + "bg": "No tag found — skipping publish.", + "de": "No tag found — skipping publish.", + "en": "No tag found — skipping publish.", + "pl": "Nie znaleziono tagu — pomijanie publikacji.", + "ru": "No tag found — skipping publish.", + "zh": "No tag found — skipping publish.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "No tags found — treating all changes as user-facing.": { + "bg": "No tags found — treating all changes as user-facing.", + "de": "No tags found — treating all changes as user-facing.", + "en": "No tags found — treating all changes as user-facing.", + "pl": "Nie znaleziono tagów — traktowanie wszystkich zmian jako widocznych dla użytkownika.", + "ru": "No tags found — treating all changes as user-facing.", + "zh": "No tags found — treating all changes as user-facing.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "No task ID ({prefix}-N) found in commit message: {msg}. Every non-infrastructure commit must have a task ID.": { + "bg": "No task ID ({prefix}-N) found in commit message: {msg}. Every non-infrastructure commit must have a task ID.", + "de": "No task ID ({prefix}-N) found in commit message: {msg}. Every non-infrastructure commit must have a task ID.", + "en": "No task ID ({prefix}-N) found in commit message: {msg}. Every non-infrastructure commit must have a task ID.", + "pl": "Nie znaleziono ID zadania ({prefix}-N) w wiadomości commit: {msg}. Każdy commit nie-infrastrukturalny musi mieć ID zadania.", + "ru": "No task ID ({prefix}-N) found in commit message: {msg}. Every non-infrastructure commit must have a task ID.", + "zh": "No task ID ({prefix}-N) found in commit message: {msg}. Every non-infrastructure commit must have a task ID.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" }, "No task ID found in branch '{branch}'. Expected format: {prefix}-N-description.": { "bg": "No task ID found in branch '{branch}'. Expected format: {prefix}-N-description.", @@ -3733,7 +2915,259 @@ "en": "No task ID found in branch '{branch}'. Expected format: {prefix}-N-description.", "pl": "No task ID found in branch '{branch}'. Expected format: {prefix}-N-description.", "ru": "No task ID found in branch '{branch}'. Expected format: {prefix}-N-description.", - "zh": "No task ID found in branch '{branch}'. Expected format: {prefix}-N-description." + "zh": "No task ID found in branch '{branch}'. Expected format: {prefix}-N-description.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "No task ID found in branch name '{branch}'. Expected format: <PREFIX>-N-description.": { + "bg": "No task ID found in branch name '{branch}'. Expected format: <PREFIX>-N-description.", + "de": "No task ID found in branch name '{branch}'. Expected format: <PREFIX>-N-description.", + "en": "No task ID found in branch name '{branch}'. Expected format: <PREFIX>-N-description.", + "pl": "No task ID found in branch name '{branch}'. Expected format: <PREFIX>-N-description.", + "ru": "No task ID found in branch name '{branch}'. Expected format: <PREFIX>-N-description.", + "zh": "No task ID found in branch name '{branch}'. Expected format: <PREFIX>-N-description.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "No task ID found in branch name '{branch}'. Expected format: {prefix}-N-description.": { + "bg": "No task ID found in branch name '{branch}'. Expected format: {prefix}-N-description.", + "de": "No task ID found in branch name '{branch}'. Expected format: {prefix}-N-description.", + "en": "No task ID found in branch name '{branch}'. Expected format: {prefix}-N-description.", + "pl": "No task ID found in branch name '{branch}'. Expected format: {prefix}-N-description.", + "ru": "No task ID found in branch name '{branch}'. Expected format: {prefix}-N-description.", + "zh": "No task ID found in branch name '{branch}'. Expected format: {prefix}-N-description.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "No unreleased changes found. Nothing to release.": { + "bg": "No unreleased changes found. Nothing to release.", + "de": "No unreleased changes found. Nothing to release.", + "en": "No unreleased changes found. Nothing to release.", + "pl": "Nie znaleziono nieopublikowanych zmian. Nic do wydania.", + "ru": "No unreleased changes found. Nothing to release.", + "zh": "No unreleased changes found. Nothing to release.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "No user-facing changes since {tag} — only workflow/infrastructure files changed. Skipping release.": { + "bg": "No user-facing changes since {tag} — only workflow/infrastructure files changed. Skipping release.", + "de": "No user-facing changes since {tag} — only workflow/infrastructure files changed. Skipping release.", + "en": "No user-facing changes since {tag} — only workflow/infrastructure files changed. Skipping release.", + "pl": "Brak zmian widocznych dla użytkownika od {tag} — tylko pliki workflow/infrastruktury uległy zmianie. Pomijanie wydania.", + "ru": "No user-facing changes since {tag} — only workflow/infrastructure files changed. Skipping release.", + "zh": "No user-facing changes since {tag} — only workflow/infrastructure files changed. Skipping release.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "No versions found.": { + "bg": "No versions found.", + "de": "No versions found.", + "en": "No versions found.", + "pl": "No versions found.", + "ru": "No versions found.", + "zh": "No versions found.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "No workflow runs found for SHA {sha}.": { + "bg": "No workflow runs found for SHA {sha}.", + "de": "No workflow runs found for SHA {sha}.", + "en": "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}.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Nothing to push.": { + "bg": "Nothing to push.", + "de": "Nothing to push.", + "en": "Nothing to push.", + "pl": "Nothing to push.", + "ru": "Nothing to push.", + "zh": "Nothing to push.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Only check staged files (for pre-commit)": { + "bg": "Only check staged files (for pre-commit)", + "de": "Only check staged files (for pre-commit)", + "en": "Only check staged files (for pre-commit)", + "pl": "Only check staged files (for pre-commit)", + "ru": "Only check staged files (for pre-commit)", + "zh": "Only check staged files (for pre-commit)", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Oops! Commit message must follow conventional commit format.\n Expected: <type>: <description>\n Got: {subject}\n Allowed types: feat, fix, chore, docs, style, refactor,\n perf, test, ci, build, revert, BREAKING CHANGE": { + "bg": "Опа! Съобщението за commit трябва да следва конвенционален формат.\n Очаква се: <type>: <description>\n Получено: {subject}\n Разрешени типове: feat, fix, chore, docs, style, refactor,\n perf, test, ci, build, revert, BREAKING CHANGE", + "de": "Ups! Commit-Nachricht muss dem konventionellen Commit-Format folgen.\n Erwartet: <type>: <description>\n Erhalten: {subject}\n Erlaubte Typen: feat, fix, chore, docs, style, refactor,\n perf, test, ci, build, revert, BREAKING CHANGE", + "en": "Oops! Commit message must follow conventional commit format.\n Expected: <type>: <description>\n Got: {subject}\n Allowed types: feat, fix, chore, docs, style, refactor,\n perf, test, ci, build, revert, BREAKING CHANGE", + "pl": "Ups! Wiadomość commit musi być w formacie conventional commit.\n Oczekiwano: <typ>: <opis>\n Otrzymano: {subject}\n Dozwolone typy: feat, fix, chore, docs, style, refactor,\n perf, test, ci, build, revert, BREAKING CHANGE", + "ru": "Ой! Сообщение коммита должно соответствовать формату conventional commit.\n Ожидается: <type>: <description>\n Получено: {subject}\n Допустимые типы: feat, fix, chore, docs, style, refactor,\n perf, test, ci, build, revert, BREAKING CHANGE", + "zh": "哎呀!提交消息必须遵循 conventional commit 格式。\n 预期格式: <type>: <description>\n 实际: {subject}\n 允许的类型: feat, fix, chore, docs, style, refactor,\n perf, test, ci, build, revert, BREAKING CHANGE", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Oops! Do not include task ID ({prefix}-N) in feature branch commits.\n The task ID will be added automatically on merge via CI.": { + "bg": "Oops! Do not include task ID ({prefix}-N) in feature branch commits.\n The task ID will be added automatically on merge via CI.", + "de": "Oops! Do not include task ID ({prefix}-N) in feature branch commits.\n The task ID will be added automatically on merge via CI.", + "en": "Oops! Do not include task ID ({prefix}-N) in feature branch commits.\n The task ID will be added automatically on merge via CI.", + "pl": "Ups! Nie dołączaj ID zadania ({prefix}-N) w commitach gałęzi feature.\n ID zadania zostanie dodane automatycznie przy scaleniu przez CI.", + "ru": "Oops! Do not include task ID ({prefix}-N) in feature branch commits.\n The task ID will be added automatically on merge via CI.", + "zh": "Oops! Do not include task ID ({prefix}-N) in feature branch commits.\n The task ID will be added automatically on merge via CI.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Oops! Gitea PyPI registry publish failed:\n{stderr}": { + "bg": "Опа! Публикуването в Gitea PyPI registry неуспешно:\n{stderr}", + "de": "Ups! Veröffentlichung in der Gitea PyPI-Registry fehlgeschlagen:\n{stderr}", + "en": "Oops! Gitea PyPI registry publish failed:\n{stderr}", + "pl": "Ups! Publikacja w rejestrze Gitea PyPI nie powiodła się:\n{stderr}", + "ru": "Ой! Публикация в Gitea PyPI registry не удалась:\n{stderr}", + "zh": "哎呀!Gitea PyPI registry 发布失败:\n{stderr}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Oops! Master branch commit must follow conventional format after task ID.\n Expected: {prefix}-N: <type>: <description>\n Got: {subject}": { + "bg": "Oops! Master branch commit must follow conventional format after task ID.\n Expected: {prefix}-N: <type>: <description>\n Got: {subject}", + "de": "Oops! Master branch commit must follow conventional format after task ID.\n Expected: {prefix}-N: <type>: <description>\n Got: {subject}", + "en": "Oops! Master branch commit must follow conventional format after task ID.\n Expected: {prefix}-N: <type>: <description>\n Got: {subject}", + "pl": "Ups! Commit gałęzi master musi być w formacie conventional po ID zadania.\n Oczekiwano: {prefix}-N: <typ>: <opis>\n Otrzymano: {subject}", + "ru": "Oops! Master branch commit must follow conventional format after task ID.\n Expected: {prefix}-N: <type>: <description>\n Got: {subject}", + "zh": "Oops! Master branch commit must follow conventional format after task ID.\n Expected: {prefix}-N: <type>: <description>\n Got: {subject}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Oops! Master branch commits must start with a task ID.\n Expected: {prefix}-N: <conventional commit message>\n Got: {subject}": { + "bg": "Oops! Master branch commits must start with a task ID.\n Expected: {prefix}-N: <conventional commit message>\n Got: {subject}", + "de": "Oops! Master branch commits must start with a task ID.\n Expected: {prefix}-N: <conventional commit message>\n Got: {subject}", + "en": "Oops! Master branch commits must start with a task ID.\n Expected: {prefix}-N: <conventional commit message>\n Got: {subject}", + "pl": "Ups! Commity gałęzi master muszą zaczynać się od ID zadania.\n Oczekiwano: {prefix}-N: <conwencjonalna wiadomość commit>\n Otrzymano: {subject}", + "ru": "Oops! Master branch commits must start with a task ID.\n Expected: {prefix}-N: <conventional commit message>\n Got: {subject}", + "zh": "Oops! Master branch commits must start with a task ID.\n Expected: {prefix}-N: <conventional commit message>\n Got: {subject}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Oops! No task ID found in branch name '{branch}'. Branch names must include the task ID prefix (e.g., DEVX-31-fix-bug).": { + "bg": "Ой! Не е намерен ID на задача в името на клона '{branch}'. Имената на клонове трябва да включват префикса за ID на задача (напр. DEVX-31-fix-bug).", + "de": "Hoppla! Keine Task-ID im Branch-Namen '{branch}' gefunden. Branch-Namen müssen das Task-ID-Präfix enthalten (z.B. DEVX-31-fix-bug).", + "en": "Oops! No task ID found in branch name '{branch}'. Branch names must include the task ID prefix (e.g., DEVX-31-fix-bug).", + "pl": "Ups! Nie znaleziono ID zadania w nazwie gałęzi '{branch}'. Nazwy gałęzi muszą zawierać prefiks ID zadania (np., DEVX-31-fix-bug).", + "ru": "Ой! ID задачи не найден в имени ветки '{branch}'. Имена веток должны включать префикс ID задачи (например, DEVX-31-fix-bug).", + "zh": "哎呀!在分支名称 '{branch}' 中未找到任务 ID。分支名称必须包含任务 ID 前缀(例如 DEVX-31-fix-bug)。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Oops! PR title must follow format '{prefix}-N: <task title>'.\n Expected: {task_id}: <task title>\n Got: {pr_title}": { + "bg": "Oops! PR title must follow format '{prefix}-N: <task title>'.\n Expected: {task_id}: <task title>\n Got: {pr_title}", + "de": "Oops! PR title must follow format '{prefix}-N: <task title>'.\n Expected: {task_id}: <task title>\n Got: {pr_title}", + "en": "Oops! PR title must follow format '{prefix}-N: <task title>'.\n Expected: {task_id}: <task title>\n Got: {pr_title}", + "pl": "Ups! Tytuł PR musi być w formacie '{prefix}-N: <tytuł zadania>'.\n Oczekiwano: {task_id}: <tytuł zadania>\n Otrzymano: {pr_title}", + "ru": "Oops! PR title must follow format '{prefix}-N: <task title>'.\n Expected: {task_id}: <task title>\n Got: {pr_title}", + "zh": "Oops! PR title must follow format '{prefix}-N: <task title>'.\n Expected: {task_id}: <task title>\n Got: {pr_title}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Oops! PR title task ID mismatch.\n Branch task ID: {task_id}\n PR title: {pr_title}": { + "bg": "Oops! PR title task ID mismatch.\n Branch task ID: {task_id}\n PR title: {pr_title}", + "de": "Oops! PR title task ID mismatch.\n Branch task ID: {task_id}\n PR title: {pr_title}", + "en": "Oops! PR title task ID mismatch.\n Branch task ID: {task_id}\n PR title: {pr_title}", + "pl": "Ups! Niezgodność ID zadania w tytule PR.\n ID zadania z gałęzi: {task_id}\n Tytuł PR: {pr_title}", + "ru": "Oops! PR title task ID mismatch.\n Branch task ID: {task_id}\n PR title: {pr_title}", + "zh": "Oops! PR title task ID mismatch.\n Branch task ID: {task_id}\n PR title: {pr_title}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Oops! Package build failed:\n{stderr}": { + "bg": "Опа! Сборката на пакета неуспешна:\n{stderr}", + "de": "Ups! Paket-Build fehlgeschlagen:\n{stderr}", + "en": "Oops! Package build failed:\n{stderr}", + "pl": "Ups! Budowanie pakietu nie powiodło się:\n{stderr}", + "ru": "Ой! Сборка пакета не удалась:\n{stderr}", + "zh": "哎呀!包构建失败:\n{stderr}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Oops! PyPI publish failed:\n{stderr}": { + "bg": "Опа! Публикуването в PyPI неуспешно:\n{stderr}", + "de": "Ups! PyPI-Veröffentlichung fehlgeschlagen:\n{stderr}", + "en": "Oops! PyPI publish failed:\n{stderr}", + "pl": "Ups! Publikacja PyPI nie powiodła się:\n{stderr}", + "ru": "Ой! Публикация в PyPI не удалась:\n{stderr}", + "zh": "哎呀!PyPI 发布失败:\n{stderr}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "PASS: All documentation checks passed!": { + "bg": "PASS: All documentation checks passed!", + "de": "PASS: All documentation checks passed!", + "en": "PASS: All documentation checks passed!", + "pl": "PASS: All documentation checks passed!", + "ru": "PASS: All documentation checks passed!", + "zh": "PASS: All documentation checks passed!", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "PR #{pr} rebased successfully. A new CI run will start automatically.\nIf auto-merge is enabled (ready-to-merge label), the next CI run\nwill attempt to merge this PR.": { + "bg": "PR #{pr} rebased successfully. A new CI run will start automatically.\nIf auto-merge is enabled (ready-to-merge label), the next CI run\nwill attempt to merge this PR.", + "de": "PR #{pr} rebased successfully. A new CI run will start automatically.\nIf auto-merge is enabled (ready-to-merge label), the next CI run\nwill attempt to merge this PR.", + "en": "PR #{pr} rebased successfully. A new CI run will start automatically.\nIf auto-merge is enabled (ready-to-merge label), the next CI run\nwill attempt to merge this PR.", + "pl": "PR #{pr} rebased successfully. A new CI run will start automatically.\nIf auto-merge is enabled (ready-to-merge label), the next CI run\nwill attempt to merge this PR.", + "ru": "PR #{pr} rebased successfully. A new CI run will start automatically.\nIf auto-merge is enabled (ready-to-merge label), the next CI run\nwill attempt to merge this PR.", + "zh": "PR #{pr} rebased successfully. A new CI run will start automatically.\nIf auto-merge is enabled (ready-to-merge label), the next CI run\nwill attempt to merge this PR.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "PR already exists: #{index} — {url}": { + "bg": "PR вече съществува: #{index} — {url}", + "de": "PR existiert bereits: #{index} — {url}", + "en": "PR already exists: #{index} — {url}", + "pl": "PR już istnieje: #{index} — {url}", + "ru": "PR уже существует: #{index} — {url}", + "zh": "PR 已存在: #{index} — {url}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "PR has {file_count} files changed (max {max_files}). Excluded: {excluded_count} files.": { + "bg": "PR has {file_count} files changed (max {max_files}). Excluded: {excluded_count} files.", + "de": "PR has {file_count} files changed (max {max_files}). Excluded: {excluded_count} files.", + "en": "PR has {file_count} files changed (max {max_files}). Excluded: {excluded_count} files.", + "pl": "PR has {file_count} files changed (max {max_files}). Excluded: {excluded_count} files.", + "ru": "PR has {file_count} files changed (max {max_files}). Excluded: {excluded_count} files.", + "zh": "PR has {file_count} files changed (max {max_files}). Excluded: {excluded_count} files.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "PR has {line_count} lines changed (max {max_lines}). Excluded: {excluded_count} files.": { + "bg": "PR has {line_count} lines changed (max {max_lines}). Excluded: {excluded_count} files.", + "de": "PR has {line_count} lines changed (max {max_lines}). Excluded: {excluded_count} files.", + "en": "PR has {line_count} lines changed (max {max_lines}). Excluded: {excluded_count} files.", + "pl": "PR has {line_count} lines changed (max {max_lines}). Excluded: {excluded_count} files.", + "ru": "PR has {line_count} lines changed (max {max_lines}). Excluded: {excluded_count} files.", + "zh": "PR has {line_count} lines changed (max {max_lines}). Excluded: {excluded_count} files.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "PR number (to fetch title from Gitea)": { + "bg": "PR number (to fetch title from Gitea)", + "de": "PR number (to fetch title from Gitea)", + "en": "PR number (to fetch title from Gitea)", + "pl": "PR number (to fetch title from Gitea)", + "ru": "PR number (to fetch title from Gitea)", + "zh": "PR number (to fetch title from Gitea)", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "PR number must be an integer, got: {pr_number}": { + "bg": "PR number must be an integer, got: {pr_number}", + "de": "PR number must be an integer, got: {pr_number}", + "en": "PR number must be an integer, got: {pr_number}", + "pl": "Numer PR musi być liczbą całkowitą, otrzymano: {pr_number}", + "ru": "PR number must be an integer, got: {pr_number}", + "zh": "PR number must be an integer, got: {pr_number}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" }, "PR number to fix": { "bg": "PR number to fix", @@ -3741,7 +3175,309 @@ "en": "PR number to fix", "pl": "PR number to fix", "ru": "PR number to fix", - "zh": "PR number to fix" + "zh": "PR number to fix", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "PR size OK: {file_count} files, {line_count} lines (max {max_files} files, {max_lines} lines).": { + "bg": "PR size OK: {file_count} files, {line_count} lines (max {max_files} files, {max_lines} lines).", + "de": "PR size OK: {file_count} files, {line_count} lines (max {max_files} files, {max_lines} lines).", + "en": "PR size OK: {file_count} files, {line_count} lines (max {max_files} files, {max_lines} lines).", + "pl": "PR size OK: {file_count} files, {line_count} lines (max {max_files} files, {max_lines} lines).", + "ru": "PR size OK: {file_count} files, {line_count} lines (max {max_files} files, {max_lines} lines).", + "zh": "PR size OK: {file_count} files, {line_count} lines (max {max_files} files, {max_lines} lines).", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "PR size check failed.": { + "bg": "PR size check failed.", + "de": "PR size check failed.", + "en": "PR size check failed.", + "pl": "PR size check failed.", + "ru": "PR size check failed.", + "zh": "PR size check failed.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "PR title (auto-fetched if --pr-number given)": { + "bg": "PR title (auto-fetched if --pr-number given)", + "de": "PR title (auto-fetched if --pr-number given)", + "en": "PR title (auto-fetched if --pr-number given)", + "pl": "PR title (auto-fetched if --pr-number given)", + "ru": "PR title (auto-fetched if --pr-number given)", + "zh": "PR title (auto-fetched if --pr-number given)", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {pr_title}": { + "bg": "PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {pr_title}", + "de": "PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {pr_title}", + "en": "PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {pr_title}", + "pl": "Tytuł PR nie pasuje do tytułu zadania Vikunja.\n Oczekiwano: {expected}\n Otrzymano: {pr_title}", + "ru": "PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {pr_title}", + "zh": "PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {pr_title}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {title}": { + "bg": "PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {title}", + "de": "PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {title}", + "en": "PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {title}", + "pl": "PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {title}", + "ru": "PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {title}", + "zh": "PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {title}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "PR title must follow format '{prefix}-N: <task title>'.\n Got: {title}": { + "bg": "PR title must follow format '{prefix}-N: <task title>'.\n Got: {title}", + "de": "PR title must follow format '{prefix}-N: <task title>'.\n Got: {title}", + "en": "PR title must follow format '{prefix}-N: <task title>'.\n Got: {title}", + "pl": "PR title must follow format '{prefix}-N: <task title>'.\n Got: {title}", + "ru": "PR title must follow format '{prefix}-N: <task title>'.\n Got: {title}", + "zh": "PR title must follow format '{prefix}-N: <task title>'.\n Got: {title}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "PR title task ID mismatch.\n Branch task ID: {task_id}\n PR title: {title}": { + "bg": "PR title task ID mismatch.\n Branch task ID: {task_id}\n PR title: {title}", + "de": "PR title task ID mismatch.\n Branch task ID: {task_id}\n PR title: {title}", + "en": "PR title task ID mismatch.\n Branch task ID: {task_id}\n PR title: {title}", + "pl": "PR title task ID mismatch.\n Branch task ID: {task_id}\n PR title: {title}", + "ru": "PR title task ID mismatch.\n Branch task ID: {task_id}\n PR title: {title}", + "zh": "PR title task ID mismatch.\n Branch task ID: {task_id}\n PR title: {title}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "PYPI_TOKEN not set and no registry URL configured — skipping PyPI publish. No worries, we'll just create the Gitea release.": { + "bg": "PYPI_TOKEN не е зададен и няма конфигуриран URL на registry — пропускаме публикуването в PyPI. Без притеснения, просто ще създадем Gitea release.", + "de": "PYPI_TOKEN nicht gesetzt und keine Registry-URL konfiguriert — PyPI-Veröffentlichung wird übersprungen. Keine Sorge, wir erstellen einfach das Gitea-Release.", + "en": "PYPI_TOKEN not set and no registry URL configured — skipping PyPI publish. No worries, we'll just create the Gitea release.", + "pl": "PYPI_TOKEN nie ustawiony i brak URL rejestru — pomijanie publikacji PyPI. Bez obaw, utworzymy tylko wydanie Gitea.", + "ru": "PYPI_TOKEN не задан и URL registry не настроен — пропускаем публикацию в PyPI. Не беспокойтесь, мы просто создадим Gitea release.", + "zh": "未设置 PYPI_TOKEN 且未配置 registry URL — 跳过 PyPI 发布。别担心,我们直接创建 Gitea release。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Package name to bump (e.g., grm, sso-bridge)": { + "bg": "Package name to bump (e.g., grm, sso-bridge)", + "de": "Package name to bump (e.g., grm, sso-bridge)", + "en": "Package name to bump (e.g., grm, sso-bridge)", + "pl": "Package name to bump (e.g., grm, sso-bridge)", + "ru": "Package name to bump (e.g., grm, sso-bridge)", + "zh": "Package name to bump (e.g., grm, sso-bridge)", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Package owner not specified. Use --owner or set [tool.devx] repo_owner.": { + "bg": "Package owner not specified. Use --owner or set [tool.devx] repo_owner.", + "de": "Package owner not specified. Use --owner or set [tool.devx] repo_owner.", + "en": "Package owner not specified. Use --owner or set [tool.devx] repo_owner.", + "pl": "Package owner not specified. Use --owner or set [tool.devx] repo_owner.", + "ru": "Package owner not specified. Use --owner or set [tool.devx] repo_owner.", + "zh": "Package owner not specified. Use --owner or set [tool.devx] repo_owner.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Package: {owner}/{name}": { + "bg": "Package: {owner}/{name}", + "de": "Package: {owner}/{name}", + "en": "Package: {owner}/{name}", + "pl": "Package: {owner}/{name}", + "ru": "Package: {owner}/{name}", + "zh": "Package: {owner}/{name}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Parsed owner={owner}, repo={repo} from DEVX_REPO_NAME": { + "bg": "Разбор на owner={owner}, repo={repo} от DEVX_REPO_NAME", + "de": "Owner={owner}, repo={repo} aus DEVX_REPO_NAME analysiert", + "en": "Parsed owner={owner}, repo={repo} from DEVX_REPO_NAME", + "pl": "Przeanalizowano owner={owner}, repo={repo} z DEVX_REPO_NAME", + "ru": "Извлечён owner={owner}, repo={repo} из DEVX_REPO_NAME", + "zh": "从 DEVX_REPO_NAME 解析 owner={owner}, repo={repo}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Path to pyproject.toml (default: pyproject.toml in CWD).": { + "bg": "Path to pyproject.toml (default: pyproject.toml in CWD).", + "de": "Path to pyproject.toml (default: pyproject.toml in CWD).", + "en": "Path to pyproject.toml (default: pyproject.toml in CWD).", + "pl": "Path to pyproject.toml (default: pyproject.toml in CWD).", + "ru": "Path to pyproject.toml (default: pyproject.toml in CWD).", + "zh": "Path to pyproject.toml (default: pyproject.toml in CWD).", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Per-test speed check FAILED: {count} test(s) exceed {limit}s limit.": { + "bg": "Per-test speed check FAILED: {count} test(s) exceed {limit}s limit.", + "de": "Per-test speed check FAILED: {count} test(s) exceed {limit}s limit.", + "en": "Per-test speed check FAILED: {count} test(s) exceed {limit}s limit.", + "pl": "Kontrola szybkości pojedynczego testu NIEUDANA: {count} test(ów) przekracza limit {limit}s.", + "ru": "Per-test speed check FAILED: {count} test(s) exceed {limit}s limit.", + "zh": "Per-test speed check FAILED: {count} test(s) exceed {limit}s limit.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Pre-merge validation failed.": { + "bg": "Pre-merge validation failed.", + "de": "Pre-merge validation failed.", + "en": "Pre-merge validation failed.", + "pl": "Pre-merge validation failed.", + "ru": "Pre-merge validation failed.", + "zh": "Pre-merge validation failed.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Pre-push check passed: task {task_id} exists.": { + "bg": "Pre-push проверката премина: задача {task_id} съществува.", + "de": "Pre-push-Prüfung bestanden: Task {task_id} existiert.", + "en": "Pre-push check passed: task {task_id} exists.", + "pl": "Sprawdzanie pre-push zakończone: zadanie {task_id} istnieje.", + "ru": "Pre-push проверка пройдена: задача {task_id} существует.", + "zh": "Pre-push 检查通过: 任务 {task_id} 存在。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Print warnings but always exit 0": { + "bg": "Print warnings but always exit 0", + "de": "Print warnings but always exit 0", + "en": "Print warnings but always exit 0", + "pl": "Print warnings but always exit 0", + "ru": "Print warnings but always exit 0", + "zh": "Print warnings but always exit 0", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Provide --manifest or both --dockerfile and --name": { + "bg": "Provide --manifest or both --dockerfile and --name", + "de": "Provide --manifest or both --dockerfile and --name", + "en": "Provide --manifest or both --dockerfile and --name", + "pl": "Provide --manifest or both --dockerfile and --name", + "ru": "Provide --manifest or both --dockerfile and --name", + "zh": "Provide --manifest or both --dockerfile and --name", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Provide a commit message file or use --git.": { + "bg": "Provide a commit message file or use --git.", + "de": "Provide a commit message file or use --git.", + "en": "Provide a commit message file or use --git.", + "pl": "Podaj plik komunikatu commitu lub użyj --git.", + "ru": "Provide a commit message file or use --git.", + "zh": "Provide a commit message file or use --git.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Published to Gitea PyPI registry.": { + "bg": "Публикувано в Gitea PyPI registry.", + "de": "In der Gitea PyPI-Registry veröffentlicht.", + "en": "Published to Gitea PyPI registry.", + "pl": "Opublikowano w rejestrze Gitea PyPI.", + "ru": "Опубликовано в Gitea PyPI registry.", + "zh": "已发布到 Gitea PyPI registry。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Published to PyPI.": { + "bg": "Публикувано в PyPI.", + "de": "In PyPI veröffentlicht.", + "en": "Published to PyPI.", + "pl": "Opublikowano w PyPI.", + "ru": "Опубликовано в PyPI.", + "zh": "已发布到 PyPI。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Publishing release {tag}...": { + "bg": "Publishing release {tag}...", + "de": "Publishing release {tag}...", + "en": "Publishing release {tag}...", + "pl": "Publikowanie wydania {tag}...", + "ru": "Publishing release {tag}...", + "zh": "Publishing release {tag}...", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Push attempt {n}/3 failed: {err}": { + "bg": "Push attempt {n}/3 failed: {err}", + "de": "Push attempt {n}/3 failed: {err}", + "en": "Push attempt {n}/3 failed: {err}", + "pl": "Push attempt {n}/3 failed: {err}", + "ru": "Push attempt {n}/3 failed: {err}", + "zh": "Push attempt {n}/3 failed: {err}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Push failed for {tag}: {error}": { + "bg": "Push failed for {tag}: {error}", + "de": "Push failed for {tag}: {error}", + "en": "Push failed for {tag}: {error}", + "pl": "Push failed for {tag}: {error}", + "ru": "Push failed for {tag}: {error}", + "zh": "Push failed for {tag}: {error}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Push failed: {error}": { + "bg": "", + "de": "", + "en": "Push failed: {error}", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Pushed README update with badge SHA {sha}": { + "bg": "Pushed README update with badge SHA {sha}", + "de": "Pushed README update with badge SHA {sha}", + "en": "Pushed README update with badge SHA {sha}", + "pl": "Pushed README update with badge SHA {sha}", + "ru": "Pushed README update with badge SHA {sha}", + "zh": "Pushed README update with badge SHA {sha}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Pushed release commit to master.": { + "bg": "Pushed release commit to master.", + "de": "Pushed release commit to master.", + "en": "Pushed release commit to master.", + "pl": "Wypchnięto commit wydania do master.", + "ru": "Pushed release commit to master.", + "zh": "Pushed release commit to master.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Pushed {branch} to origin.": { + "bg": "Pushed {branch} to origin.", + "de": "Pushed {branch} to origin.", + "en": "Pushed {branch} to origin.", + "pl": "Pushed {branch} to origin.", + "ru": "Pushed {branch} to origin.", + "zh": "Pushed {branch} to origin.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "PyPI publish failed (non-fatal — continuing to Gitea release):\n{error}": { + "bg": "Публикуването в PyPI неуспешно (некритично — продължава към Gitea release):\n{error}", + "de": "PyPI-Veröffentlichung fehlgeschlagen (nicht fatal — Gitea-Release wird fortgesetzt):\n{error}", + "en": "PyPI publish failed (non-fatal — continuing to Gitea release):\n{error}", + "pl": "Publikacja PyPI nie powiodła się (niekrytyczne — kontynuacja Gitea release):\n{error}", + "ru": "Публикация в PyPI не удалась (некритично — продолжаем создание Gitea release):\n{error}", + "zh": "PyPI 发布失败(非致命 — 继续创建 Gitea release):\n{error}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "REPO argument is required (or set GITHUB_REPOSITORY env var).": { + "bg": "REPO argument is required (or set GITHUB_REPOSITORY env var).", + "de": "REPO argument is required (or set GITHUB_REPOSITORY env var).", + "en": "REPO argument is required (or set GITHUB_REPOSITORY env var).", + "pl": "Argument REPO jest wymagany (lub ustaw zmienną GITHUB_REPOSITORY).", + "ru": "REPO argument is required (or set GITHUB_REPOSITORY env var).", + "zh": "REPO argument is required (or set GITHUB_REPOSITORY env var).", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" }, "Real subprocess call(s) detected in test '{test}' without @patch:": { "bg": "Real subprocess call(s) detected in test '{test}' without @patch:", @@ -3749,7 +3485,319 @@ "en": "Real subprocess call(s) detected in test '{test}' without @patch:", "pl": "Real subprocess call(s) detected in test '{test}' without @patch:", "ru": "Real subprocess call(s) detected in test '{test}' without @patch:", - "zh": "Real subprocess call(s) detected in test '{test}' without @patch:" + "zh": "Real subprocess call(s) detected in test '{test}' without @patch:", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Rebase attempt {n}/3 failed: {err}": { + "bg": "Rebase attempt {n}/3 failed: {err}", + "de": "Rebase attempt {n}/3 failed: {err}", + "en": "Rebase attempt {n}/3 failed: {err}", + "pl": "Rebase attempt {n}/3 failed: {err}", + "ru": "Rebase attempt {n}/3 failed: {err}", + "zh": "Rebase attempt {n}/3 failed: {err}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Rebase failed (conflicts or other error):\n{error}\nResolve conflicts and run: git rebase --continue": { + "bg": "Rebase failed (conflicts or other error):\n{error}\nResolve conflicts and run: git rebase --continue", + "de": "Rebase failed (conflicts or other error):\n{error}\nResolve conflicts and run: git rebase --continue", + "en": "Rebase failed (conflicts or other error):\n{error}\nResolve conflicts and run: git rebase --continue", + "pl": "Rebase failed (conflicts or other error):\n{error}\nResolve conflicts and run: git rebase --continue", + "ru": "Rebase failed (conflicts or other error):\n{error}\nResolve conflicts and run: git rebase --continue", + "zh": "Rebase failed (conflicts or other error):\n{error}\nResolve conflicts and run: git rebase --continue", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Rebase failed with HTTP {status}: {message}": { + "bg": "Rebase failed with HTTP {status}: {message}", + "de": "Rebase failed with HTTP {status}: {message}", + "en": "Rebase failed with HTTP {status}: {message}", + "pl": "Rebase failed with HTTP {status}: {message}", + "ru": "Rebase failed with HTTP {status}: {message}", + "zh": "Rebase failed with HTTP {status}: {message}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Rebase successful.": { + "bg": "Rebase successful.", + "de": "Rebase successful.", + "en": "Rebase successful.", + "pl": "Rebase successful.", + "ru": "Rebase successful.", + "zh": "Rebase successful.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Rebasing PR #{pr} via Gitea API...": { + "bg": "Rebasing PR #{pr} via Gitea API...", + "de": "Rebasing PR #{pr} via Gitea API...", + "en": "Rebasing PR #{pr} via Gitea API...", + "pl": "Rebasing PR #{pr} via Gitea API...", + "ru": "Rebasing PR #{pr} via Gitea API...", + "zh": "Rebasing PR #{pr} via Gitea API...", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Registry credentials required: set CI_GITEA_TOKEN and CI_GITEA_USERNAME env vars": { + "bg": "Registry credentials required: set CI_GITEA_TOKEN and CI_GITEA_USERNAME env vars", + "de": "Registry credentials required: set CI_GITEA_TOKEN and CI_GITEA_USERNAME env vars", + "en": "Registry credentials required: set CI_GITEA_TOKEN and CI_GITEA_USERNAME env vars", + "pl": "Registry credentials required: set CI_GITEA_TOKEN and CI_GITEA_USERNAME env vars", + "ru": "Registry credentials required: set CI_GITEA_TOKEN and CI_GITEA_USERNAME env vars", + "zh": "Registry credentials required: set CI_GITEA_TOKEN and CI_GITEA_USERNAME env vars", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Registry login failed": { + "bg": "Registry login failed", + "de": "Registry login failed", + "en": "Registry login failed", + "pl": "Registry login failed", + "ru": "Registry login failed", + "zh": "Registry login failed", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Registry login failed: {error}": { + "bg": "Registry login failed: {error}", + "de": "Registry login failed: {error}", + "en": "Registry login failed: {error}", + "pl": "Registry login failed: {error}", + "ru": "Registry login failed: {error}", + "zh": "Registry login failed: {error}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Regular merge commit — running all post-merge jobs.": { + "bg": "Regular merge commit — running all post-merge jobs.", + "de": "Regular merge commit — running all post-merge jobs.", + "en": "Regular merge commit — running all post-merge jobs.", + "pl": "Regular merge commit — running all post-merge jobs.", + "ru": "Regular merge commit — running all post-merge jobs.", + "zh": "Regular merge commit — running all post-merge jobs.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Release commit — skipping all post-merge jobs.": { + "bg": "Release commit — skipping all post-merge jobs.", + "de": "Release commit — skipping all post-merge jobs.", + "en": "Release commit — skipping all post-merge jobs.", + "pl": "Release commit — skipping all post-merge jobs.", + "ru": "Release commit — skipping all post-merge jobs.", + "zh": "Release commit — skipping all post-merge jobs.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Release creation failed: {error}": { + "bg": "Release creation failed: {error}", + "de": "Release creation failed: {error}", + "en": "Release creation failed: {error}", + "pl": "Tworzenie wydania nie powiodło się: {error}", + "ru": "Release creation failed: {error}", + "zh": "Release creation failed: {error}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Release must be run on master, currently on '{branch}'.": { + "bg": "Release must be run on master, currently on '{branch}'.", + "de": "Release must be run on master, currently on '{branch}'.", + "en": "Release must be run on master, currently on '{branch}'.", + "pl": "Wydanie musi być uruchomione na master, obecnie na '{branch}'.", + "ru": "Release must be run on master, currently on '{branch}'.", + "zh": "Release must be run on master, currently on '{branch}'.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Repo must be in 'owner/name' format, got: {repo}": { + "bg": "Repo must be in 'owner/name' format, got: {repo}", + "de": "Repo must be in 'owner/name' format, got: {repo}", + "en": "Repo must be in 'owner/name' format, got: {repo}", + "pl": "Repo musi być w formacie 'owner/name', otrzymano: {repo}", + "ru": "Repo must be in 'owner/name' format, got: {repo}", + "zh": "Repo must be in 'owner/name' format, got: {repo}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Repository configuration complete.": { + "bg": "Конфигурирането на хранилището е завършено.", + "de": "Repository-Konfiguration abgeschlossen.", + "en": "Repository configuration complete.", + "pl": "Konfiguracja repozytorium zakończona.", + "ru": "Конфигурация репозитория завершена.", + "zh": "仓库配置完成。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Repository in owner/name format": { + "bg": "Repository in owner/name format", + "de": "Repository in owner/name format", + "en": "Repository in owner/name format", + "pl": "Repository in owner/name format", + "ru": "Repository in owner/name format", + "zh": "Repository in owner/name format", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "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.", + "en": "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.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "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.", + "en": "Repository owner not set. Use --owner or DEVX_REPO_OWNER env var.", + "pl": "Właściciel repozytorium nie jest ustawiony. Użyj --owner lub DEVX_REPO_OWNER env var.", + "ru": "Владелец репозитория не установлен. Используйте --owner или DEVX_REPO_OWNER env var.", + "zh": "仓库所有者未设置。使用 --owner 或 DEVX_REPO_OWNER 环境变量。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Required tools missing.": { + "bg": "Липсват задължителни инструменти.", + "de": "Erforderliche Werkzeuge fehlen.", + "en": "Required tools missing.", + "pl": "Brak wymaganych narzędzi.", + "ru": "Отсутствуют обязательные инструменты.", + "zh": "缺少必需的工具。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Roles directory not found: {path}": { + "bg": "Roles directory not found: {path}", + "de": "Roles directory not found: {path}", + "en": "Roles directory not found: {path}", + "pl": "Katalog ról nie znaleziony: {path}", + "ru": "Roles directory not found: {path}", + "zh": "Roles directory not found: {path}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Runner count: {count}": { + "bg": "Runner count: {count}", + "de": "Runner count: {count}", + "en": "Runner count: {count}", + "pl": "Runner count: {count}", + "ru": "Runner count: {count}", + "zh": "Runner count: {count}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Runner index {index} out of range (0..{max})": { + "bg": "Индексът на runner {index} е извън диапазона (0..{max})", + "de": "Runner-Index {index} außerhalb des Bereichs (0..{max})", + "en": "Runner index {index} out of range (0..{max})", + "pl": "Indeks runnera {index} poza zakresem (0..{max})", + "ru": "Индекс runner {index} вне диапазона (0..{max})", + "zh": "Runner 索引 {index} 超出范围 (0..{max})", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Runner index {runner_index} is out of range (must be >= 1)": { + "bg": "Runner index {runner_index} is out of range (must be >= 1)", + "de": "Runner index {runner_index} is out of range (must be >= 1)", + "en": "Runner index {runner_index} is out of range (must be >= 1)", + "pl": "Runner index {runner_index} is out of range (must be >= 1)", + "ru": "Runner index {runner_index} is out of range (must be >= 1)", + "zh": "Runner index {runner_index} is out of range (must be >= 1)", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Runner indices: {indices}": { + "bg": "Runner indices: {indices}", + "de": "Runner indices: {indices}", + "en": "Runner indices: {indices}", + "pl": "Runner indices: {indices}", + "ru": "Runner indices: {indices}", + "zh": "Runner indices: {indices}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Runner {i}: {labels}": { + "bg": "Runner {i}: {labels}", + "de": "Runner {i}: {labels}", + "en": "Runner {i}: {labels}", + "pl": "Runner {i}: {labels}", + "ru": "Runner {i}: {labels}", + "zh": "Runner {i}: {labels}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Running lint checks...": { + "bg": "Running lint checks...", + "de": "Running lint checks...", + "en": "Running lint checks...", + "pl": "Uruchamianie kontroli lint...", + "ru": "Running lint checks...", + "zh": "Running lint checks...", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Running tests...": { + "bg": "Running tests...", + "de": "Running tests...", + "en": "Running tests...", + "pl": "Uruchamianie testów...", + "ru": "Running tests...", + "zh": "Running tests...", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Running: {cmd}": { + "bg": "Running: {cmd}", + "de": "Running: {cmd}", + "en": "Running: {cmd}", + "pl": "Running: {cmd}", + "ru": "Running: {cmd}", + "zh": "Running: {cmd}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "SSH key set up successfully": { + "bg": "SSH ключът е настроен успешно", + "de": "SSH-Schlüssel erfolgreich eingerichtet", + "en": "SSH key set up successfully", + "pl": "Klucz SSH skonfigurowany pomyślnie", + "ru": "SSH-ключ успешно настроен", + "zh": "SSH 密钥设置成功", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "SSH key setup skipped (no key provided)": { + "bg": "Настройката на SSH ключ е пропусната (не е предоставен ключ)", + "de": "SSH-Schlüssel-Setup übersprungen (kein Schlüssel bereitgestellt)", + "en": "SSH key setup skipped (no key provided)", + "pl": "Pominięto konfigurację klucza SSH (brak klucza)", + "ru": "Настройка SSH-ключа пропущена (ключ не предоставлен)", + "zh": "SSH 密钥设置已跳过(未提供密钥)", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "SSH_PRIVATE_KEY not set — skipping SSH key setup": { + "bg": "SSH_PRIVATE_KEY не е зададен — пропускане на SSH ключ настройката", + "de": "SSH_PRIVATE_KEY nicht gesetzt — SSH-Schlüssel-Setup übersprungen", + "en": "SSH_PRIVATE_KEY not set — skipping SSH key setup", + "pl": "SSH_PRIVATE_KEY nie ustawione — pomijanie konfiguracji klucza SSH", + "ru": "SSH_PRIVATE_KEY не задан — пропуск настройки SSH-ключа", + "zh": "SSH_PRIVATE_KEY 未设置 — 跳过 SSH 密钥设置", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Show what would be done without creating PR": { + "bg": "Show what would be done without creating PR", + "de": "Show what would be done without creating PR", + "en": "Show what would be done without creating PR", + "pl": "Show what would be done without creating PR", + "ru": "Show what would be done without creating PR", + "zh": "Show what would be done without creating PR", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" }, "Show what would change without updating": { "bg": "Show what would change without updating", @@ -3757,7 +3805,189 @@ "en": "Show what would change without updating", "pl": "Show what would change without updating", "ru": "Show what would change without updating", - "zh": "Show what would change without updating" + "zh": "Show what would change without updating", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Single platform to test against": { + "bg": "Single platform to test against", + "de": "Single platform to test against", + "en": "Single platform to test against", + "pl": "Single platform to test against", + "ru": "Single platform to test against", + "zh": "Single platform to test against", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Skip Vikunja title match check": { + "bg": "Skip Vikunja title match check", + "de": "Skip Vikunja title match check", + "en": "Skip Vikunja title match check", + "pl": "Skip Vikunja title match check", + "ru": "Skip Vikunja title match check", + "zh": "Skip Vikunja title match check", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Skip branch-behind-master check": { + "bg": "Skip branch-behind-master check", + "de": "Skip branch-behind-master check", + "en": "Skip branch-behind-master check", + "pl": "Skip branch-behind-master check", + "ru": "Skip branch-behind-master check", + "zh": "Skip branch-behind-master check", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Skipping commit push — no staged changes.": { + "bg": "Skipping commit push — no staged changes.", + "de": "Skipping commit push — no staged changes.", + "en": "Skipping commit push — no staged changes.", + "pl": "Pomijanie wypchnięcia commit — brak zmian w staging.", + "ru": "Skipping commit push — no staged changes.", + "zh": "Skipping commit push — no staged changes.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Skipping — runner index {runner_index} > max runners {max_runners}": { + "bg": "Skipping — runner index {runner_index} > max runners {max_runners}", + "de": "Skipping — runner index {runner_index} > max runners {max_runners}", + "en": "Skipping — runner index {runner_index} > max runners {max_runners}", + "pl": "Skipping — runner index {runner_index} > max runners {max_runners}", + "ru": "Skipping — runner index {runner_index} > max runners {max_runners}", + "zh": "Skipping — runner index {runner_index} > max runners {max_runners}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Source repo that published (owner/name)": { + "bg": "Source repo that published (owner/name)", + "de": "Source repo that published (owner/name)", + "en": "Source repo that published (owner/name)", + "pl": "Source repo that published (owner/name)", + "ru": "Source repo that published (owner/name)", + "zh": "Source repo that published (owner/name)", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Spec validation failed.": { + "bg": "Spec validation failed.", + "de": "Spec validation failed.", + "en": "Spec validation failed.", + "pl": "Spec validation failed.", + "ru": "Spec validation failed.", + "zh": "Spec validation failed.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Synced to latest origin/{branch}": { + "bg": "Synced to latest origin/{branch}", + "de": "Synced to latest origin/{branch}", + "en": "Synced to latest origin/{branch}", + "pl": "Synced to latest origin/{branch}", + "ru": "Synced to latest origin/{branch}", + "zh": "Synced to latest origin/{branch}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Syncing files...": { + "bg": "", + "de": "", + "en": "Syncing files...", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Syncing {count} documentation pages to wiki via Git...": { + "bg": "", + "de": "", + "en": "Syncing {count} documentation pages to wiki via Git...", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Tag consistency check failed.": { + "bg": "Tag consistency check failed.", + "de": "Tag consistency check failed.", + "en": "Tag consistency check failed.", + "pl": "Kontrola zgodności tagów nie powiodła się.", + "ru": "Tag consistency check failed.", + "zh": "Tag consistency check failed.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Tag is required (or use --from-tag).": { + "bg": "Tag is required (or use --from-tag).", + "de": "Tag is required (or use --from-tag).", + "en": "Tag is required (or use --from-tag).", + "pl": "Tag jest wymagany (lub użyj --from-tag).", + "ru": "Tag is required (or use --from-tag).", + "zh": "Tag is required (or use --from-tag).", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Tag v{version} already existed. Publish workflow should already have been triggered.": { + "bg": "Tag v{version} already existed. Publish workflow should already have been triggered.", + "de": "Tag v{version} already existed. Publish workflow should already have been triggered.", + "en": "Tag v{version} already existed. Publish workflow should already have been triggered.", + "pl": "Tag v{version} już istniał. Workflow publikacji powinien już być uruchomiony.", + "ru": "Tag v{version} already existed. Publish workflow should already have been triggered.", + "zh": "Tag v{version} already existed. Publish workflow should already have been triggered.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Tag {tag} already exists and points to HEAD. Skipping creation.": { + "bg": "Tag {tag} already exists and points to HEAD. Skipping creation.", + "de": "Tag {tag} already exists and points to HEAD. Skipping creation.", + "en": "Tag {tag} already exists and points to HEAD. Skipping creation.", + "pl": "Tag {tag} już istnieje i wskazuje na HEAD. Pomijanie tworzenia.", + "ru": "Tag {tag} already exists and points to HEAD. Skipping creation.", + "zh": "Tag {tag} already exists and points to HEAD. Skipping creation.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Tag {tag} already exists but points to {tag_commit} (expected HEAD {head_commit}). This indicates a tag/commit misalignment. Run 'python3 -m devx.ci.release --verify' for details.": { + "bg": "Tag {tag} already exists but points to {tag_commit} (expected HEAD {head_commit}). This indicates a tag/commit misalignment. Run 'python3 -m devx.ci.release --verify' for details.", + "de": "Tag {tag} already exists but points to {tag_commit} (expected HEAD {head_commit}). This indicates a tag/commit misalignment. Run 'python3 -m devx.ci.release --verify' for details.", + "en": "Tag {tag} already exists but points to {tag_commit} (expected HEAD {head_commit}). This indicates a tag/commit misalignment. Run 'python3 -m devx.ci.release --verify' for details.", + "pl": "Tag {tag} już istnieje ale wskazuje na {tag_commit} (oczekiwano HEAD {head_commit}). Wskazuje to na niezgodność tag/commit. Uruchom 'python3 -m devx.ci.release --verify', aby uzyskać szczegóły.", + "ru": "Tag {tag} already exists but points to {tag_commit} (expected HEAD {head_commit}). This indicates a tag/commit misalignment. Run 'python3 -m devx.ci.release --verify' for details.", + "zh": "Tag {tag} already exists but points to {tag_commit} (expected HEAD {head_commit}). This indicates a tag/commit misalignment. Run 'python3 -m devx.ci.release --verify' for details.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Target repo (owner/name) to create PR in": { + "bg": "Target repo (owner/name) to create PR in", + "de": "Target repo (owner/name) to create PR in", + "en": "Target repo (owner/name) to create PR in", + "pl": "Target repo (owner/name) to create PR in", + "ru": "Target repo (owner/name) to create PR in", + "zh": "Target repo (owner/name) to create PR in", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Task ID: {task_id}": { + "bg": "Task ID: {task_id}", + "de": "Task ID: {task_id}", + "en": "Task ID: {task_id}", + "pl": "ID zadania: {task_id}", + "ru": "Task ID: {task_id}", + "zh": "Task ID: {task_id}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Test '{name}' took {elapsed:.2f}s (limit: {limit}s). Optimise: use lighter fixtures, reduce I/O, or mock external calls.": { + "bg": "Test '{name}' took {elapsed:.2f}s (limit: {limit}s). Optimise: use lighter fixtures, reduce I/O, or mock external calls.", + "de": "Test '{name}' took {elapsed:.2f}s (limit: {limit}s). Optimise: use lighter fixtures, reduce I/O, or mock external calls.", + "en": "Test '{name}' took {elapsed:.2f}s (limit: {limit}s). Optimise: use lighter fixtures, reduce I/O, or mock external calls.", + "pl": "Test '{name}' trwał {elapsed:.2f}s (limit: {limit}s). Optymalizuj: użyj lżejszych fixtures, zmniejsz I/O, lub mockuj zewnętrzne wywołania.", + "ru": "Test '{name}' took {elapsed:.2f}s (limit: {limit}s). Optimise: use lighter fixtures, reduce I/O, or mock external calls.", + "zh": "Test '{name}' took {elapsed:.2f}s (limit: {limit}s). Optimise: use lighter fixtures, reduce I/O, or mock external calls.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" }, "Test isolation check FAILED: {count} violation(s) in {files} file(s).": { "bg": "Test isolation check FAILED: {count} violation(s) in {files} file(s).", @@ -3765,7 +3995,9 @@ "en": "Test isolation check FAILED: {count} violation(s) in {files} file(s).", "pl": "Test isolation check FAILED: {count} violation(s) in {files} file(s).", "ru": "Test isolation check FAILED: {count} violation(s) in {files} file(s).", - "zh": "Test isolation check FAILED: {count} violation(s) in {files} file(s)." + "zh": "Test isolation check FAILED: {count} violation(s) in {files} file(s).", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" }, "Test isolation check passed with {count} advisory warning(s) in {files} file(s).": { "bg": "Test isolation check passed with {count} advisory warning(s) in {files} file(s).", @@ -3773,7 +4005,49 @@ "en": "Test isolation check passed with {count} advisory warning(s) in {files} file(s).", "pl": "Test isolation check passed with {count} advisory warning(s) in {files} file(s).", "ru": "Test isolation check passed with {count} advisory warning(s) in {files} file(s).", - "zh": "Test isolation check passed with {count} advisory warning(s) in {files} file(s)." + "zh": "Test isolation check passed with {count} advisory warning(s) in {files} file(s).", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Test isolation check passed: {count} test files analyzed, no violations found.": { + "bg": "Проверката за изолация на тестове премина: анализирани са {count} тестови файла, няма нарушения.", + "de": "Testisolationsprüfung bestanden: {count} Testdateien analysiert, keine Verstöße gefunden.", + "en": "Test isolation check passed: {count} test files analyzed, no violations found.", + "pl": "Sprawdzenie izolacji testów zaliczone: przeanalizowano {count} plików testowych, brak naruszeń.", + "ru": "Проверка изоляции тестов пройдена: проанализировано {count} тестовых файлов, нарушений не найдено.", + "zh": "测试隔离检查通过:已分析 {count} 个测试文件,未发现违规。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Tests failed — refusing to release. Fix test failures first.\n{stderr}": { + "bg": "Tests failed — refusing to release. Fix test failures first.\n{stderr}", + "de": "Tests failed — refusing to release. Fix test failures first.\n{stderr}", + "en": "Tests failed — refusing to release. Fix test failures first.\n{stderr}", + "pl": "Testy nie powiodły się — odmowa wydania. Najpierw napraw niepowodzenia testów.\n{stderr}", + "ru": "Tests failed — refusing to release. Fix test failures first.\n{stderr}", + "zh": "Tests failed — refusing to release. Fix test failures first.\n{stderr}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Tests passed.": { + "bg": "Tests passed.", + "de": "Tests passed.", + "en": "Tests passed.", + "pl": "Testy zakończone pomyślnie.", + "ru": "Tests passed.", + "zh": "Tests passed.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Timeout reached after {timeout}s.": { + "bg": "Timeout reached after {timeout}s.", + "de": "Timeout reached after {timeout}s.", + "en": "Timeout reached after {timeout}s.", + "pl": "Timeout reached after {timeout}s.", + "ru": "Timeout reached after {timeout}s.", + "zh": "Timeout reached after {timeout}s.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" }, "Transitive-subprocess advisories (runtime audit is authoritative):": { "bg": "Transitive-subprocess advisories (runtime audit is authoritative):", @@ -3781,7 +4055,879 @@ "en": "Transitive-subprocess advisories (runtime audit is authoritative):", "pl": "Transitive-subprocess advisories (runtime audit is authoritative):", "ru": "Transitive-subprocess advisories (runtime audit is authoritative):", - "zh": "Transitive-subprocess advisories (runtime audit is authoritative):" + "zh": "Transitive-subprocess advisories (runtime audit is authoritative):", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Unit tests passed in {duration:.2f}s (under {max}s limit, all tests under {single}s per-test limit).": { + "bg": "Unit tests passed in {duration:.2f}s (under {max}s limit, all tests under {single}s per-test limit).", + "de": "Unit tests passed in {duration:.2f}s (under {max}s limit, all tests under {single}s per-test limit).", + "en": "Unit tests passed in {duration:.2f}s (under {max}s limit, all tests under {single}s per-test limit).", + "pl": "Testy jednostkowe zakończone pomyślnie w {duration:.2f}s (poniżej limitu {max}s, wszystkie testy poniżej limitu {single}s na test).", + "ru": "Unit tests passed in {duration:.2f}s (under {max}s limit, all tests under {single}s per-test limit).", + "zh": "Unit tests passed in {duration:.2f}s (under {max}s limit, all tests under {single}s per-test limit).", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Unit tests too slow: {duration:.2f}s (max allowed: {max}s).\n Fix: run 'make pytest-cov' to profile, then optimise slow tests.\n Hint: avoid unnecessary imports, use lighter mocks, or cache fixtures.": { + "bg": "Unit tests too slow: {duration:.2f}s (max allowed: {max}s).\n Fix: run 'make pytest-cov' to profile, then optimise slow tests.\n Hint: avoid unnecessary imports, use lighter mocks, or cache fixtures.", + "de": "Unit tests too slow: {duration:.2f}s (max allowed: {max}s).\n Fix: run 'make pytest-cov' to profile, then optimise slow tests.\n Hint: avoid unnecessary imports, use lighter mocks, or cache fixtures.", + "en": "Unit tests too slow: {duration:.2f}s (max allowed: {max}s).\n Fix: run 'make pytest-cov' to profile, then optimise slow tests.\n Hint: avoid unnecessary imports, use lighter mocks, or cache fixtures.", + "pl": "Testy jednostkowe zbyt wolne: {duration:.2f}s (maks. dozwolone: {max}s).\n Naprawa: uruchom 'make pytest-cov' do profilowania, następnie zoptymalizuj wolne testy.\n Wskazówka: unikaj niepotrzebnych importów, użyj lżejszych mocków, lub buforuj fixtures.", + "ru": "Unit tests too slow: {duration:.2f}s (max allowed: {max}s).\n Fix: run 'make pytest-cov' to profile, then optimise slow tests.\n Hint: avoid unnecessary imports, use lighter mocks, or cache fixtures.", + "zh": "Unit tests too slow: {duration:.2f}s (max allowed: {max}s).\n Fix: run 'make pytest-cov' to profile, then optimise slow tests.\n Hint: avoid unnecessary imports, use lighter mocks, or cache fixtures.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Unknown check category '{check}'. Available: all, user-facing{tags}": { + "bg": "Unknown check category '{check}'. Available: all, user-facing{tags}", + "de": "Unknown check category '{check}'. Available: all, user-facing{tags}", + "en": "Unknown check category '{check}'. Available: all, user-facing{tags}", + "pl": "Nieznana kategoria kontroli '{check}'. Dostępne: all, user-facing{tags}", + "ru": "Unknown check category '{check}'. Available: all, user-facing{tags}", + "zh": "Unknown check category '{check}'. Available: all, user-facing{tags}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Updated badge URLs in {filename}": { + "bg": "Updated badge URLs in {filename}", + "de": "Updated badge URLs in {filename}", + "en": "Updated badge URLs in {filename}", + "pl": "Updated badge URLs in {filename}", + "ru": "Updated badge URLs in {filename}", + "zh": "Updated badge URLs in {filename}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Updated documentation version references to v{version}": { + "bg": "", + "de": "", + "en": "Updated documentation version references to v{version}", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Updated version in {init}": { + "bg": "Updated version in {init}", + "de": "Updated version in {init}", + "en": "Updated version in {init}", + "pl": "Zaktualizowano wersję w {init}", + "ru": "Updated version in {init}", + "zh": "Updated version in {init}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Updated {changelog_file}": { + "bg": "Updated {changelog_file}", + "de": "Updated {changelog_file}", + "en": "Updated {changelog_file}", + "pl": "Zaktualizowano {changelog_file}", + "ru": "Updated {changelog_file}", + "zh": "Updated {changelog_file}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Use string comparison or _is_truthy()/_is_falsy() helpers instead. Add '{marker}' to suppress individual lines.": { + "bg": "Използвайте сравнение на низове или _is_truthy()/_is_falsy() помощници. Добавете '{marker}' за потискане на отделни редове.", + "de": "Verwenden Sie String-Vergleich oder _is_truthy()/_is_falsy() Hilfsfunktionen. Fügen Sie '{marker}' hinzu, um einzelne Zeilen zu unterdrücken.", + "en": "Use string comparison or _is_truthy()/_is_falsy() helpers instead. Add '{marker}' to suppress individual lines.", + "pl": "Użyj porównania ciągów lub pomocników _is_truthy()/_is_falsy(). Dodaj '{marker}', aby pominąć pojedyncze linie.", + "ru": "Используйте строковое сравнение или помощники _is_truthy()/_is_falsy(). Добавьте '{marker}' для подавления отдельных строк.", + "zh": "使用字符串比较或 _is_truthy()/_is_falsy() 辅助函数。添加 '{marker}' 以抑制个别行。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "VIKUNJA_TOKEN is not set. Required to derive PR title.": { + "bg": "VIKUNJA_TOKEN не е зададен. Необходим за извличане на PR заглавие.", + "de": "VIKUNJA_TOKEN nicht gesetzt. Erforderlich zum Ableiten des PR-Titels.", + "en": "VIKUNJA_TOKEN is not set. Required to derive PR title.", + "pl": "VIKUNJA_TOKEN nie jest ustawiony. Wymagany do pobrania tytułu PR.", + "ru": "VIKUNJA_TOKEN не установлен. Требуется для получения заголовка PR.", + "zh": "VIKUNJA_TOKEN 未设置。推导 PR 标题所需。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "VIKUNJA_TOKEN is not set. Set it in .env or environment.": { + "bg": "VIKUNJA_TOKEN не е зададен. Задайте го в .env или средата.", + "de": "VIKUNJA_TOKEN nicht gesetzt. In .env oder Umgebung setzen.", + "en": "VIKUNJA_TOKEN is not set. Set it in .env or environment.", + "pl": "VIKUNJA_TOKEN nie jest ustawiony. Ustaw go w .env lub środowisku.", + "ru": "VIKUNJA_TOKEN не установлен. Установите его в .env или среде.", + "zh": "VIKUNJA_TOKEN 未设置。在 .env 或环境中设置它。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "VIKUNJA_TOKEN is not set. This is required in CI to validate PR titles.": { + "bg": "VIKUNJA_TOKEN is not set. This is required in CI to validate PR titles.", + "de": "VIKUNJA_TOKEN is not set. This is required in CI to validate PR titles.", + "en": "VIKUNJA_TOKEN is not set. This is required in CI to validate PR titles.", + "pl": "VIKUNJA_TOKEN nie jest ustawiony. Jest to wymagane w CI do walidacji tytułów PR.", + "ru": "VIKUNJA_TOKEN is not set. This is required in CI to validate PR titles.", + "zh": "VIKUNJA_TOKEN is not set. This is required in CI to validate PR titles.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Version file: {file}": { + "bg": "Version file: {file}", + "de": "Version file: {file}", + "en": "Version file: {file}", + "pl": "Plik wersji: {file}", + "ru": "Version file: {file}", + "zh": "Version file: {file}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Version stays at v{version} — no version bump from git-cliff. Commits since last tag don't warrant a new release. Skipping.": { + "bg": "", + "de": "", + "en": "Version stays at v{version} — no version bump from git-cliff. Commits since last tag don't warrant a new release. Skipping.", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Vikunja API error (HTTP {status}): {message}. Task {task_id} was NOT updated. The merge succeeded but the Vikunja task needs manual update.": { + "bg": "Vikunja API error (HTTP {status}): {message}. Task {task_id} was NOT updated. The merge succeeded but the Vikunja task needs manual update.", + "de": "Vikunja API error (HTTP {status}): {message}. Task {task_id} was NOT updated. The merge succeeded but the Vikunja task needs manual update.", + "en": "Vikunja API error (HTTP {status}): {message}. Task {task_id} was NOT updated. The merge succeeded but the Vikunja task needs manual update.", + "pl": "Błąd API Vikunja (HTTP {status}): {message}. Zadanie {task_id} NIE zostało zaktualizowane. Scalenie powiodło się ale zadanie Vikunja wymaga ręcznej aktualizacji.", + "ru": "Vikunja API error (HTTP {status}): {message}. Task {task_id} was NOT updated. The merge succeeded but the Vikunja task needs manual update.", + "zh": "Vikunja API error (HTTP {status}): {message}. Task {task_id} was NOT updated. The merge succeeded but the Vikunja task needs manual update.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Vikunja task title '{title}' starts with '{prefix}:'. The task title should NOT include the '{prefix}' prefix — it is automatically added to the PR title. Update the Vikunja task title to remove the prefix.": { + "bg": "Заглавието на задачата във Vikunja '{title}' започва с '{prefix}:'. Заглавието на задачата НЕ трябва да съдържа префикса '{prefix}' — той се добавя автоматично към заглавието на PR. Актуализирайте заглавието на задачата във Vikunja, за да премахнете префикса.", + "de": "Der Vikunja-Aufgabentitel '{title}' beginnt mit '{prefix}:'. Der Aufgabentitel darf NICHT den Präfix '{prefix}' enthalten — er wird automatisch zum PR-Titel hinzugefügt. Aktualisieren Sie den Vikunja-Aufgabentitel, um den Präfix zu entfernen.", + "en": "Vikunja task title '{title}' starts with '{prefix}:'. The task title should NOT include the '{prefix}' prefix — it is automatically added to the PR title. Update the Vikunja task title to remove the prefix.", + "pl": "Tytuł zadania Vikunja '{title}' zaczyna się od '{prefix}:'. Tytuł zadania nie powinien zawierać prefiksu '{prefix}' — jest on automatycznie dodawany do tytułu PR. Zaktualizuj tytuł zadania Vikunja, aby usunąć prefiks.", + "ru": "Заголовок задачи Vikunja '{title}' начинается с '{prefix}:'. Заголовок задачи НЕ должен включать префикс '{prefix}' — он автоматически добавляется к заголовку PR. Обновите заголовок задачи Vikunja, чтобы удалить префикс.", + "zh": "Vikunja 任务标题 '{title}' 以 '{prefix}:' 开头。任务标题不应包含 '{prefix}' 前缀 — 它会自动添加到 PR 标题中。请更新 Vikunja 任务标题以删除前缀。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Vikunja task {task_id} not found in project {project_id}.\n Create it first:\n python -m devx.tools.create_task --title \"Task title\"\n Or check that the task ID in the branch name is correct.": { + "bg": "Vikunja задача {task_id} не е намерена в проект {project_id}.\n Създайте я първо:\n python -m devx.tools.create_task --title \"Заглавие на задача\"\n Или проверете че ID на задачата в името на клона е правилно.", + "de": "Vikunja-Task {task_id} in Projekt {project_id} nicht gefunden.\n Zuerst erstellen:\n python -m devx.tools.create_task --title \"Task-Titel\"\n Oder prüfen, ob die Task-ID im Branch-Namen korrekt ist.", + "en": "Vikunja task {task_id} not found in project {project_id}.\n Create it first:\n python -m devx.tools.create_task --title \"Task title\"\n Or check that the task ID in the branch name is correct.", + "pl": "Zadanie Vikunja {task_id} nie znalezione w projekcie {project_id}.\n Utwórz je najpierw:\n python -m devx.tools.create_task --title \"Tytuł zadania\"\n Lub sprawdź, czy ID zadania w nazwie gałęzi jest poprawne.", + "ru": "Задача Vikunja {task_id} не найдена в проекте {project_id}.\n Сначала создайте её:\n python -m devx.tools.create_task --title \"Заголовок задачи\"\n Или проверьте, что ID задачи в имени ветки корректен.", + "zh": "在项目 {project_id} 中找不到 Vikunja 任务 {task_id}。\n 请先创建:\n python -m devx.tools.create_task --title \"任务标题\"\n 或检查分支名称中的任务 ID 是否正确。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "WARN: .venv has Python {version}, but >={req} is required.": { + "bg": "ПРЕДУПРЕЖДЕНИЕ: .venv има Python {version}, но се изисква >={req}.", + "de": "WARNUNG: .venv hat Python {version}, aber >={req} ist erforderlich.", + "en": "WARN: .venv has Python {version}, but >={req} is required.", + "pl": "OSTRZEŻENIE: .venv ma Python {version}, ale wymagane jest >={req}.", + "ru": "ПРЕДУПРЕЖДЕНИЕ: в .venv установлен Python {version}, но требуется >={req}.", + "zh": "警告: .venv 的 Python 版本为 {version},但要求 >={req}。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "WARN: .venv not found. Run 'make setup-venv' to create it.": { + "bg": "ПРЕДУПРЕЖДЕНИЕ: .venv не е намерен. Изпълнете 'make setup-venv' за създаване.", + "de": "WARNUNG: .venv nicht gefunden. Führen Sie 'make setup-venv' aus, um es zu erstellen.", + "en": "WARN: .venv not found. Run 'make setup-venv' to create it.", + "pl": "OSTRZEŻENIE: Nie znaleziono .venv. Uruchom 'make setup-venv', aby utworzyć.", + "ru": "ПРЕДУПРЕЖДЕНИЕ: .venv не найден. Выполните 'make setup-venv' для создания.", + "zh": "警告: 未找到 .venv。运行 'make setup-venv' 来创建。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "WARN: Could not determine Python version in .venv.": { + "bg": "ПРЕДУПРЕЖДЕНИЕ: Не може да се определи версията на Python в .venv.", + "de": "WARNUNG: Python-Version in .venv konnte nicht bestimmt werden.", + "en": "WARN: Could not determine Python version in .venv.", + "pl": "OSTRZEŻENIE: Nie można określić wersji Python w .venv.", + "ru": "ПРЕДУПРЕЖДЕНИЕ: Не удалось определить версию Python в .venv.", + "zh": "警告: 无法确定 .venv 中的 Python 版本。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "WARN: Could not parse Python version '{version}'.": { + "bg": "ПРЕДУПРЕЖДЕНИЕ: Не може да се анализира версията на Python '{version}'.", + "de": "WARNUNG: Python-Version '{version}' konnte nicht analysiert werden.", + "en": "WARN: Could not parse Python version '{version}'.", + "pl": "OSTRZEŻENIE: Nie można przeanalizować wersji Python '{version}'.", + "ru": "ПРЕДУПРЕЖДЕНИЕ: Не удалось разобрать версию Python '{version}'.", + "zh": "警告: 无法解析 Python 版本 '{version}'。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "WARNING: --skip-tests passed — skipping test verification.": { + "bg": "WARNING: --skip-tests passed — skipping test verification.", + "de": "WARNING: --skip-tests passed — skipping test verification.", + "en": "WARNING: --skip-tests passed — skipping test verification.", + "pl": "OSTRZEŻENIE: --skip-tests przekazane — pomijanie weryfikacji testów.", + "ru": "WARNING: --skip-tests passed — skipping test verification.", + "zh": "WARNING: --skip-tests passed — skipping test verification.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "WARNING: .taskid file ({file_id}) is deprecated and disagrees with branch name ({branch_id}). Delete .taskid from the repo — branch name is the sole source of truth.": { + "bg": "ВНИМАНИЕ: Файлът .taskid ({file_id}) е остарял и не съвпада с името на клона ({branch_id}). Изтрийте .taskid от хранилището — името на клона е единственият източник на истината.", + "de": "WARNUNG: Die Datei .taskid ({file_id}) ist veraltet und stimmt nicht mit dem Branch-Namen ({branch_id}) überein. Löschen Sie .taskid aus dem Repo — der Branch-Name ist die einzige Wahrheitsquelle.", + "en": "WARNING: .taskid file ({file_id}) is deprecated and disagrees with branch name ({branch_id}). Delete .taskid from the repo — branch name is the sole source of truth.", + "pl": "OSTRZEŻENIE: plik .taskid ({file_id}) jest przestarzały i niezgodny z nazwą gałęzi ({branch_id}). Usuń .taskid z repozytorium — nazwa gałęzi jest jedynym źródłem prawdy.", + "ru": "ВНИМАНИЕ: Файл .taskid ({file_id}) устарел и не совпадает с именем ветки ({branch_id}). Удалите .taskid из репозитория — имя ветки — единственный источник истины.", + "zh": "警告:.taskid 文件 ({file_id}) 已弃用,与分支名称 ({branch_id}) 不一致。请从仓库中删除 .taskid — 分支名称是唯一的真实来源。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "WARNING: VIKUNJA_TOKEN not set — skipping task existence check. Set it in .env to enable full validation.": { + "bg": "ПРЕДУПРЕЖДЕНИЕ: VIKUNJA_TOKEN не е зададен — пропускане на проверката за съществуване на задача. Задайте го в .env за пълна валидация.", + "de": "WARNUNG: VIKUNJA_TOKEN nicht gesetzt — Task-Existenzprüfung übersprungen. In .env setzen für volle Validierung.", + "en": "WARNING: VIKUNJA_TOKEN not set — skipping task existence check. Set it in .env to enable full validation.", + "pl": "OSTRZEŻENIE: VIKUNJA_TOKEN nie jest ustawiony — pomijanie sprawdzania istnienia zadania. Ustaw w .env, aby włączyć pełną walidację.", + "ru": "ПРЕДУПРЕЖДЕНИЕ: VIKUNJA_TOKEN не установлен — пропуск проверки существования задачи. Установите в .env для полной проверки.", + "zh": "警告: VIKUNJA_TOKEN 未设置 — 跳过任务存在性检查。在 .env 中设置以启用完整验证。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "WARNING: Version badge shows stale version (expected v{version}) — regenerating": { + "bg": "", + "de": "", + "en": "WARNING: Version badge shows stale version (expected v{version}) — regenerating", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "WARNING: check_doc_versions --fix failed (rc={rc}): {err}": { + "bg": "", + "de": "", + "en": "WARNING: check_doc_versions --fix failed (rc={rc}): {err}", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Waiting 5s for Gitea to process pushed commits...": { + "bg": "", + "de": "", + "en": "Waiting 5s for Gitea to process pushed commits...", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "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)...", + "en": "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)...", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Warning: could not fetch tags from origin.": { + "bg": "Warning: could not fetch tags from origin.", + "de": "Warning: could not fetch tags from origin.", + "en": "Warning: could not fetch tags from origin.", + "pl": "Ostrzeżenie: nie udało się pobrać tagów z origin.", + "ru": "Warning: could not fetch tags from origin.", + "zh": "Warning: could not fetch tags from origin.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Warning: instance-level runners query failed: {error}": { + "bg": "Warning: instance-level runners query failed: {error}", + "de": "Warning: instance-level runners query failed: {error}", + "en": "Warning: instance-level runners query failed: {error}", + "pl": "Warning: instance-level runners query failed: {error}", + "ru": "Warning: instance-level runners query failed: {error}", + "zh": "Warning: instance-level runners query failed: {error}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Warning: instance-level runners query returned HTTP {status}": { + "bg": "Warning: instance-level runners query returned HTTP {status}", + "de": "Warning: instance-level runners query returned HTTP {status}", + "en": "Warning: instance-level runners query returned HTTP {status}", + "pl": "Warning: instance-level runners query returned HTTP {status}", + "ru": "Warning: instance-level runners query returned HTTP {status}", + "zh": "Warning: instance-level runners query returned HTTP {status}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Warning: org-level runners query failed: {error}": { + "bg": "Warning: org-level runners query failed: {error}", + "de": "Warning: org-level runners query failed: {error}", + "en": "Warning: org-level runners query failed: {error}", + "pl": "Warning: org-level runners query failed: {error}", + "ru": "Warning: org-level runners query failed: {error}", + "zh": "Warning: org-level runners query failed: {error}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Warning: org-level runners query returned HTTP {status}": { + "bg": "Warning: org-level runners query returned HTTP {status}", + "de": "Warning: org-level runners query returned HTTP {status}", + "en": "Warning: org-level runners query returned HTTP {status}", + "pl": "Warning: org-level runners query returned HTTP {status}", + "ru": "Warning: org-level runners query returned HTTP {status}", + "zh": "Warning: org-level runners query returned HTTP {status}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Warning: repo-level runners query failed: {error}": { + "bg": "Warning: repo-level runners query failed: {error}", + "de": "Warning: repo-level runners query failed: {error}", + "en": "Warning: repo-level runners query failed: {error}", + "pl": "Warning: repo-level runners query failed: {error}", + "ru": "Warning: repo-level runners query failed: {error}", + "zh": "Warning: repo-level runners query failed: {error}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Warning: repo-level runners query returned HTTP {status}": { + "bg": "Warning: repo-level runners query returned HTTP {status}", + "de": "Warning: repo-level runners query returned HTTP {status}", + "en": "Warning: repo-level runners query returned HTTP {status}", + "pl": "Warning: repo-level runners query returned HTTP {status}", + "ru": "Warning: repo-level runners query returned HTTP {status}", + "zh": "Warning: repo-level runners query returned HTTP {status}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Wiki repo not found or empty — initializing fresh.": { + "bg": "", + "de": "", + "en": "Wiki repo not found or empty — initializing fresh.", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Wiki synced successfully.": { + "bg": "", + "de": "", + "en": "Wiki synced successfully.", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Wiki verification failed — could not clone wiki": { + "bg": "", + "de": "", + "en": "Wiki verification failed — could not clone wiki", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Wiki verification failed — {failures} page(s) missing": { + "bg": "", + "de": "", + "en": "Wiki verification failed — {failures} page(s) missing", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Write deploy-ref to $GITHUB_OUTPUT file.": { + "bg": "Запиши deploy-ref в $GITHUB_OUTPUT файла.", + "de": "Deploy-ref in $GITHUB_OUTPUT-Datei schreiben.", + "en": "Write deploy-ref to $GITHUB_OUTPUT file.", + "pl": "Zapisz deploy-ref do pliku $GITHUB_OUTPUT.", + "ru": "Записать deploy-ref в файл $GITHUB_OUTPUT.", + "zh": "将 deploy-ref 写入 $GITHUB_OUTPUT 文件。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Write results to $GITHUB_OUTPUT": { + "bg": "Write results to $GITHUB_OUTPUT", + "de": "Write results to $GITHUB_OUTPUT", + "en": "Write results to $GITHUB_OUTPUT", + "pl": "Write results to $GITHUB_OUTPUT", + "ru": "Write results to $GITHUB_OUTPUT", + "zh": "Write results to $GITHUB_OUTPUT", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "Wrote tag {tag} to GITHUB_OUTPUT.": { + "bg": "Wrote tag {tag} to GITHUB_OUTPUT.", + "de": "Wrote tag {tag} to GITHUB_OUTPUT.", + "en": "Wrote tag {tag} to GITHUB_OUTPUT.", + "pl": "Wrote tag {tag} to GITHUB_OUTPUT.", + "ru": "Wrote tag {tag} to GITHUB_OUTPUT.", + "zh": "Wrote tag {tag} to GITHUB_OUTPUT.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "[check-api-identity-checks] Passed: no unsafe identity checks found": { + "bg": "[check-api-identity-checks] Мина: не са намерени небрежни проверки за идентичност", + "de": "[check-api-identity-checks] Bestanden: keine unsicheren Identitätsprüfungen gefunden", + "en": "[check-api-identity-checks] Passed: no unsafe identity checks found", + "pl": "[check-api-identity-checks] Passed: nie znaleziono niebezpiecznych sprawdzeń tożsamości", + "ru": "[check-api-identity-checks] Пройдено: небезопасных проверок идентичности не найдено", + "zh": "[check-api-identity-checks] 通过:未发现不安全的身份检查", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "[check-dep-docs] Passed: all dependencies are documented": { + "bg": "[check-dep-docs] Passed: all dependencies are documented", + "de": "[check-dep-docs] Passed: all dependencies are documented", + "en": "[check-dep-docs] Passed: all dependencies are documented", + "pl": "[check-dep-docs] Passed: all dependencies are documented", + "ru": "[check-dep-docs] Passed: all dependencies are documented", + "zh": "[check-dep-docs] Passed: all dependencies are documented", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "[check-deps] All core tools present.": { + "bg": "[check-deps] Всички основни инструменти са налични.", + "de": "[check-deps] Alle Kernwerkzeuge vorhanden.", + "en": "[check-deps] All core tools present.", + "pl": "[check-deps] Wszystkie podstawowe narzędzia są dostępne.", + "ru": "[check-deps] Все основные инструменты доступны.", + "zh": "[check-deps] 所有核心工具均已就绪。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "[check-deps] Verifying tools...": { + "bg": "[check-deps] Проверка на инструментите...", + "de": "[check-deps] Werkzeuge werden überprüft...", + "en": "[check-deps] Verifying tools...", + "pl": "[check-deps] Sprawdzanie narzędzi...", + "ru": "[check-deps] Проверка инструментов...", + "zh": "[check-deps] 正在验证工具...", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "[check-deps] Virtualenv .venv ready (Python {version}).": { + "bg": "[check-deps] Виртуална среда .venv готова (Python {version}).", + "de": "[check-deps] Virtuelle Umgebung .venv bereit (Python {version}).", + "en": "[check-deps] Virtualenv .venv ready (Python {version}).", + "pl": "[check-deps] Środowisko wirtualne .venv gotowe (Python {version}).", + "ru": "[check-deps] Виртуальное окружение .venv готово (Python {version}).", + "zh": "[check-deps] 虚拟环境 .venv 已就绪 (Python {version})。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "[check-mutable-globals] Passed: no mutable path globals found": { + "bg": "[check-mutable-globals] Passed: no mutable path globals found", + "de": "[check-mutable-globals] Passed: no mutable path globals found", + "en": "[check-mutable-globals] Passed: no mutable path globals found", + "pl": "[check-mutable-globals] Passed: no mutable path globals found", + "ru": "[check-mutable-globals] Passed: no mutable path globals found", + "zh": "[check-mutable-globals] Passed: no mutable path globals found", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "[check-test-speed] CI environment detected — scaling limits by {factor}x (total: {orig}s → {eff}s, per-test: {orig_s}s → {eff_s}s)": { + "bg": "[check-test-speed] CI environment detected — scaling limits by {factor}x (total: {orig}s → {eff}s, per-test: {orig_s}s → {eff_s}s)", + "de": "[check-test-speed] CI environment detected — scaling limits by {factor}x (total: {orig}s → {eff}s, per-test: {orig_s}s → {eff_s}s)", + "en": "[check-test-speed] CI environment detected — scaling limits by {factor}x (total: {orig}s → {eff}s, per-test: {orig_s}s → {eff_s}s)", + "pl": "[check-test-speed] CI environment detected — scaling limits by {factor}x (total: {orig}s → {eff}s, per-test: {orig_s}s → {eff_s}s)", + "ru": "[check-test-speed] CI environment detected — scaling limits by {factor}x (total: {orig}s → {eff}s, per-test: {orig_s}s → {eff_s}s)", + "zh": "[check-test-speed] CI environment detected — scaling limits by {factor}x (total: {orig}s → {eff}s, per-test: {orig_s}s → {eff_s}s)", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "[check_agent_docs] Passed: scanned {count} file(s), no stale references": { + "bg": "[check_agent_docs] Passed: scanned {count} file(s), no stale references", + "de": "[check_agent_docs] Passed: scanned {count} file(s), no stale references", + "en": "[check_agent_docs] Passed: scanned {count} file(s), no stale references", + "pl": "[check_agent_docs] Passed: scanned {count} file(s), no stale references", + "ru": "[check_agent_docs] Passed: scanned {count} file(s), no stale references", + "zh": "[check_agent_docs] Passed: scanned {count} file(s), no stale references", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "[check_test_coverage] No changed files to check.": { + "bg": "[check_test_coverage] No changed files to check.", + "de": "[check_test_coverage] No changed files to check.", + "en": "[check_test_coverage] No changed files to check.", + "pl": "[check_test_coverage] No changed files to check.", + "ru": "[check_test_coverage] No changed files to check.", + "zh": "[check_test_coverage] No changed files to check.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "[dep-pr] Bumping {pkg} from {old} to {new} in {file}": { + "bg": "[dep-pr] Bumping {pkg} from {old} to {new} in {file}", + "de": "[dep-pr] Bumping {pkg} from {old} to {new} in {file}", + "en": "[dep-pr] Bumping {pkg} from {old} to {new} in {file}", + "pl": "[dep-pr] Bumping {pkg} from {old} to {new} in {file}", + "ru": "[dep-pr] Bumping {pkg} from {old} to {new} in {file}", + "zh": "[dep-pr] Bumping {pkg} from {old} to {new} in {file}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "[dep-pr] Could not find pinned version for {pkg} in infra repo.": { + "bg": "[dep-pr] Could not find pinned version for {pkg} in infra repo.", + "de": "[dep-pr] Could not find pinned version for {pkg} in infra repo.", + "en": "[dep-pr] Could not find pinned version for {pkg} in infra repo.", + "pl": "[dep-pr] Could not find pinned version for {pkg} in infra repo.", + "ru": "[dep-pr] Could not find pinned version for {pkg} in infra repo.", + "zh": "[dep-pr] Could not find pinned version for {pkg} in infra repo.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "[dep-pr] Created PR #{number}: {title}": { + "bg": "[dep-pr] Created PR #{number}: {title}", + "de": "[dep-pr] Created PR #{number}: {title}", + "en": "[dep-pr] Created PR #{number}: {title}", + "pl": "[dep-pr] Created PR #{number}: {title}", + "ru": "[dep-pr] Created PR #{number}: {title}", + "zh": "[dep-pr] Created PR #{number}: {title}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "[dep-pr] PR already exists: #{number}": { + "bg": "[dep-pr] PR already exists: #{number}", + "de": "[dep-pr] PR already exists: #{number}", + "en": "[dep-pr] PR already exists: #{number}", + "pl": "[dep-pr] PR already exists: #{number}", + "ru": "[dep-pr] PR already exists: #{number}", + "zh": "[dep-pr] PR already exists: #{number}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "[dep-pr] {pkg} already at {version} — no PR needed.": { + "bg": "[dep-pr] {pkg} already at {version} — no PR needed.", + "de": "[dep-pr] {pkg} already at {version} — no PR needed.", + "en": "[dep-pr] {pkg} already at {version} — no PR needed.", + "pl": "[dep-pr] {pkg} already at {version} — no PR needed.", + "ru": "[dep-pr] {pkg} already at {version} — no PR needed.", + "zh": "[dep-pr] {pkg} already at {version} — no PR needed.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "[docker-login] Logged in to {registry}.": { + "bg": "[docker-login] Влязъл в {registry}.", + "de": "[docker-login] Angemeldet bei {registry}.", + "en": "[docker-login] Logged in to {registry}.", + "pl": "[docker-login] Zalogowano do {registry}.", + "ru": "[docker-login] Выполнен вход в {registry}.", + "zh": "[docker-login] 已登录到 {registry}。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "[docker-login] Login to {registry} failed (continuing).": { + "bg": "[docker-login] Влизането в {registry} не успя (продължава).", + "de": "[docker-login] Anmeldung bei {registry} fehlgeschlagen (wird fortgesetzt).", + "en": "[docker-login] Login to {registry} failed (continuing).", + "pl": "[docker-login] Logowanie do {registry} nie powiodło się (kontynuowanie).", + "ru": "[docker-login] Ошибка входа в {registry} (продолжаем).", + "zh": "[docker-login] 登录 {registry} 失败(继续)。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "[docker-login] Skipping {registry} (token {env} not set).": { + "bg": "[docker-login] Пропускане на {registry} (токен {env} не е зададен).", + "de": "[docker-login] {registry} übersprungen (Token {env} nicht gesetzt).", + "en": "[docker-login] Skipping {registry} (token {env} not set).", + "pl": "[docker-login] Pomijanie {registry} (token {env} nie ustawiony).", + "ru": "[docker-login] Пропуск {registry} (токен {env} не задан).", + "zh": "[docker-login] 跳过 {registry}(未设置令牌 {env})。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "[dry-run] No changes pushed.": { + "bg": "", + "de": "", + "en": "[dry-run] No changes pushed.", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "[dry-run] Would commit and push wiki changes": { + "bg": "", + "de": "", + "en": "[dry-run] Would commit and push wiki changes", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "[dry-run] Would commit: release: v{version} [skip ci]": { + "bg": "[dry-run] Would commit: release: v{version} [skip ci]", + "de": "[dry-run] Would commit: release: v{version} [skip ci]", + "en": "[dry-run] Would commit: release: v{version} [skip ci]", + "pl": "[dry-run] Utworzono by commit: release: v{version} [skip ci]", + "ru": "[dry-run] Would commit: release: v{version} [skip ci]", + "zh": "[dry-run] Would commit: release: v{version} [skip ci]", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "[dry-run] Would create tag: v{version}": { + "bg": "[dry-run] Would create tag: v{version}", + "de": "[dry-run] Would create tag: v{version}", + "en": "[dry-run] Would create tag: v{version}", + "pl": "[dry-run] Utworzono by tag: v{version}", + "ru": "[dry-run] Would create tag: v{version}", + "zh": "[dry-run] Would create tag: v{version}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "[dry-run] Would create tag: {tag}": { + "bg": "[dry-run] Would create tag: {tag}", + "de": "[dry-run] Would create tag: {tag}", + "en": "[dry-run] Would create tag: {tag}", + "pl": "[dry-run] Utworzono by tag: {tag}", + "ru": "[dry-run] Would create tag: {tag}", + "zh": "[dry-run] Would create tag: {tag}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "[dry-run] Would push commit to master": { + "bg": "[dry-run] Would push commit to master", + "de": "[dry-run] Would push commit to master", + "en": "[dry-run] Would push commit to master", + "pl": "[dry-run] Wypchnięto by commit do master", + "ru": "[dry-run] Would push commit to master", + "zh": "[dry-run] Would push commit to master", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "[dry-run] Would update doc version references via check_doc_versions --fix": { + "bg": "", + "de": "", + "en": "[dry-run] Would update doc version references via check_doc_versions --fix", + "pl": "", + "ru": "", + "zh": "", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "[dry-run] Would update {changelog_file}": { + "bg": "[dry-run] Would update {changelog_file}", + "de": "[dry-run] Would update {changelog_file}", + "en": "[dry-run] Would update {changelog_file}", + "pl": "[dry-run] Zaktualizowano by {changelog_file}", + "ru": "[dry-run] Would update {changelog_file}", + "zh": "[dry-run] Would update {changelog_file}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "[dry-run] Would update {init}": { + "bg": "[dry-run] Would update {init}", + "de": "[dry-run] Would update {init}", + "en": "[dry-run] Would update {init}", + "pl": "[dry-run] Zaktualizowano by {init}", + "ru": "[dry-run] Would update {init}", + "zh": "[dry-run] Would update {init}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "[fast-molecule] Changed roles: {roles}": { + "bg": "[fast-molecule] Changed roles: {roles}", + "de": "[fast-molecule] Changed roles: {roles}", + "en": "[fast-molecule] Changed roles: {roles}", + "pl": "[fast-molecule] Changed roles: {roles}", + "ru": "[fast-molecule] Changed roles: {roles}", + "zh": "[fast-molecule] Changed roles: {roles}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "[nightly-gate] Nightly FAILED{run}. Staging deploys are blocked until nightly passes.": { + "bg": "[nightly-gate] Nightly FAILED{run}. Staging deploys are blocked until nightly passes.", + "de": "[nightly-gate] Nightly FAILED{run}. Staging deploys are blocked until nightly passes.", + "en": "[nightly-gate] Nightly FAILED{run}. Staging deploys are blocked until nightly passes.", + "pl": "[nightly-gate] Nightly FAILED{run}. Staging deploys are blocked until nightly passes.", + "ru": "[nightly-gate] Nightly FAILED{run}. Staging deploys are blocked until nightly passes.", + "zh": "[nightly-gate] Nightly FAILED{run}. Staging deploys are blocked until nightly passes.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "[nightly-gate] Set NIGHTLY_STATUS=failed{run}": { + "bg": "[nightly-gate] Set NIGHTLY_STATUS=failed{run}", + "de": "[nightly-gate] Set NIGHTLY_STATUS=failed{run}", + "en": "[nightly-gate] Set NIGHTLY_STATUS=failed{run}", + "pl": "[nightly-gate] Set NIGHTLY_STATUS=failed{run}", + "ru": "[nightly-gate] Set NIGHTLY_STATUS=failed{run}", + "zh": "[nightly-gate] Set NIGHTLY_STATUS=failed{run}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "[nightly-gate] Set NIGHTLY_STATUS=passed{run}": { + "bg": "[nightly-gate] Set NIGHTLY_STATUS=passed{run}", + "de": "[nightly-gate] Set NIGHTLY_STATUS=passed{run}", + "en": "[nightly-gate] Set NIGHTLY_STATUS=passed{run}", + "pl": "[nightly-gate] Set NIGHTLY_STATUS=passed{run}", + "ru": "[nightly-gate] Set NIGHTLY_STATUS=passed{run}", + "zh": "[nightly-gate] Set NIGHTLY_STATUS=passed{run}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "[spec-check] Spec validated: {path}": { + "bg": "[spec-check] Spec validated: {path}", + "de": "[spec-check] Spec validated: {path}", + "en": "[spec-check] Spec validated: {path}", + "pl": "[spec-check] Spec validated: {path}", + "ru": "[spec-check] Spec validated: {path}", + "zh": "[spec-check] Spec validated: {path}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "[tofu-init] Done.": { + "bg": "[tofu-init] Готово.", + "de": "[tofu-init] Fertig.", + "en": "[tofu-init] Done.", + "pl": "[tofu-init] Gotowe.", + "ru": "[tofu-init] Готово.", + "zh": "[tofu-init] 完成。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "[tofu-init] Initializing {dir}...": { + "bg": "[tofu-init] Инициализиране на {dir}...", + "de": "[tofu-init] Initialisiere {dir}...", + "en": "[tofu-init] Initializing {dir}...", + "pl": "[tofu-init] Inicjalizacja {dir}...", + "ru": "[tofu-init] Инициализация {dir}...", + "zh": "[tofu-init] 正在初始化 {dir}...", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "[tofu-{mode}] All configurations valid.": { + "bg": "[tofu-{mode}] Всички конфигурации са валидни.", + "de": "[tofu-{mode}] Alle Konfigurationen gültig.", + "en": "[tofu-{mode}] All configurations valid.", + "pl": "[tofu-{mode}] Wszystkie konfiguracje są poprawne.", + "ru": "[tofu-{mode}] Все конфигурации валидны.", + "zh": "[tofu-{mode}] 所有配置有效。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "[tofu-{mode}] Validating OpenTofu configurations...": { + "bg": "[tofu-{mode}] Проверка на OpenTofu конфигурациите...", + "de": "[tofu-{mode}] Validiere OpenTofu-Konfigurationen...", + "en": "[tofu-{mode}] Validating OpenTofu configurations...", + "pl": "[tofu-{mode}] Sprawdzanie konfiguracji OpenTofu...", + "ru": "[tofu-{mode}] Проверка конфигураций OpenTofu...", + "zh": "[tofu-{mode}] 正在验证 OpenTofu 配置...", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "[tool.devx] missing required keys: {keys}": { + "bg": "[tool.devx] липсват задължителни ключове: {keys}", + "de": "[tool.devx] fehlt erforderliche Schlüssel: {keys}", + "en": "[tool.devx] missing required keys: {keys}", + "pl": "[tool.devx] brak wymaganych kluczy: {keys}", + "ru": "[tool.devx] отсутствуют обязательные ключи: {keys}", + "zh": "[tool.devx] 缺少必需的键: {keys}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "active": { + "bg": "активен", + "de": "aktiv", + "en": "active", + "pl": "aktywny", + "ru": "активен", + "zh": "活跃", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "completed": { + "bg": "завършен", + "de": "abgeschlossen", + "en": "completed", + "pl": "ukończony", + "ru": "завершён", + "zh": "已完成", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "count={count}": { + "bg": "count={count}", + "de": "count={count}", + "en": "count={count}", + "pl": "count={count}", + "ru": "count={count}", + "zh": "count={count}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "devx version mismatch across extras: {detail}": { + "bg": "несъответствие на версията на devx между extras: {detail}", + "de": "devx-Versionskonflikt zwischen Extras: {detail}", + "en": "devx version mismatch across extras: {detail}", + "pl": "niezgodność wersji devx między extras: {detail}", + "ru": "несоответствие версии devx между extras: {detail}", + "zh": "devx 版本在 extras 之间不一致: {detail}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "failed": { + "bg": "неуспешен", + "de": "fehlgeschlagen", + "en": "failed", + "pl": "nieudany", + "ru": "неудачный", + "zh": "失败", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "git command failed ({cmd}): {stderr}": { + "bg": "git command failed ({cmd}): {stderr}", + "de": "git command failed ({cmd}): {stderr}", + "en": "git command failed ({cmd}): {stderr}", + "pl": "polecenie git nie powiodło się ({cmd}): {stderr}", + "ru": "git command failed ({cmd}): {stderr}", + "zh": "git command failed ({cmd}): {stderr}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "git diff --numstat failed: {stderr}": { + "bg": "git diff --numstat failed: {stderr}", + "de": "git diff --numstat failed: {stderr}", + "en": "git diff --numstat failed: {stderr}", + "pl": "git diff --numstat failed: {stderr}", + "ru": "git diff --numstat failed: {stderr}", + "zh": "git diff --numstat failed: {stderr}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "git-cliff generated empty changelog for v{version}. Check cliff.toml and commit history.": { + "bg": "git-cliff generated empty changelog for v{version}. Check cliff.toml and commit history.", + "de": "git-cliff generated empty changelog for v{version}. Check cliff.toml and commit history.", + "en": "git-cliff generated empty changelog for v{version}. Check cliff.toml and commit history.", + "pl": "git-cliff wygenerował pusty changelog dla v{version}. Sprawdź cliff.toml i historię commitów.", + "ru": "git-cliff generated empty changelog for v{version}. Check cliff.toml and commit history.", + "zh": "git-cliff generated empty changelog for v{version}. Check cliff.toml and commit history.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "git-cliff returned empty version.": { + "bg": "git-cliff returned empty version.", + "de": "git-cliff returned empty version.", + "en": "git-cliff returned empty version.", + "pl": "git-cliff zwrócił pustą wersję.", + "ru": "git-cliff returned empty version.", + "zh": "git-cliff returned empty version.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "git-cliff returned invalid version format: {version}. Expected semver (e.g., 0.4.1).": { + "bg": "git-cliff returned invalid version format: {version}. Expected semver (e.g., 0.4.1).", + "de": "git-cliff returned invalid version format: {version}. Expected semver (e.g., 0.4.1).", + "en": "git-cliff returned invalid version format: {version}. Expected semver (e.g., 0.4.1).", + "pl": "git-cliff zwrócił nieprawidłowy format wersji: {version}. Oczekiwano semver (np., 0.4.1).", + "ru": "git-cliff returned invalid version format: {version}. Expected semver (e.g., 0.4.1).", + "zh": "git-cliff returned invalid version format: {version}. Expected semver (e.g., 0.4.1).", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" }, "importlib.reload({mod}) called {n} time(s) in test '{test}' — odd count leaves module in modified state. Add a final reload to restore defaults or wrap in try/finally.": { "bg": "importlib.reload({mod}) called {n} time(s) in test '{test}' — odd count leaves module in modified state. Add a final reload to restore defaults or wrap in try/finally.", @@ -3789,14 +4935,222 @@ "en": "importlib.reload({mod}) called {n} time(s) in test '{test}' — odd count leaves module in modified state. Add a final reload to restore defaults or wrap in try/finally.", "pl": "importlib.reload({mod}) called {n} time(s) in test '{test}' — odd count leaves module in modified state. Add a final reload to restore defaults or wrap in try/finally.", "ru": "importlib.reload({mod}) called {n} time(s) in test '{test}' — odd count leaves module in modified state. Add a final reload to restore defaults or wrap in try/finally.", - "zh": "importlib.reload({mod}) called {n} time(s) in test '{test}' — odd count leaves module in modified state. Add a final reload to restore defaults or wrap in try/finally." + "zh": "importlib.reload({mod}) called {n} time(s) in test '{test}' — odd count leaves module in modified state. Add a final reload to restore defaults or wrap in try/finally.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" }, - "[check-test-speed] CI environment detected — scaling limits by {factor}x (total: {orig}s → {eff}s, per-test: {orig_s}s → {eff_s}s)": { - "en": "[check-test-speed] CI environment detected — scaling limits by {factor}x (total: {orig}s → {eff}s, per-test: {orig_s}s → {eff_s}s)", - "bg": "[check-test-speed] CI environment detected — scaling limits by {factor}x (total: {orig}s → {eff}s, per-test: {orig_s}s → {eff_s}s)", - "de": "[check-test-speed] CI environment detected — scaling limits by {factor}x (total: {orig}s → {eff}s, per-test: {orig_s}s → {eff_s}s)", - "pl": "[check-test-speed] CI environment detected — scaling limits by {factor}x (total: {orig}s → {eff}s, per-test: {orig_s}s → {eff_s}s)", - "ru": "[check-test-speed] CI environment detected — scaling limits by {factor}x (total: {orig}s → {eff}s, per-test: {orig_s}s → {eff_s}s)", - "zh": "[check-test-speed] CI environment detected — scaling limits by {factor}x (total: {orig}s → {eff}s, per-test: {orig_s}s → {eff_s}s)" + "in_progress": { + "bg": "в процес", + "de": "in Bearbeitung", + "en": "in progress", + "pl": "w toku", + "ru": "в процессе", + "zh": "进行中", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "inactive": { + "bg": "неактивен", + "de": "inaktiv", + "en": "inactive", + "pl": "nieaktywny", + "ru": "неактивен", + "zh": "未激活", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "indices={indices}": { + "bg": "indices={indices}", + "de": "indices={indices}", + "en": "indices={indices}", + "pl": "indices={indices}", + "ru": "indices={indices}", + "zh": "indices={indices}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "mapping.json keys and values must be strings, got {k}={v}": { + "bg": "mapping.json keys and values must be strings, got {k}={v}", + "de": "mapping.json keys and values must be strings, got {k}={v}", + "en": "mapping.json keys and values must be strings, got {k}={v}", + "pl": "klucze i wartości mapping.json muszą być ciągami znaków, otrzymano {k}={v}", + "ru": "mapping.json keys and values must be strings, got {k}={v}", + "zh": "mapping.json keys and values must be strings, got {k}={v}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "mapping.json must be a dict of file-path -> page-title, got {type}": { + "bg": "mapping.json must be a dict of file-path -> page-title, got {type}", + "de": "mapping.json must be a dict of file-path -> page-title, got {type}", + "en": "mapping.json must be a dict of file-path -> page-title, got {type}", + "pl": "mapping.json musi być słownikiem ścieżka-pliku -> tytuł-strony, otrzymano {type}", + "ru": "mapping.json must be a dict of file-path -> page-title, got {type}", + "zh": "mapping.json must be a dict of file-path -> page-title, got {type}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "pending": { + "bg": "в очакване", + "de": "ausstehend", + "en": "pending", + "pl": "oczekujący", + "ru": "ожидает", + "zh": "待处理", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "pyproject.toml not found in current directory.": { + "bg": "pyproject.toml не е намерен в текущата директория.", + "de": "pyproject.toml im aktuellen Verzeichnis nicht gefunden.", + "en": "pyproject.toml not found in current directory.", + "pl": "nie znaleziono pyproject.toml w bieżącym katalogu.", + "ru": "pyproject.toml не найден в текущей директории.", + "zh": "在当前目录中未找到 pyproject.toml。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "tea login '{name}' already configured.": { + "bg": "tea login '{name}' already configured.", + "de": "tea login '{name}' already configured.", + "en": "tea login '{name}' already configured.", + "pl": "tea login '{name}' already configured.", + "ru": "tea login '{name}' already configured.", + "zh": "tea login '{name}' already configured.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "tea not installed — skipping login configuration.": { + "bg": "tea not installed — skipping login configuration.", + "de": "tea not installed — skipping login configuration.", + "en": "tea not installed — skipping login configuration.", + "pl": "tea not installed — skipping login configuration.", + "ru": "tea not installed — skipping login configuration.", + "zh": "tea not installed — skipping login configuration.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "time.sleep called in test '{test}' without @patch — this causes real wall-clock delays. Add @patch(\"<module>.time.sleep\").": { + "bg": "time.sleep извикано в тест '{test}' без @patch — това причинява реални забавяния. Добавете @patch(\"<module>.time.sleep\").", + "de": "time.sleep in Test '{test}' ohne @patch aufgerufen — dies verursacht echte Wanduhr-Verzögerungen. @patch(\"<module>.time.sleep\") hinzufügen.", + "en": "time.sleep called in test '{test}' without @patch — this causes real wall-clock delays. Add @patch(\"<module>.time.sleep\").", + "pl": "time.sleep wywołane w teście '{test}' bez @patch — to powoduje rzeczywiste opóźnienia. Dodaj @patch(\"<module>.time.sleep\").", + "ru": "time.sleep вызвано в тесте '{test}' без @patch — это вызывает реальные задержки. Добавьте @patch(\"<module>.time.sleep\").", + "zh": "time.sleep 在测试 '{test}' 中被调用但没有 @patch — 这会导致真实的挂钟延迟。请添加 @patch(\"<module>.time.sleep\")。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "tofu command failed in {dir}: {error}": { + "bg": "командата tofu не успя в {dir}: {error}", + "de": "tofu-Befehl fehlgeschlagen in {dir}: {error}", + "en": "tofu command failed in {dir}: {error}", + "pl": "polecenie tofu nie powiodło się w {dir}: {error}", + "ru": "команда tofu не удалась в {dir}: {error}", + "zh": "tofu 命令在 {dir} 中失败: {error}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "unknown": { + "bg": "неизвестен", + "de": "unbekannt", + "en": "unknown", + "pl": "nieznany", + "ru": "неизвестно", + "zh": "未知", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "{call} called in test '{test}' without @patch — this spawns a real subprocess. Add @patch(\"<module>.subprocess.run\") or patch the calling function.": { + "bg": "{call} извикано в тест '{test}' без @patch — това стартира реален subprocess. Добавете @patch(\"<module>.subprocess.run\") или patch-нете извикващата функция.", + "de": "{call} in Test '{test}' ohne @patch aufgerufen — dies startet einen echten subprocess. @patch(\"<module>.subprocess.run\") hinzufügen oder die aufrufende Funktion patchen.", + "en": "{call} called in test '{test}' without @patch — this spawns a real subprocess. Add @patch(\"<module>.subprocess.run\") or patch the calling function.", + "pl": "{call} wywołane w teście '{test}' bez @patch — to uruchamia rzeczywisty subprocess. Dodaj @patch(\"<module>.subprocess.run\") lub patchuj wywołującą funkcję.", + "ru": "{call} вызвано в тесте '{test}' без @patch — это запускает реальный subprocess. Добавьте @patch(\"<module>.subprocess.run\") или patch вызывающую функцию.", + "zh": "{call} 在测试 '{test}' 中被调用但没有 @patch — 这会启动真实的子进程。请添加 @patch(\"<module>.subprocess.run\") 或 patch 调用函数。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "{env} is not set. Set it in your .env file or pass it as an environment variable.": { + "bg": "{env} не е зададен. Задайте го във вашия .env файл или го подайте като променлива на средата.", + "de": "{env} ist nicht gesetzt. Setzen Sie es in Ihrer .env-Datei oder übergeben Sie es als Umgebungsvariable.", + "en": "{env} is not set. Set it in your .env file or pass it as an environment variable.", + "pl": "{env} nie jest ustawiony. Ustaw go w pliku .env lub przekaż jako zmienną środowiskową.", + "ru": "{env} не задан. Установите его в файле .env или передайте как переменную окружения.", + "zh": "{env} 未设置。请在 .env 文件中设置或作为环境变量传递。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "{env} is not set. Set it in your .env file.": { + "bg": "{env} не е зададен. Задайте го във вашия .env файл.", + "de": "{env} ist nicht gesetzt. Setzen Sie es in Ihrer .env-Datei.", + "en": "{env} is not set. Set it in your .env file.", + "pl": "{env} nie jest ustawiony. Ustaw go w pliku .env.", + "ru": "{env} не задан. Установите его в файле .env.", + "zh": "{env} 未设置。请在 .env 文件中设置。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "{file} already exists. Use --force to overwrite.": { + "bg": "{file} already exists. Use --force to overwrite.", + "de": "{file} already exists. Use --force to overwrite.", + "en": "{file} already exists. Use --force to overwrite.", + "pl": "{file} już istnieje. Użyj --force, aby nadpisać.", + "ru": "{file} already exists. Use --force to overwrite.", + "zh": "{file} already exists. Use --force to overwrite.", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "{func} called in test '{test}' without @patch — this function {desc}. Add @patch(\"<module>.{func}\").": { + "bg": "{func} извикано в тест '{test}' без @patch — тази функция {desc}. Добавете @patch(\"<module>.{func}\").", + "de": "{func} in Test '{test}' ohne @patch aufgerufen — diese Funktion {desc}. @patch(\"<module>.{func}\") hinzufügen.", + "en": "{func} called in test '{test}' without @patch — this function {desc}. Add @patch(\"<module>.{func}\").", + "pl": "{func} wywołane w teście '{test}' bez @patch — ta funkcja {desc}. Dodaj @patch(\"<module>.{func}\").", + "ru": "{func} вызвано в тесте '{test}' без @patch — эта функция {desc}. Добавьте @patch(\"<module>.{func}\").", + "zh": "{func} 在测试 '{test}' 中被调用但没有 @patch — 此函数 {desc}。请添加 @patch(\"<module>.{func}\")。", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "{level}: {tool} not found.{hint}": { + "bg": "{level}: {tool} не е намерен.{hint}", + "de": "{level}: {tool} nicht gefunden.{hint}", + "en": "{level}: {tool} not found.{hint}", + "pl": "{level}: {tool} nie znaleziono.{hint}", + "ru": "{level}: {tool} не найден.{hint}", + "zh": "{level}: 未找到 {tool}。{hint}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "{separator}": { + "bg": "{separator}", + "de": "{separator}", + "en": "{separator}", + "pl": "{separator}", + "ru": "{separator}", + "zh": "{separator}", + "PR number for label check": "PR number for label check", + "Repo (owner/name) for label check": "Repo (owner/name) for label check" + }, + "PR number for label check": { + "bg": "PR number for label check", + "de": "PR number for label check", + "en": "PR number for label check", + "pl": "PR number for label check", + "ru": "PR number for label check", + "zh": "PR number for label check" + }, + "Repo (owner/name) for label check": { + "bg": "Repo (owner/name) for label check", + "de": "Repo (owner/name) for label check", + "en": "Repo (owner/name) for label check", + "pl": "Repo (owner/name) for label check", + "ru": "Repo (owner/name) for label check", + "zh": "Repo (owner/name) for label check" + }, + "PR has 'refactoring' label — size check bypassed.": { + "bg": "PR has 'refactoring' label — size check bypassed.", + "de": "PR has 'refactoring' label — size check bypassed.", + "en": "PR has 'refactoring' label — size check bypassed.", + "pl": "PR has 'refactoring' label — size check bypassed.", + "ru": "PR has 'refactoring' label — size check bypassed.", + "zh": "PR has 'refactoring' label — size check bypassed." } } diff --git a/tests/unit/test_check_pr_size.py b/tests/unit/test_check_pr_size.py new file mode 100644 index 0000000..0fb9451 --- /dev/null +++ b/tests/unit/test_check_pr_size.py @@ -0,0 +1,169 @@ +"""Unit tests for devx.ci.check_pr_size.""" + +from unittest.mock import MagicMock, patch + +from click.testing import CliRunner + +from devx.ci.check_pr_size import ( + check_size, + cli, + get_diff_stats, + has_refactoring_label, + is_excluded, +) + + +class TestIsExcluded: + def test_excludes_changelog(self) -> None: + assert is_excluded("CHANGELOG.md", ["CHANGELOG.md"]) + + def test_excludes_svg_glob(self) -> None: + assert is_excluded("docs/badges/coverage.svg", ["*.svg"]) + + def test_does_not_exclude_source(self) -> None: + assert not is_excluded("src/devx/ci/check_pr_size.py", ["CHANGELOG.md", "*.svg"]) + + def test_excludes_readme(self) -> None: + assert is_excluded("README.md", ["README.md"]) + + +class TestCheckSize: + def test_under_limits_passes(self) -> None: + stats = [("src/main.py", 100, 50), ("tests/test_main.py", 80, 20)] + ok, detail = check_size(stats, max_lines=500, max_files=10, excluded_patterns=[]) + assert ok is True + assert "250" in detail # 100+50+80+20 + + def test_over_lines_fails(self) -> None: + stats = [("src/main.py", 300, 300)] + ok, detail = check_size(stats, max_lines=500, max_files=10, excluded_patterns=[]) + assert ok is False + assert "600" in detail + + def test_over_files_fails(self) -> None: + stats = [(f"src/file{i}.py", 10, 5) for i in range(15)] + ok, detail = check_size(stats, max_lines=500, max_files=10, excluded_patterns=[]) + assert ok is False + assert "15" in detail + + def test_excluded_files_not_counted(self) -> None: + stats = [("CHANGELOG.md", 500, 500), ("src/main.py", 10, 5)] + ok, detail = check_size(stats, max_lines=500, max_files=10, excluded_patterns=["CHANGELOG.md"]) + assert ok is True + assert "15" in detail # only 10+5 + + def test_empty_stats_passes(self) -> None: + ok, detail = check_size([], max_lines=500, max_files=10, excluded_patterns=[]) + assert ok is True + + +class TestGetDiffStats: + @patch("devx.ci.check_pr_size.subprocess.run") + def test_parses_numstat_output(self, mock_run: MagicMock) -> None: + mock_run.return_value = MagicMock( + returncode=0, + stdout="10\t5\tsrc/main.py\n20\t10\ttests/test_main.py\n", + stderr="", + ) + stats = get_diff_stats("origin/master", "HEAD") + assert len(stats) == 2 + assert stats[0] == ("src/main.py", 10, 5) + assert stats[1] == ("tests/test_main.py", 20, 10) + + @patch("devx.ci.check_pr_size.subprocess.run") + def test_handles_binary_files(self, mock_run: MagicMock) -> None: + mock_run.return_value = MagicMock( + returncode=0, + stdout="-\t-\timage.png\n", + stderr="", + ) + stats = get_diff_stats("origin/master", "HEAD") + assert len(stats) == 1 + assert stats[0] == ("image.png", 0, 0) + + @patch("devx.ci.check_pr_size.subprocess.run") + def test_empty_output(self, mock_run: MagicMock) -> None: + mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="") + stats = get_diff_stats("origin/master", "HEAD") + assert stats == [] + + @patch("devx.ci.check_pr_size.subprocess.run") + def test_git_diff_failure_raises(self, mock_run: MagicMock) -> None: + import pytest + + mock_run.return_value = MagicMock(returncode=1, stdout="", stderr="fatal: bad ref") + with pytest.raises(Exception, match="git diff|bad ref"): + get_diff_stats("origin/master", "HEAD") + + @patch("devx.ci.check_pr_size.subprocess.run") + def test_malformed_line_skipped(self, mock_run: MagicMock) -> None: + mock_run.return_value = MagicMock( + returncode=0, + stdout="not_a_valid_line\n10\t5\tsrc/main.py\n", + stderr="", + ) + stats = get_diff_stats("origin/master", "HEAD") + assert len(stats) == 1 + assert stats[0] == ("src/main.py", 10, 5) + + +class TestCli: + @patch("devx.ci.check_pr_size.subprocess.run") + def test_passes_when_small(self, mock_run: MagicMock) -> None: + mock_run.return_value = MagicMock( + returncode=0, + stdout="10\t5\tsrc/main.py\n", + stderr="", + ) + runner = CliRunner() + result = runner.invoke(cli, ["--base", "origin/master", "--head", "HEAD"]) + assert result.exit_code == 0 + assert "OK" in result.output + + @patch("devx.ci.check_pr_size.subprocess.run") + def test_fails_when_too_large(self, mock_run: MagicMock) -> None: + mock_run.return_value = MagicMock( + returncode=0, + stdout="300\t300\tsrc/main.py\n", + stderr="", + ) + runner = CliRunner() + result = runner.invoke(cli, ["--base", "origin/master", "--head", "HEAD", "--max-lines", "500"]) + assert result.exit_code != 0 + assert "600" in result.output + + @patch("devx.ci.check_pr_size.subprocess.run") + @patch("devx.ci.check_pr_size.has_refactoring_label", return_value=True) + def test_bypasses_with_refactoring_label(self, mock_label: MagicMock, mock_run: MagicMock) -> None: + mock_run.return_value = MagicMock( + returncode=0, + stdout="300\t300\tsrc/main.py\n", + stderr="", + ) + runner = CliRunner() + result = runner.invoke( + cli, + ["--base", "origin/master", "--head", "HEAD", "--repo", "owner/repo", "--pr-number", "42"], + ) + assert result.exit_code == 0 + assert "bypassed" in result.output.lower() + + +class TestHasRefactoringLabel: + @patch("devx.ci.check_pr_size.GiteaClient") + @patch("devx.ci.check_pr_size.get_ci_token", return_value="fake-token") + def test_returns_true_when_label_present(self, mock_token: MagicMock, mock_client_cls: MagicMock) -> None: + mock_client = mock_client_cls.return_value + mock_client.get_pr.return_value = {"labels": [{"name": "refactoring"}, {"name": "bug"}]} + assert has_refactoring_label("owner/repo", 42) is True + + @patch("devx.ci.check_pr_size.GiteaClient") + @patch("devx.ci.check_pr_size.get_ci_token", return_value="fake-token") + def test_returns_false_when_label_absent(self, mock_token: MagicMock, mock_client_cls: MagicMock) -> None: + mock_client = mock_client_cls.return_value + mock_client.get_pr.return_value = {"labels": [{"name": "bug"}]} + assert has_refactoring_label("owner/repo", 42) is False + + @patch("devx.ci.check_pr_size.get_ci_token", side_effect=Exception("no token")) + def test_returns_false_on_error(self, mock_token: MagicMock) -> None: + assert has_refactoring_label("owner/repo", 42) is False diff --git a/tests/unit/test_cli.py b/tests/unit/test_cli.py index 4f05ea0..abbfb80 100644 --- a/tests/unit/test_cli.py +++ b/tests/unit/test_cli.py @@ -107,13 +107,6 @@ class TestCiCommands: assert result.exit_code == 0 mock_run.assert_called_once_with("devx.ci.post_merge", ["DEVX-1"]) - @patch("devx.cli._run_module") - def test_ci_pr_review(self, mock_run: MagicMock) -> None: - runner = CliRunner() - result = runner.invoke(cli, ["ci", "pr-review", "42"]) - assert result.exit_code == 0 - mock_run.assert_called_once_with("devx.ci.pr_review", ["42"]) - @patch("devx.cli._run_module") def test_ci_publish(self, mock_run: MagicMock) -> None: runner = CliRunner() diff --git a/tests/unit/test_create_dependency_pr.py b/tests/unit/test_create_dependency_pr.py new file mode 100644 index 0000000..9449160 --- /dev/null +++ b/tests/unit/test_create_dependency_pr.py @@ -0,0 +1,182 @@ +"""Unit tests for devx.ci.create_dependency_pr.""" + +from pathlib import Path +from unittest.mock import MagicMock, patch + +import click +from click.testing import CliRunner + +from devx.ci.create_dependency_pr import ( + cli, + create_vikunja_task, + find_existing_pr, + find_pinned_version, + update_pinned_version, +) + + +class TestFindPinnedVersion: + def test_finds_pip_git_pin(self, tmp_path: Path) -> None: + content = "grm @ git+https://git.example.com/repo.git@v0.5.1" + path = tmp_path / "pyproject.toml" + path.write_text(content) + version = find_pinned_version("grm", str(path)) + assert version == "0.5.1" + + def test_finds_pyproject_pin(self, tmp_path: Path) -> None: + content = 'grm = "0.5.1"' + path = tmp_path / "pyproject.toml" + path.write_text(content) + version = find_pinned_version("grm", str(path)) + assert version == "0.5.1" + + def test_finds_ansible_var_pin(self, tmp_path: Path) -> None: + content = 'grm_version: "0.5.1"' + path = tmp_path / "images.yml" + path.write_text(content) + version = find_pinned_version("grm", str(path)) + assert version == "0.5.1" + + def test_finds_image_version_pin(self, tmp_path: Path) -> None: + content = 'sso_bridge_image_version: "1.2.3"' + path = tmp_path / "images.yml" + path.write_text(content) + version = find_pinned_version("sso_bridge", str(path)) + assert version == "1.2.3" + + def test_returns_none_when_not_found(self, tmp_path: Path) -> None: + path = tmp_path / "pyproject.toml" + path.write_text('other = "1.0.0"') + assert find_pinned_version("grm", str(path)) is None + + def test_returns_none_when_file_missing(self, tmp_path: Path) -> None: + assert find_pinned_version("grm", str(tmp_path / "nonexistent.toml")) is None + + +class TestUpdatePinnedVersion: + def test_updates_pip_git_pin(self, tmp_path: Path) -> None: + content = "grm @ git+https://git.example.com/repo.git@v0.5.1" + path = tmp_path / "pyproject.toml" + path.write_text(content) + changed = update_pinned_version(str(path), "grm", "0.5.1", "0.5.2") + assert changed is True + assert "0.5.2" in path.read_text() + assert "0.5.1" not in path.read_text() + + def test_updates_pyproject_pin(self, tmp_path: Path) -> None: + content = 'grm = "0.5.1"' + path = tmp_path / "pyproject.toml" + path.write_text(content) + changed = update_pinned_version(str(path), "grm", "0.5.1", "0.5.2") + assert changed is True + assert 'grm = "0.5.2"' in path.read_text() + + def test_no_change_when_version_not_found(self, tmp_path: Path) -> None: + content = 'other = "1.0.0"' + path = tmp_path / "pyproject.toml" + path.write_text(content) + changed = update_pinned_version(str(path), "grm", "0.5.1", "0.5.2") + assert changed is False + + def test_no_change_when_file_missing(self, tmp_path: Path) -> None: + changed = update_pinned_version(str(tmp_path / "nonexistent"), "grm", "0.5.1", "0.5.2") + assert changed is False + + +class TestFindExistingPr: + @patch("devx.tools.create_pr.GiteaClient") + def test_returns_pr_when_found(self, mock_client_cls: MagicMock) -> None: + mock_client = mock_client_cls.return_value + mock_client.list_prs.return_value = [ + {"head": {"ref": "deps/grm-0.5.2"}, "number": 42}, + {"head": {"ref": "other-branch"}, "number": 43}, + ] + result = find_existing_pr(mock_client, "deps/grm-0.5.2") + assert result is not None + assert result["number"] == 42 + + @patch("devx.tools.create_pr.GiteaClient") + def test_returns_none_when_not_found(self, mock_client_cls: MagicMock) -> None: + mock_client = mock_client_cls.return_value + mock_client.list_prs.return_value = [] + result = find_existing_pr(mock_client, "deps/grm-0.5.2") + assert result is None + + +class TestCli: + @patch("devx.ci.create_dependency_pr.find_pinned_version") + @patch("devx.ci.create_dependency_pr.get_ci_token") + def test_same_version_no_pr(self, mock_token: MagicMock, mock_find: MagicMock) -> None: + mock_token.return_value = "fake-token" + mock_find.return_value = "0.5.2" + runner = CliRunner() + result = runner.invoke( + cli, + [ + "--package", + "grm", + "--new-version", + "0.5.2", + "--source-repo", + "oblachno/grm", + ], + ) + assert result.exit_code == 0 + assert "no pr needed" in result.output.lower() + + @patch("devx.ci.create_dependency_pr.find_pinned_version") + @patch("devx.ci.create_dependency_pr.get_ci_token") + def test_dry_run(self, mock_token: MagicMock, mock_find: MagicMock) -> None: + mock_token.return_value = "fake-token" + mock_find.return_value = "0.5.1" + runner = CliRunner() + result = runner.invoke( + cli, + [ + "--package", + "grm", + "--new-version", + "0.5.2", + "--source-repo", + "oblachno/grm", + "--dry-run", + ], + ) + assert result.exit_code == 0 + assert "DRY RUN" in result.output + + @patch("devx.ci.create_dependency_pr.find_pinned_version") + @patch("devx.ci.create_dependency_pr.get_ci_token") + def test_version_not_found_fails(self, mock_token: MagicMock, mock_find: MagicMock) -> None: + mock_token.return_value = "fake-token" + mock_find.return_value = None + runner = CliRunner() + result = runner.invoke( + cli, + [ + "--package", + "nonexistent", + "--new-version", + "1.0.0", + "--source-repo", + "oblachno/test", + ], + ) + assert result.exit_code != 0 + + +class TestCreateVikunjaTask: + def test_returns_none_when_no_token(self) -> None: + with patch("devx.ci.create_dependency_pr.get_vikunja_token", side_effect=click.ClickException("no token")): + result = create_vikunja_task("Test", "desc") + assert result is None + + def test_returns_identifier_on_success(self) -> None: + with ( + patch("devx.ci.create_dependency_pr.get_vikunja_token", return_value="fake-token"), + patch("devx.api_clients.VikunjaClient") as mock_client_cls, + ): + mock_client = mock_client_cls.return_value + mock_client.create_task.return_value = {"identifier": "OBL-INFRA-999"} + result = create_vikunja_task("Test", "desc") + assert result == "OBL-INFRA-999" diff --git a/tests/unit/test_fast_molecule.py b/tests/unit/test_fast_molecule.py new file mode 100644 index 0000000..cbaed11 --- /dev/null +++ b/tests/unit/test_fast_molecule.py @@ -0,0 +1,82 @@ +"""Unit tests for devx.ci.fast_molecule.""" + +from pathlib import Path +from unittest.mock import MagicMock, patch + +from click.testing import CliRunner + +from devx.ci.fast_molecule import ( + build_molecule_commands, + cli, + get_molecule_scenarios, +) + + +class TestGetMoleculeScenarios: + def test_finds_scenarios(self, tmp_path: Path) -> None: + roles_dir = tmp_path / "ansible" / "roles" / "myrole" / "molecule" + roles_dir.mkdir(parents=True) + (roles_dir / "default").mkdir() + (roles_dir / "default" / "molecule.yml").write_text("name: default") + (roles_dir / "full").mkdir() + (roles_dir / "full" / "molecule.yml").write_text("name: full") + (roles_dir / "no_scenario").mkdir() # No molecule.yml + + scenarios = get_molecule_scenarios("myrole", str(tmp_path / "ansible" / "roles")) + assert sorted(scenarios) == ["default", "full"] + + def test_returns_empty_when_no_molecule_dir(self, tmp_path: Path) -> None: + scenarios = get_molecule_scenarios("nonexistent", str(tmp_path / "ansible" / "roles")) + assert scenarios == [] + + +class TestBuildMoleculeCommands: + def test_builds_commands_for_roles(self, tmp_path: Path) -> None: + roles_dir = tmp_path / "ansible" / "roles" + for role in ["role_a", "role_b"]: + mol_dir = roles_dir / role / "molecule" / "default" + mol_dir.mkdir(parents=True) + (mol_dir / "molecule.yml").write_text("name: default") + + commands = build_molecule_commands({"role_a", "role_b"}, str(roles_dir)) + assert len(commands) == 2 + assert all("molecule test -s default" in c for c in commands) + assert all("--destroy=never" in c for c in commands) + assert all("ubuntu-2604" in c for c in commands) + + def test_empty_when_no_scenarios(self, tmp_path: Path) -> None: + commands = build_molecule_commands({"nonexistent"}, str(tmp_path / "ansible" / "roles")) + assert commands == [] + + def test_empty_when_no_roles(self) -> None: + assert build_molecule_commands(set()) == [] + + +class TestCli: + @patch("devx.ci.fast_molecule.get_changed_files") + def test_no_changes(self, mock_get: MagicMock) -> None: + mock_get.return_value = [] + runner = CliRunner() + result = runner.invoke(cli, ["--base", "origin/master", "--head", "HEAD"]) + assert result.exit_code == 0 + assert "No files changed" in result.output + + @patch("devx.ci.fast_molecule.detect_changed_roles") + @patch("devx.ci.fast_molecule.get_changed_files") + def test_no_ansible_changes(self, mock_get: MagicMock, mock_detect: MagicMock) -> None: + mock_get.return_value = ["src/main.py", "README.md"] + mock_detect.return_value = set() + runner = CliRunner() + result = runner.invoke(cli, ["--base", "origin/master", "--head", "HEAD"]) + assert result.exit_code == 0 + assert "No Ansible roles changed" in result.output + + @patch("devx.ci.fast_molecule.detect_changed_roles") + @patch("devx.ci.fast_molecule.get_changed_files") + def test_detects_changed_roles(self, mock_get: MagicMock, mock_detect: MagicMock) -> None: + mock_get.return_value = ["ansible/roles/sso_bridge/tasks/main.yml"] + mock_detect.return_value = {"sso_bridge"} + runner = CliRunner() + result = runner.invoke(cli, ["--base", "origin/master", "--head", "HEAD"]) + assert result.exit_code == 0 + assert "sso_bridge" in result.output diff --git a/tests/unit/test_nightly_gate.py b/tests/unit/test_nightly_gate.py new file mode 100644 index 0000000..255acd8 --- /dev/null +++ b/tests/unit/test_nightly_gate.py @@ -0,0 +1,106 @@ +"""Unit tests for devx.ci.nightly_gate.""" + +from unittest.mock import MagicMock, patch + +from click.testing import CliRunner + +from devx.ci.nightly_gate import cli, get_nightly_status, set_nightly_status + + +class TestGetNightlyStatus: + @patch("devx.ci.nightly_gate.GiteaClient") + def test_returns_value_when_set(self, mock_client_cls: MagicMock) -> None: + mock_client = mock_client_cls.return_value + mock_client.get_repo_variable.return_value = "passed:12345" + result = get_nightly_status(mock_client) + assert result == "passed:12345" + + @patch("devx.ci.nightly_gate.GiteaClient") + def test_returns_empty_when_not_set(self, mock_client_cls: MagicMock) -> None: + mock_client = mock_client_cls.return_value + mock_client.get_repo_variable.return_value = None + result = get_nightly_status(mock_client) + assert result == "" + + +class TestSetNightlyStatus: + @patch("devx.ci.nightly_gate.GiteaClient") + def test_sets_passed(self, mock_client_cls: MagicMock) -> None: + mock_client = mock_client_cls.return_value + set_nightly_status(mock_client, "passed:12345") + mock_client.set_repo_variable.assert_called_once_with("NIGHTLY_STATUS", "passed:12345") + + @patch("devx.ci.nightly_gate.GiteaClient") + def test_sets_failed(self, mock_client_cls: MagicMock) -> None: + mock_client = mock_client_cls.return_value + set_nightly_status(mock_client, "failed:99999") + mock_client.set_repo_variable.assert_called_once_with("NIGHTLY_STATUS", "failed:99999") + + +class TestCli: + @patch("devx.ci.nightly_gate.GiteaClient") + @patch("devx.ci.nightly_gate.get_ci_token") + def test_check_bootstrap_allows_deploy(self, mock_token: MagicMock, mock_client_cls: MagicMock) -> None: + mock_token.return_value = "fake-token" + mock_client = mock_client_cls.return_value + mock_client.get_repo_variable.return_value = None + runner = CliRunner() + result = runner.invoke(cli, ["--repo", "oblachno/infra", "--action", "check"]) + assert result.exit_code == 0 + assert "bootstrap" in result.output.lower() + + @patch("devx.ci.nightly_gate.GiteaClient") + @patch("devx.ci.nightly_gate.get_ci_token") + def test_check_passed_allows_deploy(self, mock_token: MagicMock, mock_client_cls: MagicMock) -> None: + mock_token.return_value = "fake-token" + mock_client = mock_client_cls.return_value + mock_client.get_repo_variable.return_value = "passed:12345" + runner = CliRunner() + result = runner.invoke(cli, ["--repo", "oblachno/infra", "--action", "check"]) + assert result.exit_code == 0 + assert "passed" in result.output.lower() + + @patch("devx.ci.nightly_gate.GiteaClient") + @patch("devx.ci.nightly_gate.get_ci_token") + def test_check_failed_blocks_deploy(self, mock_token: MagicMock, mock_client_cls: MagicMock) -> None: + mock_token.return_value = "fake-token" + mock_client = mock_client_cls.return_value + mock_client.get_repo_variable.return_value = "failed:99999" + runner = CliRunner() + result = runner.invoke(cli, ["--repo", "oblachno/infra", "--action", "check"]) + assert result.exit_code != 0 + assert "blocked" in result.output.lower() + + @patch("devx.ci.nightly_gate.GiteaClient") + @patch("devx.ci.nightly_gate.get_ci_token") + def test_set_passed(self, mock_token: MagicMock, mock_client_cls: MagicMock) -> None: + mock_token.return_value = "fake-token" + mock_client = mock_client_cls.return_value + runner = CliRunner() + result = runner.invoke(cli, ["--repo", "oblachno/infra", "--action", "set-passed", "--run-id", "12345"]) + assert result.exit_code == 0 + mock_client.set_repo_variable.assert_called_once_with("NIGHTLY_STATUS", "passed:12345") + + @patch("devx.ci.nightly_gate.GiteaClient") + @patch("devx.ci.nightly_gate.get_ci_token") + def test_set_failed(self, mock_token: MagicMock, mock_client_cls: MagicMock) -> None: + mock_token.return_value = "fake-token" + mock_client = mock_client_cls.return_value + runner = CliRunner() + result = runner.invoke(cli, ["--repo", "oblachno/infra", "--action", "set-failed", "--run-id", "99999"]) + assert result.exit_code == 0 + mock_client.set_repo_variable.assert_called_once_with("NIGHTLY_STATUS", "failed:99999") + + @patch("devx.ci.nightly_gate.get_ci_token") + def test_fails_without_token(self, mock_token: MagicMock) -> None: + import click as click_mod + + mock_token.side_effect = click_mod.ClickException("No token") + runner = CliRunner() + result = runner.invoke(cli, ["--repo", "oblachno/infra", "--action", "check"]) + assert result.exit_code != 0 + + def test_fails_with_invalid_repo(self) -> None: + runner = CliRunner() + result = runner.invoke(cli, ["--repo", "invalid", "--action", "check"]) + assert result.exit_code != 0 diff --git a/tests/unit/test_pr_review.py b/tests/unit/test_pr_review.py deleted file mode 100644 index 70dd239..0000000 --- a/tests/unit/test_pr_review.py +++ /dev/null @@ -1,1022 +0,0 @@ -"""Unit tests for scripts/ci/pr_review.py.""" - -from unittest.mock import MagicMock, patch - -import pytest -from click.testing import CliRunner - -from devx.ci.pr_review import ( - ReviewResult, - build_review_body, - check_architecture_compliance, - check_best_practices, - check_commit_conventions, - check_documentation, - check_function_length, - check_i18n, - check_resource_management, - check_security, - check_test_coverage, - is_python_file, - is_workflow_only, - main, - post_review, - run_review, -) -from devx.exceptions import APIError - - -class TestIsPythonFile: - def test_python_file_in_src(self) -> None: - assert is_python_file("src/devx/cli.py") is True - - def test_python_file_in_scripts(self) -> None: - assert is_python_file("scripts/ci/release.py") is True - - def test_test_file_excluded(self) -> None: - assert is_python_file("tests/unit/test_cli.py") is False - - def test_non_python_file(self) -> None: - assert is_python_file("README.md") is False - - def test_yaml_file(self) -> None: - assert is_python_file(".gitea/workflows/ci.yml") is False - - -class TestIsWorkflowOnly: - def test_yaml_is_workflow(self) -> None: - assert is_workflow_only(".gitea/workflows/ci.yml") is True - - def test_md_is_workflow(self) -> None: - assert is_workflow_only("README.md") is True - - def test_python_is_not_workflow(self) -> None: - assert is_workflow_only("src/devx/cli.py") is False - - def test_ansible_is_workflow(self) -> None: - assert is_workflow_only("ansible/tasks/main.yml") is True - - -class TestReviewResult: - def test_empty_result_has_no_issues(self) -> None: - result = ReviewResult() - assert result.has_issues is False - - def test_add_issue_makes_has_issues_true(self) -> None: - result = ReviewResult() - result.add_issue("src/foo.py", 10, "bad code") - assert result.has_issues is True - assert len(result.issues) == 1 - assert result.issues[0]["path"] == "src/foo.py" - assert result.issues[0]["new_position"] == 10 - - def test_add_summary(self) -> None: - result = ReviewResult() - result.add_summary("all good") - assert "all good" in result.summary - - -class TestCheckArchitectureCompliance: - def test_subprocess_in_cli_triggers_issue(self) -> None: - result = ReviewResult() - files = [ - { - "filename": "src/devx/cli.py", - "patch": "@@ -10,3 +10,4 @@\n def foo():\n pass\n+ subprocess.run(['ls'])\n", - } - ] - check_architecture_compliance(files, result) - assert result.has_issues - assert "subprocess" in result.issues[0]["body"].lower() - - def test_subprocess_in_other_file_ok(self) -> None: - result = ReviewResult() - files = [ - { - "filename": "src/devx/executor.py", - "patch": "@@ -10,3 +10,4 @@\n def foo():\n pass\n+ subprocess.run(['ls'])\n", - } - ] - check_architecture_compliance(files, result) - assert not result.has_issues - - def test_no_changes_adds_ok_summary(self) -> None: - result = ReviewResult() - files = [{"filename": "src/devx/cli.py", "patch": ""}] - check_architecture_compliance(files, result) - assert any("Architecture compliance: OK" in s for s in result.summary) - - def test_non_python_file_skipped(self) -> None: - result = ReviewResult() - files = [{"filename": "README.md", "patch": "@@ -1,1 +1,2 @@\n+subprocess.run(['ls'])\n"}] - check_architecture_compliance(files, result) - assert not result.has_issues - - def test_empty_patch_skipped(self) -> None: - result = ReviewResult() - files = [{"filename": "src/devx/cli.py", "patch": ""}] - check_architecture_compliance(files, result) - assert not result.has_issues - - def test_os_system_in_cli_triggers_issue(self) -> None: - result = ReviewResult() - files = [ - { - "filename": "src/devx/cli.py", - "patch": "@@ -10,3 +10,4 @@\n def foo():\n pass\n+ os.system('ls')\n", - } - ] - check_architecture_compliance(files, result) - assert result.has_issues - assert "os.system" in result.issues[0]["body"] - - def test_malformed_hunk_header_no_line_number(self) -> None: - """A @@ header without a +N line number is handled gracefully.""" - result = ReviewResult() - files = [ - { - "filename": "src/devx/cli.py", - "patch": "@@ -1,2 @@\n+ subprocess.run(['ls'])\n", - } - ] - check_architecture_compliance(files, result) - assert result.has_issues - - -class TestCheckBestPractices: - def test_print_triggers_warning(self) -> None: - result = ReviewResult() - files = [ - { - "filename": "src/devx/cli.py", - "patch": "@@ -10,3 +10,4 @@\n def foo():\n pass\n+ print('hello')\n", - } - ] - check_best_practices(files, result) - assert result.has_issues - assert "print()" in result.issues[0]["body"] - - def test_bare_except_triggers_warning(self) -> None: - result = ReviewResult() - files = [ - { - "filename": "src/devx/runner_manager.py", - "patch": "@@ -10,3 +10,4 @@\n def foo():\n pass\n+ except:\n pass\n", - } - ] - check_best_practices(files, result) - assert result.has_issues - assert "bare except" in result.issues[0]["body"] - - def test_todo_triggers_warning(self) -> None: - result = ReviewResult() - files = [ - { - "filename": "src/devx/cli.py", - "patch": "@@ -10,3 +10,4 @@\n def foo():\n pass\n+ # TODO: fix this\n", - } - ] - check_best_practices(files, result) - assert result.has_issues - assert "TODO" in result.issues[0]["body"] - - def test_clean_code_no_issues(self) -> None: - result = ReviewResult() - files = [ - { - "filename": "src/devx/cli.py", - "patch": "@@ -10,3 +10,4 @@\n def foo():\n pass\n+ click.echo('hello')\n", - } - ] - check_best_practices(files, result) - assert not result.has_issues - - def test_empty_patch_skipped(self) -> None: - result = ReviewResult() - files = [{"filename": "src/devx/cli.py", "patch": ""}] - check_best_practices(files, result) - assert not result.has_issues - - def test_non_python_file_skipped(self) -> None: - result = ReviewResult() - files = [{"filename": "README.md", "patch": "@@ -1,1 +1,2 @@\n+print('hello')\n"}] - check_best_practices(files, result) - assert not result.has_issues - - def test_malformed_hunk_header_no_line_number(self) -> None: - """A @@ header without a +N line number is handled gracefully.""" - result = ReviewResult() - files = [ - { - "filename": "src/devx/cli.py", - "patch": "@@ -1,2 @@\n+ print('hello')\n", - } - ] - check_best_practices(files, result) - assert result.has_issues - assert "print()" in result.issues[0]["body"] - - -class TestCheckSecurity: - def test_hardcoded_secret_triggers_error(self) -> None: - result = ReviewResult() - files = [ - { - "filename": "src/devx/config.py", - "patch": "@@ -10,3 +10,4 @@\n def foo():\n pass\n+ token = 'abc123secrettoken456'\n", - } - ] - check_security(files, result) - assert result.has_issues - assert "secret" in result.issues[0]["body"].lower() - - def test_example_token_not_flagged(self) -> None: - result = ReviewResult() - files = [ - { - "filename": ".env.example", - "patch": "@@ -1,1 +1,2 @@\n+token = your-example-token\n", - } - ] - check_security(files, result) - assert not result.has_issues - - def test_shell_true_triggers_warning(self) -> None: - result = ReviewResult() - files = [ - { - "filename": "src/devx/executor.py", - "patch": "@@ -10,3 +10,4 @@\n def foo():\n pass\n+ subprocess.run('ls', shell=True)\n", - } - ] - check_best_practices(files, result) - assert result.has_issues - assert "shell=True" in result.issues[0]["body"] - - def test_empty_patch_skipped(self) -> None: - result = ReviewResult() - files = [{"filename": "src/devx/config.py", "patch": ""}] - check_security(files, result) - assert not result.has_issues - - def test_non_python_file_skipped(self) -> None: - result = ReviewResult() - files = [{"filename": "docs/config.md", "patch": "@@ -1,1 +1,2 @@\n+token = 'abc123secrettoken456'\n"}] - check_security(files, result) - assert not result.has_issues - - def test_malformed_hunk_header_no_line_number(self) -> None: - """A @@ header without a +N line number is handled gracefully.""" - result = ReviewResult() - files = [ - { - "filename": "src/devx/config.py", - "patch": "@@ -1,2 @@\n+ token = 'abc123secrettoken456'\n", - } - ] - check_security(files, result) - assert result.has_issues - assert "secret" in result.issues[0]["body"].lower() - - -class TestCheckI18n: - def test_raw_string_in_echo_triggers_warning(self) -> None: - result = ReviewResult() - files = [{"filename": "src/devx/cli.py", "patch": '@@ -1,1 +1,1 @@\n+click.echo("Hello world")\n'}] - check_i18n(files, result) - assert result.has_issues - assert any("i18n" in i["body"] for i in result.issues) - - def test_translated_string_no_warning(self) -> None: - result = ReviewResult() - files = [{"filename": "src/devx/cli.py", "patch": '@@ -1,1 +1,1 @@\n+click.echo(_("Hello world"))\n'}] - check_i18n(files, result) - assert not result.has_issues - - def test_fstring_in_echo_triggers_warning(self) -> None: - result = ReviewResult() - files = [{"filename": "src/devx/cli.py", "patch": '@@ -1,1 +1,1 @@\n+click.echo(f"Hello {name}")\n'}] - check_i18n(files, result) - assert result.has_issues - - def test_raw_exception_triggers_warning(self) -> None: - result = ReviewResult() - files = [ - { - "filename": "src/devx/cli.py", - "patch": '@@ -1,1 +1,1 @@\n+raise click.ClickException("Something went wrong")\n', - } - ] - check_i18n(files, result) - assert result.has_issues - - def test_non_src_file_skipped(self) -> None: - result = ReviewResult() - files = [{"filename": "scripts/ci/test.py", "patch": '@@ -1,1 +1,1 @@\n+click.echo("Hello world")\n'}] - check_i18n(files, result) - assert not result.has_issues - - def test_comment_skipped(self) -> None: - result = ReviewResult() - files = [{"filename": "src/devx/cli.py", "patch": '@@ -1,1 +1,1 @@\n+# click.echo("Hello world")\n'}] - check_i18n(files, result) - assert not result.has_issues - - def test_empty_patch_skipped(self) -> None: - result = ReviewResult() - files = [{"filename": "src/devx/cli.py", "patch": ""}] - check_i18n(files, result) - assert not result.has_issues - - def test_clean_code_adds_ok_summary(self) -> None: - result = ReviewResult() - files = [{"filename": "src/devx/cli.py", "patch": '@@ -1,1 +1,1 @@\n+click.echo(_("Hello world"))\n'}] - check_i18n(files, result) - assert any("i18n: OK" in s for s in result.summary) - - def test_malformed_hunk_header_no_line_number(self) -> None: - """A @@ header without a +N line number is handled gracefully.""" - result = ReviewResult() - files = [{"filename": "src/devx/cli.py", "patch": '@@ -1,2 @@\n+click.echo("Hello world")\n'}] - check_i18n(files, result) - assert result.has_issues - assert any("i18n" in i["body"] for i in result.issues) - - -class TestCheckResourceManagement: - def test_open_without_with_triggers_warning(self) -> None: - result = ReviewResult() - files = [{"filename": "src/devx/cli.py", "patch": '@@ -1,1 +1,1 @@\n+f = open("file.txt")\n'}] - check_resource_management(files, result) - assert result.has_issues - assert any("resource" in i["body"].lower() for i in result.issues) - - def test_open_with_with_no_warning(self) -> None: - result = ReviewResult() - files = [ - { - "filename": "src/devx/cli.py", - "patch": '@@ -1,1 +1,1 @@\n+with open("file.txt") as f:\n+ pass\n', - } - ] - check_resource_management(files, result) - assert not result.has_issues - - def test_popen_without_cleanup_triggers_warning(self) -> None: - result = ReviewResult() - files = [ - { - "filename": "src/devx/executor.py", - "patch": '@@ -1,1 +1,1 @@\n+proc = subprocess.Popen(["cmd"])\n', - } - ] - check_resource_management(files, result) - assert result.has_issues - - def test_popen_with_communicate_no_warning(self) -> None: - result = ReviewResult() - files = [ - { - "filename": "src/devx/executor.py", - "patch": '@@ -1,1 +1,1 @@\n+out, err = subprocess.Popen(["cmd"], stdout=PIPE).communicate()\n', - } - ] - check_resource_management(files, result) - assert not result.has_issues - - def test_comment_skipped(self) -> None: - result = ReviewResult() - files = [{"filename": "src/devx/cli.py", "patch": '@@ -1,1 +1,1 @@\n+# f = open("file.txt")\n'}] - check_resource_management(files, result) - assert not result.has_issues - - def test_empty_patch_skipped(self) -> None: - result = ReviewResult() - files = [{"filename": "src/devx/cli.py", "patch": ""}] - check_resource_management(files, result) - assert not result.has_issues - - def test_non_python_file_skipped(self) -> None: - result = ReviewResult() - files = [{"filename": "docs/config.md", "patch": '@@ -1,1 +1,1 @@\n+f = open("file.txt")\n'}] - check_resource_management(files, result) - assert not result.has_issues - - def test_clean_code_adds_ok_summary(self) -> None: - result = ReviewResult() - files = [ - { - "filename": "src/devx/cli.py", - "patch": '@@ -1,1 +1,1 @@\n+with open("file.txt") as f:\n+ data = f.read()\n', - } - ] - check_resource_management(files, result) - assert any("Resource management: OK" in s for s in result.summary) - - def test_malformed_hunk_header_no_line_number(self) -> None: - """A @@ header without a +N line number is handled gracefully.""" - result = ReviewResult() - files = [{"filename": "src/devx/cli.py", "patch": '@@ -1,2 @@\n+f = open("file.txt")\n'}] - check_resource_management(files, result) - assert result.has_issues - assert any("resource" in i["body"].lower() for i in result.issues) - - -class TestCheckFunctionLength: - def test_long_function_triggers_warning(self) -> None: - result = ReviewResult() - # Create a patch with a function that adds > 50 lines - added_lines = "\n".join(f"+ x = {i}" for i in range(55)) - patch = f"@@ -10,3 +10,59 @@\n+def foo():\n+ pass\n{added_lines}\n" - files = [{"filename": "src/devx/cli.py", "patch": patch}] - check_function_length(files, result) - assert result.has_issues - assert "foo" in result.issues[0]["body"] - - def test_short_function_no_warning(self) -> None: - result = ReviewResult() - patch = "@@ -10,3 +10,8 @@\n def foo():\n pass\n+ x = 1\n+ y = 2\n+ z = 3\n" - files = [{"filename": "src/devx/cli.py", "patch": patch}] - check_function_length(files, result) - assert not result.has_issues - - def test_empty_patch_skipped(self) -> None: - result = ReviewResult() - files = [{"filename": "src/devx/cli.py", "patch": ""}] - check_function_length(files, result) - assert not result.has_issues - - def test_non_python_file_skipped(self) -> None: - result = ReviewResult() - added_lines = "\n".join(f"+ x = {i}" for i in range(55)) - patch = f"@@ -10,3 +10,59 @@\n+def foo():\n+ pass\n{added_lines}\n" - files = [{"filename": "README.md", "patch": patch}] - check_function_length(files, result) - assert not result.has_issues - - def test_multiple_functions_resets_count(self) -> None: - """Two short functions back-to-back should not trigger the length warning.""" - result = ReviewResult() - patch = "@@ -10,3 +10,15 @@\n+def foo():\n+ x = 1\n+def bar():\n+ y = 2\n" - files = [{"filename": "src/devx/cli.py", "patch": patch}] - check_function_length(files, result) - assert not result.has_issues - - def test_long_function_followed_by_new_hunk(self) -> None: - """Long function followed by @@ header triggers the warning at hunk boundary.""" - result = ReviewResult() - added_lines = "\n".join(f"+ x = {i}" for i in range(55)) - patch = ( - f"@@ -10,3 +10,59 @@\n+def foo():\n+ pass\n{added_lines}\n@@ -100,3 +100,5 @@\n+def bar():\n+ pass\n" - ) - files = [{"filename": "src/devx/cli.py", "patch": patch}] - check_function_length(files, result) - assert result.has_issues - assert "foo" in result.issues[0]["body"] - - def test_long_function_followed_by_new_def(self) -> None: - """Long function followed by another def triggers the warning at def boundary.""" - result = ReviewResult() - added_lines = "\n".join(f"+ x = {i}" for i in range(55)) - patch = f"@@ -10,3 +10,60 @@\n+def foo():\n+ pass\n{added_lines}\n+def bar():\n+ pass\n" - files = [{"filename": "src/devx/cli.py", "patch": patch}] - check_function_length(files, result) - assert result.has_issues - assert "foo" in result.issues[0]["body"] - - def test_malformed_hunk_header_no_line_number(self) -> None: - """A @@ header without a +N line number is handled gracefully.""" - result = ReviewResult() - files = [{"filename": "src/devx/cli.py", "patch": "@@ -1,2 @@\n+def foo():\n+ pass\n"}] - check_function_length(files, result) - assert not result.has_issues - - -class TestCheckDocumentation: - def test_src_changes_without_docs_warns(self) -> None: - result = ReviewResult() - files = [{"filename": "src/devx/cli.py"}] - check_documentation(files, result) - assert any("WARNING" in s for s in result.summary) - - def test_src_changes_with_docs_ok(self) -> None: - result = ReviewResult() - files = [{"filename": "src/devx/cli.py"}, {"filename": "docs/user/cli-commands.md"}] - check_documentation(files, result) - assert any("Documentation: OK" in s for s in result.summary) - - def test_ansible_changes_without_docs_warns(self) -> None: - result = ReviewResult() - files = [{"filename": "ansible/roles/gitea-runner/tasks/main.yml"}] - check_documentation(files, result) - assert any("WARNING" in s for s in result.summary) - - def test_only_doc_changes_ok(self) -> None: - result = ReviewResult() - files = [{"filename": "README.md"}] - check_documentation(files, result) - assert any("Documentation: OK" in s for s in result.summary) - - def test_tofu_changes_without_docs_warns(self) -> None: - result = ReviewResult() - files = [{"filename": "tofu/modules/hetzner-vm/main.tf"}] - check_documentation(files, result) - assert any("WARNING" in s for s in result.summary) - - def test_workflow_changes_info(self) -> None: - result = ReviewResult() - files = [{"filename": ".gitea/workflows/ci.yml"}] - check_documentation(files, result) - assert any("INFO" in s for s in result.summary) - - def test_todo_in_doc_patch_warns(self) -> None: - result = ReviewResult() - files = [{"filename": "docs/guide.md", "patch": "+TODO: fix this later\n+Some content\n"}] - check_documentation(files, result) - assert any("TODO" in s for s in result.summary) - - def test_todo_in_readme_patch_warns(self) -> None: - result = ReviewResult() - files = [{"filename": "README.md", "patch": "+FIXME: broken\n"}] - check_documentation(files, result) - assert any("FIXME" in s for s in result.summary) - - def test_no_todo_in_doc_patch_ok(self) -> None: - result = ReviewResult() - files = [{"filename": "docs/guide.md", "patch": "+Some content\n"}] - check_documentation(files, result) - assert not any("TODO" in s for s in result.summary) - - -class TestCheckTestCoverage: - def test_src_changes_without_tests_warns(self) -> None: - result = ReviewResult() - files = [{"filename": "src/devx/cli.py"}] - check_test_coverage(files, result) - assert any("WARNING" in s for s in result.summary) - - def test_src_changes_with_tests_ok(self) -> None: - result = ReviewResult() - files = [{"filename": "src/devx/cli.py"}, {"filename": "tests/unit/test_cli.py"}] - check_test_coverage(files, result) - assert any("Tests: OK" in s for s in result.summary) - - def test_only_test_changes_ok(self) -> None: - result = ReviewResult() - files = [{"filename": "tests/unit/test_cli.py"}] - check_test_coverage(files, result) - assert any("Tests: OK" in s for s in result.summary) - - -class TestBuildReviewBody: - def test_body_contains_summary(self) -> None: - result = ReviewResult() - result.add_summary("- Architecture compliance: OK") - body = build_review_body(result) - assert "Architecture compliance: OK" in body - assert "Automated PR Review" in body - - def test_body_contains_issues(self) -> None: - result = ReviewResult() - result.add_issue("src/foo.py", 10, "bad code") - body = build_review_body(result) - assert "1 issue(s) found" in body - assert "src/foo.py:10" in body - assert "bad code" in body - - def test_body_contains_no_issues_message(self) -> None: - result = ReviewResult() - body = build_review_body(result) - assert "No issues found" in body - - def test_body_contains_auto_merge_note(self) -> None: - """Review body must mention auto-merge.""" - result = ReviewResult() - body = build_review_body(result) - assert "Auto-merge" in body - - -class TestRunReview: - @patch("devx.ci.pr_review.GiteaClient") - def test_run_review_with_no_files(self, mock_client_class: MagicMock) -> None: - mock_client = mock_client_class.return_value - mock_client.get_pr_files.return_value = [] - result = run_review(mock_client, "42") - assert "No files changed" in result.summary[0] - - @patch("devx.ci.pr_review.GiteaClient") - def test_run_review_finds_issues(self, mock_client_class: MagicMock) -> None: - mock_client = mock_client_class.return_value - mock_client.get_pr_files.return_value = [ - { - "filename": "src/devx/cli.py", - "patch": "@@ -10,3 +10,4 @@\n def foo():\n pass\n+ print('hello')\n", - } - ] - mock_client.get_pr_commits.return_value = [{"commit": {"message": "fix: resolve print issue"}}] - result = run_review(mock_client, "42") - assert result.has_issues - - def test_run_review_handles_api_error(self) -> None: - client = MagicMock() - client.get_pr_files.side_effect = APIError(404, "Not found") - result = run_review(client, "42") - assert any("ERROR" in s for s in result.summary) - - -class TestCheckCommitConventions: - def test_conventional_commit_found(self) -> None: - """Should report OK when at least one commit is conventional.""" - client = MagicMock() - client.get_pr_commits.return_value = [ - {"commit": {"message": "fix: resolve bug\n\nDetails"}}, - {"commit": {"message": "wip: testing"}}, - ] - result = ReviewResult() - check_commit_conventions(client, "42", result) - assert any("OK" in s for s in result.summary) - - def test_no_conventional_commit(self) -> None: - """Should warn when no commits are conventional.""" - client = MagicMock() - client.get_pr_commits.return_value = [ - {"commit": {"message": "updated stuff"}}, - {"commit": {"message": "wip: testing"}}, - ] - result = ReviewResult() - check_commit_conventions(client, "42", result) - assert any("WARNING" in s for s in result.summary) - - def test_merge_commits_excluded(self) -> None: - """Merge commits should be excluded from the check.""" - client = MagicMock() - client.get_pr_commits.return_value = [ - {"commit": {"message": "Merge branch 'feature' into master"}}, - {"commit": {"message": "fix: resolve bug"}}, - ] - result = ReviewResult() - check_commit_conventions(client, "42", result) - assert any("OK" in s for s in result.summary) - - def test_all_merges_and_reverts(self) -> None: - """Should report OK when all commits are merges/reverts.""" - client = MagicMock() - client.get_pr_commits.return_value = [ - {"commit": {"message": "Merge branch 'feature' into master"}}, - {"commit": {"message": "Revert: bad commit"}}, - ] - result = ReviewResult() - check_commit_conventions(client, "42", result) - assert any("merges/reverts" in s for s in result.summary) - - def test_no_commits(self) -> None: - """Should report OK when there are no commits.""" - client = MagicMock() - client.get_pr_commits.return_value = [] - result = ReviewResult() - check_commit_conventions(client, "42", result) - assert any("no commits" in s for s in result.summary) - - def test_api_error(self) -> None: - """Should report ERROR when API call fails.""" - client = MagicMock() - client.get_pr_commits.side_effect = APIError(500, "server error") - result = ReviewResult() - check_commit_conventions(client, "42", result) - assert any("ERROR" in s for s in result.summary) - - -class TestPostReview: - def test_post_review_with_issues(self) -> None: - client = MagicMock() - result = ReviewResult() - result.add_issue("src/foo.py", 10, "bad code") - post_review(client, "42", result) - client.create_review.assert_called_once() - call_args = client.create_review.call_args - assert call_args[1]["event"] == "REQUEST_CHANGES" - assert call_args[1]["comments"] == result.issues - - def test_post_review_without_issues_uses_comment_not_approve(self) -> None: - """Automated review posts COMMENT, not APPROVE (self-approval not allowed).""" - client = MagicMock() - result = ReviewResult() - post_review(client, "42", result) - client.create_review.assert_called_once() - call_args = client.create_review.call_args - assert call_args[1]["event"] == "COMMENT" - assert call_args[1]["comments"] == [] - - -class TestMain: - @patch("devx.ci.pr_review.run_review") - @patch("devx.ci.pr_review.GiteaClient") - def test_dry_run_does_not_post(self, mock_client_class: MagicMock, mock_run: MagicMock) -> None: - mock_run.return_value = ReviewResult() - runner = CliRunner() - result = runner.invoke(main, ["42", "my-org/my-repo", "--dry-run"], env={"CI_GITEA_TOKEN": "fake"}) - assert result.exit_code == 0 - assert "[dry-run]" in result.output - mock_client_class.return_value.create_review.assert_not_called() - - @patch("devx.ci.pr_review.run_review") - @patch("devx.ci.pr_review.GiteaClient") - def test_post_review_on_success(self, mock_client_class: MagicMock, mock_run: MagicMock) -> None: - mock_run.return_value = ReviewResult() - mock_client_class.return_value.create_review.return_value = {"id": 123} - runner = CliRunner() - result = runner.invoke(main, ["42", "my-org/my-repo"], env={"CI_GITEA_TOKEN": "fake"}) - assert result.exit_code == 0 - assert "Review #123" in result.output - mock_client_class.return_value.create_review.assert_called_once() - - @patch("devx.ci.pr_review.run_review") - @patch("devx.ci.pr_review.GiteaClient") - def test_self_approval_falls_back_to_comment(self, mock_client_class: MagicMock, mock_run: MagicMock) -> None: - """If REQUEST_CHANGES fails with 422 (self-approval), fall back to COMMENT.""" - mock_run.return_value = ReviewResult() - client = mock_client_class.return_value - client.create_review.side_effect = [ - APIError(422, "approve your own pull is not allowed"), - {"id": 124}, - ] - runner = CliRunner() - result = runner.invoke(main, ["42", "my-org/my-repo"], env={"CI_GITEA_TOKEN": "fake"}) - assert result.exit_code == 0 - assert "Review #124" in result.output - assert client.create_review.call_count == 2 - - @patch("devx.ci.pr_review.run_review") - @patch("devx.ci.pr_review.GiteaClient") - def test_other_api_error_re_raises(self, mock_client_class: MagicMock, mock_run: MagicMock) -> None: - """Non-approval API errors should re-raise, not fall back.""" - mock_run.return_value = ReviewResult() - client = mock_client_class.return_value - client.create_review.side_effect = APIError(500, "Internal server error") - runner = CliRunner() - result = runner.invoke(main, ["42", "my-org/my-repo"], env={"CI_GITEA_TOKEN": "fake"}) - assert result.exit_code != 0 - - @patch.dict("os.environ", {"CI_GITEA_API_TOKEN": "", "CI_GITEA_TOKEN": ""}) - def test_no_token_raises(self) -> None: - runner = CliRunner() - result = runner.invoke(main, ["42", "my-org/my-repo"], env={"CI_GITEA_API_TOKEN": "", "CI_GITEA_TOKEN": ""}) - assert result.exit_code != 0 - assert "CI_GITEA_TOKEN" in result.output - - -class TestManualReview: - @patch("devx.ci.pr_review.GiteaClient") - def test_approve_success(self, mock_client_class: MagicMock) -> None: - mock_client_class.return_value.create_review.return_value = {"id": 200} - runner = CliRunner() - result = runner.invoke( - main, - [ - "42", - "oblachno-oss/devx", - "--event", - "APPROVE", - "--body", - "All 13 REVIEW_CHECKLIST.md categories verified. Architecture: clean. Security: no issues.", - "--checklist-confirmed", - "--checklist-categories", - "1,2,3,4,5,6,7,8,9,10,11,12,13", - ], - env={"CI_GITEA_TOKEN": "fake"}, - ) - assert result.exit_code == 0 - assert "Review #200" in result.output - - @patch("devx.ci.pr_review.GiteaClient") - def test_approve_without_checklist_confirmed_fails(self, mock_client_class: MagicMock) -> None: - runner = CliRunner() - result = runner.invoke( - main, - [ - "42", - "oblachno-oss/devx", - "--event", - "APPROVE", - "--body", - "x" * 60, - "--checklist-categories", - "1,2,3,4,5,6,7,8", - ], - env={"CI_GITEA_TOKEN": "fake"}, - ) - assert result.exit_code != 0 - assert "checklist-confirmed" in result.output - - @patch("devx.ci.pr_review.GiteaClient") - def test_approve_with_too_few_categories_fails(self, mock_client_class: MagicMock) -> None: - runner = CliRunner() - result = runner.invoke( - main, - [ - "42", - "oblachno-oss/devx", - "--event", - "APPROVE", - "--body", - "x" * 60, - "--checklist-confirmed", - "--checklist-categories", - "1,2,3", - ], - env={"CI_GITEA_TOKEN": "fake"}, - ) - assert result.exit_code != 0 - assert "at least 8" in result.output - - @patch("devx.ci.pr_review.GiteaClient") - def test_approve_with_short_body_fails(self, mock_client_class: MagicMock) -> None: - runner = CliRunner() - result = runner.invoke( - main, - [ - "42", - "oblachno-oss/devx", - "--event", - "APPROVE", - "--body", - "LGTM", - "--checklist-confirmed", - "--checklist-categories", - "1,2,3,4,5,6,7,8", - ], - env={"CI_GITEA_TOKEN": "fake"}, - ) - assert result.exit_code != 0 - assert "50 characters" in result.output - - @patch("devx.ci.pr_review.GiteaClient") - def test_approve_with_invalid_category_fails(self, mock_client_class: MagicMock) -> None: - runner = CliRunner() - result = runner.invoke( - main, - [ - "42", - "oblachno-oss/devx", - "--event", - "APPROVE", - "--body", - "x" * 60, - "--checklist-confirmed", - "--checklist-categories", - "1,2,abc,4", - ], - env={"CI_GITEA_TOKEN": "fake"}, - ) - assert result.exit_code != 0 - assert "Invalid" in result.output - - @patch("devx.ci.pr_review.GiteaClient") - def test_request_changes_success(self, mock_client_class: MagicMock) -> None: - mock_client_class.return_value.create_review.return_value = {"id": 201} - runner = CliRunner() - result = runner.invoke( - main, - [ - "42", - "oblachno-oss/devx", - "--event", - "REQUEST_CHANGES", - "--body", - "Please fix the architecture issues in the CLI module before merging.", - ], - env={"CI_GITEA_TOKEN": "fake"}, - ) - assert result.exit_code == 0 - assert "Review #201" in result.output - - @patch("devx.ci.pr_review.GiteaClient") - def test_manual_review_dry_run(self, mock_client_class: MagicMock) -> None: - runner = CliRunner() - result = runner.invoke( - main, - ["42", "oblachno-oss/devx", "--event", "COMMENT", "--body", "x" * 60, "--dry-run"], - env={"CI_GITEA_TOKEN": "fake"}, - ) - assert result.exit_code == 0 - assert "[dry-run]" in result.output - mock_client_class.return_value.create_review.assert_not_called() - - @patch("devx.ci.pr_review.GiteaClient") - def test_manual_review_self_approval_fallback_to_comment( - self, mock_client_class: MagicMock, monkeypatch: pytest.MonkeyPatch - ) -> None: - """Self-approval with no CI token available → fall back to COMMENT.""" - monkeypatch.delenv("CI_GITEA_API_TOKEN", raising=False) - monkeypatch.delenv("CI_GITEA_TOKEN", raising=False) - client = mock_client_class.return_value - client.create_review.side_effect = [ - APIError(422, "approve your own pull is not allowed"), - {"id": 202}, - ] - runner = CliRunner() - result = runner.invoke( - main, - [ - "42", - "oblachno-oss/devx", - "--event", - "APPROVE", - "--body", - "x" * 60, - "--checklist-confirmed", - "--checklist-categories", - "1,2,3,4,5,6,7,8", - ], - env={"REVIEWER_GITEA_API_TOKEN": "fake-reviewer"}, - ) - assert result.exit_code == 0 - assert "Review #202" in result.output - # Without CI_GITEA_API_TOKEN, the fallback is COMMENT - assert "Self-approval not allowed. Posting COMMENT instead." in result.output - assert client.create_review.call_count == 2 - assert client.create_review.call_args_list[1].kwargs.get("event") == "COMMENT" - - @patch("devx.ci.pr_review.GiteaClient") - def test_manual_review_self_approval_falls_back_to_ci_token(self, mock_client_class: MagicMock) -> None: - """Self-approval with CI token available → retry APPROVE with CI token (different user).""" - client = mock_client_class.return_value - client.create_review.side_effect = [ - APIError(422, "approve your own pull is not allowed"), - {"id": 303}, - ] - runner = CliRunner() - result = runner.invoke( - main, - [ - "42", - "oblachno-oss/devx", - "--event", - "APPROVE", - "--body", - "x" * 60, - "--checklist-confirmed", - "--checklist-categories", - "1,2,3,4,5,6,7,8", - ], - env={"REVIEWER_GITEA_API_TOKEN": "fake-reviewer", "CI_GITEA_API_TOKEN": "fake-ci"}, - ) - assert result.exit_code == 0 - assert "Review #303" in result.output - assert "Retrying with CI token" in result.output - # Second call should still be APPROVE (CI token retry) - assert client.create_review.call_count == 2 - assert client.create_review.call_args_list[1].kwargs.get("event") == "APPROVE" - - @patch("devx.ci.pr_review.GiteaClient") - def test_manual_review_ci_token_also_fails_falls_back_to_comment(self, mock_client_class: MagicMock) -> None: - """Self-approval + CI token retry also fails → fall back to COMMENT.""" - client = mock_client_class.return_value - client.create_review.side_effect = [ - APIError(422, "approve your own pull is not allowed"), - APIError(422, "approve your own pull is not allowed"), - {"id": 404}, - ] - runner = CliRunner() - result = runner.invoke( - main, - [ - "42", - "oblachno-oss/devx", - "--event", - "APPROVE", - "--body", - "x" * 60, - "--checklist-confirmed", - "--checklist-categories", - "1,2,3,4,5,6,7,8", - ], - env={"REVIEWER_GITEA_API_TOKEN": "fake-reviewer", "CI_GITEA_API_TOKEN": "fake-ci"}, - ) - assert result.exit_code == 0 - assert "Review #404" in result.output - assert "CI token also cannot approve" in result.output - # Third call should be COMMENT (final fallback) - assert client.create_review.call_count == 3 - assert client.create_review.call_args_list[2].kwargs.get("event") == "COMMENT" - - @patch("devx.ci.pr_review.GiteaClient") - def test_manual_review_other_error_re_raises(self, mock_client_class: MagicMock) -> None: - client = mock_client_class.return_value - client.create_review.side_effect = APIError(500, "Internal server error") - runner = CliRunner() - result = runner.invoke( - main, - ["42", "oblachno-oss/devx", "--event", "COMMENT", "--body", "x" * 60], - env={"CI_GITEA_TOKEN": "fake"}, - ) - assert result.exit_code != 0 - - -def test_main_module_block() -> None: - import devx.ci.pr_review as pr - - with patch.object(pr, "main") as mock_main: - with patch.object(pr, "__name__", "__main__"): - pr.main([]) - mock_main.assert_called_once_with([]) diff --git a/tests/unit/test_spec_driven_workflows.py b/tests/unit/test_spec_driven_workflows.py new file mode 100644 index 0000000..a7a81ee --- /dev/null +++ b/tests/unit/test_spec_driven_workflows.py @@ -0,0 +1,835 @@ +"""Structural tests for spec-driven development workflows and skills. + +These tests parse the actual workflow YAML files in each repo and assert +that the new spec-driven development steps, jobs, and env vars are present +and correctly wired. They also validate that the spec-driven-development +skill exists in each repo's .devin/skills/ directory with required sections. + +This is a "contract test" — it verifies that the workflows we wrote match +the intended structure, catching regressions if someone edits a workflow +and accidentally removes a step or breaks a job dependency. +""" + +from __future__ import annotations + +from pathlib import Path + +import pytest +import yaml + +# Repo root paths +# __file__ = .../devx/tests/unit/test_spec_driven_workflows.py +# parents[3] = .../oblachno (the monorepo root containing all repos) +_OBLACHNO_ROOT = Path(__file__).resolve().parents[3] +_INFRA = _OBLACHNO_ROOT / "infra" +_GRM = _OBLACHNO_ROOT / "grm" +_SSO_BRIDGE = _OBLACHNO_ROOT / "sso-bridge" +_DEVX = _OBLACHNO_ROOT / "devx" + + +def _load_workflow(repo_path: Path, filename: str) -> dict: + """Load a workflow YAML file and return parsed dict.""" + path = repo_path / ".gitea" / "workflows" / filename + if not path.exists(): + pytest.skip(f"Workflow {filename} not found in {repo_path.name}") + with open(path, encoding="utf-8") as f: + return yaml.safe_load(f) + + +def _skip_if_repo_missing(repo_name: str) -> None: + """Skip test if the sibling repo directory doesn't exist (CI only checks out one repo).""" + repo_path = _OBLACHNO_ROOT / repo_name + if not repo_path.is_dir(): + pytest.skip(f"Repo {repo_name} not found at {repo_path} (CI only checks out devx)") + + +def _read_skill(repo_name: str, skill_name: str) -> str: + """Read a skill file from a repo, skipping if the repo or file doesn't exist.""" + _skip_if_repo_missing(repo_name) + skill_path = _OBLACHNO_ROOT / repo_name / ".devin" / "skills" / skill_name / "SKILL.md" + if not skill_path.exists(): + pytest.skip(f"SKILL.md not found in {repo_name}/{skill_name}") + return skill_path.read_text(encoding="utf-8") + + +def _get_step_names(job: dict) -> list[str]: + """Extract step names from a job dict.""" + names = [] + for step in job.get("steps", []): + if "name" in step: + names.append(step["name"]) + return names + + +def _find_step(job: dict, name_part: str) -> dict | None: + """Find a step by partial name match.""" + for step in job.get("steps", []): + if "name" in step and name_part.lower() in step["name"].lower(): + return step + return None + + +def _get_run_commands(step: dict) -> str: + """Get the run command from a step.""" + return step.get("run", "") + + +# ============================================================================ +# Infra ci.yml — spec validation + PR size + fast molecule +# ============================================================================ + + +class TestInfraCiWorkflow: + @pytest.fixture + def workflow(self) -> dict: + return _load_workflow(_INFRA, "ci.yml") + + def test_validate_job_exists(self, workflow: dict) -> None: + assert "validate" in workflow["jobs"] + + def test_has_spec_validation_step(self, workflow: dict) -> None: + steps = _get_step_names(workflow["jobs"]["validate"]) + assert any("Validate spec file" in s for s in steps), "validate job must have 'Validate spec file' step" + + def test_has_pr_size_check_step(self, workflow: dict) -> None: + steps = _get_step_names(workflow["jobs"]["validate"]) + assert any("Check PR size" in s for s in steps), "validate job must have 'Check PR size' step" + + def test_spec_validation_uses_correct_module(self, workflow: dict) -> None: + step = _find_step(workflow["jobs"]["validate"], "Validate spec file") + assert step is not None + cmd = _get_run_commands(step) + assert "devx.ci.validate_spec" in cmd + assert "--github-output" in cmd + + def test_pr_size_uses_correct_module(self, workflow: dict) -> None: + step = _find_step(workflow["jobs"]["validate"], "Check PR size") + assert step is not None + cmd = _get_run_commands(step) + assert "devx.ci.check_pr_size" in cmd + assert "--github-output" in cmd + + def test_spec_validation_sets_task_prefix(self, workflow: dict) -> None: + step = _find_step(workflow["jobs"]["validate"], "Validate spec file") + assert step is not None + env = step.get("env", {}) + assert env.get("DEVX_TASK_PREFIX") == "OBL-INFRA" + + def test_has_fast_molecule_job(self, workflow: dict) -> None: + assert "fast-molecule" in workflow["jobs"], "ci.yml must have 'fast-molecule' job (replaced molecule-tests)" + + def test_no_full_molecule_tests_job(self, workflow: dict) -> None: + assert "molecule-tests" not in workflow["jobs"], "ci.yml must NOT have 'molecule-tests' job (moved to nightly)" + + def test_no_staging_deploy_in_ci(self, workflow: dict) -> None: + # The staging deploy was moved to post-merge (auto-deploy-staging) + job_names = list(workflow["jobs"].keys()) + assert "staging-health-gate" not in job_names, "staging-health-gate removed from ci.yml (moved to nightly)" + assert "pre-deploy-checks" not in job_names, "pre-deploy-checks removed from ci.yml (moved to nightly)" + assert "deploy" not in job_names, "deploy job removed from ci.yml (moved to post-merge)" + + def test_fast_molecule_uses_devx_module(self, workflow: dict) -> None: + job = workflow["jobs"]["fast-molecule"] + step = _find_step(job, "Detect changed roles") + assert step is not None + cmd = _get_run_commands(step) + assert "devx.ci.fast_molecule" in cmd + assert "--github-output" in cmd + + def test_fast_molecule_timeout_is_short(self, workflow: dict) -> None: + job = workflow["jobs"]["fast-molecule"] + assert job.get("timeout-minutes", 999) <= 30, ( + "fast-molecule timeout should be <= 30 min (was 120 for full suite)" + ) + + def test_fast_molecule_no_matrix(self, workflow: dict) -> None: + job = workflow["jobs"]["fast-molecule"] + assert "strategy" not in job or "matrix" not in job.get("strategy", {}), ( + "fast-molecule should not use matrix (single runner)" + ) + + def test_auto_merge_depends_on_fast_molecule(self, workflow: dict) -> None: + job = workflow["jobs"].get("auto-merge", {}) + needs = job.get("needs", []) + assert "fast-molecule" in needs, "auto-merge must depend on fast-molecule (not deploy)" + + def test_auto_merge_does_not_depend_on_deploy(self, workflow: dict) -> None: + job = workflow["jobs"].get("auto-merge", {}) + needs = job.get("needs", []) + assert "deploy" not in needs, "auto-merge must NOT depend on deploy (removed from PR pipeline)" + + +# ============================================================================ +# Infra nightly.yml — full molecule + staging deploy + gate +# ============================================================================ + + +class TestInfraNightlyWorkflow: + @pytest.fixture + def workflow(self) -> dict: + return _load_workflow(_INFRA, "nightly.yml") + + def test_nightly_workflow_exists(self, workflow: dict) -> None: + assert workflow is not None + + def test_has_full_molecule_job(self, workflow: dict) -> None: + assert "full-molecule" in workflow["jobs"] + + def test_has_set_gate_status_job(self, workflow: dict) -> None: + assert "set-gate-status" in workflow["jobs"] + + def test_has_staging_deploy_job(self, workflow: dict) -> None: + assert "staging-deploy" in workflow["jobs"] + + def test_full_molecule_uses_matrix(self, workflow: dict) -> None: + job = workflow["jobs"]["full-molecule"] + strategy = job.get("strategy", {}) + assert "matrix" in strategy, "full-molecule must use matrix (6 runners)" + assert "runner-index" in strategy["matrix"] + + def test_full_molecule_timeout_is_long(self, workflow: dict) -> None: + job = workflow["jobs"]["full-molecule"] + assert job.get("timeout-minutes", 0) >= 90, "full-molecule timeout should be >= 90 min (full suite)" + + def test_set_gate_status_depends_on_full_molecule(self, workflow: dict) -> None: + job = workflow["jobs"]["set-gate-status"] + needs = job.get("needs", []) + assert "full-molecule" in needs + + def test_set_gate_status_uses_nightly_gate_module(self, workflow: dict) -> None: + job = workflow["jobs"]["set-gate-status"] + step = _find_step(job, "Set nightly gate") + assert step is not None + cmd = _get_run_commands(step) + assert "devx.ci.nightly_gate" in cmd + assert "set-passed" in cmd or "set-failed" in cmd + + def test_staging_deploy_depends_on_gate(self, workflow: dict) -> None: + job = workflow["jobs"]["staging-deploy"] + needs = job.get("needs", []) + assert "set-gate-status" in needs + assert "full-molecule" in needs + + def test_staging_deploy_only_on_success(self, workflow: dict) -> None: + job = workflow["jobs"]["staging-deploy"] + if_cond = job.get("if", "") + assert "success" in if_cond, "staging-deploy must only run when full-molecule succeeds" + + def test_nightly_runs_on_schedule(self, workflow: dict) -> None: + on = workflow.get("on", workflow.get(True, {})) + # YAML may parse 'on' as True (boolean) + if isinstance(on, dict): + assert "schedule" in on, "nightly must have schedule trigger" + else: + pytest.fail("Could not parse 'on' trigger from nightly.yml") + + +# ============================================================================ +# Infra post-merge.yml — auto-deploy staging with nightly gate +# ============================================================================ + + +class TestInfraPostMergeWorkflow: + @pytest.fixture + def workflow(self) -> dict: + return _load_workflow(_INFRA, "post-merge.yml") + + def test_has_auto_deploy_staging_job(self, workflow: dict) -> None: + assert "auto-deploy-staging" in workflow["jobs"], "post-merge must have 'auto-deploy-staging' job" + + def test_has_staging_deploy_job(self, workflow: dict) -> None: + assert "staging-deploy" in workflow["jobs"], "post-merge must have 'staging-deploy' reusable workflow job" + + def test_auto_deploy_staging_checks_nightly_gate(self, workflow: dict) -> None: + job = workflow["jobs"]["auto-deploy-staging"] + step = _find_step(job, "Check nightly gate") + assert step is not None + cmd = _get_run_commands(step) + assert "devx.ci.nightly_gate" in cmd + assert "--action check" in cmd + + def test_staging_deploy_depends_on_auto_deploy_staging(self, workflow: dict) -> None: + job = workflow["jobs"]["staging-deploy"] + needs = job.get("needs", []) + assert "auto-deploy-staging" in needs + + def test_staging_deploy_gated_on_gate_passed(self, workflow: dict) -> None: + job = workflow["jobs"]["staging-deploy"] + if_cond = job.get("if", "") + assert "gate-passed" in if_cond, "staging-deploy must check gate-passed output" + + def test_auto_deploy_production_waits_for_staging(self, workflow: dict) -> None: + job = workflow["jobs"].get("auto-deploy-production", {}) + needs = job.get("needs", []) + assert "staging-deploy" in needs, "auto-deploy-production must wait for staging-deploy" + + +# ============================================================================ +# GRM ci.yml — spec validation + PR size +# ============================================================================ + + +class TestGrmCiWorkflow: + @pytest.fixture + def workflow(self) -> dict: + return _load_workflow(_GRM, "ci.yml") + + def test_has_spec_validation_step(self, workflow: dict) -> None: + steps = _get_step_names(workflow["jobs"]["validate"]) + assert any("Validate spec file" in s for s in steps) + + def test_has_pr_size_check_step(self, workflow: dict) -> None: + steps = _get_step_names(workflow["jobs"]["validate"]) + assert any("Check PR size" in s for s in steps) + + def test_spec_validation_sets_task_prefix(self, workflow: dict) -> None: + step = _find_step(workflow["jobs"]["validate"], "Validate spec file") + assert step is not None + env = step.get("env", {}) + assert env.get("DEVX_TASK_PREFIX") == "GRM" + + +# ============================================================================ +# GRM post-merge.yml — auto-create infra dependency PR +# ============================================================================ + + +class TestGrmPostMergeWorkflow: + @pytest.fixture + def workflow(self) -> dict: + return _load_workflow(_GRM, "post-merge.yml") + + def test_has_create_dependency_pr_step(self, workflow: dict) -> None: + job = workflow["jobs"].get("release-and-maintain", {}) + step = _find_step(job, "Create infra dependency PR") + assert step is not None, "grm post-merge must have 'Create infra dependency PR' step" + + def test_dependency_pr_uses_correct_module(self, workflow: dict) -> None: + job = workflow["jobs"].get("release-and-maintain", {}) + step = _find_step(job, "Create infra dependency PR") + assert step is not None + cmd = _get_run_commands(step) + assert "devx.ci.create_dependency_pr" in cmd + assert "--package grm" in cmd + assert "--repo oblachno/infra" in cmd + + def test_dependency_pr_is_best_effort(self, workflow: dict) -> None: + import re + + job = workflow["jobs"].get("release-and-maintain", {}) + step = _find_step(job, "Create infra dependency PR") + assert step is not None + cmd = _get_run_commands(step) + # Must not fail the workflow if PR creation fails. + # The || echo may be split across lines with backslash continuation in YAML. + # Normalize: remove backslashes and collapse whitespace. + cmd_normalized = " ".join(cmd.replace("\\", " ").split()) + assert bool(re.search(r"\|\|\s*echo", cmd_normalized)) or "continue-on-error" in step, ( + "dependency PR step must be best-effort (|| echo or continue-on-error)" + ) + + +# ============================================================================ +# sso-bridge ci.yml — spec validation + PR size +# ============================================================================ + + +class TestSsoBridgeCiWorkflow: + @pytest.fixture + def workflow(self) -> dict: + return _load_workflow(_SSO_BRIDGE, "ci.yml") + + def test_has_spec_validation_step(self, workflow: dict) -> None: + steps = _get_step_names(workflow["jobs"]["validate"]) + assert any("Validate spec file" in s for s in steps) + + def test_has_pr_size_check_step(self, workflow: dict) -> None: + steps = _get_step_names(workflow["jobs"]["validate"]) + assert any("Check PR size" in s for s in steps) + + def test_spec_validation_sets_task_prefix(self, workflow: dict) -> None: + step = _find_step(workflow["jobs"]["validate"], "Validate spec file") + assert step is not None + env = step.get("env", {}) + assert env.get("DEVX_TASK_PREFIX") == "SSO" + + +# ============================================================================ +# sso-bridge post-merge.yml — auto-publish + auto-create dependency PR +# ============================================================================ + + +class TestSsoBridgePostMergeWorkflow: + @pytest.fixture + def workflow(self) -> dict: + return _load_workflow(_SSO_BRIDGE, "post-merge.yml") + + def test_post_merge_workflow_exists(self, workflow: dict) -> None: + assert workflow is not None + + def test_has_release_and_maintain_job(self, workflow: dict) -> None: + assert "release-and-maintain" in workflow["jobs"] + + def test_has_create_dependency_pr_step(self, workflow: dict) -> None: + job = workflow["jobs"].get("release-and-maintain", {}) + step = _find_step(job, "Create infra dependency PR") + assert step is not None + + def test_dependency_pr_uses_correct_module(self, workflow: dict) -> None: + job = workflow["jobs"].get("release-and-maintain", {}) + step = _find_step(job, "Create infra dependency PR") + assert step is not None + cmd = _get_run_commands(step) + assert "devx.ci.create_dependency_pr" in cmd + assert "--package sso_bridge" in cmd + assert "--repo oblachno/infra" in cmd + + def test_dependency_pr_is_best_effort(self, workflow: dict) -> None: + import re + + job = workflow["jobs"].get("release-and-maintain", {}) + step = _find_step(job, "Create infra dependency PR") + assert step is not None + cmd = _get_run_commands(step) + # The || echo may be split across lines with backslash continuation in YAML. + cmd_normalized = " ".join(cmd.replace("\\", " ").split()) + assert bool(re.search(r"\|\|\s*echo", cmd_normalized)) or "continue-on-error" in step + + def test_has_publish_step(self, workflow: dict) -> None: + job = workflow["jobs"].get("release-and-maintain", {}) + steps = _get_step_names(job) + assert any("publish" in s.lower() for s in steps), "sso-bridge post-merge must have a publish step" + + +# ============================================================================ +# devx ci.yml — spec validation + PR size +# ============================================================================ + + +class TestDevxCiWorkflow: + @pytest.fixture + def workflow(self) -> dict: + return _load_workflow(_DEVX, "ci.yml") + + def test_has_spec_validation_step(self, workflow: dict) -> None: + steps = _get_step_names(workflow["jobs"]["validate"]) + assert any("Validate spec file" in s for s in steps) + + def test_has_pr_size_check_step(self, workflow: dict) -> None: + steps = _get_step_names(workflow["jobs"]["validate"]) + assert any("Check PR size" in s for s in steps) + + def test_spec_validation_sets_task_prefix(self, workflow: dict) -> None: + step = _find_step(workflow["jobs"]["validate"], "Validate spec file") + assert step is not None + env = step.get("env", {}) + assert env.get("DEVX_TASK_PREFIX") == "DEVX" + + +# ============================================================================ +# Skill files — spec-driven-development SKILL.md in all repos +# ============================================================================ + + +class TestSpecDrivenDevelopmentSkill: + REQUIRED_SECTIONS = [ + "## Overview", + "## Workflow", + "## Spec Template", + "## CI Validation", + "## Acceptance Criteria", + ] + + @pytest.mark.parametrize("repo_name", ["infra", "grm", "sso-bridge", "devx"]) + def test_skill_exists_in_repo(self, repo_name: str) -> None: + _skip_if_repo_missing(repo_name) + skill_path = _OBLACHNO_ROOT / repo_name / ".devin" / "skills" / "spec-driven-development" / "SKILL.md" + assert skill_path.exists(), f"SKILL.md not found in {repo_name}" + + @pytest.mark.parametrize("repo_name", ["infra", "grm", "sso-bridge", "devx"]) + def test_skill_has_required_sections(self, repo_name: str) -> None: + _skip_if_repo_missing(repo_name) + skill_path = _OBLACHNO_ROOT / repo_name / ".devin" / "skills" / "spec-driven-development" / "SKILL.md" + content = skill_path.read_text(encoding="utf-8") + for section in self.REQUIRED_SECTIONS: + assert section in content, f"SKILL.md in {repo_name} missing section: {section}" + + @pytest.mark.parametrize("repo_name", ["infra", "grm", "sso-bridge", "devx"]) + def test_skill_mentions_req_ids(self, repo_name: str) -> None: + _skip_if_repo_missing(repo_name) + skill_path = _OBLACHNO_ROOT / repo_name / ".devin" / "skills" / "spec-driven-development" / "SKILL.md" + content = skill_path.read_text(encoding="utf-8") + assert "REQ-" in content, f"SKILL.md in {repo_name} must mention REQ-ID format" + + @pytest.mark.parametrize("repo_name", ["infra", "grm", "sso-bridge", "devx"]) + def test_skill_mentions_pr_size_limit(self, repo_name: str) -> None: + _skip_if_repo_missing(repo_name) + skill_path = _OBLACHNO_ROOT / repo_name / ".devin" / "skills" / "spec-driven-development" / "SKILL.md" + content = skill_path.read_text(encoding="utf-8") + assert "500" in content, f"SKILL.md in {repo_name} must mention 500 line PR size limit" + + @pytest.mark.parametrize("repo_name", ["infra", "grm", "sso-bridge", "devx"]) + def test_skill_mentions_nightly_gate(self, repo_name: str) -> None: + _skip_if_repo_missing(repo_name) + skill_path = _OBLACHNO_ROOT / repo_name / ".devin" / "skills" / "spec-driven-development" / "SKILL.md" + content = skill_path.read_text(encoding="utf-8") + assert "nightly" in content.lower(), f"SKILL.md in {repo_name} must mention nightly gate" + + def test_skill_exists_in_shared_dir(self) -> None: + skill_path = _OBLACHNO_ROOT / ".devin" / "skills" / "spec-driven-development" / "SKILL.md" + if not skill_path.exists(): + pytest.skip("Shared .devin/skills/ not found (CI only checks out devx repo)") + assert skill_path.exists(), "SKILL.md not found in shared .devin/skills/" + + +# ============================================================================ +# devx-workflow skill — exists in repos with PR workflow, mentions spec gates +# ============================================================================ + + +class TestDevxWorkflowSkill: + # Repos that have a PR workflow and need the devx-workflow skill + REPOS_WITH_PR_WORKFLOW = ["infra", "grm", "sso-bridge", "devx"] + + @pytest.mark.parametrize("repo_name", REPOS_WITH_PR_WORKFLOW) + def test_skill_exists(self, repo_name: str) -> None: + _skip_if_repo_missing(repo_name) + path = _OBLACHNO_ROOT / repo_name / ".devin" / "skills" / "devx-workflow" / "SKILL.md" + assert path.exists(), f"devx-workflow SKILL.md not found in {repo_name}" + + @pytest.mark.parametrize("repo_name", REPOS_WITH_PR_WORKFLOW) + def test_mentions_spec_validation(self, repo_name: str) -> None: + _skip_if_repo_missing(repo_name) + path = _OBLACHNO_ROOT / repo_name / ".devin" / "skills" / "devx-workflow" / "SKILL.md" + content = path.read_text(encoding="utf-8") + assert "validate_spec" in content, f"devx-workflow skill in {repo_name} must mention validate_spec" + + @pytest.mark.parametrize("repo_name", REPOS_WITH_PR_WORKFLOW) + def test_mentions_pr_size_check(self, repo_name: str) -> None: + _skip_if_repo_missing(repo_name) + path = _OBLACHNO_ROOT / repo_name / ".devin" / "skills" / "devx-workflow" / "SKILL.md" + content = path.read_text(encoding="utf-8") + assert "check_pr_size" in content, f"devx-workflow skill in {repo_name} must mention check_pr_size" + + @pytest.mark.parametrize("repo_name", REPOS_WITH_PR_WORKFLOW) + def test_mentions_pr_workflow_commands(self, repo_name: str) -> None: + _skip_if_repo_missing(repo_name) + path = _OBLACHNO_ROOT / repo_name / ".devin" / "skills" / "devx-workflow" / "SKILL.md" + content = path.read_text(encoding="utf-8") + assert "make create-pr" in content or "make push-with-pr" in content, ( + f"devx-workflow skill in {repo_name} must mention PR creation commands" + ) + + @pytest.mark.parametrize("repo_name", REPOS_WITH_PR_WORKFLOW) + def test_mentions_auto_merge(self, repo_name: str) -> None: + _skip_if_repo_missing(repo_name) + path = _OBLACHNO_ROOT / repo_name / ".devin" / "skills" / "devx-workflow" / "SKILL.md" + content = path.read_text(encoding="utf-8") + assert "auto-merge" in content.lower() or "ready-to-merge" in content, ( + f"devx-workflow skill in {repo_name} must mention auto-merge" + ) + + @pytest.mark.parametrize("repo_name", REPOS_WITH_PR_WORKFLOW) + def test_has_correct_task_prefix(self, repo_name: str) -> None: + _skip_if_repo_missing(repo_name) + """Each repo's devx-workflow skill must mention its correct task prefix.""" + path = _OBLACHNO_ROOT / repo_name / ".devin" / "skills" / "devx-workflow" / "SKILL.md" + content = path.read_text(encoding="utf-8") + expected_prefixes = { + "infra": "OBL-INFRA", + "grm": "GRM", + "sso-bridge": "SSO", + "devx": "DEVX", + } + prefix = expected_prefixes[repo_name] + assert prefix in content, f"devx-workflow skill in {repo_name} must mention task prefix {prefix}" + + def test_not_in_mattermost_oidc(self) -> None: + """mattermost-oidc has no PR workflow — should NOT have devx-workflow skill.""" + path = _OBLACHNO_ROOT / "mattermost-oidc" / ".devin" / "skills" / "devx-workflow" / "SKILL.md" + assert not path.exists(), "mattermost-oidc should NOT have devx-workflow skill (no PR workflow)" + + # Repo-specific content checks + def test_infra_mentions_nightly_gate(self) -> None: + _skip_if_repo_missing("infra") + path = _OBLACHNO_ROOT / "infra" / ".devin" / "skills" / "devx-workflow" / "SKILL.md" + content = path.read_text(encoding="utf-8") + assert "nightly" in content.lower(), "infra devx-workflow skill must mention nightly gate" + assert "nightly_gate" in content, "infra devx-workflow skill must mention devx.ci.nightly_gate module" + + def test_infra_mentions_fast_molecule(self) -> None: + _skip_if_repo_missing("infra") + path = _OBLACHNO_ROOT / "infra" / ".devin" / "skills" / "devx-workflow" / "SKILL.md" + content = path.read_text(encoding="utf-8") + assert "fast_molecule" in content, "infra devx-workflow skill must mention devx.ci.fast_molecule" + + def test_infra_mentions_auto_deploy_staging(self) -> None: + _skip_if_repo_missing("infra") + path = _OBLACHNO_ROOT / "infra" / ".devin" / "skills" / "devx-workflow" / "SKILL.md" + content = path.read_text(encoding="utf-8") + assert "staging" in content.lower(), "infra devx-workflow skill must mention staging auto-deploy" + + def test_grm_mentions_dependency_pr(self) -> None: + _skip_if_repo_missing("grm") + path = _OBLACHNO_ROOT / "grm" / ".devin" / "skills" / "devx-workflow" / "SKILL.md" + content = path.read_text(encoding="utf-8") + assert "create_dependency_pr" in content, "grm devx-workflow skill must mention create_dependency_pr" + + def test_sso_bridge_mentions_dependency_pr(self) -> None: + _skip_if_repo_missing("sso-bridge") + path = _OBLACHNO_ROOT / "sso-bridge" / ".devin" / "skills" / "devx-workflow" / "SKILL.md" + content = path.read_text(encoding="utf-8") + assert "create_dependency_pr" in content, "sso-bridge devx-workflow skill must mention create_dependency_pr" + + +# ============================================================================ +# testing-and-debugging skill — exists in all repos, mentions spec workflow +# ============================================================================ + + +class TestTestingAndDebuggingSkill: + # All repos have a testing-and-debugging skill + ALL_REPOS = ["infra", "grm", "sso-bridge", "devx", "mattermost-oidc"] + + @pytest.mark.parametrize("repo_name", ALL_REPOS) + def test_skill_exists(self, repo_name: str) -> None: + _skip_if_repo_missing(repo_name) + path = _OBLACHNO_ROOT / repo_name / ".devin" / "skills" / "testing-and-debugging" / "SKILL.md" + assert path.exists(), f"testing-and-debugging SKILL.md not found in {repo_name}" + + @pytest.mark.parametrize("repo_name", ALL_REPOS) + def test_has_required_sections(self, repo_name: str) -> None: + _skip_if_repo_missing(repo_name) + path = _OBLACHNO_ROOT / repo_name / ".devin" / "skills" / "testing-and-debugging" / "SKILL.md" + content = path.read_text(encoding="utf-8") + # All testing-and-debugging skills should have a CI failure investigation section + assert "CI Failure Investigation" in content or "CI failure" in content, ( + f"testing-and-debugging skill in {repo_name} must have CI failure section" + ) + + # Repos with PR workflow should mention spec-driven workflow + @pytest.mark.parametrize("repo_name", ["infra", "grm", "sso-bridge", "devx"]) + def test_mentions_spec_driven_workflow(self, repo_name: str) -> None: + _skip_if_repo_missing(repo_name) + path = _OBLACHNO_ROOT / repo_name / ".devin" / "skills" / "testing-and-debugging" / "SKILL.md" + content = path.read_text(encoding="utf-8") + assert "spec" in content.lower(), ( + f"testing-and-debugging skill in {repo_name} must mention spec-driven workflow" + ) + + def test_infra_mentions_nightly(self) -> None: + _skip_if_repo_missing("infra") + path = _OBLACHNO_ROOT / "infra" / ".devin" / "skills" / "testing-and-debugging" / "SKILL.md" + content = path.read_text(encoding="utf-8") + assert "nightly" in content.lower(), "infra testing-and-debugging skill must mention nightly tests" + + def test_infra_mentions_fast_molecule(self) -> None: + _skip_if_repo_missing("infra") + path = _OBLACHNO_ROOT / "infra" / ".devin" / "skills" / "testing-and-debugging" / "SKILL.md" + content = path.read_text(encoding="utf-8") + assert "fast" in content.lower() and "molecule" in content.lower(), ( + "infra testing-and-debugging skill must mention fast molecule" + ) + + def test_mattermost_oidc_no_spec_mention(self) -> None: + """mattermost-oidc has no spec-driven workflow — skill should NOT mention it.""" + _skip_if_repo_missing("mattermost-oidc") + path = _OBLACHNO_ROOT / "mattermost-oidc" / ".devin" / "skills" / "testing-and-debugging" / "SKILL.md" + content = path.read_text(encoding="utf-8") + # mattermost-oidc has no PR workflow, no spec validation + assert "validate_spec" not in content, ( + "mattermost-oidc testing-and-debugging skill should NOT mention validate_spec" + ) + + +# ============================================================================ +# pr-review skill — deep review with auto-fix, exists in repos with PR workflow +# ============================================================================ + + +class TestPrReviewSkill: + REPOS_WITH_PR_WORKFLOW = ["infra", "grm", "sso-bridge", "devx"] + + @pytest.mark.parametrize("repo_name", REPOS_WITH_PR_WORKFLOW) + def test_skill_exists(self, repo_name: str) -> None: + _skip_if_repo_missing(repo_name) + path = _OBLACHNO_ROOT / repo_name / ".devin" / "skills" / "pr-review" / "SKILL.md" + assert path.exists(), f"pr-review SKILL.md not found in {repo_name}" + + @pytest.mark.parametrize("repo_name", REPOS_WITH_PR_WORKFLOW) + def test_mentions_all_review_categories(self, repo_name: str) -> None: + _skip_if_repo_missing(repo_name) + path = _OBLACHNO_ROOT / repo_name / ".devin" / "skills" / "pr-review" / "SKILL.md" + content = path.read_text(encoding="utf-8") + required_categories = [ + "Functional Correctness", + "Completeness", + "Architecture", + "Reliability", + "Robustness", + "Security", + "Technical Excellence", + "Test Quality", + ] + for cat in required_categories: + assert cat in content, f"pr-review skill in {repo_name} missing category: {cat}" + + @pytest.mark.parametrize("repo_name", REPOS_WITH_PR_WORKFLOW) + def test_mentions_auto_fix(self, repo_name: str) -> None: + _skip_if_repo_missing(repo_name) + path = _OBLACHNO_ROOT / repo_name / ".devin" / "skills" / "pr-review" / "SKILL.md" + content = path.read_text(encoding="utf-8") + assert "auto-fix" in content.lower() or "auto fix" in content.lower(), ( + f"pr-review skill in {repo_name} must mention auto-fix" + ) + + @pytest.mark.parametrize("repo_name", REPOS_WITH_PR_WORKFLOW) + def test_mentions_gitea_mcp(self, repo_name: str) -> None: + _skip_if_repo_missing(repo_name) + path = _OBLACHNO_ROOT / repo_name / ".devin" / "skills" / "pr-review" / "SKILL.md" + content = path.read_text(encoding="utf-8") + assert "mcp" in content.lower(), f"pr-review skill in {repo_name} must mention Gitea MCP" + + @pytest.mark.parametrize("repo_name", REPOS_WITH_PR_WORKFLOW) + def test_mentions_inline_comments(self, repo_name: str) -> None: + _skip_if_repo_missing(repo_name) + path = _OBLACHNO_ROOT / repo_name / ".devin" / "skills" / "pr-review" / "SKILL.md" + content = path.read_text(encoding="utf-8") + assert "inline" in content.lower(), f"pr-review skill in {repo_name} must mention inline comments" + + @pytest.mark.parametrize("repo_name", REPOS_WITH_PR_WORKFLOW) + def test_mentions_ready_to_merge(self, repo_name: str) -> None: + _skip_if_repo_missing(repo_name) + path = _OBLACHNO_ROOT / repo_name / ".devin" / "skills" / "pr-review" / "SKILL.md" + content = path.read_text(encoding="utf-8") + assert "ready-to-merge" in content, f"pr-review skill in {repo_name} must mention ready-to-merge label" + + @pytest.mark.parametrize("repo_name", REPOS_WITH_PR_WORKFLOW) + def test_mentions_resolve_discussion(self, repo_name: str) -> None: + _skip_if_repo_missing(repo_name) + path = _OBLACHNO_ROOT / repo_name / ".devin" / "skills" / "pr-review" / "SKILL.md" + content = path.read_text(encoding="utf-8") + assert "resolve" in content.lower(), f"pr-review skill in {repo_name} must mention resolving discussions" + + @pytest.mark.parametrize("repo_name", REPOS_WITH_PR_WORKFLOW) + def test_mentions_summary(self, repo_name: str) -> None: + _skip_if_repo_missing(repo_name) + path = _OBLACHNO_ROOT / repo_name / ".devin" / "skills" / "pr-review" / "SKILL.md" + content = path.read_text(encoding="utf-8") + assert "summary" in content.lower(), f"pr-review skill in {repo_name} must mention posting a summary" + + def test_not_in_mattermost_oidc(self) -> None: + """mattermost-oidc has no PR workflow — should NOT have pr-review skill.""" + path = _OBLACHNO_ROOT / "mattermost-oidc" / ".devin" / "skills" / "pr-review" / "SKILL.md" + assert not path.exists(), "mattermost-oidc should NOT have pr-review skill (no PR workflow)" + + def test_no_pr_review_module_remains(self) -> None: + """The old devx.ci.pr_review module should be deleted.""" + path = _DEVX / "src" / "devx" / "ci" / "pr_review.py" + assert not path.exists(), "devx.ci.pr_review module should be deleted (replaced by pr-review skill)" + + def test_no_pr_review_test_remains(self) -> None: + """The old test_pr_review.py should be deleted.""" + path = _DEVX / "tests" / "unit" / "test_pr_review.py" + assert not path.exists(), "tests/unit/test_pr_review.py should be deleted" + + def test_no_pr_review_in_workflows(self) -> None: + """No CI workflow should reference devx.ci.pr_review.""" + for repo_name in ["infra", "grm", "sso-bridge", "devx"]: + wf_dir = _OBLACHNO_ROOT / repo_name / ".gitea" / "workflows" + if not wf_dir.exists(): + continue + for wf_file in wf_dir.glob("*.yml"): + content = wf_file.read_text(encoding="utf-8") + assert "devx.ci.pr_review" not in content, ( + f"{repo_name}/{wf_file.name} still references devx.ci.pr_review" + ) + + +# ============================================================================ +# Skill consistency — all skills have proper structure +# ============================================================================ + + +class TestSkillConsistency: + ALL_SKILLS = [ + ("infra", "devx-workflow"), + ("infra", "testing-and-debugging"), + ("infra", "spec-driven-development"), + ("infra", "pr-review"), + ("grm", "devx-workflow"), + ("grm", "testing-and-debugging"), + ("grm", "spec-driven-development"), + ("grm", "pr-review"), + ("sso-bridge", "devx-workflow"), + ("sso-bridge", "testing-and-debugging"), + ("sso-bridge", "spec-driven-development"), + ("sso-bridge", "pr-review"), + ("devx", "devx-workflow"), + ("devx", "testing-and-debugging"), + ("devx", "spec-driven-development"), + ("devx", "pr-review"), + ("mattermost-oidc", "testing-and-debugging"), + ] + + @pytest.mark.parametrize("repo_name, skill_name", ALL_SKILLS) + def test_skill_has_title(self, repo_name: str, skill_name: str) -> None: + _skip_if_repo_missing(repo_name) + path = _OBLACHNO_ROOT / repo_name / ".devin" / "skills" / skill_name / "SKILL.md" + content = path.read_text(encoding="utf-8") + first_line = content.strip().split("\n")[0] + assert first_line.startswith("# "), f"{repo_name}/{skill_name}: SKILL.md must start with a # title" + + @pytest.mark.parametrize("repo_name, skill_name", ALL_SKILLS) + def test_skill_not_empty(self, repo_name: str, skill_name: str) -> None: + _skip_if_repo_missing(repo_name) + path = _OBLACHNO_ROOT / repo_name / ".devin" / "skills" / skill_name / "SKILL.md" + content = path.read_text(encoding="utf-8").strip() + assert len(content) > 100, f"{repo_name}/{skill_name}: SKILL.md is too short ({len(content)} chars)" + + @pytest.mark.parametrize("repo_name, skill_name", ALL_SKILLS) + def test_skill_has_sections(self, repo_name: str, skill_name: str) -> None: + _skip_if_repo_missing(repo_name) + path = _OBLACHNO_ROOT / repo_name / ".devin" / "skills" / skill_name / "SKILL.md" + content = path.read_text(encoding="utf-8") + # Must have at least 2 ## sections + section_count = content.count("\n## ") + assert section_count >= 2, ( + f"{repo_name}/{skill_name}: SKILL.md must have at least 2 sections (found {section_count})" + ) + + +# ============================================================================ +# AGENTS.md — spec-driven development section in all repos +# ============================================================================ + + +class TestAgentsMdSpecSection: + @pytest.mark.parametrize("repo_name", ["infra", "grm", "sso-bridge", "devx"]) + def test_agents_md_has_spec_driven_section(self, repo_name: str) -> None: + path = _OBLACHNO_ROOT / repo_name / "AGENTS.md" + if not path.exists(): + pytest.skip(f"AGENTS.md not found in {repo_name}") + content = path.read_text(encoding="utf-8") + assert "## Spec-Driven Development" in content, ( + f"AGENTS.md in {repo_name} must have '## Spec-Driven Development' section" + ) + + @pytest.mark.parametrize("repo_name", ["infra", "grm", "sso-bridge", "devx"]) + def test_agents_md_mentions_validate_spec(self, repo_name: str) -> None: + path = _OBLACHNO_ROOT / repo_name / "AGENTS.md" + if not path.exists(): + pytest.skip(f"AGENTS.md not found in {repo_name}") + content = path.read_text(encoding="utf-8") + assert "validate_spec" in content or "devx.ci.validate_spec" in content, ( + f"AGENTS.md in {repo_name} must mention devx.ci.validate_spec" + ) + + @pytest.mark.parametrize("repo_name", ["infra", "grm", "sso-bridge", "devx"]) + def test_agents_md_pr_workflow_section_intact(self, repo_name: str) -> None: + """Ensure the PR Workflow section wasn't accidentally deleted.""" + path = _OBLACHNO_ROOT / repo_name / "AGENTS.md" + if not path.exists(): + pytest.skip(f"AGENTS.md not found in {repo_name}") + content = path.read_text(encoding="utf-8") + assert "## PR Workflow" in content, f"AGENTS.md in {repo_name} must still have '## PR Workflow' section" diff --git a/tests/unit/test_validate_spec.py b/tests/unit/test_validate_spec.py new file mode 100644 index 0000000..83d0eee --- /dev/null +++ b/tests/unit/test_validate_spec.py @@ -0,0 +1,223 @@ +"""Unit tests for devx.ci.validate_spec.""" + +from pathlib import Path +from unittest.mock import patch + +from click.testing import CliRunner + +from devx.ci.validate_spec import ( + AC_CHECKED_RE, + AC_UNCHECKED_RE, + REQ_ID_RE, + cli, + find_spec_file, + validate_spec_content, +) + +VALID_SPEC = """\ +# OBL-INFRA-531: Fix sso-bridge role for pip install + +## Problem +The sso-bridge role uses scripts.sso_bridge.listener but the pip +package uses sso_bridge.listener. + +## Approach +REQ-1: Update molecule verify.yml to use sso_bridge.listener +REQ-2: Add infra repo clone task to sso_bridge role + +## Test Plan +- Run molecule test for sso_bridge role +- Verify pip package is installed correctly + +## Deploy Plan +- Merge PR +- Auto-deploy to staging + +## Rollback Plan +- Revert PR +- Re-deploy previous version + +## Acceptance Criteria +- [x] Molecule test passes with sso_bridge.listener +- [x] Infra repo is cloned by sso_bridge role +""" + + +SPEC_MISSING_SECTION = """\ +# OBL-INFRA-531: Fix sso-bridge + +## Problem +Something is broken. + +## Approach +REQ-1: Fix it + +## Test Plan +Run tests +""" + + +SPEC_UNCHECKED_AC = """\ +# OBL-INFRA-531: Fix sso-bridge + +## Problem +Broken. + +## Approach +REQ-1: Fix it + +## Test Plan +Run tests + +## Deploy Plan +Deploy + +## Rollback Plan +Revert + +## Acceptance Criteria +- [x] Fixed +- [ ] Verified in staging +""" + + +SPEC_NO_REQ_IDS = """\ +# OBL-INFRA-531: Fix sso-bridge + +## Problem +Broken. + +## Approach +Fix it. + +## Test Plan +Run tests + +## Deploy Plan +Deploy + +## Rollback Plan +Revert + +## Acceptance Criteria +- [x] Fixed +""" + + +class TestFindSpecFile: + def test_finds_exact_match(self, tmp_path: Path) -> None: + specs_dir = tmp_path / "specs" + specs_dir.mkdir() + (specs_dir / "OBL-INFRA-531.md").write_text("content") + result = find_spec_file("OBL-INFRA-531", str(specs_dir)) + assert result is not None + assert result.name == "OBL-INFRA-531.md" + + def test_finds_case_insensitive(self, tmp_path: Path) -> None: + specs_dir = tmp_path / "specs" + specs_dir.mkdir() + (specs_dir / "obl-infra-531.md").write_text("content") + result = find_spec_file("OBL-INFRA-531", str(specs_dir)) + assert result is not None + + def test_returns_none_when_not_found(self, tmp_path: Path) -> None: + specs_dir = tmp_path / "specs" + specs_dir.mkdir() + result = find_spec_file("OBL-INFRA-999", str(specs_dir)) + assert result is None + + def test_returns_none_when_dir_missing(self, tmp_path: Path) -> None: + result = find_spec_file("OBL-INFRA-531", str(tmp_path / "nonexistent")) + assert result is None + + +class TestValidateSpecContent: + def test_valid_spec_passes(self) -> None: + errors = validate_spec_content(VALID_SPEC) + assert errors == [] + + def test_missing_sections(self) -> None: + errors = validate_spec_content(SPEC_MISSING_SECTION) + assert len(errors) >= 3 # Missing Deploy Plan, Rollback Plan, Acceptance Criteria + assert any("Deploy Plan" in e for e in errors) + assert any("Rollback Plan" in e for e in errors) + assert any("Acceptance Criteria" in e for e in errors) + + def test_unchecked_ac_fails(self) -> None: + errors = validate_spec_content(SPEC_UNCHECKED_AC) + assert len(errors) == 1 + assert "unchecked" in errors[0].lower() + + def test_no_req_ids_fails(self) -> None: + errors = validate_spec_content(SPEC_NO_REQ_IDS) + assert any("REQ-ID" in e for e in errors) + + def test_empty_content_fails(self) -> None: + errors = validate_spec_content("") + assert len(errors) >= 2 # Missing sections + no REQ-IDs + + +class TestRegexPatterns: + def test_req_id_re_matches(self) -> None: + assert REQ_ID_RE.search("REQ-1: Do something") + assert REQ_ID_RE.search("REQ-42: Another thing") + assert not REQ_ID_RE.search("REQ: no number") + + def test_ac_checked_re_matches(self) -> None: + assert AC_CHECKED_RE.search("- [x] Done") + assert AC_CHECKED_RE.search(" - [x] Indented") + assert not AC_CHECKED_RE.search("- [ ] Not done") + + def test_ac_unchecked_re_matches(self) -> None: + assert AC_UNCHECKED_RE.search("- [ ] Not done") + assert AC_UNCHECKED_RE.search(" - [ ] Indented") + assert not AC_UNCHECKED_RE.search("- [x] Done") + + +class TestCli: + def test_fails_without_task_id(self) -> None: + runner = CliRunner() + result = runner.invoke(cli, ["--branch", "no-task-id"]) + assert result.exit_code != 0 + + def test_allow_missing_succeeds_without_task_id(self) -> None: + runner = CliRunner() + result = runner.invoke(cli, ["--branch", "no-task-id", "--allow-missing"]) + assert result.exit_code == 0 + assert "WARNING" in result.output + + def test_fails_when_spec_not_found(self, tmp_path: Path) -> None: + runner = CliRunner() + with patch("devx.ci.validate_spec.extract_task_id", return_value="OBL-INFRA-999"): + result = runner.invoke( + cli, + ["--branch", "OBL-INFRA-999-test", "--specs-dir", str(tmp_path / "specs")], + ) + assert result.exit_code != 0 + assert "No spec file found" in result.output + + def test_passes_with_valid_spec(self, tmp_path: Path) -> None: + specs_dir = tmp_path / "specs" + specs_dir.mkdir() + (specs_dir / "OBL-INFRA-531.md").write_text(VALID_SPEC) + runner = CliRunner() + with patch("devx.ci.validate_spec.extract_task_id", return_value="OBL-INFRA-531"): + result = runner.invoke( + cli, + ["--branch", "OBL-INFRA-531-fix-foo", "--specs-dir", str(specs_dir)], + ) + assert result.exit_code == 0 + assert "Spec validated" in result.output + + def test_fails_with_unchecked_ac(self, tmp_path: Path) -> None: + specs_dir = tmp_path / "specs" + specs_dir.mkdir() + (specs_dir / "OBL-INFRA-531.md").write_text(SPEC_UNCHECKED_AC) + runner = CliRunner() + with patch("devx.ci.validate_spec.extract_task_id", return_value="OBL-INFRA-531"): + result = runner.invoke( + cli, + ["--branch", "OBL-INFRA-531-fix-foo", "--specs-dir", str(specs_dir)], + ) + assert result.exit_code != 0 + assert "unchecked" in result.output.lower()