orbi-cli 0.7.24__tar.gz → 0.7.26__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.24 → orbi_cli-0.7.26}/PKG-INFO +1 -1
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/pyproject.toml +1 -1
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/__init__.py +1 -1
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/claim.py +2 -1
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/clarify.py +21 -22
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/criteria_command.py +4 -4
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/language_terms.py +10 -9
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi_cli.egg-info/PKG-INFO +1 -1
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_clarify.py +161 -4
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_criteria_command.py +51 -7
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_prompt_contract.py +101 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/LICENSE +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/README.md +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/docs/licenses/sustainable-use-license.md +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/setup.cfg +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/base_ci_red.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/blocked_comment.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/branch_reclaim.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/check_permission.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/checks.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/cli.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/cli_commands.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/cli_doctor.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/cli_parser.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/cli_source.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/cli_status.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/cli_ticket_command.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/comment_kind.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/comment_trust.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/config.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/delivery_comment.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/delivery_labels.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/delivery_scene.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/engine_source.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/example_config.toml +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/extensions/clarify_verdict.ts +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/failure.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/failure_report.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/gh_response.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/git_identity.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/git_transport.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/github.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/github_feedback.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/gitops.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/human_review.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/journal.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/language.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/launchd_deploy.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/merge_exec.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/merge_gate.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/merge_handoff.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/milestone.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/milestone_command.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/notify.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/pi_activity.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/pi_command.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/pi_idle.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/pi_process.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/pi_recovery.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/pi_session.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/pi_stream_log.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/pilot_setup.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/pilot_slots.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/pr_verification.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/progress.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/progress_display.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/progress_outcome.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/progress_summary.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/release.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/release_closeout.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/release_comments.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/release_declaration.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/release_git.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/release_notes.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/release_run.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/repo_config.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/resume_scan.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/resume_verification.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/review_merge.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/review_rounds.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/review_verdict.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/run_state.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/runner.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/runner_health.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/runner_marker.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/runner_source.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/scene.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/scheduler.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/scheduler_units.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/session_command.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/setup_checks.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/setup_config.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/source_pool.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/split_command.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/status_observe.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/suggest.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/systemd_deploy.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/ticket_command.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/user_summary.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi_cli.egg-info/SOURCES.txt +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi_cli.egg-info/dependency_links.txt +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi_cli.egg-info/entry_points.txt +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi_cli.egg-info/top_level.txt +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_agents_md.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_apply_label_patch.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_base_branch_failure.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_base_ci_red.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_blocked_comment.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_bootstrap_adapters.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_bootstrap_delivery.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_bootstrap_pi.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_bootstrap_preflight.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_bootstrap_release.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_bootstrap_runner.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_branch_reclaim.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_check_permission.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_ci_failure_issue_workflow.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_ci_failure_triage.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_ci_macos_workflow.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_ci_workflow.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_claude_plugin.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_cli_golden.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_cli_install.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_cli_milestone.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_cli_packaging.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_cli_source.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_cli_ticket_commands.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_command_receipt_contract.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_comment_identity.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_comment_readers_contract.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_comment_trust.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_concurrency_e2e.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_constitution.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_coverage_gate.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_deliver_pr_fakes.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_delivery_branch_delete.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_delivery_labels.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_delivery_scene.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_docker_docs.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_docker_entrypoint.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_docker_image_workflow.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_docs_i18n.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_docs_issue_shapes.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_docs_labels.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_docs_mermaid.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_docs_releases.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_docs_single_source.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_docs_site.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_engine_source.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_failure.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_failure_report.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_fix_needed_loop.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_gh_read_retry.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_gh_response.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_git_base_smoke.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_git_merge_smoke.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_git_network_retry.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_git_transport.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_github.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_github_fakes.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_gitops.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_gitops_fakes.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_human_review.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_import_graph.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_install_sh.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_issue_1367.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_issue_461.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_issue_template_config.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_journal.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_journal_events.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_launchd_deploy.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_license.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_merge_exec_extraction.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_message_language.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_milestone_command.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_notify.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_ops_e2e.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_orbi.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_orbi_dashboard.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_orbi_exporter.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_patch_ratchet.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_pi_activity.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_pi_command.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_pi_extensions.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_pi_provider_templates.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_pi_recovery.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_pi_session.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_pilot_setup.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_pilot_slots.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_pr_issue_link_workflow.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_pr_verification.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_prerequisite_check.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_progress.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_progress_wiring.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_public_contracts.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_public_helpers.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_publish_workflow.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_release_docs_workflow.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_release_extraction.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_release_ticket_templates.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_repo_config.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_resume_continue_e2e.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_resume_e2e.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_resume_pr.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_review_merge.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_review_verdict.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_run_artifacts.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_run_id_e2e.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_run_id_fixture.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_runner_config.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_runner_health.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_runner_source_freshness.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_scene.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_scheduler.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_seam.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_setup_output_golden.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_size_ratchet.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_split_command.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_src_layout.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_status_observe.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_steering_review.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_suggest.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_suite_hygiene.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_systemd_deploy.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_systemd_timer.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_ticket_command.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_user_acceptance_contract.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_user_summary.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_versioned_comment_block.py +0 -0
- {orbi_cli-0.7.24 → orbi_cli-0.7.26}/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.26
|
|
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.26"
|
|
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"
|
|
@@ -18,7 +18,8 @@ from pathlib import Path
|
|
|
18
18
|
from orbi import base_ci_red, check_permission, gitops, ticket_command
|
|
19
19
|
# Imported for its registration side effect: `ai-needs-detail` tickets run
|
|
20
20
|
# the registered commands below, and `/orbi split` (Issue #1927) and
|
|
21
|
-
# `/orbi
|
|
21
|
+
# `/orbi amend` (Issue #1929, typed word renamed by #1975) must be in the
|
|
22
|
+
# registry by then.
|
|
22
23
|
from orbi import criteria_command, split_command # noqa: F401
|
|
23
24
|
from orbi import milestone as milestone_bookkeeping
|
|
24
25
|
from orbi.config import RunnerConfig
|
|
@@ -133,7 +133,8 @@ CLARIFY_SYSTEM_PROMPT = (
|
|
|
133
133
|
"state. When the text lacks a fact a proposal needs, or writing one "
|
|
134
134
|
"needs a decision only a person can make, omit the argument and "
|
|
135
135
|
"answer with missing alone.\n"
|
|
136
|
-
'-
|
|
136
|
+
'- single_outcome failed, with or without "acceptance_condition" '
|
|
137
|
+
'also failing: suggestion {"kind": "split", '
|
|
137
138
|
'"items": [{"result": "...", "acceptance": "...", "after": 1}]}. '
|
|
138
139
|
"One item per new ticket, each result and acceptance stated in the "
|
|
139
140
|
"text: the fewest vertical slices that cover every goal of the "
|
|
@@ -215,7 +216,9 @@ class ClarifyVerdict:
|
|
|
215
216
|
|
|
216
217
|
"suggestion" is the optional proposal the model attached to a failing
|
|
217
218
|
verdict (Issue #1926): present only when the ticket text was enough to
|
|
218
|
-
write one, and rendered only when it answers the
|
|
219
|
+
write one, and rendered only when it answers the failed checks — a split
|
|
220
|
+
answers `single_outcome` with or without a missing acceptance condition
|
|
221
|
+
(Issue #1978), a criteria draft an acceptance condition alone.
|
|
219
222
|
"suggestion_dropped" says WHY a proposal the answer carried was
|
|
220
223
|
discarded (Issue #1958) — "misplaced" when the keys sat at the top
|
|
221
224
|
level instead of under "suggestion", "unusable" when the value failed
|
|
@@ -244,15 +247,6 @@ SUGGESTION_START = f"<!-- {SUGGESTION_MARKER} kind="
|
|
|
244
247
|
SUGGESTION_END = "<!-- /orbi/suggestion -->"
|
|
245
248
|
SUGGESTION_START_PREFIX = f"<!-- {SUGGESTION_MARKER}"
|
|
246
249
|
|
|
247
|
-
# The one missing piece each proposal kind answers: a split plan proposes
|
|
248
|
-
# the split a "single_outcome" failure asks for, a criteria draft the
|
|
249
|
-
# acceptance an "acceptance_condition" failure asks for. Every other
|
|
250
|
-
# verdict renders no proposal.
|
|
251
|
-
SUGGESTION_FOR_MISSING = {
|
|
252
|
-
"single_outcome": SUGGESTION_SPLIT,
|
|
253
|
-
"acceptance_condition": SUGGESTION_CRITERIA,
|
|
254
|
-
}
|
|
255
|
-
|
|
256
250
|
# The one command sentence each proposal kind adds below its edit sentence
|
|
257
251
|
# (Issue #1927 for a split, Issue #1929 for criteria): the reader never
|
|
258
252
|
# copies the lines by hand, and the command reads the SAME section.
|
|
@@ -404,17 +398,22 @@ def render_suggestion(suggestion: Suggestion, language_code: str) -> str:
|
|
|
404
398
|
def verdict_suggestion(verdict: ClarifyVerdict, language_code: str) -> str:
|
|
405
399
|
"""The proposal section for one failing verdict, or "".
|
|
406
400
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
401
|
+
A split is rendered whenever `single_outcome` is among the failed checks,
|
|
402
|
+
with or without a missing acceptance condition (Issue #1978): every split
|
|
403
|
+
item carries its own acceptance line, so one split plan answers both
|
|
404
|
+
failures, and a ticket that should be split gets the split plan. The
|
|
405
|
+
criteria draft keeps its single-piece rule — it answers an
|
|
406
|
+
`acceptance_condition` failure alone (Issue #1926). An unusable proposal
|
|
407
|
+
renders nothing, so every other stop keeps the pre-#1926 comment byte for
|
|
408
|
+
byte: no section, no edit sentence.
|
|
411
409
|
"""
|
|
412
410
|
suggestion = verdict.suggestion
|
|
413
|
-
if suggestion is None or
|
|
414
|
-
return ""
|
|
415
|
-
if suggestion.kind != SUGGESTION_FOR_MISSING.get(verdict.missing[0]):
|
|
411
|
+
if suggestion is None or not _usable(suggestion):
|
|
416
412
|
return ""
|
|
417
|
-
if
|
|
413
|
+
if suggestion.kind == SUGGESTION_SPLIT:
|
|
414
|
+
if "single_outcome" not in verdict.missing:
|
|
415
|
+
return ""
|
|
416
|
+
elif verdict.missing != ("acceptance_condition",):
|
|
418
417
|
return ""
|
|
419
418
|
return render_suggestion(suggestion, language_code)
|
|
420
419
|
|
|
@@ -813,9 +812,9 @@ def render_comment(issue: dict, verdict: ClarifyVerdict, run_id: str, *,
|
|
|
813
812
|
pieces = "\n".join(
|
|
814
813
|
f"- {missing_text(item, language_code)}" for item in verdict.missing
|
|
815
814
|
)
|
|
816
|
-
# The editable proposal, only when this verdict's
|
|
817
|
-
#
|
|
818
|
-
#
|
|
815
|
+
# The editable proposal, only when it answers this verdict's failed
|
|
816
|
+
# checks (Issue #1926, #1978). Without one the string is empty, so the
|
|
817
|
+
# comment stays byte for byte as before.
|
|
819
818
|
proposal = verdict_suggestion(verdict, language_code)
|
|
820
819
|
# A proposal IS the instruction (Issue #1962): the comment then carries
|
|
821
820
|
# the proposal, its edit sentence and its command sentence, and never
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
"""`/orbi
|
|
1
|
+
"""`/orbi amend`: put the proposed acceptance criteria on the ticket.
|
|
2
2
|
|
|
3
3
|
The clarification gate stops a thin ticket and may attach a `kind=criteria`
|
|
4
4
|
proposal to its stop comment (Issue #1926). This module registers the one
|
|
5
5
|
command that acts on it: a person with write permission replies
|
|
6
|
-
`/orbi
|
|
6
|
+
`/orbi amend` and the proposed conditions — **as the comment reads at
|
|
7
7
|
that moment**, so editing it first changes what lands — are appended to the
|
|
8
8
|
ticket body under an acceptance heading that follows the repository's
|
|
9
9
|
`message_language` (`## Acceptance` in English, `## 验收` in Chinese,
|
|
@@ -231,12 +231,12 @@ def _criteria_apply(
|
|
|
231
231
|
|
|
232
232
|
ADD_CRITERIA_COMMAND = CommandSpec(
|
|
233
233
|
name="add_criteria",
|
|
234
|
-
subcommand="
|
|
234
|
+
subcommand="amend",
|
|
235
235
|
arity=0,
|
|
236
236
|
permission="write",
|
|
237
237
|
validate=_criteria_validate,
|
|
238
238
|
apply=_criteria_apply,
|
|
239
|
-
usage="/orbi
|
|
239
|
+
usage="/orbi amend",
|
|
240
240
|
description=(
|
|
241
241
|
"append the proposed acceptance criteria to this ticket and queue it"
|
|
242
242
|
),
|
|
@@ -246,8 +246,8 @@ CLARIFY_SUGGESTION_MESSAGES: dict[str, tuple[str, str]] = {
|
|
|
246
246
|
# names the command that appends these conditions to the ticket and
|
|
247
247
|
# starts it, so the reader never copies them into the body by hand.
|
|
248
248
|
"clarify.suggestion.criteria_reply": (
|
|
249
|
-
"Reply `/orbi
|
|
250
|
-
"回复 `/orbi
|
|
249
|
+
"Reply `/orbi amend` to add these and start.",
|
|
250
|
+
"回复 `/orbi amend` 即可把这些加入工单并开始交付。",
|
|
251
251
|
),
|
|
252
252
|
# The language the PROPOSAL VALUES are written in (Issue #1971): the
|
|
253
253
|
# engine's own setting, named in the gate's prompt instead of left to
|
|
@@ -343,11 +343,12 @@ SPLIT_MESSAGES: dict[str, tuple[str, str]] = {
|
|
|
343
343
|
),
|
|
344
344
|
}
|
|
345
345
|
|
|
346
|
-
# The `/orbi
|
|
347
|
-
# receipt it leaves. Every entry is display
|
|
348
|
-
# markers, the labels) stays in English
|
|
349
|
-
# is display text too: it follows the
|
|
350
|
-
# the marker that makes the append
|
|
346
|
+
# The `/orbi amend` command (Issue #1929, typed word renamed by #1975):
|
|
347
|
+
# its rejections and the ONE receipt it leaves. Every entry is display
|
|
348
|
+
# text; the machine contract (the markers, the labels) stays in English
|
|
349
|
+
# and outside the catalog. The heading is display text too: it follows the
|
|
350
|
+
# message language (Issue #1971), while the marker that makes the append
|
|
351
|
+
# idempotent is the same in every language.
|
|
351
352
|
ADD_CRITERIA_MESSAGES: dict[str, tuple[str, str]] = {
|
|
352
353
|
"criteria.reason_no_suggestion": (
|
|
353
354
|
"the newest stop comment carries no acceptance-criteria proposal; "
|
|
@@ -362,10 +363,10 @@ ADD_CRITERIA_MESSAGES: dict[str, tuple[str, str]] = {
|
|
|
362
363
|
),
|
|
363
364
|
"criteria.reason_not_criteria": (
|
|
364
365
|
"the newest stop comment proposes `{kind}`, not acceptance "
|
|
365
|
-
"criteria; `/orbi
|
|
366
|
+
"criteria; `/orbi amend` only adds a `kind=criteria` "
|
|
366
367
|
"proposal",
|
|
367
368
|
"最新一条停止评论的提案是 `{kind}`,不是验收条件;"
|
|
368
|
-
"`/orbi
|
|
369
|
+
"`/orbi amend` 只添加 `kind=criteria` 提案",
|
|
369
370
|
),
|
|
370
371
|
"criteria.receipt_title": (
|
|
371
372
|
"**Added the proposed acceptance criteria to this ticket.**",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: orbi-cli
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.26
|
|
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
|
|
@@ -1031,6 +1031,47 @@ def fake_pi_answer(answer: dict) -> str:
|
|
|
1031
1031
|
)
|
|
1032
1032
|
|
|
1033
1033
|
|
|
1034
|
+
@pytest.mark.parametrize("language_code", ["en", "zh"])
|
|
1035
|
+
def test_the_real_runner_proposes_the_split_for_a_bundled_ticket(
|
|
1036
|
+
clone, tmp_path, monkeypatch, language_code,
|
|
1037
|
+
):
|
|
1038
|
+
"""Acceptance 1 (Issue #1978) at the real entry point: a ticket that
|
|
1039
|
+
fails `single_outcome` AND `acceptance_condition` (the evidence shape:
|
|
1040
|
+
xqliu/orbi-e2e-2609260042#253) is stopped with the split plan, not with
|
|
1041
|
+
the generic repair block. `/orbi split` reads that very section."""
|
|
1042
|
+
fix_run_id(monkeypatch, RUN_ID)
|
|
1043
|
+
answer = {
|
|
1044
|
+
"missing": ["acceptance_condition", "single_outcome"],
|
|
1045
|
+
"suggestion": SPLIT_ANSWER["suggestion"],
|
|
1046
|
+
}
|
|
1047
|
+
install_fake_pi(monkeypatch, tmp_path, fake_pi_answer(answer))
|
|
1048
|
+
comments: list[str] = []
|
|
1049
|
+
labels = {ISSUE_NUMBER: [delivery_labels.READY_LABEL]}
|
|
1050
|
+
install_fake_gh(monkeypatch, comments, labels)
|
|
1051
|
+
|
|
1052
|
+
result = runner.process_issue(
|
|
1053
|
+
THIN_ISSUE,
|
|
1054
|
+
gate_config(clone, tmp_path, message_language=language_code),
|
|
1055
|
+
REPO,
|
|
1056
|
+
)
|
|
1057
|
+
|
|
1058
|
+
assert result == runner.IssueResult("needs-detail", None)
|
|
1059
|
+
assert len(comments) == 1
|
|
1060
|
+
body = comments[0]
|
|
1061
|
+
assert (FIXTURES / f"split.{language_code}.md").read_text() in body
|
|
1062
|
+
# The section the comment carries is exactly what `/orbi split` parses.
|
|
1063
|
+
assert clarify.parse_suggestion(body) == SPLIT_PROPOSAL
|
|
1064
|
+
assert language.text(
|
|
1065
|
+
language_code, clarify.SUGGESTION_REPLY[clarify.SUGGESTION_SPLIT],
|
|
1066
|
+
) in body
|
|
1067
|
+
# One instruction: no generic repair block beside the proposal.
|
|
1068
|
+
assert language.text(
|
|
1069
|
+
language_code, "clarify.repair", ready_label="ai-ready",
|
|
1070
|
+
) not in body
|
|
1071
|
+
assert labels[ISSUE_NUMBER] == [delivery_labels.NEEDS_DETAIL_LABEL]
|
|
1072
|
+
assert not worktree_for(clone, RUN_ID).exists()
|
|
1073
|
+
|
|
1074
|
+
|
|
1034
1075
|
@pytest.mark.parametrize("language_code", ["en", "zh"])
|
|
1035
1076
|
@pytest.mark.parametrize("answer, kind", [
|
|
1036
1077
|
(SPLIT_ANSWER, "split"),
|
|
@@ -1382,6 +1423,20 @@ def test_the_rendered_proposal_matches_its_fixture(kind, language_code):
|
|
|
1382
1423
|
assert block.rstrip().endswith(tail)
|
|
1383
1424
|
|
|
1384
1425
|
|
|
1426
|
+
@pytest.mark.parametrize("language_code, expected", [
|
|
1427
|
+
("en", "Reply `/orbi amend` to add these and start."),
|
|
1428
|
+
("zh", "回复 `/orbi amend` 即可把这些加入工单并开始交付。"),
|
|
1429
|
+
])
|
|
1430
|
+
def test_the_criteria_proposal_tells_the_reader_to_reply_amend(
|
|
1431
|
+
language_code, expected,
|
|
1432
|
+
):
|
|
1433
|
+
"""Acceptance 3 (Issue #1975): the rendered stop comment names the typed
|
|
1434
|
+
command word `amend` in both languages."""
|
|
1435
|
+
assert language.text(
|
|
1436
|
+
language_code, clarify.SUGGESTION_REPLY[clarify.SUGGESTION_CRITERIA],
|
|
1437
|
+
) == expected
|
|
1438
|
+
|
|
1439
|
+
|
|
1385
1440
|
@pytest.mark.parametrize("language_code", LANGUAGES)
|
|
1386
1441
|
@pytest.mark.parametrize("kind", sorted(PROPOSALS))
|
|
1387
1442
|
def test_the_stop_comment_carries_the_proposal_and_the_edit_sentence(
|
|
@@ -1444,15 +1499,101 @@ def test_a_stop_without_a_proposal_keeps_the_repair_block(language_code):
|
|
|
1444
1499
|
assert clarify.SUGGESTION_START_PREFIX not in body
|
|
1445
1500
|
|
|
1446
1501
|
|
|
1502
|
+
@pytest.mark.parametrize("language_code", LANGUAGES)
|
|
1503
|
+
def test_a_bundled_ticket_gets_the_split_proposal(language_code):
|
|
1504
|
+
"""Acceptance 1 (Issue #1978): the most common broken shape — several
|
|
1505
|
+
unrelated goals and no acceptance condition — gets the split plan.
|
|
1506
|
+
|
|
1507
|
+
Every split item carries its own acceptance line, so one split answers
|
|
1508
|
+
both failures; the comment still gives ONE instruction (Issue #1962):
|
|
1509
|
+
the proposal and the `/orbi split` sentence, never the generic repair
|
|
1510
|
+
block beside it."""
|
|
1511
|
+
verdict = _failing(
|
|
1512
|
+
("acceptance_condition", "single_outcome"), SPLIT_PROPOSAL,
|
|
1513
|
+
)
|
|
1514
|
+
body = clarify.render_comment(
|
|
1515
|
+
ISSUE, verdict, RUN_ID, language_code=language_code,
|
|
1516
|
+
)
|
|
1517
|
+
block = clarify.verdict_suggestion(verdict, language_code)
|
|
1518
|
+
assert block
|
|
1519
|
+
assert block in body
|
|
1520
|
+
assert block.startswith(clarify.SUGGESTION_START + "split -->")
|
|
1521
|
+
# Both failed pieces are still named, and before the instruction.
|
|
1522
|
+
for item in ("acceptance_condition", "single_outcome"):
|
|
1523
|
+
assert clarify.missing_text(item, language_code) in body
|
|
1524
|
+
assert body.index(clarify.missing_text(item, language_code)) < (
|
|
1525
|
+
body.index(block)
|
|
1526
|
+
)
|
|
1527
|
+
# The split instruction, and only it: no criteria sentence, no generic
|
|
1528
|
+
# repair block, no `Outcome/Acceptance` template, no follow-up.
|
|
1529
|
+
reply = language.text(
|
|
1530
|
+
language_code, clarify.SUGGESTION_REPLY[clarify.SUGGESTION_SPLIT],
|
|
1531
|
+
)
|
|
1532
|
+
assert reply in body
|
|
1533
|
+
assert language.text(
|
|
1534
|
+
language_code,
|
|
1535
|
+
clarify.SUGGESTION_REPLY[clarify.SUGGESTION_CRITERIA],
|
|
1536
|
+
) not in body
|
|
1537
|
+
for key in (
|
|
1538
|
+
"clarify.repair", "clarify.repair_outcome",
|
|
1539
|
+
"clarify.repair_acceptance", "clarify.followup",
|
|
1540
|
+
):
|
|
1541
|
+
assert language.text(
|
|
1542
|
+
language_code, key, ready_label="ai-ready",
|
|
1543
|
+
needs_detail_label=delivery_labels.NEEDS_DETAIL_LABEL,
|
|
1544
|
+
) not in body
|
|
1545
|
+
assert "```" not in body
|
|
1546
|
+
|
|
1547
|
+
|
|
1548
|
+
@pytest.mark.parametrize("language_code", LANGUAGES)
|
|
1549
|
+
def test_a_split_is_rendered_whatever_else_is_missing(language_code):
|
|
1550
|
+
"""Issue #1978: the renderer maps a verdict that fails
|
|
1551
|
+
`single_outcome` to the split proposal, whatever else also failed —
|
|
1552
|
+
one rule, not a per-pair special case."""
|
|
1553
|
+
verdict = _failing(
|
|
1554
|
+
("single_outcome", "observable_result"), SPLIT_PROPOSAL,
|
|
1555
|
+
)
|
|
1556
|
+
body = clarify.render_comment(
|
|
1557
|
+
ISSUE, verdict, RUN_ID, language_code=language_code,
|
|
1558
|
+
)
|
|
1559
|
+
assert clarify.SUGGESTION_START + "split -->" in body
|
|
1560
|
+
assert language.text(
|
|
1561
|
+
language_code, clarify.SUGGESTION_REPLY[clarify.SUGGESTION_SPLIT],
|
|
1562
|
+
) in body
|
|
1563
|
+
|
|
1564
|
+
|
|
1565
|
+
@pytest.mark.parametrize("language_code", LANGUAGES)
|
|
1566
|
+
def test_an_acceptance_only_stop_still_renders_the_criteria_draft(
|
|
1567
|
+
language_code,
|
|
1568
|
+
):
|
|
1569
|
+
"""Acceptance 2 (Issue #1978): a stop for `acceptance_condition` alone
|
|
1570
|
+
keeps today's criteria draft and its `/orbi amend` sentence, and never
|
|
1571
|
+
the split instruction."""
|
|
1572
|
+
verdict = _failing(("acceptance_condition",), CRITERIA_PROPOSAL)
|
|
1573
|
+
body = clarify.render_comment(
|
|
1574
|
+
ISSUE, verdict, RUN_ID, language_code=language_code,
|
|
1575
|
+
)
|
|
1576
|
+
block = clarify.verdict_suggestion(verdict, language_code)
|
|
1577
|
+
assert block
|
|
1578
|
+
assert block.startswith(clarify.SUGGESTION_START + "criteria -->")
|
|
1579
|
+
assert block in body
|
|
1580
|
+
assert language.text(
|
|
1581
|
+
language_code, clarify.SUGGESTION_REPLY[clarify.SUGGESTION_CRITERIA],
|
|
1582
|
+
) in body
|
|
1583
|
+
assert language.text(
|
|
1584
|
+
language_code, clarify.SUGGESTION_REPLY[clarify.SUGGESTION_SPLIT],
|
|
1585
|
+
) not in body
|
|
1586
|
+
assert "```" not in body
|
|
1587
|
+
|
|
1588
|
+
|
|
1447
1589
|
@pytest.mark.parametrize("suggestion, missing", [
|
|
1448
1590
|
# The model declined, or could not propose: nothing is rendered.
|
|
1449
1591
|
(None, ("single_outcome",)),
|
|
1450
1592
|
(None, ("acceptance_condition",)),
|
|
1451
|
-
# Kind mismatch:
|
|
1593
|
+
# Kind mismatch: the split answers `single_outcome` and the criteria
|
|
1594
|
+
# draft `acceptance_condition` alone, never each other's failure.
|
|
1452
1595
|
(SPLIT_PROPOSAL, ("acceptance_condition",)),
|
|
1453
1596
|
(CRITERIA_PROPOSAL, ("single_outcome",)),
|
|
1454
|
-
# A second missing piece: the proposal would cover only part of it.
|
|
1455
|
-
(SPLIT_PROPOSAL, ("single_outcome", "observable_result")),
|
|
1456
1597
|
(CRITERIA_PROPOSAL, ("acceptance_condition", "single_outcome")),
|
|
1457
1598
|
(SPLIT_PROPOSAL, ("observable_result",)),
|
|
1458
1599
|
# Unusable proposals.
|
|
@@ -1512,6 +1653,22 @@ def test_an_unusable_proposal_keeps_the_stop_comment_byte_for_byte(
|
|
|
1512
1653
|
assert "Editing this comment" not in with_proposal
|
|
1513
1654
|
|
|
1514
1655
|
|
|
1656
|
+
def test_the_gate_prompt_allows_the_split_when_acceptance_also_fails():
|
|
1657
|
+
"""Acceptance 3 (Issue #1978): the split proposal is allowed whenever
|
|
1658
|
+
`single_outcome` fails, with or without a missing acceptance condition,
|
|
1659
|
+
because every split item carries its own acceptance line; the criteria
|
|
1660
|
+
draft keeps its "ONLY acceptance_condition" rule."""
|
|
1661
|
+
prompt = clarify.CLARIFY_SYSTEM_PROMPT
|
|
1662
|
+
assert (
|
|
1663
|
+
'- single_outcome failed, with or without "acceptance_condition" '
|
|
1664
|
+
'also failing: suggestion {"kind": "split", '
|
|
1665
|
+
) in prompt
|
|
1666
|
+
assert '- ONLY acceptance_condition failed: suggestion {"kind": ' in prompt
|
|
1667
|
+
# The pre-#1978 restriction is gone: a split was proposed only when
|
|
1668
|
+
# `single_outcome` failed alone.
|
|
1669
|
+
assert "ONLY single_outcome failed" not in prompt
|
|
1670
|
+
|
|
1671
|
+
|
|
1515
1672
|
def test_the_gate_prompt_names_the_configured_output_language():
|
|
1516
1673
|
"""Issue #1971: the proposal's VALUES follow the repository's
|
|
1517
1674
|
`message_language`, named from the catalog.
|
|
@@ -1536,7 +1693,7 @@ def test_the_gate_prompt_guards_the_proposal():
|
|
|
1536
1693
|
missing (Issue #1926)."""
|
|
1537
1694
|
prompt = clarify.CLARIFY_SYSTEM_PROMPT
|
|
1538
1695
|
for marker in (
|
|
1539
|
-
|
|
1696
|
+
'single_outcome failed, with or without "acceptance_condition" ',
|
|
1540
1697
|
"ONLY acceptance_condition failed",
|
|
1541
1698
|
"adding no goal or scope the text does not state",
|
|
1542
1699
|
"no count cap",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
"""`/orbi
|
|
2
|
-
(Issue #1929).
|
|
1
|
+
"""`/orbi amend`: the proposed acceptance criteria start the ticket
|
|
2
|
+
(Issue #1929; renamed from `add-criteria` in Issue #1975).
|
|
3
3
|
|
|
4
4
|
Every scenario here drives the REAL tick entry (`claim.pick_next_delivery`)
|
|
5
5
|
over the in-memory gh fake: the stopped ticket is picked up by the
|
|
@@ -15,13 +15,14 @@ import subprocess
|
|
|
15
15
|
import pytest
|
|
16
16
|
|
|
17
17
|
import orbi.criteria_command as criteria_command
|
|
18
|
-
from orbi import claim, clarify, journal, language, ticket_command
|
|
18
|
+
from orbi import claim, clarify, cli, journal, language, ticket_command
|
|
19
19
|
from orbi.config import RunnerConfig
|
|
20
20
|
from orbi.delivery_labels import NEEDS_DETAIL_LABEL, READY_LABEL
|
|
21
21
|
from orbi.resume_scan import ResumeHooks
|
|
22
22
|
from seam import seam
|
|
23
23
|
|
|
24
24
|
from tests.fakes.github import FakeGh
|
|
25
|
+
from tests.test_cli_milestone import make_world
|
|
25
26
|
|
|
26
27
|
REPO = "acme/app"
|
|
27
28
|
TICKET = 100
|
|
@@ -92,7 +93,7 @@ def stopped_ticket(
|
|
|
92
93
|
|
|
93
94
|
|
|
94
95
|
def reply(gh: FakeGh, *, login: str = "maintainer",
|
|
95
|
-
command: str = "/orbi
|
|
96
|
+
command: str = "/orbi amend", permission: str = "write",
|
|
96
97
|
number: int = TICKET) -> None:
|
|
97
98
|
gh.comment(number, command, login=login)
|
|
98
99
|
gh.add_collaborator(login, permission)
|
|
@@ -537,9 +538,9 @@ def test_the_command_is_registered_with_the_shared_layer():
|
|
|
537
538
|
spec = ticket_command.TICKET_COMMANDS["add_criteria"]
|
|
538
539
|
assert spec is criteria_command.ADD_CRITERIA_COMMAND
|
|
539
540
|
assert (spec.name, spec.subcommand, spec.arity, spec.permission) == (
|
|
540
|
-
"add_criteria", "
|
|
541
|
+
"add_criteria", "amend", 0, "write",
|
|
541
542
|
)
|
|
542
|
-
assert spec.usage == "/orbi
|
|
543
|
+
assert spec.usage == "/orbi amend"
|
|
543
544
|
assert spec.aliases == ()
|
|
544
545
|
assert "add_criteria_command_applied" in journal.JOURNAL_EVENTS
|
|
545
546
|
assert "add_criteria_command_failed" in journal.JOURNAL_EVENTS
|
|
@@ -581,7 +582,7 @@ def test_apply_refuses_a_ticket_without_a_proposal(monkeypatch):
|
|
|
581
582
|
|
|
582
583
|
|
|
583
584
|
def test_the_terminal_spelling_runs_the_same_append(monkeypatch):
|
|
584
|
-
"""`orbi
|
|
585
|
+
"""`orbi amend --repo … --issue N` (Issues #1930, #1975) is the same
|
|
585
586
|
execution path: the host login is the one occurrence and the ticket
|
|
586
587
|
itself supplies the proposal."""
|
|
587
588
|
gh = _gh(monkeypatch)
|
|
@@ -604,6 +605,49 @@ def test_the_terminal_spelling_runs_the_same_append(monkeypatch):
|
|
|
604
605
|
assert "comment=cli@" in receipts[0]
|
|
605
606
|
|
|
606
607
|
|
|
608
|
+
def test_the_terminal_subcommand_is_orbi_amend(tmp_path, monkeypatch):
|
|
609
|
+
"""Acceptance 4 (Issue #1975): `orbi amend --repo … --issue N` on the
|
|
610
|
+
host runs the same command through the real CLI entry."""
|
|
611
|
+
config_path = make_world(tmp_path, milestone=None, repo=REPO)
|
|
612
|
+
gh = _gh(monkeypatch)
|
|
613
|
+
stopped_ticket(gh)
|
|
614
|
+
gh.add_collaborator("orbi-bot", "write")
|
|
615
|
+
|
|
616
|
+
exit_code = cli.main([
|
|
617
|
+
"amend", "--repo", REPO, "--issue", str(TICKET),
|
|
618
|
+
"--config", str(config_path),
|
|
619
|
+
])
|
|
620
|
+
|
|
621
|
+
assert exit_code == 0
|
|
622
|
+
body = gh.issues[TICKET]["body"]
|
|
623
|
+
assert body.count(APPENDED_MARKER) == 1
|
|
624
|
+
assert "- The tag survives a reload" in body
|
|
625
|
+
assert READY_LABEL in labels(gh)
|
|
626
|
+
assert NEEDS_DETAIL_LABEL not in labels(gh)
|
|
627
|
+
receipts = success_receipts(gh)
|
|
628
|
+
assert len(receipts) == 1
|
|
629
|
+
assert "comment=cli@" in receipts[0]
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
def test_the_old_word_is_not_a_command(monkeypatch, tmp_path):
|
|
633
|
+
"""Acceptance 2 (Issue #1975): the command word was renamed and no alias
|
|
634
|
+
kept, so the old spelling is an ordinary comment: nothing is written and
|
|
635
|
+
no receipt is posted."""
|
|
636
|
+
gh = _gh(monkeypatch)
|
|
637
|
+
stopped_ticket(gh)
|
|
638
|
+
reply(gh, command="/orbi add-criteria")
|
|
639
|
+
|
|
640
|
+
tick(monkeypatch, tmp_path, gh)
|
|
641
|
+
|
|
642
|
+
assert gh.issues[TICKET]["body"] == BODY
|
|
643
|
+
assert READY_LABEL not in labels(gh)
|
|
644
|
+
assert NEEDS_DETAIL_LABEL in labels(gh)
|
|
645
|
+
assert [
|
|
646
|
+
body for body in comment_bodies(gh)
|
|
647
|
+
if "orbi-add_criteria-command" in body
|
|
648
|
+
] == []
|
|
649
|
+
|
|
650
|
+
|
|
607
651
|
def test_the_context_reads_the_ticket_and_its_proposal(monkeypatch):
|
|
608
652
|
"""The comment scan hands neither over, so the command reads both from
|
|
609
653
|
the ticket itself; a caller that already has them costs no second read."""
|
|
@@ -886,3 +886,104 @@ def test_prompt_md_step_4_does_not_use_pytest_as_completion_evidence():
|
|
|
886
886
|
text = _text(PROMPT)
|
|
887
887
|
assert "unit tests alone are not completion evidence" in text
|
|
888
888
|
assert "pytest alone is not completion evidence" not in text
|
|
889
|
+
|
|
890
|
+
|
|
891
|
+
# --- Issue #1980: a delivery changes only what the ticket asks for -----------
|
|
892
|
+
|
|
893
|
+
# The beta incident (Issue #1980) had two shapes on the same evening: ticket
|
|
894
|
+
# #260 -> PR #261 deleted a false-red test file, and ticket #262 -> PR #265
|
|
895
|
+
# removed an `exit 1` CI job from .github/workflows/ci.yml, both unmentioned,
|
|
896
|
+
# review passed, both merged. The base-red gate itself works (#254 paused), so
|
|
897
|
+
# the missing piece is the scope rule: a delivery changes only what the ticket
|
|
898
|
+
# asks for and never edits unrelated code, tests or CI configuration to get its
|
|
899
|
+
# own checks green. Both prompts must state it up front; these tests render the
|
|
900
|
+
# real templates (the exact string Pi receives) so the rule cannot silently
|
|
901
|
+
# drift away.
|
|
902
|
+
|
|
903
|
+
BASE_RED_PROMPT_ITEMS = (
|
|
904
|
+
# The general scope sentence (the ticket's acceptance 1), not a per-file
|
|
905
|
+
# list: unrelated code, tests AND CI configuration are all named.
|
|
906
|
+
("scope-only-ticket", "change only what the ticket asks for"),
|
|
907
|
+
("scope-unrelated-files",
|
|
908
|
+
"do not edit unrelated code, tests or ci configuration to make your own "
|
|
909
|
+
"checks pass"),
|
|
910
|
+
# A check already red at the frozen base belongs to the base, not the
|
|
911
|
+
# ticket — the Runner's base-red pause decides it.
|
|
912
|
+
("scope-base-check-not-mine",
|
|
913
|
+
"a check already failing at "
|
|
914
|
+
"`77e7f0ea54d8e9748f2debfd5a8f49739dd89b66` is not this ticket's to fix"),
|
|
915
|
+
("base-red-failing-test",
|
|
916
|
+
"already fails at `77e7f0ea54d8e9748f2debfd5a8f49739dd89b66`"),
|
|
917
|
+
("base-red-outside", "outside this ticket"),
|
|
918
|
+
("base-red-forbidden-actions", "delete, skip, xfail or edit it"),
|
|
919
|
+
("base-red-no-weaken", "do not weaken its assertion"),
|
|
920
|
+
("base-red-leave-failing", "leave it failing"),
|
|
921
|
+
("base-red-runner-handling", "base-red handling"),
|
|
922
|
+
# Step 5 forbids committing a change whose tests were seen failing; the
|
|
923
|
+
# carve-out keeps that rule from stalling the delivery on the base's own
|
|
924
|
+
# red test (the ticket must still end in a commit).
|
|
925
|
+
("base-red-step5-carve-out",
|
|
926
|
+
"never includes a test that already fails at "
|
|
927
|
+
"`77e7f0ea54d8e9748f2debfd5a8f49739dd89b66`"),
|
|
928
|
+
)
|
|
929
|
+
|
|
930
|
+
|
|
931
|
+
def test_prompt_md_leaves_a_test_that_already_fails_on_the_base_alone():
|
|
932
|
+
"""Issue #1980: the rendered implementer prompt carries the scope sentence
|
|
933
|
+
— change only what the ticket asks for, never unrelated code, tests or CI
|
|
934
|
+
configuration to get checks green — and states that a check already
|
|
935
|
+
failing at the frozen base SHA is outside the ticket, never deleted,
|
|
936
|
+
skipped, xfail-ed or weakened; it stays failing and the Runner's base-red
|
|
937
|
+
handling takes it from there."""
|
|
938
|
+
from orbi import pi_session
|
|
939
|
+
|
|
940
|
+
rendered = pi_session.render_prompt(
|
|
941
|
+
PROMPT.read_text(encoding="utf-8"),
|
|
942
|
+
{"BASE_SHA": "77e7f0ea54d8e9748f2debfd5a8f49739dd89b66",
|
|
943
|
+
"BASE_BRANCH": "main"},
|
|
944
|
+
)
|
|
945
|
+
text = " ".join(rendered.lower().split())
|
|
946
|
+
missing = _missing(text, BASE_RED_PROMPT_ITEMS)
|
|
947
|
+
assert not missing, (
|
|
948
|
+
f"prompt.md does not leave a base-red test alone (Issue #1980): "
|
|
949
|
+
f"{missing}"
|
|
950
|
+
)
|
|
951
|
+
# Rendered against a real base SHA, the rule names that SHA.
|
|
952
|
+
assert "77e7f0ea54d8e9748f2debfd5a8f49739dd89b66" in text
|
|
953
|
+
|
|
954
|
+
|
|
955
|
+
BASE_RED_REVIEW_ITEMS = (
|
|
956
|
+
# Acceptance 2: out-of-scope changes made to get checks green are a
|
|
957
|
+
# blocking finding, and the rule names all three file kinds, so a removed
|
|
958
|
+
# CI job (the #265 shape) is caught like a deleted test (the #261 shape).
|
|
959
|
+
("review-base-red-scope",
|
|
960
|
+
"out-of-scope changes made to get checks green are a **blocking "
|
|
961
|
+
"finding (blocker)**"),
|
|
962
|
+
("review-base-red-delete", "deleting, skipping or weakening"),
|
|
963
|
+
("review-base-red-unrelated", "unrelated existing test"),
|
|
964
|
+
("review-base-red-other-files",
|
|
965
|
+
"editing unrelated code, tests or ci configuration"),
|
|
966
|
+
("review-base-red-ci", "disabled ci job"),
|
|
967
|
+
)
|
|
968
|
+
|
|
969
|
+
|
|
970
|
+
def test_prompt_review_md_flags_a_deleted_or_weakened_existing_test():
|
|
971
|
+
"""Issue #1980: the rendered review prompt treats out-of-scope changes
|
|
972
|
+
made to get checks green — a deleted or weakened test, unrelated code or
|
|
973
|
+
CI configuration, a disabled CI job — as a blocking finding, so the PR
|
|
974
|
+
that removes the customer's red test or red job is rejected instead of
|
|
975
|
+
merged."""
|
|
976
|
+
from orbi import pi_session
|
|
977
|
+
|
|
978
|
+
rendered = pi_session.render_prompt(
|
|
979
|
+
PROMPT_REVIEW.read_text(encoding="utf-8"),
|
|
980
|
+
{"BASE_SHA": "77e7f0ea54d8e9748f2debfd5a8f49739dd89b66",
|
|
981
|
+
"BASE_BRANCH": "main"},
|
|
982
|
+
)
|
|
983
|
+
text = " ".join(rendered.lower().split())
|
|
984
|
+
missing = _missing(text, BASE_RED_REVIEW_ITEMS)
|
|
985
|
+
assert not missing, (
|
|
986
|
+
f"prompt_review.md does not flag a deleted or weakened existing test "
|
|
987
|
+
f"(Issue #1980): {missing}"
|
|
988
|
+
)
|
|
989
|
+
assert "77e7f0ea54d8e9748f2debfd5a8f49739dd89b66" in text
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|