orbi-cli 0.7.4__tar.gz → 0.7.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {orbi_cli-0.7.4/src/orbi_cli.egg-info → orbi_cli-0.7.5}/PKG-INFO +1 -1
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/pyproject.toml +1 -1
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/__init__.py +1 -1
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/claim.py +42 -232
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/delivery_comment.py +5 -1
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/failure.py +38 -2
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/language.py +18 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/language_terms.py +5 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/merge_handoff.py +7 -4
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/progress.py +2 -1
- orbi_cli-0.7.5/src/orbi/resume_scan.py +314 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/runner.py +105 -97
- {orbi_cli-0.7.4 → orbi_cli-0.7.5/src/orbi_cli.egg-info}/PKG-INFO +1 -1
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi_cli.egg-info/SOURCES.txt +2 -0
- orbi_cli-0.7.5/tests/test_base_branch_failure.py +216 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_bootstrap_delivery.py +7 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_bootstrap_runner.py +6 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_delivery_scene.py +4 -4
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_docs_labels.py +10 -2
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_failure.py +75 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_message_language.py +64 -1
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_pilot_setup.py +6 -1
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_resume_pr.py +194 -20
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/LICENSE +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/README.md +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/docs/licenses/sustainable-use-license.md +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/setup.cfg +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/blocked_comment.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/branch_reclaim.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/checks.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/clarify.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/cli.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/cli_source.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/comment_kind.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/config.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/delivery_labels.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/delivery_scene.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/engine_source.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/example_config.toml +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/failure_report.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/git_identity.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/git_transport.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/github.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/gitops.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/human_review.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/journal.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/launchd_deploy.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/merge_gate.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/milestone.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/milestone_command.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/notify.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/pi_activity.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/pi_command.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/pi_process.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/pi_recovery.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/pi_session.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/pilot_setup.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/pilot_slots.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/progress_display.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/progress_outcome.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/progress_summary.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/release.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/release_comments.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/release_git.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/release_notes.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/repo_config.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/review_merge.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/review_rounds.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/run_state.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/runner_health.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/scene.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/scheduler.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/scheduler_units.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/session_command.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/source_pool.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/status_observe.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/suggest.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/systemd_deploy.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi/ticket_command.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi_cli.egg-info/dependency_links.txt +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi_cli.egg-info/entry_points.txt +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/src/orbi_cli.egg-info/top_level.txt +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_agents_md.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_apply_label_patch.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_blocked_comment.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_bootstrap_adapters.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_bootstrap_pi.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_bootstrap_preflight.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_bootstrap_release.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_branch_reclaim.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_ci_failure_issue_workflow.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_ci_failure_triage.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_ci_macos_workflow.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_ci_workflow.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_clarify.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_claude_plugin.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_cli_install.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_cli_milestone.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_cli_packaging.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_cli_source.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_comment_identity.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_comment_readers_contract.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_concurrency_e2e.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_constitution.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_coverage_gate.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_deliver_pr_fakes.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_delivery_branch_delete.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_delivery_labels.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_docker_docs.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_docker_entrypoint.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_docker_image_workflow.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_docs_i18n.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_docs_issue_shapes.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_docs_mermaid.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_docs_releases.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_docs_single_source.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_docs_site.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_engine_source.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_failure_report.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_fix_needed_loop.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_gh_read_retry.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_git_base_smoke.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_git_merge_smoke.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_git_network_retry.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_git_transport.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_github.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_github_fakes.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_gitops.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_gitops_fakes.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_human_review.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_import_graph.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_install_sh.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_issue_1367.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_issue_461.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_issue_template_config.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_journal.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_journal_events.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_launchd_deploy.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_license.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_milestone_command.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_notify.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_ops_e2e.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_orbi.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_orbi_dashboard.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_orbi_exporter.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_patch_ratchet.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_pi_activity.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_pi_command.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_pi_extensions.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_pi_provider_templates.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_pi_recovery.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_pi_session.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_pilot_slots.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_pr_issue_link_workflow.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_prerequisite_check.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_progress.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_progress_wiring.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_prompt_contract.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_public_contracts.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_publish_workflow.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_release_docs_workflow.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_release_ticket_templates.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_repo_config.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_resume_continue_e2e.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_resume_e2e.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_review_merge.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_run_artifacts.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_run_id_e2e.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_runner_config.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_runner_health.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_runner_source_freshness.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_scene.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_scheduler.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_size_ratchet.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_src_layout.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_status_observe.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_steering_review.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_suggest.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_suite_hygiene.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_systemd_deploy.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_systemd_timer.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_ticket_command.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_user_acceptance_contract.py +0 -0
- {orbi_cli-0.7.4 → orbi_cli-0.7.5}/tests/test_worktree_reclaim.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: orbi-cli
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.5
|
|
4
4
|
Summary: Orbi — local AI development worker: claims GitHub Issues, runs Pi in isolated worktrees, and delivers PRs (GitHub Issues + labels are the only state store).
|
|
5
5
|
Author: xqliu
|
|
6
6
|
License-Expression: AGPL-3.0-only OR LicenseRef-Sustainable-Use-1.0
|
|
@@ -44,7 +44,7 @@ build-backend = "setuptools.build_meta"
|
|
|
44
44
|
|
|
45
45
|
[project]
|
|
46
46
|
name = "orbi-cli"
|
|
47
|
-
version = "0.7.
|
|
47
|
+
version = "0.7.5"
|
|
48
48
|
description = "Orbi — local AI development worker: claims GitHub Issues, runs Pi in isolated worktrees, and delivers PRs (GitHub Issues + labels are the only state store)."
|
|
49
49
|
readme = "README.md"
|
|
50
50
|
requires-python = ">=3.14"
|
|
@@ -4,7 +4,8 @@ Extracted from `orbi.runner` (Issue #1259, Article 3.2): the five claim scans
|
|
|
4
4
|
(P0, bug, plain, release fallback, external takeover), the classifiers and
|
|
5
5
|
reconcilers they share, and the resumable-PR / in-flight / fresh-claim
|
|
6
6
|
selection order. The module never imports `orbi.runner` (Article 3.3): the
|
|
7
|
-
delivery-side helpers arrive as `ResumeHooks`.
|
|
7
|
+
delivery-side helpers arrive as `ResumeHooks`. The opened-PR candidate scan
|
|
8
|
+
itself lives in `orbi.resume_scan` (Article 3.5 split, Issue #1229).
|
|
8
9
|
"""
|
|
9
10
|
from __future__ import annotations
|
|
10
11
|
|
|
@@ -12,17 +13,13 @@ import json
|
|
|
12
13
|
import logging
|
|
13
14
|
import os
|
|
14
15
|
import re
|
|
15
|
-
from collections.abc import
|
|
16
|
+
from collections.abc import Sequence
|
|
16
17
|
from pathlib import Path
|
|
17
|
-
from typing import NamedTuple
|
|
18
18
|
|
|
19
19
|
from orbi import milestone as milestone_bookkeeping
|
|
20
|
-
from orbi import scene
|
|
21
|
-
from orbi.blocked_comment import post_blocked_comment
|
|
22
|
-
from orbi.checks import _classify_rollup, _render_check
|
|
23
20
|
from orbi.config import RunnerConfig
|
|
24
21
|
from orbi.delivery_labels import (
|
|
25
|
-
|
|
22
|
+
BLOCKED_LABEL, EPIC_LABEL,
|
|
26
23
|
FIX_NEEDED_LABEL, IN_PROGRESS_LABEL, MERGED_LABEL, P0_LABEL,
|
|
27
24
|
PR_OPENED_LABEL, READY_LABEL, RELEASE_LABEL,
|
|
28
25
|
)
|
|
@@ -32,13 +29,15 @@ from orbi.delivery_scene import (
|
|
|
32
29
|
from orbi.github import (
|
|
33
30
|
_verify_epic_complete, close_issue,
|
|
34
31
|
comment_issue, issue_comments, issue_priority, issue_view,
|
|
35
|
-
|
|
36
|
-
open_blocker_numbers,
|
|
37
|
-
|
|
32
|
+
list_issues, milestone_open_issue_count,
|
|
33
|
+
open_blocker_numbers, pr_comments,
|
|
34
|
+
run_gh_read_command,
|
|
38
35
|
)
|
|
39
|
-
from orbi.gitops import task_branch
|
|
40
36
|
from orbi.journal import LOGGER, current_run_id, event, new_run_id, set_run_id
|
|
41
37
|
from orbi.pilot_slots import slot_held_deliveries, slot_occupancy
|
|
38
|
+
from orbi.resume_scan import (
|
|
39
|
+
ResumeHooks, pick_awaiting_merge_delivery, pick_resumable_delivery,
|
|
40
|
+
)
|
|
42
41
|
from orbi import language
|
|
43
42
|
from orbi.progress import run_marker
|
|
44
43
|
from orbi.release import _epic_audit, epic_audit_posted, release_target_milestone
|
|
@@ -47,23 +46,6 @@ from orbi.repo_config import (
|
|
|
47
46
|
)
|
|
48
47
|
|
|
49
48
|
|
|
50
|
-
class ResumeHooks(NamedTuple):
|
|
51
|
-
"""The delivery-side helpers `runner.main` passes in (Article 3.3).
|
|
52
|
-
|
|
53
|
-
The physical scene handling of `pick_resumable_delivery` plus the PR
|
|
54
|
-
comment writer `reconcile_orphan_prs` reports its orphan through.
|
|
55
|
-
Every hook that writes a comment takes the claim's already-resolved
|
|
56
|
-
per-repository `language_code` (Issue #1687): the claim knows the
|
|
57
|
-
merged repository config before the first comment is written.
|
|
58
|
-
"""
|
|
59
|
-
resume_scene: Callable[[list[dict]], dict]
|
|
60
|
-
route_external_pr_ticket: Callable[..., bool]
|
|
61
|
-
block_scene_failure: Callable[..., None]
|
|
62
|
-
recover_missing_pr_scene: Callable[..., dict | None]
|
|
63
|
-
has_recoverable_pr_scene: Callable[[dict, str, Path], bool]
|
|
64
|
-
comment_pr: Callable[..., None]
|
|
65
|
-
|
|
66
|
-
|
|
67
49
|
# Ready scans: P0 urgent Issues are claimed before
|
|
68
50
|
# bugs, bugs before new features — if the delivery loop is broken,
|
|
69
51
|
# claiming enhancements only piles up unreviewed PRs, and a production
|
|
@@ -632,7 +614,13 @@ def pick_in_progress_issue(
|
|
|
632
614
|
def pick_next_issue(
|
|
633
615
|
repos: list[str], active_milestone: str | None = None,
|
|
634
616
|
) -> tuple[str, dict] | None:
|
|
635
|
-
"""Scan sources in order; return the first ready issue and its source.
|
|
617
|
+
"""Scan sources in order; return the first ready issue and its source.
|
|
618
|
+
|
|
619
|
+
Only the fresh ready scan lives here; the opened-PR resume, the
|
|
620
|
+
in-flight restart and the deferred `ai-awaiting-merge` re-check are
|
|
621
|
+
`pick_next_delivery`'s scans (Issue #1745: a delivery waiting for a
|
|
622
|
+
maintainer never competes with them).
|
|
623
|
+
"""
|
|
636
624
|
for repo in repos:
|
|
637
625
|
issue = pick_issue(repo, active_milestone)
|
|
638
626
|
if issue is not None:
|
|
@@ -640,209 +628,6 @@ def pick_next_issue(
|
|
|
640
628
|
return None
|
|
641
629
|
|
|
642
630
|
|
|
643
|
-
def pick_resumable_delivery(
|
|
644
|
-
repo: str, slot_dir: Path, max_concurrency: int,
|
|
645
|
-
repo_dir: Path | None = None, *, hooks: ResumeHooks,
|
|
646
|
-
language_code: str,
|
|
647
|
-
) -> tuple[dict, dict] | None:
|
|
648
|
-
"""Return the oldest FREE opened-PR delivery and its resume scene.
|
|
649
|
-
|
|
650
|
-
All opened-PR states are scanned: `ai-fix-needed`
|
|
651
|
-
(awaiting the next review session after a finding or a base
|
|
652
|
-
conflict — the review session fixes findings in the same
|
|
653
|
-
session), `ai-pr-opened` (awaiting review), and
|
|
654
|
-
`ai-awaiting-merge` (a known policy blocker whose maintainer action
|
|
655
|
-
is retried without another review). The `ai-pr-opened`
|
|
656
|
-
scan exists because the delivery that opened the PR can be gone: the
|
|
657
|
-
runner can die inside the delivery wait loop, leaving a valid
|
|
658
|
-
MERGEABLE PR with no owner. Without the scan such a delivery is
|
|
659
|
-
picked up by no other scan (`pick_issue` excludes `ai-pr-opened`)
|
|
660
|
-
and is stranded forever. `ai-blocked` Issues are excluded (they need
|
|
661
|
-
a human decision first), as are merged Issues and closed Issues.
|
|
662
|
-
`ai-in-progress` is NOT excluded: a runner killed
|
|
663
|
-
during review leaves the backfilled in-flight label behind on the
|
|
664
|
-
opened-PR delivery, and the same scan must pick it back up — the
|
|
665
|
-
positive `label:ai-fix-needed,ai-pr-opened` qualifier already
|
|
666
|
-
restricts the scan to opened-PR Issues (an implement-phase Issue
|
|
667
|
-
has `ai-ready`+`ai-in-progress` but neither opened-PR label, so it
|
|
668
|
-
never matches). A missing scene on `ai-fix-needed` with exactly one
|
|
669
|
-
open PR on the stable internal branch is a fresh-claim takeover: the PR
|
|
670
|
-
supplies the continuation anchor and the claim writes a new trusted
|
|
671
|
-
scene. Other scenes that cannot be recovered are SINGLE-Issue failures:
|
|
672
|
-
the Issue is marked `ai-blocked` with the concrete reason
|
|
673
|
-
(`block_scene_failure`) and the scan moves on to the next candidate, so
|
|
674
|
-
the tick continues with the in-flight and ready scans and exits 0 — one
|
|
675
|
-
corrupted Issue must never make every tick crash while the whole queue
|
|
676
|
-
waits.
|
|
677
|
-
|
|
678
|
-
Only the deliveries a live co-runner CURRENTLY holds are skipped:
|
|
679
|
-
every holder names its (repo, issue) in its slot file
|
|
680
|
-
(`pilot_slots.mark_slot_delivery`), so this scan skips exactly the
|
|
681
|
-
in-flight deliveries. That is the round-1 protection at
|
|
682
|
-
the right granularity — a held delivery is never resumed here, so a
|
|
683
|
-
second review Pi never starts in the same worktree/branch/run and a
|
|
684
|
-
second `gh pr merge --match-head-commit` never hits the merged PR.
|
|
685
|
-
The pre-#809 guard abandoned the WHOLE scan whenever any slot in the
|
|
686
|
-
(often shared, multi-repo) slot dir was held: with any concurrency
|
|
687
|
-
the review never ran while fresh claims kept opening PRs — the
|
|
688
|
-
reported starvation (nine MERGEABLE PRs, the oldest 90 minutes,
|
|
689
|
-
zero review ticks). A free delivery is now resumed even while other
|
|
690
|
-
deliveries are in flight, and the review backlog drains at the
|
|
691
|
-
concurrency rate instead of only growing. This runner's own slot is
|
|
692
|
-
excluded: `main` took it before the claim scan and holds it for the
|
|
693
|
-
whole delivery.
|
|
694
|
-
|
|
695
|
-
The query page is `max_concurrency + 1` candidates, ordered OLDEST
|
|
696
|
-
Issue first (`sort:created-asc`, Issue #1734). At most
|
|
697
|
-
`max_concurrency` deliveries can be held by live co-runners, so a
|
|
698
|
-
free candidate is inside the page when one exists; one whose OPEN PR
|
|
699
|
-
still waits for CI (Issue #1473) is skipped too. The scan reviews
|
|
700
|
-
the oldest FREE candidate (held ones are skipped before any
|
|
701
|
-
candidate read — an in-flight delivery is never touched).
|
|
702
|
-
"""
|
|
703
|
-
held = slot_held_deliveries(slot_dir, max_concurrency)
|
|
704
|
-
# `label:a,b` is GitHub's OR within one label qualifier
|
|
705
|
-
# (verified live: repeating the qualifier matches only the
|
|
706
|
-
# first label). `ai-in-progress` is intentionally NOT excluded:
|
|
707
|
-
# a killed review runner leaves the backfilled
|
|
708
|
-
# in-flight label behind, and the positive qualifier above
|
|
709
|
-
# already keeps implement-phase Issues out.
|
|
710
|
-
# `labels`: a resumed P0 delivery keeps its
|
|
711
|
-
# priority in the progress comment through review/merge.
|
|
712
|
-
# `body`: the scene classification reads the
|
|
713
|
-
# delivery markers — without it the #726 external routing of a
|
|
714
|
-
# marker ticket with no trusted scene comment is unreachable in
|
|
715
|
-
# production (the probe read a body the query never fetched).
|
|
716
|
-
issues = list_issues(
|
|
717
|
-
repo, state="open",
|
|
718
|
-
search=(
|
|
719
|
-
f"label:{FIX_NEEDED_LABEL},{PR_OPENED_LABEL},"
|
|
720
|
-
f"{AWAITING_MERGE_LABEL} "
|
|
721
|
-
f"-label:{BLOCKED_LABEL} -label:{MERGED_LABEL} sort:created-asc"
|
|
722
|
-
),
|
|
723
|
-
json_fields="number,title,state,url,labels,body",
|
|
724
|
-
limit=max_concurrency + 1,
|
|
725
|
-
)
|
|
726
|
-
for issue in issues:
|
|
727
|
-
if issue.get("state") != "OPEN":
|
|
728
|
-
# Left the opened-PR state between the query and this read
|
|
729
|
-
# (a close/merge race): this candidate is gone, not the scan.
|
|
730
|
-
continue
|
|
731
|
-
if (repo, int(issue["number"])) in held:
|
|
732
|
-
# In flight in another live runner: never a
|
|
733
|
-
# second review Pi for it — and no candidate read either.
|
|
734
|
-
continue
|
|
735
|
-
comments = issue_comments(int(issue["number"]), repo=repo)
|
|
736
|
-
try:
|
|
737
|
-
found = hooks.resume_scene(comments)
|
|
738
|
-
except scene.SceneError as exc:
|
|
739
|
-
# A trusted scene comment exists but is corrupted:
|
|
740
|
-
# probe the #726 external route first; otherwise this is the
|
|
741
|
-
# ONLY trigger of `block_scene_failure` — a present-but-broken
|
|
742
|
-
# scene is a writer bug or tampering and needs a human.
|
|
743
|
-
if hooks.route_external_pr_ticket(issue, repo, language_code=language_code):
|
|
744
|
-
continue
|
|
745
|
-
hooks.block_scene_failure(issue, exc, repo, comments, language_code=language_code)
|
|
746
|
-
continue
|
|
747
|
-
except scene.SceneMissingError as exc:
|
|
748
|
-
# No trusted comment carries a scene at all — a distinct branch
|
|
749
|
-
# from corruption. The original #726 incident was
|
|
750
|
-
# exactly this shape, so the external route is probed first.
|
|
751
|
-
if hooks.route_external_pr_ticket(issue, repo, language_code=language_code):
|
|
752
|
-
continue
|
|
753
|
-
# Issue #1216: `awaiting_merge` can deliberately remove the
|
|
754
|
-
# queue label before the delivery returns to `ai-fix-needed`.
|
|
755
|
-
# An open PR on the stable internal branch is enough durable
|
|
756
|
-
# identity to route this through the ordinary fresh-claim
|
|
757
|
-
# takeover, which writes a new trusted scene before review.
|
|
758
|
-
# Probe the exact branch (and require the helper's sole-PR
|
|
759
|
-
# contract); no open PR falls through to the existing missing-
|
|
760
|
-
# scene failure below.
|
|
761
|
-
current_labels = _issue_label_set(issue)
|
|
762
|
-
if (repo_dir is not None
|
|
763
|
-
and FIX_NEEDED_LABEL in current_labels
|
|
764
|
-
and open_pr_for_branch(
|
|
765
|
-
repo_dir,
|
|
766
|
-
task_branch(repo, int(issue["number"])),
|
|
767
|
-
) is not None):
|
|
768
|
-
found_scene = classify(
|
|
769
|
-
labels=current_labels, scene=None, pr_state="OPEN",
|
|
770
|
-
body_markers=body_markers(issue.get("body")),
|
|
771
|
-
)
|
|
772
|
-
if found_scene is DeliveryScene.FRESH_CLAIM:
|
|
773
|
-
return issue, None
|
|
774
|
-
event(
|
|
775
|
-
"claim_yield", issue=int(issue["number"]),
|
|
776
|
-
reason=f"scene_{found_scene.value}",
|
|
777
|
-
)
|
|
778
|
-
continue
|
|
779
|
-
# A runner may have created the PR and label, then lost the
|
|
780
|
-
# scene comment write. Retry it from the durable local run
|
|
781
|
-
# state; keep ai-pr-opened for another tick if the retry fails.
|
|
782
|
-
recovered = (
|
|
783
|
-
hooks.recover_missing_pr_scene(issue, repo, repo_dir, language_code=language_code)
|
|
784
|
-
if repo_dir is not None else None
|
|
785
|
-
)
|
|
786
|
-
if recovered is not None:
|
|
787
|
-
found = recovered
|
|
788
|
-
elif repo_dir is not None and hooks.has_recoverable_pr_scene(
|
|
789
|
-
issue, repo, repo_dir):
|
|
790
|
-
LOGGER.warning(
|
|
791
|
-
"issue=%s resume scene recovery pending: %s",
|
|
792
|
-
issue["number"], exc,
|
|
793
|
-
)
|
|
794
|
-
continue
|
|
795
|
-
else:
|
|
796
|
-
number = int(issue["number"])
|
|
797
|
-
LOGGER.error("issue=%s resume scene is missing: %s", number, exc)
|
|
798
|
-
marker = latest_run_marker(comments)
|
|
799
|
-
try:
|
|
800
|
-
post_blocked_comment(
|
|
801
|
-
number, repo=repo,
|
|
802
|
-
body=(f"{marker}\n" if marker else "")
|
|
803
|
-
+ language.text(
|
|
804
|
-
language_code, "claim.scene_missing", error=exc,
|
|
805
|
-
),
|
|
806
|
-
current_labels={FIX_NEEDED_LABEL},
|
|
807
|
-
language_code=language_code,
|
|
808
|
-
)
|
|
809
|
-
except Exception:
|
|
810
|
-
LOGGER.exception("issue=%s failure reporting failed", number)
|
|
811
|
-
continue
|
|
812
|
-
# The scan and the dispatch classify with the same pure
|
|
813
|
-
# function. This scan owns the resumable route only: a candidate
|
|
814
|
-
# that classifies elsewhere left the opened-PR state between the
|
|
815
|
-
# query and this read (a relabel race) — this candidate is skipped
|
|
816
|
-
# and the scan moves on. A candidate with no readable labels fails
|
|
817
|
-
# open (the is_epic / is_release convention): the trusted scene is
|
|
818
|
-
# the authority.
|
|
819
|
-
current_labels = _issue_label_set(issue)
|
|
820
|
-
if current_labels:
|
|
821
|
-
found_scene = classify(
|
|
822
|
-
labels=current_labels, scene=found, pr_state=None,
|
|
823
|
-
body_markers=body_markers(issue.get("body")),
|
|
824
|
-
)
|
|
825
|
-
if found_scene is not DeliveryScene.RESUME_REVIEW:
|
|
826
|
-
event(
|
|
827
|
-
"claim_yield", issue=int(issue["number"]),
|
|
828
|
-
reason=f"scene_{found_scene.value}",
|
|
829
|
-
)
|
|
830
|
-
continue
|
|
831
|
-
# Issue #1473: skip a CI-pending OPEN PR; MERGED/CLOSED and a
|
|
832
|
-
# failed rollup read still return the candidate.
|
|
833
|
-
try:
|
|
834
|
-
pr_state, rollup = pr_delivery_rollup(found["pr_url"], repo)
|
|
835
|
-
except Exception:
|
|
836
|
-
return issue, found
|
|
837
|
-
pending = _classify_rollup(rollup)[0] if pr_state == "OPEN" else []
|
|
838
|
-
if pending:
|
|
839
|
-
event("delivery_ci_pending", issue=int(issue["number"]),
|
|
840
|
-
pr=found["pr_url"], pending="; ".join(map(_render_check, pending)))
|
|
841
|
-
continue
|
|
842
|
-
return issue, found
|
|
843
|
-
return None
|
|
844
|
-
|
|
845
|
-
|
|
846
631
|
def pick_next_delivery(
|
|
847
632
|
repos: Sequence[str], slot_dir: Path, max_concurrency: int,
|
|
848
633
|
active_milestone: str | None = None, config: RunnerConfig | None = None,
|
|
@@ -860,6 +645,15 @@ def pick_next_delivery(
|
|
|
860
645
|
run id, so the same progress comment is kept instead of a second
|
|
861
646
|
run being started on an Issue that is already in flight.
|
|
862
647
|
|
|
648
|
+
Issue #1745: an `ai-awaiting-merge` delivery waits for a maintainer,
|
|
649
|
+
so it is NOT part of the opened-PR scan. It is re-checked
|
|
650
|
+
(`pick_awaiting_merge_delivery`, the existing merge-only path) only
|
|
651
|
+
after all three scans above found nothing for this tick. A
|
|
652
|
+
maintainer-blocked PR therefore no longer starves the review, the
|
|
653
|
+
restart and the fresh-claim scans; the cost is that after the
|
|
654
|
+
maintainer acts the merge happens on the first otherwise-idle tick,
|
|
655
|
+
not on the very next one.
|
|
656
|
+
|
|
863
657
|
`active_milestone` scopes only the FRESH claim
|
|
864
658
|
(`pick_issue`) — the resumable-PR and in-flight restart scans are
|
|
865
659
|
resume states, and running an in-flight or opened-PR delivery to
|
|
@@ -932,6 +726,22 @@ def pick_next_delivery(
|
|
|
932
726
|
issue = _pick_issue_with_repo_policy(repo, active_milestone, config, held)
|
|
933
727
|
if issue is not None:
|
|
934
728
|
return repo, issue, None
|
|
729
|
+
# Issue #1745: a delivery waiting for a maintainer is re-checked LAST —
|
|
730
|
+
# only on a tick where nothing above could move. Everything it needs
|
|
731
|
+
# (the merge gate, the de-duplicated #1422 handoff) already exists; it
|
|
732
|
+
# just never takes the tick away from reviewable, resumable or fresh
|
|
733
|
+
# work.
|
|
734
|
+
for repo in repos:
|
|
735
|
+
repo_language = (language.DEFAULT_LANGUAGE if config is None
|
|
736
|
+
else merged_repo_language(config, repo))
|
|
737
|
+
selected = pick_awaiting_merge_delivery(
|
|
738
|
+
repo, slot_dir, max_concurrency,
|
|
739
|
+
repo_dir=config.repo_dir if config is not None else None,
|
|
740
|
+
hooks=hooks, language_code=repo_language,
|
|
741
|
+
)
|
|
742
|
+
if selected is not None:
|
|
743
|
+
issue, scene = selected
|
|
744
|
+
return repo, issue, scene
|
|
935
745
|
return None
|
|
936
746
|
|
|
937
747
|
|
|
@@ -56,10 +56,14 @@ def started_pi_comment_body(ctx: RunContext, run_info: str,
|
|
|
56
56
|
for key in ("run_id", "priority")
|
|
57
57
|
if key in info
|
|
58
58
|
}
|
|
59
|
+
# The headline's key is the same display label the rows use
|
|
60
|
+
# (Issue #1746): `run_id` has no catalogue entry and stays the
|
|
61
|
+
# machine row, `priority` renders `优先级` in a Chinese comment.
|
|
59
62
|
headline = language.text(
|
|
60
63
|
language_code, "delivery.started_headline",
|
|
61
64
|
) + " " + " ".join(
|
|
62
|
-
f"{key}={value}"
|
|
65
|
+
f"{field_label(language_code, key)}={value}"
|
|
66
|
+
for key, value in rendered_info.items()
|
|
63
67
|
)
|
|
64
68
|
detail_keys = {"base_sha", "repo_config", "branch", "session"}
|
|
65
69
|
return field_block(
|
|
@@ -55,6 +55,9 @@ REASON_CODES = frozenset({
|
|
|
55
55
|
"credential_missing",
|
|
56
56
|
# The model provider quota is exhausted (429/RESOURCE_EXHAUSTED).
|
|
57
57
|
"provider_quota",
|
|
58
|
+
# The configured base branch does not exist in the repository
|
|
59
|
+
# (Issue #1749): a repository/config precondition a human repairs.
|
|
60
|
+
"base_branch_missing",
|
|
58
61
|
# Any other runner exception the classifier does not recognize.
|
|
59
62
|
"unclassified",
|
|
60
63
|
})
|
|
@@ -244,6 +247,12 @@ _WORKFLOW_PERMISSION_CAUSE_KEY = "failure.workflow_cause"
|
|
|
244
247
|
_TESTS_FAILED_RE = re.compile(
|
|
245
248
|
r"\btests? failed\b|\d+ failed", re.IGNORECASE,
|
|
246
249
|
)
|
|
250
|
+
# Git's answer to `git fetch origin <branch>` when the remote has no such
|
|
251
|
+
# branch (Issue #1749): the two wordings real Git emits, and nothing else —
|
|
252
|
+
# a generic fetch failure keeps its existing classification.
|
|
253
|
+
_BASE_BRANCH_MISSING_RE = re.compile(
|
|
254
|
+
r"could ?n['o]t find remote ref", re.IGNORECASE,
|
|
255
|
+
)
|
|
247
256
|
_GITHUB_TRANSIENT_RE = re.compile(
|
|
248
257
|
r"http 5\d\d|http 429|rate limit|"
|
|
249
258
|
r"something went wrong while executing your query|"
|
|
@@ -264,6 +273,7 @@ DISPOSITIONS: dict[str, tuple[str, bool]] = {
|
|
|
264
273
|
"human_decision_required": ("decide", False),
|
|
265
274
|
"credential_missing": ("check_credentials", False),
|
|
266
275
|
"provider_quota": ("wait_quota", True),
|
|
276
|
+
"base_branch_missing": ("fix_ticket", False),
|
|
267
277
|
"unclassified": ("fix_ticket", False),
|
|
268
278
|
}
|
|
269
279
|
|
|
@@ -292,6 +302,8 @@ def classify(detail: str, *, outcome: str = "blocked",
|
|
|
292
302
|
# cannot grant: the existing `credential_missing` /
|
|
293
303
|
# `check_credentials` category, a repair required before retry.
|
|
294
304
|
reason_code = "credential_missing"
|
|
305
|
+
elif _BASE_BRANCH_MISSING_RE.search(detail):
|
|
306
|
+
reason_code = "base_branch_missing"
|
|
295
307
|
elif _CREDENTIAL_MISSING_RE.search(detail):
|
|
296
308
|
reason_code = "credential_missing"
|
|
297
309
|
elif _TESTS_FAILED_RE.search(detail):
|
|
@@ -362,14 +374,38 @@ def terminal_action(record: Failure,
|
|
|
362
374
|
"""The reader action for the implement-phase terminal handler.
|
|
363
375
|
|
|
364
376
|
An unrecognized failure says the cause could not be determined
|
|
365
|
-
instead of implying the Issue text is the problem (Issue #1651);
|
|
366
|
-
|
|
377
|
+
instead of implying the Issue text is the problem (Issue #1651); a
|
|
378
|
+
missing base branch names its one repair (Issue #1749); every other
|
|
379
|
+
recognized terminal failure keeps the existing generic action.
|
|
367
380
|
"""
|
|
368
381
|
if record.reason_code == "unclassified":
|
|
369
382
|
return language.text(language_code, "failure.terminal_unclassified")
|
|
383
|
+
if record.reason_code == "base_branch_missing":
|
|
384
|
+
return language.text(
|
|
385
|
+
language_code, "failure.terminal_base_branch_missing",
|
|
386
|
+
)
|
|
370
387
|
return language.text(language_code, "failure.terminal_generic")
|
|
371
388
|
|
|
372
389
|
|
|
390
|
+
def terminal_reason(detail: str, record: Failure, *,
|
|
391
|
+
base_branch: str | None, language_code: str) -> str:
|
|
392
|
+
"""The reader-facing reason of the implement-phase terminal handler.
|
|
393
|
+
|
|
394
|
+
The missing-base-branch failure names the CONFIGURED branch: Git's own
|
|
395
|
+
`couldn't find remote ref <value>` line is the evidence, never the
|
|
396
|
+
setting a human has to repair (Issue #1749). Every other terminal
|
|
397
|
+
failure keeps the generic stopped-delivery sentence.
|
|
398
|
+
"""
|
|
399
|
+
if record.reason_code == "base_branch_missing":
|
|
400
|
+
return language.text(
|
|
401
|
+
language_code, "failure.base_branch_missing_reason",
|
|
402
|
+
base_branch=base_branch or "-", detail=detail,
|
|
403
|
+
)
|
|
404
|
+
return language.text(
|
|
405
|
+
language_code, "runner.delivery_stopped_reason", detail=detail,
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
|
|
373
409
|
# --------------------------------------------------------------------------
|
|
374
410
|
# The comment: bounded detail + the reader-facing hierarchy
|
|
375
411
|
# --------------------------------------------------------------------------
|
|
@@ -726,6 +726,24 @@ MESSAGES: dict[str, tuple[str, str]] = {
|
|
|
726
726
|
"Fix the failure described below and re-run this Issue.",
|
|
727
727
|
"请修复下面描述的失败,然后重新运行这个 Issue。",
|
|
728
728
|
),
|
|
729
|
+
# Issue #1749: the configured base branch does not exist in the
|
|
730
|
+
# repository. The reason names the CONFIGURED branch (Git's own line
|
|
731
|
+
# only carries the value it was asked to fetch) and the action names
|
|
732
|
+
# the one repair a human decides.
|
|
733
|
+
"failure.base_branch_missing_reason": (
|
|
734
|
+
"base branch \u0060{base_branch}\u0060 does not exist in the repository: "
|
|
735
|
+
"{detail}",
|
|
736
|
+
"仓库中不存在 base branch \u0060{base_branch}\u0060:{detail}",
|
|
737
|
+
),
|
|
738
|
+
"failure.terminal_base_branch_missing": (
|
|
739
|
+
"Create the missing base branch in the repository, or correct "
|
|
740
|
+
"\u0060base_branch\u0060 in the Runner configuration, then remove "
|
|
741
|
+
"\u0060ai-blocked\u0060 and re-add \u0060ai-ready\u0060 to run this "
|
|
742
|
+
"Issue again.",
|
|
743
|
+
"请在仓库中创建缺失的 base branch,或修正 Runner 配置里的 "
|
|
744
|
+
"\u0060base_branch\u0060,然后移除 \u0060ai-blocked\u0060 并重新添加 "
|
|
745
|
+
"\u0060ai-ready\u0060 以再次运行这个 Issue。",
|
|
746
|
+
),
|
|
729
747
|
"failure.workflow_cause": (
|
|
730
748
|
"GitHub rejected the push because the current GitHub App "
|
|
731
749
|
"credential cannot modify Actions workflows.",
|
|
@@ -48,6 +48,11 @@ DISPLAY_TERMS: dict[str, tuple[str, str]] = {
|
|
|
48
48
|
"progress.tests.passed": ("passed", "通过"),
|
|
49
49
|
"progress.tests.pending": ("pending", "等待中"),
|
|
50
50
|
"progress.tests.failed": ("failed: {names}", "失败:{names}"),
|
|
51
|
+
# The progress comment's recovery row while a session is stuck
|
|
52
|
+
# (Issue #1746): the stored state stays `wait`/`term`/`kill`.
|
|
53
|
+
"progress.recovery.wait": ("wait", "等待"),
|
|
54
|
+
"progress.recovery.term": ("term", "终止"),
|
|
55
|
+
"progress.recovery.kill": ("kill", "强制终止"),
|
|
51
56
|
"progress.activity.read": ("read", "读取"),
|
|
52
57
|
"progress.activity.edit": ("edit", "编辑"),
|
|
53
58
|
"progress.activity.command": ("command", "命令"),
|
|
@@ -47,10 +47,13 @@ def is_maintainer_actionable(stderr: str, preflight: list[str]) -> bool:
|
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
# The handoff comment identifies the exact waiting state it announced.
|
|
50
|
-
# ``ai-awaiting-merge`` is
|
|
51
|
-
#
|
|
52
|
-
# the
|
|
53
|
-
#
|
|
50
|
+
# ``ai-awaiting-merge`` is re-checked by design — but only on a tick
|
|
51
|
+
# whose queue is otherwise empty (Issue #1745: the deferred scan never
|
|
52
|
+
# takes the tick away from reviewable, resumable or fresh work), so the
|
|
53
|
+
# same policy blocker still reaches the merge gate again and again. The
|
|
54
|
+
# head OID and the blocker text decide whether the wait is a new state or
|
|
55
|
+
# the same one already posted (Issue #1422), so an unchanged blocker
|
|
56
|
+
# posts nothing.
|
|
54
57
|
AWAITING_MERGE_HANDOFF_PHRASE = (
|
|
55
58
|
"is delivered and waiting for a maintainer action"
|
|
56
59
|
)
|
|
@@ -505,7 +505,8 @@ def progress_body(state: dict) -> str:
|
|
|
505
505
|
# Exceptional state belongs with the at-a-glance status. A normal
|
|
506
506
|
# first or second review round carries no useful signal on its own.
|
|
507
507
|
if state.get("recovery"):
|
|
508
|
-
lines.append(row("recovery",
|
|
508
|
+
lines.append(row("recovery", vocabulary(
|
|
509
|
+
language_code, "recovery", state["recovery"])))
|
|
509
510
|
if state.get("review_round", 0) >= 3:
|
|
510
511
|
lines.append(row("review_round", value("review_round")))
|
|
511
512
|
|