orbi-cli 0.7.1__tar.gz → 0.7.2__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.2}/PKG-INFO +1 -1
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/pyproject.toml +1 -1
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/__init__.py +1 -1
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/claim.py +34 -31
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/clarify.py +6 -4
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/delivery_comment.py +7 -16
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/delivery_scene.py +0 -4
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/failure.py +11 -6
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/failure_report.py +46 -45
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/github.py +12 -21
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/human_review.py +9 -3
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/language.py +311 -27
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/merge_handoff.py +2 -2
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/milestone.py +57 -31
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/milestone_command.py +8 -5
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/notify.py +177 -40
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/pi_session.py +4 -2
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/progress.py +20 -15
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/progress_outcome.py +12 -1
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/release.py +12 -12
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/release_comments.py +47 -2
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/repo_config.py +18 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/review_merge.py +69 -67
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/review_rounds.py +2 -2
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/runner.py +75 -66
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/runner_health.py +17 -10
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/suggest.py +59 -15
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/ticket_command.py +61 -19
- {orbi_cli-0.7.1 → orbi_cli-0.7.2/src/orbi_cli.egg-info}/PKG-INFO +1 -1
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_bootstrap_runner.py +633 -117
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_clarify.py +3 -3
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_comment_identity.py +5 -5
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_comment_readers_contract.py +6 -5
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_deliver_pr_fakes.py +3 -3
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_delivery_branch_delete.py +5 -5
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_delivery_scene.py +64 -14
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_failure.py +21 -21
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_failure_report.py +362 -26
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_fix_needed_loop.py +28 -9
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_git_merge_smoke.py +6 -6
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_human_review.py +9 -8
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_message_language.py +271 -11
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_milestone_command.py +203 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_notify.py +266 -32
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_pilot_setup.py +27 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_progress.py +62 -30
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_progress_wiring.py +75 -6
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_repo_config.py +32 -11
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_resume_continue_e2e.py +6 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_resume_e2e.py +142 -4
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_resume_pr.py +146 -51
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_review_merge.py +33 -33
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_runner_config.py +74 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_runner_health.py +127 -2
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_steering_review.py +26 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_suggest.py +283 -20
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_ticket_command.py +3 -1
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/LICENSE +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/README.md +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/docs/licenses/sustainable-use-license.md +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/setup.cfg +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/branch_reclaim.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/checks.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/cli.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/cli_source.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/comment_kind.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/config.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/delivery_labels.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/engine_source.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/example_config.toml +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/git_identity.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/git_transport.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/gitops.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/journal.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/launchd_deploy.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/merge_gate.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/pi_activity.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/pi_command.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/pi_process.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/pi_recovery.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/pilot_setup.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/pilot_slots.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/progress_summary.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/release_git.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/release_notes.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/run_state.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/scene.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/scheduler.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/scheduler_units.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/session_command.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/source_pool.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/status_observe.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi/systemd_deploy.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi_cli.egg-info/SOURCES.txt +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi_cli.egg-info/dependency_links.txt +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi_cli.egg-info/entry_points.txt +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/src/orbi_cli.egg-info/top_level.txt +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_agents_md.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_apply_label_patch.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_branch_reclaim.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_ci_failure_issue_workflow.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_ci_failure_triage.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_ci_macos_workflow.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_ci_workflow.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_claude_plugin.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_cli_install.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_cli_milestone.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_cli_packaging.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_cli_source.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_concurrency_e2e.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_constitution.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_coverage_gate.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_delivery_labels.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_docker_docs.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_docker_entrypoint.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_docker_image_workflow.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_docs_i18n.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_docs_issue_shapes.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_docs_labels.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_docs_mermaid.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_docs_releases.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_docs_single_source.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_docs_site.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_engine_source.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_gh_read_retry.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_git_base_smoke.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_git_network_retry.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_git_transport.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_github.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_github_fakes.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_gitops.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_gitops_fakes.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_import_graph.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_install_sh.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_issue_1367.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_issue_461.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_issue_template_config.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_journal.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_journal_events.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_launchd_deploy.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_license.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_ops_e2e.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_orbi.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_orbi_dashboard.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_orbi_exporter.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_patch_ratchet.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_pi_activity.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_pi_command.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_pi_extensions.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_pi_provider_templates.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_pi_recovery.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_pi_session.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_pilot_slots.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_pr_issue_link_workflow.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_prerequisite_check.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_prompt_contract.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_public_contracts.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_publish_workflow.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_release_docs_workflow.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_release_ticket_templates.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_run_artifacts.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_run_id_e2e.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_runner_source_freshness.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_scene.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_scheduler.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_size_ratchet.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_src_layout.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_status_observe.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_suite_hygiene.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_systemd_deploy.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_systemd_timer.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/tests/test_user_acceptance_contract.py +0 -0
- {orbi_cli-0.7.1 → orbi_cli-0.7.2}/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.2
|
|
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.2"
|
|
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"
|
|
@@ -29,7 +29,7 @@ from orbi.delivery_scene import (
|
|
|
29
29
|
EXTERNAL_PR_MARKER, DeliveryScene, _issue_label_set, body_markers, classify,
|
|
30
30
|
)
|
|
31
31
|
from orbi.github import (
|
|
32
|
-
|
|
32
|
+
_verify_epic_complete, apply_label_patch, close_issue,
|
|
33
33
|
comment_issue, issue_comments, issue_priority, issue_view,
|
|
34
34
|
latest_run_marker, list_issues, milestone_open_issue_count,
|
|
35
35
|
open_blocker_numbers, open_pr_for_branch, pr_comments,
|
|
@@ -40,8 +40,10 @@ from orbi.journal import LOGGER, current_run_id, event, new_run_id, set_run_id
|
|
|
40
40
|
from orbi.pilot_slots import slot_held_deliveries, slot_occupancy
|
|
41
41
|
from orbi import language
|
|
42
42
|
from orbi.progress import run_marker
|
|
43
|
-
from orbi.release import release_target_milestone
|
|
44
|
-
from orbi.repo_config import
|
|
43
|
+
from orbi.release import _epic_audit, epic_audit_posted, release_target_milestone
|
|
44
|
+
from orbi.repo_config import (
|
|
45
|
+
RepoConfigError, RepoPolicy, load_repo_policy, merged_repo_language,
|
|
46
|
+
)
|
|
45
47
|
|
|
46
48
|
|
|
47
49
|
class ResumeHooks(NamedTuple):
|
|
@@ -49,11 +51,14 @@ class ResumeHooks(NamedTuple):
|
|
|
49
51
|
|
|
50
52
|
The physical scene handling of `pick_resumable_delivery` plus the PR
|
|
51
53
|
comment writer `reconcile_orphan_prs` reports its orphan through.
|
|
54
|
+
Every hook that writes a comment takes the claim's already-resolved
|
|
55
|
+
per-repository `language_code` (Issue #1687): the claim knows the
|
|
56
|
+
merged repository config before the first comment is written.
|
|
52
57
|
"""
|
|
53
58
|
resume_scene: Callable[[list[dict]], dict]
|
|
54
|
-
route_external_pr_ticket: Callable[
|
|
55
|
-
block_scene_failure: Callable[
|
|
56
|
-
recover_missing_pr_scene: Callable[
|
|
59
|
+
route_external_pr_ticket: Callable[..., bool]
|
|
60
|
+
block_scene_failure: Callable[..., None]
|
|
61
|
+
recover_missing_pr_scene: Callable[..., dict | None]
|
|
57
62
|
has_recoverable_pr_scene: Callable[[dict, str, Path], bool]
|
|
58
63
|
comment_pr: Callable[..., None]
|
|
59
64
|
|
|
@@ -195,7 +200,7 @@ def is_release(issue: dict) -> bool:
|
|
|
195
200
|
return False
|
|
196
201
|
|
|
197
202
|
|
|
198
|
-
def reconcile_open_epics(repo: str, run_id: str) -> list[str]:
|
|
203
|
+
def reconcile_open_epics(repo: str, run_id: str, language_code: str) -> list[str]:
|
|
199
204
|
"""Sweep open Epics once per tick; ordinary pickup must not depend on it."""
|
|
200
205
|
epics = list_issues(
|
|
201
206
|
repo, state="open", search=f"label:{EPIC_LABEL}",
|
|
@@ -211,9 +216,9 @@ def reconcile_open_epics(repo: str, run_id: str) -> list[str]:
|
|
|
211
216
|
event("epic_kept_open", issue=number, repo=repo, reason=reason)
|
|
212
217
|
evidence.append(f"Epic #{number} kept open: {reason}")
|
|
213
218
|
continue
|
|
214
|
-
audit = _epic_audit(child_evidence)
|
|
219
|
+
audit = _epic_audit(child_evidence, None, language_code)
|
|
215
220
|
comments = issue_comments(int(number), repo=repo)
|
|
216
|
-
if not
|
|
221
|
+
if not epic_audit_posted(comments, child_evidence):
|
|
217
222
|
comment_issue(int(number), repo=repo,
|
|
218
223
|
body=f"<!-- orbi:run={run_id} -->\n{audit}\nrun_id={run_id}")
|
|
219
224
|
close_issue(int(number), repo=repo)
|
|
@@ -243,7 +248,7 @@ def orphan_pr_branch_issue(head_ref: object) -> int | None:
|
|
|
243
248
|
|
|
244
249
|
|
|
245
250
|
def reconcile_orphan_prs(
|
|
246
|
-
repo: str, run_id: str, *, hooks: ResumeHooks,
|
|
251
|
+
repo: str, run_id: str, *, hooks: ResumeHooks, language_code: str,
|
|
247
252
|
) -> list[str]:
|
|
248
253
|
"""Report open delivery PRs whose source Issue is closed.
|
|
249
254
|
|
|
@@ -287,11 +292,10 @@ def reconcile_orphan_prs(
|
|
|
287
292
|
pr_number, repo=repo,
|
|
288
293
|
body=(
|
|
289
294
|
f"{ORPHAN_PR_MARK}\n{run_marker(run_id)}\n"
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
f"run_id={run_id}"
|
|
295
|
+
+ language.text(
|
|
296
|
+
language_code, "claim.orphan_pr",
|
|
297
|
+
issue_number=issue_number, run_id=run_id,
|
|
298
|
+
)
|
|
295
299
|
),
|
|
296
300
|
)
|
|
297
301
|
event(
|
|
@@ -638,7 +642,7 @@ def pick_next_issue(
|
|
|
638
642
|
def pick_resumable_delivery(
|
|
639
643
|
repo: str, slot_dir: Path, max_concurrency: int,
|
|
640
644
|
repo_dir: Path | None = None, *, hooks: ResumeHooks,
|
|
641
|
-
language_code: str
|
|
645
|
+
language_code: str,
|
|
642
646
|
) -> tuple[dict, dict] | None:
|
|
643
647
|
"""Return the newest FREE opened-PR delivery and its resume scene.
|
|
644
648
|
|
|
@@ -734,15 +738,15 @@ def pick_resumable_delivery(
|
|
|
734
738
|
# probe the #726 external route first; otherwise this is the
|
|
735
739
|
# ONLY trigger of `block_scene_failure` — a present-but-broken
|
|
736
740
|
# scene is a writer bug or tampering and needs a human.
|
|
737
|
-
if hooks.route_external_pr_ticket(issue, repo):
|
|
741
|
+
if hooks.route_external_pr_ticket(issue, repo, language_code=language_code):
|
|
738
742
|
continue
|
|
739
|
-
hooks.block_scene_failure(issue, exc, repo, comments)
|
|
743
|
+
hooks.block_scene_failure(issue, exc, repo, comments, language_code=language_code)
|
|
740
744
|
continue
|
|
741
745
|
except scene.SceneMissingError as exc:
|
|
742
746
|
# No trusted comment carries a scene at all — a distinct branch
|
|
743
747
|
# from corruption. The original #726 incident was
|
|
744
748
|
# exactly this shape, so the external route is probed first.
|
|
745
|
-
if hooks.route_external_pr_ticket(issue, repo):
|
|
749
|
+
if hooks.route_external_pr_ticket(issue, repo, language_code=language_code):
|
|
746
750
|
continue
|
|
747
751
|
# Issue #1216: `awaiting_merge` can deliberately remove the
|
|
748
752
|
# queue label before the delivery returns to `ai-fix-needed`.
|
|
@@ -774,7 +778,7 @@ def pick_resumable_delivery(
|
|
|
774
778
|
# scene comment write. Retry it from the durable local run
|
|
775
779
|
# state; keep ai-pr-opened for another tick if the retry fails.
|
|
776
780
|
recovered = (
|
|
777
|
-
hooks.recover_missing_pr_scene(issue, repo, repo_dir)
|
|
781
|
+
hooks.recover_missing_pr_scene(issue, repo, repo_dir, language_code=language_code)
|
|
778
782
|
if repo_dir is not None else None
|
|
779
783
|
)
|
|
780
784
|
if recovered is not None:
|
|
@@ -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
|
|
|
@@ -5,7 +5,7 @@ Both comments double as the run's machine-readable anchors: the started
|
|
|
5
5
|
comment carries the `started` kind marker and the opened-PR comment
|
|
6
6
|
carries the `orbi:scene:v1` block the next tick resumes from. Only their
|
|
7
7
|
HEADLINE is human prose and follows the run's bound message language
|
|
8
|
-
(
|
|
8
|
+
(the explicit `language_code`); the hidden
|
|
9
9
|
markers, the field lines and the scene block stay exactly as they were.
|
|
10
10
|
|
|
11
11
|
Extracted from `runner.py` unchanged apart from the language, so the
|
|
@@ -22,16 +22,9 @@ from orbi.journal import validate_run_id
|
|
|
22
22
|
from orbi.progress import STARTED_HEADER, _run_info_fields, field_block
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
def _language_of(ctx: RunContext, language_code: str | None) -> str:
|
|
26
|
-
"""The explicit language, else the run context's bound language."""
|
|
27
|
-
if language_code is not None:
|
|
28
|
-
return language_code
|
|
29
|
-
return getattr(ctx, "language", language.DEFAULT_LANGUAGE)
|
|
30
|
-
|
|
31
|
-
|
|
32
25
|
def started_pi_comment_body(ctx: RunContext, run_info: str,
|
|
33
26
|
extra_fields: dict | None = None, *,
|
|
34
|
-
language_code: str
|
|
27
|
+
language_code: str) -> str:
|
|
35
28
|
"""The start comment doubles as the recoverable run scene.
|
|
36
29
|
|
|
37
30
|
`extra_fields` carries the repo-config audit fields;
|
|
@@ -42,28 +35,26 @@ def started_pi_comment_body(ctx: RunContext, run_info: str,
|
|
|
42
35
|
"""
|
|
43
36
|
fields = _run_info_fields(run_info)
|
|
44
37
|
fields["branch"] = str(ctx.branch)
|
|
45
|
-
fields["worktree"] = str(ctx.worktree)
|
|
46
38
|
if extra_fields:
|
|
47
39
|
fields.update(extra_fields)
|
|
48
40
|
info = _run_info_fields(run_info)
|
|
49
|
-
bound = _language_of(ctx, language_code)
|
|
50
41
|
headline = language.text(
|
|
51
|
-
|
|
42
|
+
language_code, "delivery.started_headline",
|
|
52
43
|
) + " " + " ".join(
|
|
53
44
|
f"{key}={info[key]}" for key in ("run_id", "priority")
|
|
54
45
|
if key in info
|
|
55
46
|
)
|
|
56
47
|
return field_block(
|
|
57
48
|
ctx.run_id, headline, fields,
|
|
58
|
-
detail_keys={"base_sha", "repo_config", "branch", "
|
|
49
|
+
detail_keys={"base_sha", "repo_config", "branch", "session"},
|
|
59
50
|
kind=COMMENT_KIND_STARTED,
|
|
60
|
-
language_code=
|
|
51
|
+
language_code=language_code,
|
|
61
52
|
)
|
|
62
53
|
|
|
63
54
|
|
|
64
55
|
def opened_pr_comment_body(run_id: str, run_info: str, pr_url: str,
|
|
65
56
|
external: bool = False, *,
|
|
66
|
-
language_code: str
|
|
57
|
+
language_code: str,
|
|
67
58
|
) -> str:
|
|
68
59
|
"""The PR-opened comment records the recoverable run scene.
|
|
69
60
|
|
|
@@ -97,7 +88,7 @@ def opened_pr_comment_body(run_id: str, run_info: str, pr_url: str,
|
|
|
97
88
|
)
|
|
98
89
|
body = field_block(
|
|
99
90
|
run_id, headline, fields,
|
|
100
|
-
detail_keys={"base_sha", "repo_config", "branch", "
|
|
91
|
+
detail_keys={"base_sha", "repo_config", "branch", "session"},
|
|
101
92
|
language_code=language_code,
|
|
102
93
|
)
|
|
103
94
|
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,9 @@ 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
|
-
|
|
532
|
+
body = "\n\n".join(parts + ["\n".join(details)])
|
|
533
|
+
if retrying:
|
|
534
|
+
return body
|
|
535
|
+
# The blocked diagnostic is a problem a person must resolve, so its
|
|
536
|
+
# last paragraph is the community help line (Issue #1684).
|
|
537
|
+
return f"{body}\n\n{message('help.community')}"
|
|
@@ -270,7 +270,7 @@ def _session_summary(session_file: Path,
|
|
|
270
270
|
|
|
271
271
|
def _failure_scene(snapshot: dict, *, run_id: str, issue: str, role: str,
|
|
272
272
|
branch: str, worktree: str, pr_url: str | None = None,
|
|
273
|
-
language_code: str
|
|
273
|
+
language_code: str) -> str:
|
|
274
274
|
"""Render run facts as wrapping Markdown, never as a host-path dump.
|
|
275
275
|
|
|
276
276
|
The field labels are human prose and follow the run's message
|
|
@@ -312,8 +312,15 @@ def _failure_scene(snapshot: dict, *, run_id: str, issue: str, role: str,
|
|
|
312
312
|
)
|
|
313
313
|
|
|
314
314
|
|
|
315
|
+
# One evidence segment's cap (Issue #1711, `_fenced`'s default) and one
|
|
316
|
+
# fitting step: an oversized comment shrinks these, never its structure.
|
|
317
|
+
EVIDENCE_SEGMENT_CAP = 4000
|
|
318
|
+
_EVIDENCE_CAP_STEP = 500
|
|
319
|
+
|
|
320
|
+
|
|
315
321
|
def _failure_evidence(worktree: Path | None, exc: BaseException, *,
|
|
316
|
-
language_code: str
|
|
322
|
+
language_code: str,
|
|
323
|
+
cap: int = EVIDENCE_SEGMENT_CAP) -> str:
|
|
317
324
|
"""Render the bounded evidence that survives terminal worktree cleanup.
|
|
318
325
|
|
|
319
326
|
Pi subprocess streams come from ``CalledProcessError``. The session and
|
|
@@ -322,6 +329,8 @@ def _failure_evidence(worktree: Path | None, exc: BaseException, *,
|
|
|
322
329
|
the session tail is a structural summary whose duplicates are removed,
|
|
323
330
|
and the full session log is named for the deep-dive. The
|
|
324
331
|
failure reason itself stays the readable head of the comment.
|
|
332
|
+
|
|
333
|
+
`cap` bounds each segment, so every fence closes (Issue #1711).
|
|
325
334
|
"""
|
|
326
335
|
stderr = getattr(exc, "stderr", None)
|
|
327
336
|
stdout = getattr(exc, "output", None)
|
|
@@ -348,11 +357,11 @@ def _failure_evidence(worktree: Path | None, exc: BaseException, *,
|
|
|
348
357
|
return (
|
|
349
358
|
f"\n\n{language.text(language_code, 'failure.evidence.heading')}\n"
|
|
350
359
|
f"exit_code={return_code if return_code is not None else '<unknown>'}\n"
|
|
351
|
-
f"\nstderr_tail:\n{_fenced(_redact_local_paths(stderr))}\n"
|
|
352
|
-
f"\nstdout_tail:\n{_fenced(_redact_local_paths(stdout))}\n"
|
|
360
|
+
f"\nstderr_tail:\n{_fenced(_redact_local_paths(stderr), cap=cap)}\n"
|
|
361
|
+
f"\nstdout_tail:\n{_fenced(_redact_local_paths(stdout), cap=cap)}\n"
|
|
353
362
|
f"\n{language.text(language_code, 'failure.evidence.session_last_events', limit=SESSION_SUMMARY_LIMIT, label=session_label)}\n"
|
|
354
|
-
f"{_fenced(_redact_local_paths(session))}\n"
|
|
355
|
-
f"\ntest_log_tail:\n{_fenced(_redact_local_paths(test_log))}"
|
|
363
|
+
f"{_fenced(_redact_local_paths(session), cap=cap)}\n"
|
|
364
|
+
f"\ntest_log_tail:\n{_fenced(_redact_local_paths(test_log), cap=cap)}"
|
|
356
365
|
)
|
|
357
366
|
|
|
358
367
|
|
|
@@ -389,8 +398,8 @@ def _snapshot_or_placeholder(session_dir: Path, *, number: int) -> dict:
|
|
|
389
398
|
_BLOCKED_PRECONDITION_PHRASE_KEY = "failure.blocked_precondition"
|
|
390
399
|
|
|
391
400
|
|
|
392
|
-
# The
|
|
393
|
-
#
|
|
401
|
+
# The failure comment's size limit: the body stays under it by shrinking
|
|
402
|
+
# only the evidence segments, never by cutting the comment (Issue #1711).
|
|
394
403
|
FAILURE_COMMENT_MAX_CHARS = 20000
|
|
395
404
|
|
|
396
405
|
|
|
@@ -461,7 +470,7 @@ def _failure_streak(comments: list, run_id: str,
|
|
|
461
470
|
|
|
462
471
|
def block_scene_failure(issue: dict, error: ValueError, repo: str,
|
|
463
472
|
comments: list[dict], *,
|
|
464
|
-
language_code: str
|
|
473
|
+
language_code: str) -> None:
|
|
465
474
|
"""Mark an opened-PR Issue `ai-blocked` when its scene is malformed.
|
|
466
475
|
|
|
467
476
|
The blocked transition is scoped to this one Issue: the
|
|
@@ -488,8 +497,8 @@ def block_scene_failure(issue: dict, error: ValueError, repo: str,
|
|
|
488
497
|
# derive run_id, branch, worktree or PR without the trusted
|
|
489
498
|
# scene comment, so it cannot start a review session): the
|
|
490
499
|
# comment states the EXPLICIT reason why automatic recovery is
|
|
491
|
-
# impossible plus the human next step (restore the scene
|
|
492
|
-
#
|
|
500
|
+
# impossible plus the human next step (restore the scene comment,
|
|
501
|
+
# then the ONE shared unblock step of the catalog, Issue #1724).
|
|
493
502
|
comment_issue(
|
|
494
503
|
number, repo=repo,
|
|
495
504
|
body=(
|
|
@@ -506,8 +515,8 @@ def block_scene_failure(issue: dict, error: ValueError, repo: str,
|
|
|
506
515
|
|
|
507
516
|
def block_repo_config_failure(number: int, source_repo: str,
|
|
508
517
|
error: ValueError, run_id: str,
|
|
509
|
-
current_labels=(),
|
|
510
|
-
language_code: str
|
|
518
|
+
current_labels=(), *,
|
|
519
|
+
language_code: str,
|
|
511
520
|
) -> None:
|
|
512
521
|
"""Mark an Issue `ai-blocked` when its repo config is invalid (#527).
|
|
513
522
|
|
|
@@ -583,7 +592,7 @@ def _raise_if_preexisting_ci_failure(
|
|
|
583
592
|
|
|
584
593
|
def _report_resume_failure(*, number: int, source_repo: str, run_id: str,
|
|
585
594
|
error: Exception,
|
|
586
|
-
language_code: str
|
|
595
|
+
language_code: str,
|
|
587
596
|
) -> None:
|
|
588
597
|
"""Report a failed resume decision through the terminal path.
|
|
589
598
|
|
|
@@ -591,8 +600,8 @@ def _report_resume_failure(*, number: int, source_repo: str, run_id: str,
|
|
|
591
600
|
verified: continuing on a guessed identity risks a
|
|
592
601
|
silent fresh redo on top of unknown work, and a fresh run would
|
|
593
602
|
lose the existing work. The Issue goes `ai-blocked` ALONE (the
|
|
594
|
-
claim label removed) with the exact reason
|
|
595
|
-
— a human decides (restore or remove the worktree,
|
|
603
|
+
claim label removed) with the exact reason, then the community
|
|
604
|
+
help line — a human decides (restore or remove the worktree, relabel).
|
|
596
605
|
"""
|
|
597
606
|
apply_label_patch(
|
|
598
607
|
number, repo=source_repo, event=EVENT_BLOCKED,
|
|
@@ -605,8 +614,9 @@ def _report_resume_failure(*, number: int, source_repo: str, run_id: str,
|
|
|
605
614
|
f"{comment_kind_marker(COMMENT_KIND_FAILURE)}\n"
|
|
606
615
|
+ language.text(
|
|
607
616
|
language_code, "failure.resume_blocked",
|
|
608
|
-
detail=_failure_detail(error),
|
|
609
|
-
|
|
617
|
+
detail=_redact_local_paths(_failure_detail(error)),
|
|
618
|
+
run_id=run_id,
|
|
619
|
+
) + f"\n\n{language.text(language_code, 'help.community')}"
|
|
610
620
|
),
|
|
611
621
|
)
|
|
612
622
|
|
|
@@ -621,7 +631,7 @@ def report_delivery_failure(
|
|
|
621
631
|
blocked_suffix: str = "", review_round: int | None = None,
|
|
622
632
|
review_scene_block: str | None = None,
|
|
623
633
|
finish: bool = True, publisher: ProgressPublisher | None = None,
|
|
624
|
-
language_code: str
|
|
634
|
+
language_code: str,
|
|
625
635
|
) -> str:
|
|
626
636
|
"""Report one delivery failure through the classified-failure flow.
|
|
627
637
|
|
|
@@ -727,11 +737,11 @@ def report_delivery_failure(
|
|
|
727
737
|
streak = _failure_streak(history, run_id, fingerprint)
|
|
728
738
|
if streak + 1 >= FAILURE_STREAK_LIMIT:
|
|
729
739
|
blocked = True
|
|
730
|
-
reason
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
740
|
+
reason += language.text(
|
|
741
|
+
language_code,
|
|
742
|
+
"delivery.failure_streak_escalation",
|
|
743
|
+
count=streak + 1, run_id=run_id,
|
|
744
|
+
fingerprint=fingerprint,
|
|
735
745
|
)
|
|
736
746
|
event(
|
|
737
747
|
"failure_streak_escalated", level=logging.ERROR,
|
|
@@ -783,10 +793,8 @@ def report_delivery_failure(
|
|
|
783
793
|
current_labels if current_labels is not None
|
|
784
794
|
else issue_labels(number, source_repo)
|
|
785
795
|
)
|
|
786
|
-
evidence_detail = (
|
|
787
|
-
|
|
788
|
-
if evidence else ""
|
|
789
|
-
)
|
|
796
|
+
evidence_detail = _failure_evidence(
|
|
797
|
+
worktree, exc, language_code=language_code) if evidence else ""
|
|
790
798
|
# The milestone is a short mobile notification: for the workflow
|
|
791
799
|
# refusal it must carry the same cause AND repair path as the
|
|
792
800
|
# detailed comment, so it shows the reader-facing action instead of
|
|
@@ -837,23 +845,16 @@ def report_delivery_failure(
|
|
|
837
845
|
language_code=language_code,
|
|
838
846
|
)
|
|
839
847
|
outcome = "fix needed"
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
log=(
|
|
851
|
-
language.text(
|
|
852
|
-
language_code, "failure.scene.local_session_log")
|
|
853
|
-
if session_file else "<unavailable>"
|
|
854
|
-
),
|
|
855
|
-
)
|
|
856
|
-
)
|
|
848
|
+
# An oversized comment shrinks ONLY its evidence segments, never its
|
|
849
|
+
# structure: a cut lands inside the last fence, so the closer and the
|
|
850
|
+
# help line render as code in the collapsed block (Issue #1711).
|
|
851
|
+
cap = EVIDENCE_SEGMENT_CAP
|
|
852
|
+
while evidence_detail and cap and len(body) > FAILURE_COMMENT_MAX_CHARS:
|
|
853
|
+
cap = max(0, cap - _EVIDENCE_CAP_STEP)
|
|
854
|
+
shrunk = _failure_evidence(
|
|
855
|
+
worktree, exc, language_code=language_code, cap=cap)
|
|
856
|
+
body = body.replace(evidence_detail.lstrip(), shrunk.lstrip(), 1)
|
|
857
|
+
evidence_detail = shrunk
|
|
857
858
|
if run_id:
|
|
858
859
|
# The hidden fingerprint marker rides under the run marker on
|
|
859
860
|
# the recoverable path only: a blocked Issue is terminal, so
|
|
@@ -648,13 +648,6 @@ def _epic_child_evidence(repo: str, kind: str, number: int) -> str:
|
|
|
648
648
|
return f"Issue #{number} closed"
|
|
649
649
|
|
|
650
650
|
|
|
651
|
-
def _epic_audit(child_evidence: list[str], version: str | None = None) -> str:
|
|
652
|
-
prefix = f" for {version}" if version else ""
|
|
653
|
-
return (f"Epic reconciliation{prefix}: complete; "
|
|
654
|
-
f"children: {', '.join(child_evidence)}; "
|
|
655
|
-
"no open native blockers/dependencies.")
|
|
656
|
-
|
|
657
|
-
|
|
658
651
|
# Comments are trusted through one of two channels — a repo maintainer
|
|
659
652
|
# association, or the currently authenticated runner account itself (the
|
|
660
653
|
# login fallback in `_comment_is_trusted`, which requires the local gh
|
|
@@ -688,9 +681,9 @@ def apply_label_patch(number: int, *, repo: str, event: str,
|
|
|
688
681
|
patch. The patch is applied through `edit_issue`: one call for the
|
|
689
682
|
add plus the first remove, then one call per extra remove (the exact
|
|
690
683
|
same `edit_issue` kwargs the pre-#175 code emitted). A no-op patch
|
|
691
|
-
(nothing to add or remove) applies nothing.
|
|
692
|
-
|
|
693
|
-
|
|
684
|
+
(nothing to add or remove) applies nothing. After the last write,
|
|
685
|
+
`notify.patch_applied` (Issue #1673) owns the one notification the
|
|
686
|
+
patch warrants — a newly added attention label, never the wait.
|
|
694
687
|
"""
|
|
695
688
|
to_add, to_remove = label_patch(event, current_labels)
|
|
696
689
|
if not to_add and not to_remove:
|
|
@@ -704,17 +697,15 @@ def apply_label_patch(number: int, *, repo: str, event: str,
|
|
|
704
697
|
edit_issue(number, **kwargs)
|
|
705
698
|
for label in to_remove[1:]:
|
|
706
699
|
edit_issue(number, repo=repo, remove=label)
|
|
707
|
-
#
|
|
708
|
-
#
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
repo, number, to_add[0], _issue_title(number, repo=repo),
|
|
717
|
-
)
|
|
700
|
+
# Notifying the person is a pure bypass owned by `notify` (Issues
|
|
701
|
+
# #1673/#1691): it decides from the event, the added labels and the
|
|
702
|
+
# current labels, and reads the title lazily through the callable --
|
|
703
|
+
# so a tick with notifications off makes no extra `gh` call.
|
|
704
|
+
notify.patch_applied(
|
|
705
|
+
event, to_add, current_labels,
|
|
706
|
+
repo=repo, number=number,
|
|
707
|
+
read_title=lambda: _issue_title(number, repo=repo),
|
|
708
|
+
)
|
|
718
709
|
|
|
719
710
|
|
|
720
711
|
def _issue_title(number: int, *, repo: str) -> str | None:
|
|
@@ -111,7 +111,7 @@ def _is_deploy_path(path: str) -> bool:
|
|
|
111
111
|
|
|
112
112
|
def build_checklist(*, test_status: str | None,
|
|
113
113
|
changed_files: list[str] | None,
|
|
114
|
-
language_code: str
|
|
114
|
+
language_code: str) -> dict:
|
|
115
115
|
"""Split the delivery's evidence into the two checklist columns.
|
|
116
116
|
|
|
117
117
|
`test_status` is the PR head's CI check status (Issue #1613:
|
|
@@ -156,7 +156,7 @@ def build_checklist(*, test_status: str | None,
|
|
|
156
156
|
|
|
157
157
|
def render_checklist_comment(*, run_id: str, pr_url: str,
|
|
158
158
|
checklist: dict,
|
|
159
|
-
language_code: str
|
|
159
|
+
language_code: str,
|
|
160
160
|
) -> str:
|
|
161
161
|
"""Render the Issue comment: run marker, both columns, machine block.
|
|
162
162
|
|
|
@@ -164,7 +164,8 @@ def render_checklist_comment(*, run_id: str, pr_url: str,
|
|
|
164
164
|
`Orbi review round ` (`review_rounds_so_far` counts it and the
|
|
165
165
|
checklist would burn the bounded review budget); an empty column 2
|
|
166
166
|
says so explicitly and does not require the label; the wording never
|
|
167
|
-
claims a guarantee of direction.
|
|
167
|
+
claims a guarantee of direction. A non-empty column 2 (a person
|
|
168
|
+
must confirm) ends with the community help line (Issue #1684).
|
|
168
169
|
"""
|
|
169
170
|
def message(key: str, **fields: object) -> str:
|
|
170
171
|
return language.text(language_code, key, **fields)
|
|
@@ -214,4 +215,9 @@ def render_checklist_comment(*, run_id: str, pr_url: str,
|
|
|
214
215
|
),
|
|
215
216
|
"-->",
|
|
216
217
|
]
|
|
218
|
+
if column2:
|
|
219
|
+
# A person must confirm: this problem message ends with the
|
|
220
|
+
# community help line (Issue #1684). An empty column 2 needs no
|
|
221
|
+
# human, so it stays exactly as before.
|
|
222
|
+
lines += ["", message("help.community")]
|
|
217
223
|
return "\n".join(lines)
|