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.
Files changed (230) hide show
  1. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/PKG-INFO +1 -1
  2. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/pyproject.toml +1 -1
  3. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/__init__.py +1 -1
  4. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/claim.py +2 -1
  5. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/clarify.py +21 -22
  6. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/criteria_command.py +4 -4
  7. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/language_terms.py +10 -9
  8. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi_cli.egg-info/PKG-INFO +1 -1
  9. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_clarify.py +161 -4
  10. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_criteria_command.py +51 -7
  11. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_prompt_contract.py +101 -0
  12. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/LICENSE +0 -0
  13. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/README.md +0 -0
  14. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/docs/licenses/sustainable-use-license.md +0 -0
  15. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/setup.cfg +0 -0
  16. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/base_ci_red.py +0 -0
  17. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/blocked_comment.py +0 -0
  18. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/branch_reclaim.py +0 -0
  19. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/check_permission.py +0 -0
  20. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/checks.py +0 -0
  21. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/cli.py +0 -0
  22. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/cli_commands.py +0 -0
  23. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/cli_doctor.py +0 -0
  24. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/cli_parser.py +0 -0
  25. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/cli_source.py +0 -0
  26. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/cli_status.py +0 -0
  27. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/cli_ticket_command.py +0 -0
  28. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/comment_kind.py +0 -0
  29. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/comment_trust.py +0 -0
  30. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/config.py +0 -0
  31. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/delivery_comment.py +0 -0
  32. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/delivery_labels.py +0 -0
  33. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/delivery_scene.py +0 -0
  34. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/engine_source.py +0 -0
  35. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/example_config.toml +0 -0
  36. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/extensions/clarify_verdict.ts +0 -0
  37. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/failure.py +0 -0
  38. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/failure_report.py +0 -0
  39. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/gh_response.py +0 -0
  40. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/git_identity.py +0 -0
  41. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/git_transport.py +0 -0
  42. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/github.py +0 -0
  43. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/github_feedback.py +0 -0
  44. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/gitops.py +0 -0
  45. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/human_review.py +0 -0
  46. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/journal.py +0 -0
  47. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/language.py +0 -0
  48. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/launchd_deploy.py +0 -0
  49. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/merge_exec.py +0 -0
  50. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/merge_gate.py +0 -0
  51. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/merge_handoff.py +0 -0
  52. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/milestone.py +0 -0
  53. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/milestone_command.py +0 -0
  54. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/notify.py +0 -0
  55. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/pi_activity.py +0 -0
  56. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/pi_command.py +0 -0
  57. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/pi_idle.py +0 -0
  58. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/pi_process.py +0 -0
  59. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/pi_recovery.py +0 -0
  60. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/pi_session.py +0 -0
  61. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/pi_stream_log.py +0 -0
  62. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/pilot_setup.py +0 -0
  63. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/pilot_slots.py +0 -0
  64. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/pr_verification.py +0 -0
  65. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/progress.py +0 -0
  66. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/progress_display.py +0 -0
  67. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/progress_outcome.py +0 -0
  68. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/progress_summary.py +0 -0
  69. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/release.py +0 -0
  70. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/release_closeout.py +0 -0
  71. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/release_comments.py +0 -0
  72. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/release_declaration.py +0 -0
  73. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/release_git.py +0 -0
  74. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/release_notes.py +0 -0
  75. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/release_run.py +0 -0
  76. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/repo_config.py +0 -0
  77. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/resume_scan.py +0 -0
  78. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/resume_verification.py +0 -0
  79. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/review_merge.py +0 -0
  80. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/review_rounds.py +0 -0
  81. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/review_verdict.py +0 -0
  82. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/run_state.py +0 -0
  83. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/runner.py +0 -0
  84. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/runner_health.py +0 -0
  85. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/runner_marker.py +0 -0
  86. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/runner_source.py +0 -0
  87. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/scene.py +0 -0
  88. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/scheduler.py +0 -0
  89. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/scheduler_units.py +0 -0
  90. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/session_command.py +0 -0
  91. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/setup_checks.py +0 -0
  92. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/setup_config.py +0 -0
  93. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/source_pool.py +0 -0
  94. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/split_command.py +0 -0
  95. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/status_observe.py +0 -0
  96. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/suggest.py +0 -0
  97. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/systemd_deploy.py +0 -0
  98. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/ticket_command.py +0 -0
  99. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi/user_summary.py +0 -0
  100. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi_cli.egg-info/SOURCES.txt +0 -0
  101. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi_cli.egg-info/dependency_links.txt +0 -0
  102. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi_cli.egg-info/entry_points.txt +0 -0
  103. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/src/orbi_cli.egg-info/top_level.txt +0 -0
  104. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_agents_md.py +0 -0
  105. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_apply_label_patch.py +0 -0
  106. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_base_branch_failure.py +0 -0
  107. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_base_ci_red.py +0 -0
  108. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_blocked_comment.py +0 -0
  109. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_bootstrap_adapters.py +0 -0
  110. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_bootstrap_delivery.py +0 -0
  111. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_bootstrap_pi.py +0 -0
  112. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_bootstrap_preflight.py +0 -0
  113. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_bootstrap_release.py +0 -0
  114. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_bootstrap_runner.py +0 -0
  115. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_branch_reclaim.py +0 -0
  116. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_check_permission.py +0 -0
  117. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_ci_failure_issue_workflow.py +0 -0
  118. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_ci_failure_triage.py +0 -0
  119. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_ci_macos_workflow.py +0 -0
  120. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_ci_workflow.py +0 -0
  121. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_claude_plugin.py +0 -0
  122. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_cli_golden.py +0 -0
  123. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_cli_install.py +0 -0
  124. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_cli_milestone.py +0 -0
  125. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_cli_packaging.py +0 -0
  126. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_cli_source.py +0 -0
  127. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_cli_ticket_commands.py +0 -0
  128. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_command_receipt_contract.py +0 -0
  129. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_comment_identity.py +0 -0
  130. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_comment_readers_contract.py +0 -0
  131. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_comment_trust.py +0 -0
  132. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_concurrency_e2e.py +0 -0
  133. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_constitution.py +0 -0
  134. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_coverage_gate.py +0 -0
  135. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_deliver_pr_fakes.py +0 -0
  136. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_delivery_branch_delete.py +0 -0
  137. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_delivery_labels.py +0 -0
  138. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_delivery_scene.py +0 -0
  139. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_docker_docs.py +0 -0
  140. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_docker_entrypoint.py +0 -0
  141. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_docker_image_workflow.py +0 -0
  142. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_docs_i18n.py +0 -0
  143. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_docs_issue_shapes.py +0 -0
  144. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_docs_labels.py +0 -0
  145. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_docs_mermaid.py +0 -0
  146. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_docs_releases.py +0 -0
  147. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_docs_single_source.py +0 -0
  148. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_docs_site.py +0 -0
  149. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_engine_source.py +0 -0
  150. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_failure.py +0 -0
  151. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_failure_report.py +0 -0
  152. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_fix_needed_loop.py +0 -0
  153. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_gh_read_retry.py +0 -0
  154. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_gh_response.py +0 -0
  155. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_git_base_smoke.py +0 -0
  156. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_git_merge_smoke.py +0 -0
  157. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_git_network_retry.py +0 -0
  158. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_git_transport.py +0 -0
  159. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_github.py +0 -0
  160. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_github_fakes.py +0 -0
  161. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_gitops.py +0 -0
  162. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_gitops_fakes.py +0 -0
  163. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_human_review.py +0 -0
  164. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_import_graph.py +0 -0
  165. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_install_sh.py +0 -0
  166. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_issue_1367.py +0 -0
  167. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_issue_461.py +0 -0
  168. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_issue_template_config.py +0 -0
  169. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_journal.py +0 -0
  170. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_journal_events.py +0 -0
  171. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_launchd_deploy.py +0 -0
  172. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_license.py +0 -0
  173. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_merge_exec_extraction.py +0 -0
  174. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_message_language.py +0 -0
  175. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_milestone_command.py +0 -0
  176. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_notify.py +0 -0
  177. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_ops_e2e.py +0 -0
  178. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_orbi.py +0 -0
  179. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_orbi_dashboard.py +0 -0
  180. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_orbi_exporter.py +0 -0
  181. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_patch_ratchet.py +0 -0
  182. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_pi_activity.py +0 -0
  183. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_pi_command.py +0 -0
  184. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_pi_extensions.py +0 -0
  185. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_pi_provider_templates.py +0 -0
  186. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_pi_recovery.py +0 -0
  187. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_pi_session.py +0 -0
  188. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_pilot_setup.py +0 -0
  189. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_pilot_slots.py +0 -0
  190. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_pr_issue_link_workflow.py +0 -0
  191. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_pr_verification.py +0 -0
  192. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_prerequisite_check.py +0 -0
  193. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_progress.py +0 -0
  194. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_progress_wiring.py +0 -0
  195. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_public_contracts.py +0 -0
  196. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_public_helpers.py +0 -0
  197. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_publish_workflow.py +0 -0
  198. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_release_docs_workflow.py +0 -0
  199. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_release_extraction.py +0 -0
  200. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_release_ticket_templates.py +0 -0
  201. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_repo_config.py +0 -0
  202. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_resume_continue_e2e.py +0 -0
  203. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_resume_e2e.py +0 -0
  204. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_resume_pr.py +0 -0
  205. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_review_merge.py +0 -0
  206. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_review_verdict.py +0 -0
  207. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_run_artifacts.py +0 -0
  208. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_run_id_e2e.py +0 -0
  209. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_run_id_fixture.py +0 -0
  210. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_runner_config.py +0 -0
  211. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_runner_health.py +0 -0
  212. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_runner_source_freshness.py +0 -0
  213. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_scene.py +0 -0
  214. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_scheduler.py +0 -0
  215. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_seam.py +0 -0
  216. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_setup_output_golden.py +0 -0
  217. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_size_ratchet.py +0 -0
  218. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_split_command.py +0 -0
  219. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_src_layout.py +0 -0
  220. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_status_observe.py +0 -0
  221. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_steering_review.py +0 -0
  222. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_suggest.py +0 -0
  223. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_suite_hygiene.py +0 -0
  224. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_systemd_deploy.py +0 -0
  225. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_systemd_timer.py +0 -0
  226. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_ticket_command.py +0 -0
  227. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_user_acceptance_contract.py +0 -0
  228. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_user_summary.py +0 -0
  229. {orbi_cli-0.7.24 → orbi_cli-0.7.26}/tests/test_versioned_comment_block.py +0 -0
  230. {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.24
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.24"
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,4 +18,4 @@ the package would shadow the installed package for every process with
18
18
  the checkout root on sys.path. The direct-execution compatibility entry
19
19
  is `python3 -m orbi.cli` (development path only).
20
20
  """
21
- __version__ = "0.7.24"
21
+ __version__ = "0.7.26"
@@ -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 add-criteria` (Issue #1929) must be in the registry by then.
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
- '- ONLY single_outcome failed: suggestion {"kind": "split", '
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 one missing piece.
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
- Rendered only when exactly one piece is missing, it is the piece this
408
- proposal kind answers, and the proposal is usable. Every other stop
409
- keeps the pre-#1926 comment byte for byte: no section, no edit
410
- sentence.
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 len(verdict.missing) != 1:
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 not _usable(suggestion):
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 one missing piece is
817
- # the piece the proposal answers (Issue #1926). Without one the string
818
- # is empty, so the comment stays byte for byte as before.
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 add-criteria`: put the proposed acceptance criteria on the ticket.
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 add-criteria` and the proposed conditions — **as the comment reads at
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="add-criteria",
234
+ subcommand="amend",
235
235
  arity=0,
236
236
  permission="write",
237
237
  validate=_criteria_validate,
238
238
  apply=_criteria_apply,
239
- usage="/orbi add-criteria",
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 add-criteria` to add these and start.",
250
- "回复 `/orbi add-criteria` 即可把这些加入工单并开始交付。",
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 add-criteria` command (Issue #1929): its rejections and the ONE
347
- # receipt it leaves. Every entry is display text; the machine contract (the
348
- # markers, the labels) stays in English and outside the catalog. The heading
349
- # is display text too: it follows the message language (Issue #1971), while
350
- # the marker that makes the append idempotent is the same in every language.
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 add-criteria` only adds a `kind=criteria` "
366
+ "criteria; `/orbi amend` only adds a `kind=criteria` "
366
367
  "proposal",
367
368
  "最新一条停止评论的提案是 `{kind}`,不是验收条件;"
368
- "`/orbi add-criteria` 只添加 `kind=criteria` 提案",
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.24
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: each proposal answers one missing piece only.
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
- "ONLY single_outcome failed",
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 add-criteria`: the proposed acceptance criteria start the ticket
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 add-criteria", permission: str = "write",
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", "add-criteria", 0, "write",
541
+ "add_criteria", "amend", 0, "write",
541
542
  )
542
- assert spec.usage == "/orbi add-criteria"
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 add-criteria --repo … --issue N` (Issue #1930) is the same
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