orbi-cli 0.7.7__tar.gz → 0.7.9__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.7/src/orbi_cli.egg-info → orbi_cli-0.7.9}/PKG-INFO +1 -1
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/pyproject.toml +1 -1
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/__init__.py +1 -1
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/blocked_comment.py +4 -3
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/branch_reclaim.py +15 -10
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/claim.py +132 -36
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/cli.py +33 -15
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/delivery_labels.py +50 -20
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/failure_report.py +22 -22
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/github.py +8 -4
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/gitops.py +28 -1
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/journal.py +4 -1
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/merge_handoff.py +5 -3
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/notify.py +18 -10
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/release.py +10 -5
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/resume_scan.py +49 -20
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/review_merge.py +108 -112
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/review_rounds.py +138 -3
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/run_state.py +19 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/runner.py +26 -26
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/status_observe.py +5 -2
- {orbi_cli-0.7.7 → orbi_cli-0.7.9/src/orbi_cli.egg-info}/PKG-INFO +1 -1
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_apply_label_patch.py +42 -5
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_blocked_comment.py +3 -1
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_bootstrap_delivery.py +401 -10
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_bootstrap_release.py +77 -12
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_bootstrap_runner.py +178 -16
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_branch_reclaim.py +70 -35
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_ci_failure_issue_workflow.py +15 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_ci_failure_triage.py +156 -8
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_comment_identity.py +130 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_concurrency_e2e.py +4 -3
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_delivery_branch_delete.py +6 -3
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_delivery_labels.py +76 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_failure_report.py +66 -1
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_fix_needed_loop.py +99 -7
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_git_merge_smoke.py +138 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_github.py +4 -1
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_github_fakes.py +323 -1
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_gitops.py +25 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_human_review.py +21 -2
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_notify.py +24 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_orbi.py +47 -35
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_progress_wiring.py +126 -1
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_repo_config.py +6 -6
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_resume_continue_e2e.py +4 -3
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_resume_pr.py +155 -57
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_review_merge.py +125 -16
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_status_observe.py +66 -5
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/LICENSE +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/README.md +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/docs/licenses/sustainable-use-license.md +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/setup.cfg +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/checks.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/clarify.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/cli_source.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/comment_kind.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/config.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/delivery_comment.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/delivery_scene.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/engine_source.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/example_config.toml +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/failure.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/gh_response.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/git_identity.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/git_transport.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/human_review.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/language.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/language_terms.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/launchd_deploy.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/merge_gate.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/milestone.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/milestone_command.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/pi_activity.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/pi_command.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/pi_process.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/pi_recovery.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/pi_session.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/pilot_setup.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/pilot_slots.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/progress.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/progress_display.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/progress_outcome.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/progress_summary.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/release_comments.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/release_git.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/release_notes.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/repo_config.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/runner_health.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/scene.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/scheduler.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/scheduler_units.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/session_command.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/source_pool.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/suggest.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/systemd_deploy.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi/ticket_command.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi_cli.egg-info/SOURCES.txt +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi_cli.egg-info/dependency_links.txt +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi_cli.egg-info/entry_points.txt +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/src/orbi_cli.egg-info/top_level.txt +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_agents_md.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_base_branch_failure.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_bootstrap_adapters.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_bootstrap_pi.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_bootstrap_preflight.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_ci_macos_workflow.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_ci_workflow.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_clarify.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_claude_plugin.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_cli_install.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_cli_milestone.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_cli_packaging.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_cli_source.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_comment_readers_contract.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_constitution.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_coverage_gate.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_deliver_pr_fakes.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_delivery_scene.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_docker_docs.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_docker_entrypoint.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_docker_image_workflow.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_docs_i18n.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_docs_issue_shapes.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_docs_labels.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_docs_mermaid.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_docs_releases.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_docs_single_source.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_docs_site.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_engine_source.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_failure.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_gh_read_retry.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_gh_response.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_git_base_smoke.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_git_network_retry.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_git_transport.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_gitops_fakes.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_import_graph.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_install_sh.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_issue_1367.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_issue_461.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_issue_template_config.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_journal.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_journal_events.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_launchd_deploy.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_license.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_message_language.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_milestone_command.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_ops_e2e.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_orbi_dashboard.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_orbi_exporter.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_patch_ratchet.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_pi_activity.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_pi_command.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_pi_extensions.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_pi_provider_templates.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_pi_recovery.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_pi_session.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_pilot_setup.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_pilot_slots.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_pr_issue_link_workflow.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_prerequisite_check.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_progress.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_prompt_contract.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_public_contracts.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_publish_workflow.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_release_docs_workflow.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_release_ticket_templates.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_resume_e2e.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_run_artifacts.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_run_id_e2e.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_runner_config.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_runner_health.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_runner_source_freshness.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_scene.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_scheduler.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_size_ratchet.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_src_layout.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_steering_review.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_suggest.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_suite_hygiene.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_systemd_deploy.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_systemd_timer.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_ticket_command.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/tests/test_user_acceptance_contract.py +0 -0
- {orbi_cli-0.7.7 → orbi_cli-0.7.9}/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.9
|
|
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.9"
|
|
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"
|
|
@@ -12,12 +12,13 @@ receipt) never goes through this exit and never gets the line.
|
|
|
12
12
|
from __future__ import annotations
|
|
13
13
|
|
|
14
14
|
from orbi import language
|
|
15
|
-
from orbi.delivery_labels import EVENT_BLOCKED
|
|
15
|
+
from orbi.delivery_labels import EVENT_BLOCKED, READY_LABEL
|
|
16
16
|
from orbi.github import apply_label_patch, comment_issue
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
def post_blocked_comment(number: int, *, repo: str, body: str,
|
|
20
|
-
current_labels, language_code: str
|
|
20
|
+
current_labels, language_code: str,
|
|
21
|
+
ready_label: str = READY_LABEL) -> None:
|
|
21
22
|
"""Block `number`: apply the `ai-blocked` label patch, then comment.
|
|
22
23
|
|
|
23
24
|
The order is part of the contract: the label transition lands first, the
|
|
@@ -27,7 +28,7 @@ def post_blocked_comment(number: int, *, repo: str, body: str,
|
|
|
27
28
|
"""
|
|
28
29
|
apply_label_patch(
|
|
29
30
|
number, repo=repo, event=EVENT_BLOCKED,
|
|
30
|
-
current_labels=current_labels,
|
|
31
|
+
current_labels=current_labels, ready_label=ready_label,
|
|
31
32
|
)
|
|
32
33
|
comment_issue(
|
|
33
34
|
number, repo=repo,
|
|
@@ -23,6 +23,7 @@ from pathlib import Path
|
|
|
23
23
|
from typing import TYPE_CHECKING
|
|
24
24
|
from urllib.parse import quote
|
|
25
25
|
|
|
26
|
+
from orbi import gitops
|
|
26
27
|
from orbi.journal import GIT_NETWORK_TIMEOUT_SECONDS, event, run_command
|
|
27
28
|
|
|
28
29
|
if TYPE_CHECKING:
|
|
@@ -93,14 +94,16 @@ def delete_merged_delivery_branch(
|
|
|
93
94
|
) -> None:
|
|
94
95
|
"""Delete a MERGED delivery PR's remote head branch (Issue #1478).
|
|
95
96
|
|
|
96
|
-
Called right after `gh pr merge` lands. Only
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
97
|
+
Called right after `gh pr merge` lands. Only this source repo's own
|
|
98
|
+
delivery branch (`gitops.is_delivery_branch`, the one ownership
|
|
99
|
+
check, Issue #1788) is deleted; a head a user named
|
|
100
|
+
(`orbi/manual`), another repository's delivery name or an external
|
|
101
|
+
takeover PR's head is left to its owner. A 404/422 (the reference is
|
|
102
|
+
already gone) counts as success. Any other failure emits a warning
|
|
103
|
+
event and returns — the merge has already landed, so this cleanup
|
|
104
|
+
never fails the delivery.
|
|
102
105
|
"""
|
|
103
|
-
if not
|
|
106
|
+
if not gitops.is_delivery_branch(source_repo, branch):
|
|
104
107
|
return
|
|
105
108
|
try:
|
|
106
109
|
delete_remote_branch(source_repo, branch)
|
|
@@ -174,7 +177,7 @@ def read_merged_pull_requests(source_repo: str) -> list[dict]:
|
|
|
174
177
|
|
|
175
178
|
|
|
176
179
|
def reclaim_merged_delivery_branches(config: config_domain.RunnerConfig) -> None:
|
|
177
|
-
"""Delete remote
|
|
180
|
+
"""Delete this repo's remote delivery branches whose PR has MERGED.
|
|
178
181
|
|
|
179
182
|
A merged delivery leaves its `orbi/<slug>-issue-<n>` branch on the
|
|
180
183
|
remote forever; #1478 stops NEW ones, this pass drains the BACKLOG.
|
|
@@ -182,7 +185,9 @@ def reclaim_merged_delivery_branches(config: config_domain.RunnerConfig) -> None
|
|
|
182
185
|
idempotent, bounded (at most `BRANCH_RECLAIM_MAX_PER_TICK` deletes)
|
|
183
186
|
and never fatal.
|
|
184
187
|
|
|
185
|
-
Only a branch
|
|
188
|
+
Only a branch this source repo OWNS (`gitops.is_delivery_branch`,
|
|
189
|
+
Issue #1788: `orbi/<owner>-<repo>-issue-<n>`, never an `orbi/`
|
|
190
|
+
prefix alone) whose CURRENT remote oid equals the `headRefOid` a
|
|
186
191
|
MERGED PR reports is deleted. The delivery branch name is fixed per
|
|
187
192
|
Issue, so a reopened Issue's new attempt pushes to the same name
|
|
188
193
|
while the old merged PR still reports it: without the oid match the
|
|
@@ -201,7 +206,7 @@ def reclaim_merged_delivery_branches(config: config_domain.RunnerConfig) -> None
|
|
|
201
206
|
heads = {
|
|
202
207
|
branch: oid
|
|
203
208
|
for branch, oid in _parse_remote_heads(raw).items()
|
|
204
|
-
if
|
|
209
|
+
if gitops.is_delivery_branch(source_repo, branch)
|
|
205
210
|
}
|
|
206
211
|
if not heads:
|
|
207
212
|
return
|
|
@@ -12,10 +12,10 @@ from __future__ import annotations
|
|
|
12
12
|
import json
|
|
13
13
|
import logging
|
|
14
14
|
import os
|
|
15
|
-
import re
|
|
16
15
|
from collections.abc import Sequence
|
|
17
16
|
from pathlib import Path
|
|
18
17
|
|
|
18
|
+
from orbi import gitops
|
|
19
19
|
from orbi import milestone as milestone_bookkeeping
|
|
20
20
|
from orbi.config import RunnerConfig
|
|
21
21
|
from orbi.delivery_labels import (
|
|
@@ -214,20 +214,21 @@ def reconcile_open_epics(repo: str, run_id: str, language_code: str) -> list[str
|
|
|
214
214
|
# idempotency key (one report per PR, never one per tick).
|
|
215
215
|
ORPHAN_PR_MARK = "<!-- orbi:orphan-pr -->"
|
|
216
216
|
|
|
217
|
-
_ORPHAN_PR_BRANCH_RE = re.compile(r"orbi/.+-issue-(\d+)\Z")
|
|
218
217
|
|
|
218
|
+
def orphan_pr_branch_issue(repo: str, head_ref: object) -> int | None:
|
|
219
|
+
"""The source Issue number of THIS repo's delivery branch, or None.
|
|
219
220
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
`
|
|
224
|
-
`orbi
|
|
225
|
-
external contributor's branch) is never the Runner's business.
|
|
221
|
+
The ownership decision is `gitops.is_delivery_branch` and the number
|
|
222
|
+
comes from the same naming source (`gitops.delivery_branch_prefix`,
|
|
223
|
+
Issue #1788): only `orbi/<this source repo>-issue-<N>` names an Issue
|
|
224
|
+
here, so another repository's `orbi/x-y-issue-3` — or a human's own
|
|
225
|
+
`orbi/manual` — is never the Runner's business.
|
|
226
226
|
"""
|
|
227
|
-
if not isinstance(head_ref, str)
|
|
227
|
+
if not isinstance(head_ref, str) or not gitops.is_delivery_branch(
|
|
228
|
+
repo, head_ref,
|
|
229
|
+
):
|
|
228
230
|
return None
|
|
229
|
-
|
|
230
|
-
return int(match.group(1)) if match else None
|
|
231
|
+
return int(head_ref.removeprefix(gitops.delivery_branch_prefix(repo)))
|
|
231
232
|
|
|
232
233
|
|
|
233
234
|
def reconcile_orphan_prs(
|
|
@@ -256,7 +257,9 @@ def reconcile_orphan_prs(
|
|
|
256
257
|
if not isinstance(pr, dict):
|
|
257
258
|
continue
|
|
258
259
|
pr_number = pr.get("number")
|
|
259
|
-
issue_number = orphan_pr_branch_issue(
|
|
260
|
+
issue_number = orphan_pr_branch_issue(
|
|
261
|
+
repo, pr.get("headRefName"),
|
|
262
|
+
)
|
|
260
263
|
if not isinstance(pr_number, int) or issue_number is None:
|
|
261
264
|
continue
|
|
262
265
|
details = issue_view(issue_number, "state", repo=repo, timeout=30)
|
|
@@ -315,6 +318,7 @@ def _pick_from_scan(
|
|
|
315
318
|
active_milestone: str | None = None,
|
|
316
319
|
ready_label: str = READY_LABEL,
|
|
317
320
|
external_only: bool = False, held: frozenset = frozenset(),
|
|
321
|
+
raise_on_read_failure: bool = False,
|
|
318
322
|
) -> dict | None:
|
|
319
323
|
"""Return the first claimable Issue of one scan result, else None.
|
|
320
324
|
|
|
@@ -361,6 +365,13 @@ def _pick_from_scan(
|
|
|
361
365
|
`held` names the deliveries a live co-runner works on: such an Issue
|
|
362
366
|
is skipped (`claim_yield reason=held_by_live_runner`) and never
|
|
363
367
|
claimed twice — the skip is the FIRST guard of this loop.
|
|
368
|
+
|
|
369
|
+
`raise_on_read_failure` decides what a failed Milestone read of the
|
|
370
|
+
release gate MEANS to the caller — the same meaning every other read
|
|
371
|
+
of the decision carries (`_read_failed`): the claim fails safe
|
|
372
|
+
and skips the release this tick, an OBSERVER of the decision
|
|
373
|
+
(`orbi status`) re-raises so the failed read is named instead of
|
|
374
|
+
rendering as an empty queue (Issue #1782).
|
|
364
375
|
"""
|
|
365
376
|
for issue in issues:
|
|
366
377
|
if (repo, int(issue["number"])) in held:
|
|
@@ -386,10 +397,12 @@ def _pick_from_scan(
|
|
|
386
397
|
repo, target_milestone,
|
|
387
398
|
)
|
|
388
399
|
except Exception as exc:
|
|
389
|
-
|
|
390
|
-
"release_milestone_check_failed",
|
|
400
|
+
_read_failed(
|
|
401
|
+
"release_milestone_check_failed", exc,
|
|
402
|
+
raise_on_read_failure,
|
|
391
403
|
issue=issue.get("number"), repo=repo,
|
|
392
|
-
milestone=target_milestone,
|
|
404
|
+
milestone=target_milestone,
|
|
405
|
+
)
|
|
393
406
|
return None
|
|
394
407
|
if open_issues > 1:
|
|
395
408
|
event("release_milestone_incomplete", issue=issue.get("number"),
|
|
@@ -420,9 +433,31 @@ def _pick_from_scan(
|
|
|
420
433
|
return None
|
|
421
434
|
|
|
422
435
|
|
|
436
|
+
def _read_failed(
|
|
437
|
+
read: str, exc: Exception, raise_on_read_failure: bool, **fields: object,
|
|
438
|
+
) -> None:
|
|
439
|
+
"""Journal one failed read, then fail open OR re-raise.
|
|
440
|
+
|
|
441
|
+
THE one place `raise_on_read_failure` is decided (Issue #1794):
|
|
442
|
+
every failing read in this module calls it with its OWN journal event
|
|
443
|
+
name and fields (`release_milestone_check_failed`,
|
|
444
|
+
`blocked_by_check_failed`), so each site keeps its line while the
|
|
445
|
+
meaning of a failed read cannot drift between sites.
|
|
446
|
+
The claim fails open: a broken query must never deadlock the queue,
|
|
447
|
+
so this tick claims nothing and the next one retries. An OBSERVER of
|
|
448
|
+
the same decision (`orbi status`) must not read a failed read as an
|
|
449
|
+
empty queue — it asks for the failure instead (#1668, #1782).
|
|
450
|
+
"""
|
|
451
|
+
event(read, level=logging.ERROR, **fields, error=exc)
|
|
452
|
+
if raise_on_read_failure:
|
|
453
|
+
raise
|
|
454
|
+
|
|
455
|
+
|
|
423
456
|
def pick_issue(repo: str, active_milestone: str | None = None,
|
|
424
457
|
dispatch_label: str = READY_LABEL,
|
|
425
|
-
held: frozenset = frozenset()
|
|
458
|
+
held: frozenset = frozenset(),
|
|
459
|
+
timeout: int | None = None,
|
|
460
|
+
raise_on_read_failure: bool = False) -> dict | None:
|
|
426
461
|
# A merged delivery keeps `ai-ready` + `ai-merged` on the (still
|
|
427
462
|
# open) Issue; `ai-merged` is the success terminal state, so it is
|
|
428
463
|
# excluded from the ready scan like every other delivery state.
|
|
@@ -456,24 +491,34 @@ def pick_issue(repo: str, active_milestone: str | None = None,
|
|
|
456
491
|
# code-layer marker guard to the triage tickets an external
|
|
457
492
|
# contribution routes to — an external review is supplementary
|
|
458
493
|
# capacity, never a competitor of this version's deliveries.
|
|
494
|
+
# `timeout` is the CALLER's read bound (the status view's #1668
|
|
495
|
+
# bound); the claim itself reads unbounded. The ready decision is
|
|
496
|
+
# one function for both readers (Issue #1773), so the bound travels
|
|
497
|
+
# with the call instead of a second query being built.
|
|
498
|
+
# `raise_on_read_failure` says what a failed scan read MEANS to the
|
|
499
|
+
# caller: the claim fails open (default), the status observation
|
|
500
|
+
# re-raises so the failure is named instead of reading as an empty
|
|
501
|
+
# queue (`_read_failed`).
|
|
459
502
|
for search in ready_searches(active_milestone, dispatch_label):
|
|
460
503
|
try:
|
|
461
504
|
issues = list_issues(
|
|
462
505
|
repo, state="open", search=search,
|
|
463
506
|
json_fields="number,title,body,labels,blockedBy,author", limit=200,
|
|
507
|
+
timeout=timeout,
|
|
464
508
|
)
|
|
465
509
|
except Exception as exc:
|
|
466
510
|
# Fail open: a failed blockedBy query must
|
|
467
511
|
# never deadlock the queue. This tick claims nothing from
|
|
468
512
|
# this repo and the next tick retries the query; the error
|
|
469
513
|
# is logged, never raised, and no label is touched.
|
|
470
|
-
|
|
471
|
-
"blocked_by_check_failed",
|
|
472
|
-
repo=repo,
|
|
514
|
+
_read_failed(
|
|
515
|
+
"blocked_by_check_failed", exc, raise_on_read_failure,
|
|
516
|
+
repo=repo,
|
|
473
517
|
)
|
|
474
518
|
return None
|
|
475
519
|
picked = _pick_from_scan(
|
|
476
520
|
issues, repo, ready_label=dispatch_label, held=held,
|
|
521
|
+
raise_on_read_failure=raise_on_read_failure,
|
|
477
522
|
)
|
|
478
523
|
if picked is not None:
|
|
479
524
|
return picked
|
|
@@ -489,18 +534,18 @@ def pick_issue(repo: str, active_milestone: str | None = None,
|
|
|
489
534
|
repo, state="open",
|
|
490
535
|
search=release_fallback_search(active_milestone, dispatch_label),
|
|
491
536
|
json_fields="number,title,body,labels,blockedBy,milestone",
|
|
492
|
-
limit=200,
|
|
537
|
+
limit=200, timeout=timeout,
|
|
493
538
|
)
|
|
494
539
|
except Exception as exc:
|
|
495
|
-
|
|
496
|
-
"blocked_by_check_failed",
|
|
497
|
-
repo=repo, error=exc,
|
|
540
|
+
_read_failed(
|
|
541
|
+
"blocked_by_check_failed", exc, raise_on_read_failure, repo=repo,
|
|
498
542
|
)
|
|
499
543
|
return None
|
|
500
544
|
picked = _pick_from_scan(
|
|
501
545
|
issues, repo, allow_release=True, held=held,
|
|
502
546
|
active_milestone=active_milestone,
|
|
503
547
|
ready_label=dispatch_label,
|
|
548
|
+
raise_on_read_failure=raise_on_read_failure,
|
|
504
549
|
)
|
|
505
550
|
if picked is not None:
|
|
506
551
|
return picked
|
|
@@ -518,17 +563,17 @@ def pick_issue(repo: str, active_milestone: str | None = None,
|
|
|
518
563
|
repo, state="open",
|
|
519
564
|
search=external_takeover_search(dispatch_label),
|
|
520
565
|
json_fields="number,title,body,labels,blockedBy",
|
|
521
|
-
limit=200,
|
|
566
|
+
limit=200, timeout=timeout,
|
|
522
567
|
)
|
|
523
568
|
except Exception as exc:
|
|
524
|
-
|
|
525
|
-
"blocked_by_check_failed",
|
|
526
|
-
repo=repo, error=exc,
|
|
569
|
+
_read_failed(
|
|
570
|
+
"blocked_by_check_failed", exc, raise_on_read_failure, repo=repo,
|
|
527
571
|
)
|
|
528
572
|
return None
|
|
529
573
|
return _pick_from_scan(
|
|
530
574
|
issues, repo, external_only=True, held=held,
|
|
531
575
|
ready_label=dispatch_label,
|
|
576
|
+
raise_on_read_failure=raise_on_read_failure,
|
|
532
577
|
)
|
|
533
578
|
|
|
534
579
|
|
|
@@ -669,6 +714,7 @@ def pick_next_delivery(
|
|
|
669
714
|
for repo in repos:
|
|
670
715
|
repo_language = (language.DEFAULT_LANGUAGE if config is None
|
|
671
716
|
else merged_repo_language(config, repo))
|
|
717
|
+
_, dispatch_label = _repo_scan_keys(config, repo, active_milestone)
|
|
672
718
|
# Epic reconciliation is a per-tick bypass: a broken GitHub query or
|
|
673
719
|
# mutation must never prevent the ordinary delivery scans.
|
|
674
720
|
try:
|
|
@@ -709,6 +755,7 @@ def pick_next_delivery(
|
|
|
709
755
|
repo, slot_dir, max_concurrency,
|
|
710
756
|
repo_dir=config.repo_dir if config is not None else None,
|
|
711
757
|
hooks=hooks, language_code=repo_language,
|
|
758
|
+
ready_label=dispatch_label,
|
|
712
759
|
)
|
|
713
760
|
if selected is not None:
|
|
714
761
|
issue, scene = selected
|
|
@@ -723,7 +770,7 @@ def pick_next_delivery(
|
|
|
723
770
|
return repo, issue, None
|
|
724
771
|
for repo in repos:
|
|
725
772
|
held = slot_held_deliveries(slot_dir, max_concurrency)
|
|
726
|
-
issue =
|
|
773
|
+
issue = pick_ready_issue(repo, active_milestone, config, held)
|
|
727
774
|
if issue is not None:
|
|
728
775
|
return repo, issue, None
|
|
729
776
|
# Issue #1745: a delivery waiting for a maintainer is re-checked LAST —
|
|
@@ -734,10 +781,12 @@ def pick_next_delivery(
|
|
|
734
781
|
for repo in repos:
|
|
735
782
|
repo_language = (language.DEFAULT_LANGUAGE if config is None
|
|
736
783
|
else merged_repo_language(config, repo))
|
|
784
|
+
_, dispatch_label = _repo_scan_keys(config, repo, active_milestone)
|
|
737
785
|
selected = pick_awaiting_merge_delivery(
|
|
738
786
|
repo, slot_dir, max_concurrency,
|
|
739
787
|
repo_dir=config.repo_dir if config is not None else None,
|
|
740
788
|
hooks=hooks, language_code=repo_language,
|
|
789
|
+
ready_label=dispatch_label,
|
|
741
790
|
)
|
|
742
791
|
if selected is not None:
|
|
743
792
|
issue, scene = selected
|
|
@@ -791,19 +840,66 @@ def _repo_scan_context(
|
|
|
791
840
|
)
|
|
792
841
|
|
|
793
842
|
|
|
794
|
-
def
|
|
795
|
-
|
|
796
|
-
|
|
843
|
+
def _live_held_deliveries(
|
|
844
|
+
config: RunnerConfig | None,
|
|
845
|
+
) -> frozenset[tuple[str, int]]:
|
|
846
|
+
"""The deliveries live co-runners hold RIGHT NOW, from the host config.
|
|
847
|
+
|
|
848
|
+
The claim path reads its slots through `slot_held_deliveries`
|
|
849
|
+
(`pick_next_delivery`); this is the same read for a reader that has
|
|
850
|
+
only the configuration — a host without a readable `slot_dir`
|
|
851
|
+
contributes nothing (fail open: skip only what is provably held).
|
|
852
|
+
"""
|
|
853
|
+
if config is None or config.slot_dir is None:
|
|
854
|
+
return frozenset()
|
|
855
|
+
return frozenset(
|
|
856
|
+
slot_held_deliveries(config.slot_dir, config.max_concurrency)
|
|
857
|
+
)
|
|
858
|
+
|
|
859
|
+
|
|
860
|
+
def pick_ready_issue(
|
|
861
|
+
repo: str, active_milestone: str | None = None,
|
|
862
|
+
config: RunnerConfig | None = None,
|
|
863
|
+
held: frozenset | None = None,
|
|
864
|
+
*, timeout: int | None = None, raise_on_read_failure: bool = False,
|
|
797
865
|
) -> dict | None:
|
|
798
|
-
"""
|
|
866
|
+
"""The Issue the next fresh claim would pick: THE ready decision.
|
|
867
|
+
|
|
868
|
+
Every reader of "which Issue is ready" asks THIS function, so the
|
|
869
|
+
answer cannot drift into a second query (Issue #1773): the claim
|
|
870
|
+
loop (`pick_next_delivery`) and `orbi status` (`cli.ready_issue`, with
|
|
871
|
+
its own read bound) both arrive here. It carries the claim's own
|
|
872
|
+
searches and delivery-state exclusions (see `ready_searches`), the
|
|
873
|
+
repository policy's scan keys, the Epic/blockedBy/scene guards and
|
|
874
|
+
the release + external-takeover fallbacks.
|
|
799
875
|
|
|
800
876
|
`dispatch_label` and `active_milestone` are resolved from the
|
|
801
|
-
repository policy before the scan (see `_repo_scan_keys`),
|
|
802
|
-
|
|
877
|
+
repository policy before the scan (see `_repo_scan_keys`), `held`
|
|
878
|
+
(the deliveries live co-runners work on) is passed through, and
|
|
879
|
+
`timeout` bounds every scan's read and `raise_on_read_failure`
|
|
880
|
+
decides what a failed read means to the caller: the claim fails open,
|
|
881
|
+
while `orbi status` re-raises so a broken read is named as a failure
|
|
882
|
+
and never rendered as an empty queue (`_read_failed`).
|
|
883
|
+
|
|
884
|
+
An unset `held` reads the SAME live slots the claim reads, from the
|
|
885
|
+
configuration's own `slot_dir`/`max_concurrency`
|
|
886
|
+
(`_live_held_deliveries`): the status reader must not name a delivery
|
|
887
|
+
a live co-runner already holds during the seconds between that
|
|
888
|
+
runner's slot-identity write and its `ai-in-progress` label (Issue
|
|
889
|
+
#1782). Both readers decide on the same held set, and no held logic
|
|
890
|
+
lives in `cli.py`.
|
|
803
891
|
"""
|
|
892
|
+
if held is None:
|
|
893
|
+
held = _live_held_deliveries(config)
|
|
804
894
|
milestone, dispatch_label = _repo_scan_keys(
|
|
805
895
|
config, repo, active_milestone,
|
|
806
896
|
)
|
|
807
897
|
if dispatch_label == READY_LABEL:
|
|
808
|
-
return pick_issue(
|
|
809
|
-
|
|
898
|
+
return pick_issue(
|
|
899
|
+
repo, milestone, held=held, timeout=timeout,
|
|
900
|
+
raise_on_read_failure=raise_on_read_failure,
|
|
901
|
+
)
|
|
902
|
+
return pick_issue(
|
|
903
|
+
repo, milestone, dispatch_label=dispatch_label, held=held,
|
|
904
|
+
timeout=timeout, raise_on_read_failure=raise_on_read_failure,
|
|
905
|
+
)
|
|
@@ -32,10 +32,12 @@ from pathlib import Path
|
|
|
32
32
|
|
|
33
33
|
from orbi import (
|
|
34
34
|
__version__,
|
|
35
|
+
claim,
|
|
35
36
|
cli_source,
|
|
36
37
|
config as config_domain,
|
|
37
38
|
engine_source,
|
|
38
39
|
git_transport,
|
|
40
|
+
journal as journal_domain,
|
|
39
41
|
notify,
|
|
40
42
|
runner,
|
|
41
43
|
scheduler,
|
|
@@ -156,12 +158,15 @@ def dispatch_issue(repo: str, title: str, body: str, *,
|
|
|
156
158
|
return url
|
|
157
159
|
|
|
158
160
|
|
|
159
|
-
def list_labeled_issues(repo: str, label: str, state: str = "open",
|
|
160
|
-
search: str | None = None, *,
|
|
161
|
+
def list_labeled_issues(repo: str, label: str, state: str = "open", *,
|
|
161
162
|
timeout: int | None = None) -> list[dict]:
|
|
162
|
-
"""Return the newest
|
|
163
|
+
"""Return the newest Issue carrying one label (gh lists newest first).
|
|
164
|
+
|
|
165
|
+
The label IS the query: the status view never assembles a search of
|
|
166
|
+
its own. "Which Issue is ready" is the claim's decision (#1773).
|
|
167
|
+
"""
|
|
163
168
|
return list_issues(
|
|
164
|
-
repo, state=state, search=
|
|
169
|
+
repo, state=state, search=f"label:{label}",
|
|
165
170
|
json_fields="number,title,url,state", limit=1, timeout=timeout,
|
|
166
171
|
)
|
|
167
172
|
|
|
@@ -171,17 +176,27 @@ def current_issue(repo: str) -> dict | None:
|
|
|
171
176
|
return issues[0] if issues else None
|
|
172
177
|
|
|
173
178
|
|
|
174
|
-
def ready_issue(repo: str
|
|
175
|
-
|
|
179
|
+
def ready_issue(repo: str,
|
|
180
|
+
config: config_domain.RunnerConfig) -> dict | None:
|
|
181
|
+
"""The Issue the claim would pick, read under the status bound (#1668).
|
|
176
182
|
|
|
177
|
-
The
|
|
178
|
-
|
|
183
|
+
The ready decision lives on the claim side (Issue #1773): an Issue in
|
|
184
|
+
a delivery state (ai-pr-opened/ai-fix-needed/ai-merged/ai-blocked) is
|
|
185
|
+
never ready, and the claim's pick/skip lines stay out of the view; an
|
|
186
|
+
unreadable scan fails the observation (#1668), not an empty queue.
|
|
187
|
+
The held set and the Milestone read arrive from the claim (#1782).
|
|
179
188
|
"""
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
189
|
+
journal = journal_domain.LOGGER
|
|
190
|
+
level = journal.level
|
|
191
|
+
journal.setLevel(max(level, logging.WARNING))
|
|
192
|
+
try:
|
|
193
|
+
return claim.pick_ready_issue(
|
|
194
|
+
repo, config.active_milestone, config,
|
|
195
|
+
timeout=status_observe.STATUS_READ_TIMEOUT_SECONDS,
|
|
196
|
+
raise_on_read_failure=True,
|
|
197
|
+
)
|
|
198
|
+
finally:
|
|
199
|
+
journal.setLevel(level)
|
|
185
200
|
|
|
186
201
|
|
|
187
202
|
def recent_result(repo: str) -> dict | None:
|
|
@@ -204,7 +219,9 @@ def recent_result(repo: str) -> dict | None:
|
|
|
204
219
|
|
|
205
220
|
|
|
206
221
|
def format_issue(issue: dict) -> str:
|
|
207
|
-
|
|
222
|
+
"""Render `#N title url`; a claim-scan record carries no url (#1773)."""
|
|
223
|
+
url = issue.get("url")
|
|
224
|
+
return f"#{issue['number']} {issue['title']}" + (f" {url}" if url else "")
|
|
208
225
|
|
|
209
226
|
|
|
210
227
|
def slot_lines(state_dir: Path, capacity: int) -> list[str]:
|
|
@@ -943,7 +960,8 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
943
960
|
return 1
|
|
944
961
|
observer = status_observe.StatusObserver(
|
|
945
962
|
config, scope,
|
|
946
|
-
read_ready=ready_issue,
|
|
963
|
+
read_ready=lambda repo: ready_issue(repo, config),
|
|
964
|
+
read_result=recent_result,
|
|
947
965
|
read_base=freeze_base,
|
|
948
966
|
)
|
|
949
967
|
|