orbi-cli 0.7.1__tar.gz → 0.7.3__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.1/src/orbi_cli.egg-info → orbi_cli-0.7.3}/PKG-INFO +1 -1
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/pyproject.toml +1 -1
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/__init__.py +1 -1
- orbi_cli-0.7.3/src/orbi/blocked_comment.py +35 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/claim.py +46 -43
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/clarify.py +6 -4
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/delivery_comment.py +48 -26
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/delivery_scene.py +0 -4
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/failure.py +8 -5
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/failure_report.py +90 -92
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/github.py +12 -21
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/human_review.py +9 -3
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/language.py +341 -31
- orbi_cli-0.7.3/src/orbi/language_terms.py +77 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/merge_handoff.py +2 -2
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/milestone.py +57 -31
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/milestone_command.py +8 -5
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/notify.py +177 -40
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/pi_session.py +4 -2
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/progress.py +52 -48
- orbi_cli-0.7.3/src/orbi/progress_display.py +111 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/progress_outcome.py +12 -1
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/progress_summary.py +5 -3
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/release.py +16 -18
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/release_comments.py +47 -2
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/repo_config.py +18 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/review_merge.py +96 -80
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/review_rounds.py +2 -2
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/runner.py +91 -81
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/runner_health.py +17 -10
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/suggest.py +59 -15
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/ticket_command.py +61 -19
- {orbi_cli-0.7.1 → orbi_cli-0.7.3/src/orbi_cli.egg-info}/PKG-INFO +1 -1
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi_cli.egg-info/SOURCES.txt +9 -0
- orbi_cli-0.7.3/tests/test_blocked_comment.py +174 -0
- orbi_cli-0.7.3/tests/test_bootstrap_adapters.py +466 -0
- orbi_cli-0.7.3/tests/test_bootstrap_delivery.py +6125 -0
- orbi_cli-0.7.3/tests/test_bootstrap_pi.py +2856 -0
- orbi_cli-0.7.3/tests/test_bootstrap_preflight.py +2162 -0
- orbi_cli-0.7.3/tests/test_bootstrap_release.py +4579 -0
- orbi_cli-0.7.3/tests/test_bootstrap_runner.py +7075 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_clarify.py +3 -3
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_comment_identity.py +5 -5
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_comment_readers_contract.py +6 -5
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_deliver_pr_fakes.py +4 -4
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_delivery_branch_delete.py +5 -5
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_delivery_scene.py +64 -14
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_failure.py +21 -21
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_failure_report.py +406 -39
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_fix_needed_loop.py +66 -11
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_git_merge_smoke.py +6 -6
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_github_fakes.py +24 -2
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_human_review.py +9 -8
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_message_language.py +452 -13
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_milestone_command.py +203 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_notify.py +266 -32
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_pilot_setup.py +27 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_progress.py +62 -30
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_progress_wiring.py +177 -12
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_repo_config.py +32 -11
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_resume_continue_e2e.py +6 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_resume_e2e.py +142 -4
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_resume_pr.py +258 -62
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_review_merge.py +152 -42
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_run_id_e2e.py +2 -1
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_runner_config.py +74 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_runner_health.py +127 -2
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_steering_review.py +27 -1
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_suggest.py +283 -20
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_ticket_command.py +3 -1
- orbi_cli-0.7.1/tests/test_bootstrap_runner.py +0 -24077
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/LICENSE +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/README.md +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/docs/licenses/sustainable-use-license.md +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/setup.cfg +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/branch_reclaim.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/checks.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/cli.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/cli_source.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/comment_kind.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/config.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/delivery_labels.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/engine_source.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/example_config.toml +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/git_identity.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/git_transport.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/gitops.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/journal.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/launchd_deploy.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/merge_gate.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/pi_activity.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/pi_command.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/pi_process.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/pi_recovery.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/pilot_setup.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/pilot_slots.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/release_git.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/release_notes.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/run_state.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/scene.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/scheduler.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/scheduler_units.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/session_command.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/source_pool.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/status_observe.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi/systemd_deploy.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi_cli.egg-info/dependency_links.txt +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi_cli.egg-info/entry_points.txt +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/src/orbi_cli.egg-info/top_level.txt +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_agents_md.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_apply_label_patch.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_branch_reclaim.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_ci_failure_issue_workflow.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_ci_failure_triage.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_ci_macos_workflow.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_ci_workflow.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_claude_plugin.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_cli_install.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_cli_milestone.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_cli_packaging.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_cli_source.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_concurrency_e2e.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_constitution.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_coverage_gate.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_delivery_labels.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_docker_docs.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_docker_entrypoint.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_docker_image_workflow.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_docs_i18n.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_docs_issue_shapes.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_docs_labels.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_docs_mermaid.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_docs_releases.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_docs_single_source.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_docs_site.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_engine_source.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_gh_read_retry.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_git_base_smoke.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_git_network_retry.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_git_transport.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_github.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_gitops.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_gitops_fakes.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_import_graph.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_install_sh.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_issue_1367.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_issue_461.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_issue_template_config.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_journal.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_journal_events.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_launchd_deploy.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_license.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_ops_e2e.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_orbi.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_orbi_dashboard.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_orbi_exporter.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_patch_ratchet.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_pi_activity.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_pi_command.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_pi_extensions.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_pi_provider_templates.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_pi_recovery.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_pi_session.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_pilot_slots.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_pr_issue_link_workflow.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_prerequisite_check.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_prompt_contract.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_public_contracts.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_publish_workflow.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_release_docs_workflow.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_release_ticket_templates.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_run_artifacts.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_runner_source_freshness.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_scene.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_scheduler.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_size_ratchet.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_src_layout.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_status_observe.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_suite_hygiene.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_systemd_deploy.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_systemd_timer.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/tests/test_user_acceptance_contract.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.3}/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.3
|
|
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.3"
|
|
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"
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""The ONE exit for a comment that blocks an Issue (Issue #1730).
|
|
2
|
+
|
|
3
|
+
Ten scenes put an Issue into `ai-blocked` and each own their reason text,
|
|
4
|
+
unblock step, hidden markers and extra fields. What they must NOT each own is
|
|
5
|
+
the pointer to where a person can ask for help: it is appended here, once, so
|
|
6
|
+
a blocked scene added later through this exit carries the community help line
|
|
7
|
+
without anyone remembering to add it.
|
|
8
|
+
|
|
9
|
+
A comment that is not a blocked scene (a retrying diagnostic, progress, a
|
|
10
|
+
receipt) never goes through this exit and never gets the line.
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from orbi import language
|
|
15
|
+
from orbi.delivery_labels import EVENT_BLOCKED
|
|
16
|
+
from orbi.github import apply_label_patch, comment_issue
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def post_blocked_comment(number: int, *, repo: str, body: str,
|
|
20
|
+
current_labels, language_code: str) -> None:
|
|
21
|
+
"""Block `number`: apply the `ai-blocked` label patch, then comment.
|
|
22
|
+
|
|
23
|
+
The order is part of the contract: the label transition lands first, the
|
|
24
|
+
comment second. The body is posted exactly as the scene rendered it,
|
|
25
|
+
followed by a blank line and the `help.community` line in the run's
|
|
26
|
+
language — added here and only here, so it appears exactly once.
|
|
27
|
+
"""
|
|
28
|
+
apply_label_patch(
|
|
29
|
+
number, repo=repo, event=EVENT_BLOCKED,
|
|
30
|
+
current_labels=current_labels,
|
|
31
|
+
)
|
|
32
|
+
comment_issue(
|
|
33
|
+
number, repo=repo,
|
|
34
|
+
body=f"{body}\n\n{language.text(language_code, 'help.community')}",
|
|
35
|
+
)
|
|
@@ -18,10 +18,11 @@ from typing import NamedTuple
|
|
|
18
18
|
|
|
19
19
|
from orbi import milestone as milestone_bookkeeping
|
|
20
20
|
from orbi import scene
|
|
21
|
+
from orbi.blocked_comment import post_blocked_comment
|
|
21
22
|
from orbi.checks import _classify_rollup, _render_check
|
|
22
23
|
from orbi.config import RunnerConfig
|
|
23
24
|
from orbi.delivery_labels import (
|
|
24
|
-
AWAITING_MERGE_LABEL, BLOCKED_LABEL, EPIC_LABEL,
|
|
25
|
+
AWAITING_MERGE_LABEL, BLOCKED_LABEL, EPIC_LABEL,
|
|
25
26
|
FIX_NEEDED_LABEL, IN_PROGRESS_LABEL, MERGED_LABEL, P0_LABEL,
|
|
26
27
|
PR_OPENED_LABEL, READY_LABEL, RELEASE_LABEL,
|
|
27
28
|
)
|
|
@@ -29,7 +30,7 @@ from orbi.delivery_scene import (
|
|
|
29
30
|
EXTERNAL_PR_MARKER, DeliveryScene, _issue_label_set, body_markers, classify,
|
|
30
31
|
)
|
|
31
32
|
from orbi.github import (
|
|
32
|
-
|
|
33
|
+
_verify_epic_complete, close_issue,
|
|
33
34
|
comment_issue, issue_comments, issue_priority, issue_view,
|
|
34
35
|
latest_run_marker, list_issues, milestone_open_issue_count,
|
|
35
36
|
open_blocker_numbers, open_pr_for_branch, pr_comments,
|
|
@@ -40,8 +41,10 @@ from orbi.journal import LOGGER, current_run_id, event, new_run_id, set_run_id
|
|
|
40
41
|
from orbi.pilot_slots import slot_held_deliveries, slot_occupancy
|
|
41
42
|
from orbi import language
|
|
42
43
|
from orbi.progress import run_marker
|
|
43
|
-
from orbi.release import release_target_milestone
|
|
44
|
-
from orbi.repo_config import
|
|
44
|
+
from orbi.release import _epic_audit, epic_audit_posted, release_target_milestone
|
|
45
|
+
from orbi.repo_config import (
|
|
46
|
+
RepoConfigError, RepoPolicy, load_repo_policy, merged_repo_language,
|
|
47
|
+
)
|
|
45
48
|
|
|
46
49
|
|
|
47
50
|
class ResumeHooks(NamedTuple):
|
|
@@ -49,11 +52,14 @@ class ResumeHooks(NamedTuple):
|
|
|
49
52
|
|
|
50
53
|
The physical scene handling of `pick_resumable_delivery` plus the PR
|
|
51
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.
|
|
52
58
|
"""
|
|
53
59
|
resume_scene: Callable[[list[dict]], dict]
|
|
54
|
-
route_external_pr_ticket: Callable[
|
|
55
|
-
block_scene_failure: Callable[
|
|
56
|
-
recover_missing_pr_scene: Callable[
|
|
60
|
+
route_external_pr_ticket: Callable[..., bool]
|
|
61
|
+
block_scene_failure: Callable[..., None]
|
|
62
|
+
recover_missing_pr_scene: Callable[..., dict | None]
|
|
57
63
|
has_recoverable_pr_scene: Callable[[dict, str, Path], bool]
|
|
58
64
|
comment_pr: Callable[..., None]
|
|
59
65
|
|
|
@@ -195,7 +201,7 @@ def is_release(issue: dict) -> bool:
|
|
|
195
201
|
return False
|
|
196
202
|
|
|
197
203
|
|
|
198
|
-
def reconcile_open_epics(repo: str, run_id: str) -> list[str]:
|
|
204
|
+
def reconcile_open_epics(repo: str, run_id: str, language_code: str) -> list[str]:
|
|
199
205
|
"""Sweep open Epics once per tick; ordinary pickup must not depend on it."""
|
|
200
206
|
epics = list_issues(
|
|
201
207
|
repo, state="open", search=f"label:{EPIC_LABEL}",
|
|
@@ -211,9 +217,9 @@ def reconcile_open_epics(repo: str, run_id: str) -> list[str]:
|
|
|
211
217
|
event("epic_kept_open", issue=number, repo=repo, reason=reason)
|
|
212
218
|
evidence.append(f"Epic #{number} kept open: {reason}")
|
|
213
219
|
continue
|
|
214
|
-
audit = _epic_audit(child_evidence)
|
|
220
|
+
audit = _epic_audit(child_evidence, None, language_code)
|
|
215
221
|
comments = issue_comments(int(number), repo=repo)
|
|
216
|
-
if not
|
|
222
|
+
if not epic_audit_posted(comments, child_evidence):
|
|
217
223
|
comment_issue(int(number), repo=repo,
|
|
218
224
|
body=f"<!-- orbi:run={run_id} -->\n{audit}\nrun_id={run_id}")
|
|
219
225
|
close_issue(int(number), repo=repo)
|
|
@@ -243,7 +249,7 @@ def orphan_pr_branch_issue(head_ref: object) -> int | None:
|
|
|
243
249
|
|
|
244
250
|
|
|
245
251
|
def reconcile_orphan_prs(
|
|
246
|
-
repo: str, run_id: str, *, hooks: ResumeHooks,
|
|
252
|
+
repo: str, run_id: str, *, hooks: ResumeHooks, language_code: str,
|
|
247
253
|
) -> list[str]:
|
|
248
254
|
"""Report open delivery PRs whose source Issue is closed.
|
|
249
255
|
|
|
@@ -287,11 +293,10 @@ def reconcile_orphan_prs(
|
|
|
287
293
|
pr_number, repo=repo,
|
|
288
294
|
body=(
|
|
289
295
|
f"{ORPHAN_PR_MARK}\n{run_marker(run_id)}\n"
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
f"run_id={run_id}"
|
|
296
|
+
+ language.text(
|
|
297
|
+
language_code, "claim.orphan_pr",
|
|
298
|
+
issue_number=issue_number, run_id=run_id,
|
|
299
|
+
)
|
|
295
300
|
),
|
|
296
301
|
)
|
|
297
302
|
event(
|
|
@@ -638,9 +643,9 @@ def pick_next_issue(
|
|
|
638
643
|
def pick_resumable_delivery(
|
|
639
644
|
repo: str, slot_dir: Path, max_concurrency: int,
|
|
640
645
|
repo_dir: Path | None = None, *, hooks: ResumeHooks,
|
|
641
|
-
language_code: str
|
|
646
|
+
language_code: str,
|
|
642
647
|
) -> tuple[dict, dict] | None:
|
|
643
|
-
"""Return the
|
|
648
|
+
"""Return the oldest FREE opened-PR delivery and its resume scene.
|
|
644
649
|
|
|
645
650
|
All opened-PR states are scanned: `ai-fix-needed`
|
|
646
651
|
(awaiting the next review session after a finding or a base
|
|
@@ -687,12 +692,13 @@ def pick_resumable_delivery(
|
|
|
687
692
|
excluded: `main` took it before the claim scan and holds it for the
|
|
688
693
|
whole delivery.
|
|
689
694
|
|
|
690
|
-
The query page is `max_concurrency + 1` candidates
|
|
695
|
+
The query page is `max_concurrency + 1` candidates, ordered OLDEST
|
|
696
|
+
Issue first (`sort:created-asc`, Issue #1734). At most
|
|
691
697
|
`max_concurrency` deliveries can be held by live co-runners, so a
|
|
692
698
|
free candidate is inside the page when one exists; one whose OPEN PR
|
|
693
|
-
still waits for CI (Issue #1473) is skipped too. The scan reviews
|
|
694
|
-
|
|
695
|
-
read — an in-flight delivery is never touched).
|
|
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).
|
|
696
702
|
"""
|
|
697
703
|
held = slot_held_deliveries(slot_dir, max_concurrency)
|
|
698
704
|
# `label:a,b` is GitHub's OR within one label qualifier
|
|
@@ -712,7 +718,7 @@ def pick_resumable_delivery(
|
|
|
712
718
|
search=(
|
|
713
719
|
f"label:{FIX_NEEDED_LABEL},{PR_OPENED_LABEL},"
|
|
714
720
|
f"{AWAITING_MERGE_LABEL} "
|
|
715
|
-
f"-label:{BLOCKED_LABEL} -label:{MERGED_LABEL}"
|
|
721
|
+
f"-label:{BLOCKED_LABEL} -label:{MERGED_LABEL} sort:created-asc"
|
|
716
722
|
),
|
|
717
723
|
json_fields="number,title,state,url,labels,body",
|
|
718
724
|
limit=max_concurrency + 1,
|
|
@@ -734,15 +740,15 @@ def pick_resumable_delivery(
|
|
|
734
740
|
# probe the #726 external route first; otherwise this is the
|
|
735
741
|
# ONLY trigger of `block_scene_failure` — a present-but-broken
|
|
736
742
|
# scene is a writer bug or tampering and needs a human.
|
|
737
|
-
if hooks.route_external_pr_ticket(issue, repo):
|
|
743
|
+
if hooks.route_external_pr_ticket(issue, repo, language_code=language_code):
|
|
738
744
|
continue
|
|
739
|
-
hooks.block_scene_failure(issue, exc, repo, comments)
|
|
745
|
+
hooks.block_scene_failure(issue, exc, repo, comments, language_code=language_code)
|
|
740
746
|
continue
|
|
741
747
|
except scene.SceneMissingError as exc:
|
|
742
748
|
# No trusted comment carries a scene at all — a distinct branch
|
|
743
749
|
# from corruption. The original #726 incident was
|
|
744
750
|
# exactly this shape, so the external route is probed first.
|
|
745
|
-
if hooks.route_external_pr_ticket(issue, repo):
|
|
751
|
+
if hooks.route_external_pr_ticket(issue, repo, language_code=language_code):
|
|
746
752
|
continue
|
|
747
753
|
# Issue #1216: `awaiting_merge` can deliberately remove the
|
|
748
754
|
# queue label before the delivery returns to `ai-fix-needed`.
|
|
@@ -774,7 +780,7 @@ def pick_resumable_delivery(
|
|
|
774
780
|
# scene comment write. Retry it from the durable local run
|
|
775
781
|
# state; keep ai-pr-opened for another tick if the retry fails.
|
|
776
782
|
recovered = (
|
|
777
|
-
hooks.recover_missing_pr_scene(issue, repo, repo_dir)
|
|
783
|
+
hooks.recover_missing_pr_scene(issue, repo, repo_dir, language_code=language_code)
|
|
778
784
|
if repo_dir is not None else None
|
|
779
785
|
)
|
|
780
786
|
if recovered is not None:
|
|
@@ -791,16 +797,14 @@ def pick_resumable_delivery(
|
|
|
791
797
|
LOGGER.error("issue=%s resume scene is missing: %s", number, exc)
|
|
792
798
|
marker = latest_run_marker(comments)
|
|
793
799
|
try:
|
|
794
|
-
|
|
795
|
-
number, repo=repo, event=EVENT_BLOCKED,
|
|
796
|
-
current_labels={FIX_NEEDED_LABEL},
|
|
797
|
-
)
|
|
798
|
-
comment_issue(
|
|
800
|
+
post_blocked_comment(
|
|
799
801
|
number, repo=repo,
|
|
800
802
|
body=(f"{marker}\n" if marker else "")
|
|
801
803
|
+ language.text(
|
|
802
804
|
language_code, "claim.scene_missing", error=exc,
|
|
803
805
|
),
|
|
806
|
+
current_labels={FIX_NEEDED_LABEL},
|
|
807
|
+
language_code=language_code,
|
|
804
808
|
)
|
|
805
809
|
except Exception:
|
|
806
810
|
LOGGER.exception("issue=%s failure reporting failed", number)
|
|
@@ -869,10 +873,12 @@ def pick_next_delivery(
|
|
|
869
873
|
tick_run_id = new_run_id()
|
|
870
874
|
set_run_id(tick_run_id)
|
|
871
875
|
for repo in repos:
|
|
876
|
+
repo_language = (language.DEFAULT_LANGUAGE if config is None
|
|
877
|
+
else merged_repo_language(config, repo))
|
|
872
878
|
# Epic reconciliation is a per-tick bypass: a broken GitHub query or
|
|
873
879
|
# mutation must never prevent the ordinary delivery scans.
|
|
874
880
|
try:
|
|
875
|
-
reconcile_open_epics(repo, tick_run_id)
|
|
881
|
+
reconcile_open_epics(repo, tick_run_id, repo_language)
|
|
876
882
|
except Exception:
|
|
877
883
|
LOGGER.exception("epic_reconcile_failed repo=%s", repo)
|
|
878
884
|
# Milestone reconciliation intentionally follows the Epic sweep so a
|
|
@@ -901,18 +907,15 @@ def pick_next_delivery(
|
|
|
901
907
|
# pattern: a broken GitHub query must never prevent the ordinary
|
|
902
908
|
# delivery scans.
|
|
903
909
|
try:
|
|
904
|
-
reconcile_orphan_prs(repo, tick_run_id, hooks=hooks
|
|
910
|
+
reconcile_orphan_prs(repo, tick_run_id, hooks=hooks,
|
|
911
|
+
language_code=repo_language)
|
|
905
912
|
except Exception:
|
|
906
913
|
LOGGER.exception("orphan_pr_reconcile_failed repo=%s", repo)
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
selected = pick_resumable_delivery(
|
|
913
|
-
repo, slot_dir, max_concurrency, config.repo_dir,
|
|
914
|
-
hooks=hooks, language_code=language.of(config),
|
|
915
|
-
)
|
|
914
|
+
selected = pick_resumable_delivery(
|
|
915
|
+
repo, slot_dir, max_concurrency,
|
|
916
|
+
repo_dir=config.repo_dir if config is not None else None,
|
|
917
|
+
hooks=hooks, language_code=repo_language,
|
|
918
|
+
)
|
|
916
919
|
if selected is not None:
|
|
917
920
|
issue, scene = selected
|
|
918
921
|
return repo, issue, scene
|
|
@@ -56,7 +56,7 @@ MISSING: dict[str, str] = {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
|
|
59
|
-
def missing_text(item: str, language_code: str
|
|
59
|
+
def missing_text(item: str, language_code: str,
|
|
60
60
|
) -> str:
|
|
61
61
|
"""The human sentence for one failed check, in one language."""
|
|
62
62
|
return language.text(language_code, MISSING[item])
|
|
@@ -154,7 +154,7 @@ def parse_verdict(output: str) -> ClarifyVerdict | None:
|
|
|
154
154
|
|
|
155
155
|
def render_comment(issue: dict, verdict: ClarifyVerdict, run_id: str, *,
|
|
156
156
|
ready_label: str = READY_LABEL,
|
|
157
|
-
language_code: str
|
|
157
|
+
language_code: str) -> str:
|
|
158
158
|
"""Render the ONE comment that names the missing pieces.
|
|
159
159
|
|
|
160
160
|
The first line mentions the Issue author (`@<login>`, #1336) so the
|
|
@@ -163,7 +163,8 @@ def render_comment(issue: dict, verdict: ClarifyVerdict, run_id: str, *,
|
|
|
163
163
|
before. `ready_label` is the repository's claim label (default
|
|
164
164
|
`ai-ready`, #527): the repair instruction names the label the claim
|
|
165
165
|
scan really reads, never a hardcoded default a custom-label
|
|
166
|
-
repository does not use.
|
|
166
|
+
repository does not use. The comment ends with the community help
|
|
167
|
+
line (Issue #1684): this stop asks the author to step in.
|
|
167
168
|
"""
|
|
168
169
|
def message(key: str, **fields: object) -> str:
|
|
169
170
|
return language.text(language_code, key, **fields)
|
|
@@ -185,7 +186,8 @@ def render_comment(issue: dict, verdict: ClarifyVerdict, run_id: str, *,
|
|
|
185
186
|
f"{message('clarify.repair_acceptance')}\n"
|
|
186
187
|
"```\n\n"
|
|
187
188
|
f"{message('clarify.followup', needs_detail_label=NEEDS_DETAIL_LABEL)}"
|
|
188
|
-
f"\n\nrun_id={run_id}
|
|
189
|
+
f"\n\nrun_id={run_id}"
|
|
190
|
+
f"\n\n{message('help.community')}\n"
|
|
189
191
|
)
|
|
190
192
|
|
|
191
193
|
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
"""The runner-owned run comments
|
|
2
|
-
(Issue #1660)
|
|
1
|
+
"""The runner-owned run comments and the PR body's footer: the
|
|
2
|
+
started-Pi and opened-PR scenes (Issue #1660) and the attribution footer
|
|
3
|
+
(Issue #1729).
|
|
3
4
|
|
|
4
5
|
Both comments double as the run's machine-readable anchors: the started
|
|
5
6
|
comment carries the `started` kind marker and the opened-PR comment
|
|
6
|
-
carries the `orbi:scene:v1` block the next tick resumes from.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
carries the `orbi:scene:v1` block the next tick resumes from. Their
|
|
8
|
+
human text — the headline, the field labels and the stored display
|
|
9
|
+
values — follows the run's bound message language (the explicit
|
|
10
|
+
`language_code`); the hidden markers, the public machine rows
|
|
11
|
+
(`run_id=`, `repo_config:`) and the scene block stay exactly as they
|
|
12
|
+
were.
|
|
10
13
|
|
|
11
14
|
Extracted from `runner.py` unchanged apart from the language, so the
|
|
12
15
|
frozen runner module does not grow (Issue #1229) and a caller keeps
|
|
@@ -19,19 +22,21 @@ from orbi import language, scene
|
|
|
19
22
|
from orbi.comment_kind import COMMENT_KIND_STARTED
|
|
20
23
|
from orbi.delivery_scene import RunContext
|
|
21
24
|
from orbi.journal import validate_run_id
|
|
22
|
-
from orbi.progress import
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
from orbi.progress import (
|
|
26
|
+
STARTED_HEADER,
|
|
27
|
+
_run_info_fields,
|
|
28
|
+
field_block,
|
|
29
|
+
)
|
|
30
|
+
from orbi.progress_display import (
|
|
31
|
+
field_label,
|
|
32
|
+
localized_fields,
|
|
33
|
+
vocabulary,
|
|
34
|
+
)
|
|
30
35
|
|
|
31
36
|
|
|
32
37
|
def started_pi_comment_body(ctx: RunContext, run_info: str,
|
|
33
38
|
extra_fields: dict | None = None, *,
|
|
34
|
-
language_code: str
|
|
39
|
+
language_code: str) -> str:
|
|
35
40
|
"""The start comment doubles as the recoverable run scene.
|
|
36
41
|
|
|
37
42
|
`extra_fields` carries the repo-config audit fields;
|
|
@@ -42,28 +47,44 @@ def started_pi_comment_body(ctx: RunContext, run_info: str,
|
|
|
42
47
|
"""
|
|
43
48
|
fields = _run_info_fields(run_info)
|
|
44
49
|
fields["branch"] = str(ctx.branch)
|
|
45
|
-
fields["worktree"] = str(ctx.worktree)
|
|
46
50
|
if extra_fields:
|
|
47
51
|
fields.update(extra_fields)
|
|
48
52
|
info = _run_info_fields(run_info)
|
|
49
|
-
|
|
53
|
+
rendered_info = {
|
|
54
|
+
key: (vocabulary(language_code, "priority", info[key])
|
|
55
|
+
if key == "priority" else info[key])
|
|
56
|
+
for key in ("run_id", "priority")
|
|
57
|
+
if key in info
|
|
58
|
+
}
|
|
50
59
|
headline = language.text(
|
|
51
|
-
|
|
60
|
+
language_code, "delivery.started_headline",
|
|
52
61
|
) + " " + " ".join(
|
|
53
|
-
f"{key}={
|
|
54
|
-
if key in info
|
|
62
|
+
f"{key}={value}" for key, value in rendered_info.items()
|
|
55
63
|
)
|
|
64
|
+
detail_keys = {"base_sha", "repo_config", "branch", "session"}
|
|
56
65
|
return field_block(
|
|
57
|
-
ctx.run_id, headline, fields,
|
|
58
|
-
detail_keys={
|
|
66
|
+
ctx.run_id, headline, localized_fields(language_code, fields),
|
|
67
|
+
detail_keys={field_label(language_code, key) for key in detail_keys},
|
|
59
68
|
kind=COMMENT_KIND_STARTED,
|
|
60
|
-
language_code=
|
|
69
|
+
language_code=language_code,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def pr_attribution_footer(issue: int, attribution_link: str, *,
|
|
74
|
+
language_code: str) -> str:
|
|
75
|
+
"""The PR body's attribution footer in the bound message language.
|
|
76
|
+
|
|
77
|
+
The link URL and `#<issue>` are the machine facts and stay verbatim.
|
|
78
|
+
"""
|
|
79
|
+
return language.text(
|
|
80
|
+
language_code, "runner.pr_attribution",
|
|
81
|
+
link=attribution_link, issue=issue,
|
|
61
82
|
)
|
|
62
83
|
|
|
63
84
|
|
|
64
85
|
def opened_pr_comment_body(run_id: str, run_info: str, pr_url: str,
|
|
65
86
|
external: bool = False, *,
|
|
66
|
-
language_code: str
|
|
87
|
+
language_code: str,
|
|
67
88
|
) -> str:
|
|
68
89
|
"""The PR-opened comment records the recoverable run scene.
|
|
69
90
|
|
|
@@ -95,9 +116,10 @@ def opened_pr_comment_body(run_id: str, run_info: str, pr_url: str,
|
|
|
95
116
|
pr_url=pr_url,
|
|
96
117
|
external="true" if external else "",
|
|
97
118
|
)
|
|
119
|
+
detail_keys = {"base_sha", "repo_config", "branch", "session"}
|
|
98
120
|
body = field_block(
|
|
99
|
-
run_id, headline, fields,
|
|
100
|
-
detail_keys={
|
|
121
|
+
run_id, headline, localized_fields(language_code, fields),
|
|
122
|
+
detail_keys={field_label(language_code, key) for key in detail_keys},
|
|
101
123
|
language_code=language_code,
|
|
102
124
|
)
|
|
103
125
|
lines = body.splitlines()
|
|
@@ -204,10 +204,6 @@ class RunContext:
|
|
|
204
204
|
worktree: Path
|
|
205
205
|
source_repo: str
|
|
206
206
|
pr: str | None = None
|
|
207
|
-
# The run's bound message language (Issue #1660): `zh` or `en`, bound
|
|
208
|
-
# from the effective config (host + repository policy) before the
|
|
209
|
-
# first comment is written. Runtime state only -- never persisted.
|
|
210
|
-
language: str = "en"
|
|
211
207
|
|
|
212
208
|
|
|
213
209
|
@dataclasses.dataclass(frozen=True)
|
|
@@ -327,7 +327,7 @@ def workflow_permission_refused(detail: str) -> bool:
|
|
|
327
327
|
|
|
328
328
|
|
|
329
329
|
def workflow_permission_cause(
|
|
330
|
-
language_code: str
|
|
330
|
+
language_code: str,
|
|
331
331
|
) -> str:
|
|
332
332
|
"""The one-sentence cause of an Actions-workflow permission refusal."""
|
|
333
333
|
return language.text(language_code, _WORKFLOW_PERMISSION_CAUSE_KEY)
|
|
@@ -335,7 +335,7 @@ def workflow_permission_cause(
|
|
|
335
335
|
|
|
336
336
|
def workflow_permission_action(
|
|
337
337
|
pr_url: str | None = None, *,
|
|
338
|
-
language_code: str
|
|
338
|
+
language_code: str,
|
|
339
339
|
) -> str:
|
|
340
340
|
"""The concrete human repair path for the workflow refusal.
|
|
341
341
|
|
|
@@ -358,7 +358,7 @@ def workflow_permission_action(
|
|
|
358
358
|
|
|
359
359
|
|
|
360
360
|
def terminal_action(record: Failure,
|
|
361
|
-
language_code: str
|
|
361
|
+
language_code: str) -> str:
|
|
362
362
|
"""The reader action for the implement-phase terminal handler.
|
|
363
363
|
|
|
364
364
|
An unrecognized failure says the cause could not be determined
|
|
@@ -410,7 +410,7 @@ def _workflow_permission_line(text: str) -> str | None:
|
|
|
410
410
|
|
|
411
411
|
|
|
412
412
|
def _failure_summary(reason: str,
|
|
413
|
-
language_code: str
|
|
413
|
+
language_code: str) -> str:
|
|
414
414
|
"""Turn a runner exception description into a short reader summary."""
|
|
415
415
|
command_failed = language.text(language_code, "failure.command_failed")
|
|
416
416
|
stderr_match = re.search(r"\s+stderr=(.*)$", reason,
|
|
@@ -452,7 +452,7 @@ def _failure_comment_body(*, outcome: str, action: str, reason: str,
|
|
|
452
452
|
diagnosis: str, scene: str, evidence: str,
|
|
453
453
|
pr_url: str | None, issue: str, run_id: str,
|
|
454
454
|
failure_record: Failure | None = None,
|
|
455
|
-
language_code: str
|
|
455
|
+
language_code: str,
|
|
456
456
|
) -> str:
|
|
457
457
|
"""Build the reader-facing hierarchy shared by classified failures.
|
|
458
458
|
|
|
@@ -529,4 +529,7 @@ def _failure_comment_body(*, outcome: str, action: str, reason: str,
|
|
|
529
529
|
if evidence:
|
|
530
530
|
details.append(evidence.lstrip())
|
|
531
531
|
details.append("</details>")
|
|
532
|
+
# The blocked diagnostic's community help line is NOT added here: the
|
|
533
|
+
# ONE blocked exit appends it to every blocked comment (Issue #1730),
|
|
534
|
+
# so this renderer and that exit can never both add it.
|
|
532
535
|
return "\n\n".join(parts + ["\n".join(details)])
|