specfuse-loop 0.22.2__tar.gz → 0.23.0__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 (614) hide show
  1. {specfuse_loop-0.22.2/specfuse_loop.egg-info → specfuse_loop-0.23.0}/PKG-INFO +1 -1
  2. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/pyproject.toml +1 -1
  3. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/agent/providers/bugs.py +28 -2
  4. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/agent/severity_backfill.py +58 -5
  5. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/agent_policy.py +89 -0
  6. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/arm_eval.py +1 -0
  7. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/bug_lane.py +44 -0
  8. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/bug_lane_run.py +149 -5
  9. specfuse_loop-0.23.0/specfuse/loop/data/VERSION +1 -0
  10. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/labels.py +18 -0
  11. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/loop.py +189 -10
  12. specfuse_loop-0.23.0/specfuse/loop/red_on_base.py +123 -0
  13. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/triage.py +87 -1
  14. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/monitor/autofix_invoke.py +12 -1
  15. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0/specfuse_loop.egg-info}/PKG-INFO +1 -1
  16. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse_loop.egg-info/SOURCES.txt +8 -0
  17. specfuse_loop-0.23.0/tests/test_attribution_halt_claims.py +139 -0
  18. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_autofix_invoke.py +13 -4
  19. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_baseline_escalation_message.py +18 -5
  20. specfuse_loop-0.23.0/tests/test_dispatch_model_from_fix_scope.py +227 -0
  21. specfuse_loop-0.23.0/tests/test_ignored_cleanup_before_probe.py +292 -0
  22. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_label_registry.py +9 -2
  23. specfuse_loop-0.23.0/tests/test_needs_decision_outcome.py +126 -0
  24. specfuse_loop-0.23.0/tests/test_red_on_base_guardrail.py +114 -0
  25. specfuse_loop-0.23.0/tests/test_red_on_base_probe.py +182 -0
  26. specfuse_loop-0.23.0/tests/test_severity_backfill_end_to_end.py +215 -0
  27. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_severity_backfill_run.py +98 -0
  28. specfuse_loop-0.23.0/tests/test_severity_reconciliation.py +177 -0
  29. specfuse_loop-0.22.2/specfuse/loop/data/VERSION +0 -1
  30. specfuse_loop-0.22.2/tests/test_severity_backfill_end_to_end.py +0 -86
  31. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/LICENSE +0 -0
  32. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/NOTICE +0 -0
  33. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/README.md +0 -0
  34. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/setup.cfg +0 -0
  35. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/agent/__init__.py +0 -0
  36. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/agent/budget.py +0 -0
  37. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/agent/diagnose_invoke.py +0 -0
  38. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/agent/drafting_answers.py +0 -0
  39. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/agent/drafting_invoke.py +0 -0
  40. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/agent/drafting_questions.py +0 -0
  41. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/agent/driver_command.py +0 -0
  42. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/agent/driver_invoke.py +0 -0
  43. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/agent/invoke.py +0 -0
  44. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/agent/monitoring_read.py +0 -0
  45. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/agent/providers/__init__.py +0 -0
  46. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/agent/providers/answers.py +0 -0
  47. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/agent/providers/feature.py +0 -0
  48. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/agent/providers/findings_autofix.py +0 -0
  49. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/agent/providers/findings_diagnose.py +0 -0
  50. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/agent/providers/triage.py +0 -0
  51. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/agent/queue_read.py +0 -0
  52. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/agent/repo_detect.py +0 -0
  53. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/agent/run.py +0 -0
  54. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/agent/state.py +0 -0
  55. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/agent/triage_invoke.py +0 -0
  56. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/agent/worktree.py +0 -0
  57. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/__init__.py +0 -0
  58. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/_filelock.py +0 -0
  59. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/_miniyaml.py +0 -0
  60. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/_wu_sections.py +0 -0
  61. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/adopt_feature.py +0 -0
  62. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/arm_sweep.py +0 -0
  63. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/arm_txn.py +0 -0
  64. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/bug_lane_state.py +0 -0
  65. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/build_provenance.py +0 -0
  66. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/caller_check.py +0 -0
  67. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/changelog.py +0 -0
  68. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/closing_requirements.py +0 -0
  69. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/cost.py +0 -0
  70. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/criteria_state.py +0 -0
  71. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/data/CHANGELOG.seed.md +0 -0
  72. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/data/LEARNINGS.template.md +0 -0
  73. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/data/docs/concepts/adopting-auto-mode.md +0 -0
  74. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/data/docs/concepts/architecture-addendum-gates-and-iterative-planning.md +0 -0
  75. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/data/docs/concepts/autonomy-stop-classes.md +0 -0
  76. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/data/docs/concepts/ralph-lineage.md +0 -0
  77. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/data/docs/getting-started.md +0 -0
  78. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/data/docs/glossary.md +0 -0
  79. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/data/docs/methodology.md +0 -0
  80. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/data/docs/skills.md +0 -0
  81. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/data/gitignore.snippet +0 -0
  82. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/data/monitoring-secrets-checklist.md +0 -0
  83. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/data/monitoring.overrides.yml.example +0 -0
  84. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/data/monitoring.yml.example +0 -0
  85. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/data/roadmap.template.md +0 -0
  86. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/data/rules/close-discipline.md +0 -0
  87. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/data/rules/correlation-ids.md +0 -0
  88. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/data/rules/design-for-diagnosis.md +0 -0
  89. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/data/rules/human-output.md +0 -0
  90. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/data/rules/never-touch.md +0 -0
  91. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/data/rules/operator-escalation.md +0 -0
  92. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/data/rules/planning-discipline.md +0 -0
  93. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/data/rules/result-contract.md +0 -0
  94. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/data/rules/security-boundaries.md +0 -0
  95. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/data/rules/verification-discipline.md +0 -0
  96. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/data/rules-local/README.md +0 -0
  97. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/data/schemas/driver-event.schema.json +0 -0
  98. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/data/schemas/event.schema.json +0 -0
  99. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/data/schemas/events/initiative_created.schema.json +0 -0
  100. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/data/schemas/events/spec_issue_resolved.schema.json +0 -0
  101. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/data/schemas/events/spec_issue_routed.schema.json +0 -0
  102. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/data/schemas/events/spec_validated.schema.json +0 -0
  103. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/data/templates/DECISIONS.template.md +0 -0
  104. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/data/templates/GATE.template.md +0 -0
  105. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/data/templates/LEARNINGS-pending.template.md +0 -0
  106. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/data/templates/PLAN.template.md +0 -0
  107. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/data/templates/WU.template.md +0 -0
  108. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/data/verification.yml.example +0 -0
  109. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/data/workflows/specfuse-monitor.yml +0 -0
  110. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/decisions_format.py +0 -0
  111. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/driver_edit.py +0 -0
  112. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/escalation.py +0 -0
  113. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/events_stats.py +0 -0
  114. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/gate_commands.py +0 -0
  115. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/gate_eval.py +0 -0
  116. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/gh_backend.py +0 -0
  117. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/gh_features.py +0 -0
  118. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/heartbeat.py +0 -0
  119. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/judge.py +0 -0
  120. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/learnings_distil.py +0 -0
  121. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/learnings_query.py +0 -0
  122. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/lint_closing.py +0 -0
  123. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/lint_monitoring.py +0 -0
  124. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/lint_plan.py +0 -0
  125. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/lint_roadmap.py +0 -0
  126. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/notify.py +0 -0
  127. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/notify_escalation.py +0 -0
  128. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/notify_sla.py +0 -0
  129. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/plan_baseline.py +0 -0
  130. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/policy_proposals.py +0 -0
  131. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/policy_review.py +0 -0
  132. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/prerun.py +0 -0
  133. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/prerun_capture.py +0 -0
  134. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/promotion.py +0 -0
  135. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/rearm_migration.py +0 -0
  136. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/replay_spin.py +0 -0
  137. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/roadmap_archive.py +0 -0
  138. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/scaffold.py +0 -0
  139. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/upgrade_merge_gate.py +0 -0
  140. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/loop/validate_event.py +0 -0
  141. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/monitor/__init__.py +0 -0
  142. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/monitor/adapters.py +0 -0
  143. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/monitor/artifact.py +0 -0
  144. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/monitor/autofix.py +0 -0
  145. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/monitor/autofix_run.py +0 -0
  146. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/monitor/autofix_state.py +0 -0
  147. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/monitor/cli.py +0 -0
  148. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/monitor/diagnose_cli.py +0 -0
  149. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/monitor/diagnosis.py +0 -0
  150. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/monitor/fingerprint.py +0 -0
  151. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/monitor/issues.py +0 -0
  152. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/monitor/providers/__init__.py +0 -0
  153. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/monitor/providers/_azure_auth.py +0 -0
  154. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/monitor/providers/azure_app_insights.py +0 -0
  155. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/monitor/providers/azure_service_bus.py +0 -0
  156. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/monitor/redaction.py +0 -0
  157. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse/monitor/schedule.py +0 -0
  158. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse_loop.egg-info/dependency_links.txt +0 -0
  159. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse_loop.egg-info/entry_points.txt +0 -0
  160. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse_loop.egg-info/requires.txt +0 -0
  161. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/specfuse_loop.egg-info/top_level.txt +0 -0
  162. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_abandon_feature_skill.py +0 -0
  163. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_abandoned_work_reported.py +0 -0
  164. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_abandoned_wu_strands_gate.py +0 -0
  165. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_adopt_feature.py +0 -0
  166. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_agent_budget.py +0 -0
  167. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_agent_bug_selection_dedup.py +0 -0
  168. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_agent_default_features_root.py +0 -0
  169. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_agent_driver_command.py +0 -0
  170. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_agent_driver_invoke.py +0 -0
  171. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_agent_driver_restart.py +0 -0
  172. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_agent_escalation_on_issue.py +0 -0
  173. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_agent_escalation_run_state.py +0 -0
  174. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_agent_findings_seam.py +0 -0
  175. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_agent_invoke_timeout.py +0 -0
  176. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_agent_invoke_usage.py +0 -0
  177. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_agent_item_worktree.py +0 -0
  178. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_agent_lock.py +0 -0
  179. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_agent_policy_key_ownership.py +0 -0
  180. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_agent_policy_queue.py +0 -0
  181. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_agent_policy_schema.py +0 -0
  182. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_agent_policy_triage_dial.py +0 -0
  183. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_agent_provider_answers.py +0 -0
  184. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_agent_provider_bugs.py +0 -0
  185. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_agent_provider_feature.py +0 -0
  186. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_agent_provider_findings_autofix.py +0 -0
  187. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_agent_provider_findings_diagnose.py +0 -0
  188. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_agent_provider_isolation.py +0 -0
  189. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_agent_provider_triage.py +0 -0
  190. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_agent_queue_read.py +0 -0
  191. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_agent_relays_driver_output.py +0 -0
  192. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_agent_repo_detect.py +0 -0
  193. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_agent_run.py +0 -0
  194. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_agent_run_progress.py +0 -0
  195. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_agent_seam.py +0 -0
  196. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_agent_snapshot_refresh.py +0 -0
  197. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_agent_state.py +0 -0
  198. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_answer_escalation_skill.py +0 -0
  199. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_app_insights_adapters.py +0 -0
  200. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_archive_ref_reconciliation.py +0 -0
  201. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_arm_eval.py +0 -0
  202. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_arm_eval_lint_class.py +0 -0
  203. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_arm_eval_wiring.py +0 -0
  204. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_arm_gate_edits_uncommitted.py +0 -0
  205. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_arm_sweep.py +0 -0
  206. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_arm_sweep_gate.py +0 -0
  207. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_arm_txn.py +0 -0
  208. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_arm_wiring.py +0 -0
  209. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_artifact_redaction.py +0 -0
  210. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_attempt_note_untracked_files.py +0 -0
  211. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_attempt_outcome_contract.py +0 -0
  212. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_attempt_outcome_emission.py +0 -0
  213. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_attention_nonwriting_guard.py +0 -0
  214. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_attention_silence_section.py +0 -0
  215. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_attention_skill_structure.py +0 -0
  216. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_autoclose_deferral_visibility.py +0 -0
  217. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_autoclose_gate_note.py +0 -0
  218. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_autoclose_stub_states_what_passed.py +0 -0
  219. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_autofix_live.py +0 -0
  220. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_autofix_predicate.py +0 -0
  221. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_autofix_run.py +0 -0
  222. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_autofix_state.py +0 -0
  223. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_autosync.py +0 -0
  224. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_autosync_consent.py +0 -0
  225. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_autosync_firstrun.py +0 -0
  226. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_autosync_no_cwd_leak.py +0 -0
  227. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_autosync_plugin.py +0 -0
  228. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_azure_credential_resolution.py +0 -0
  229. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_backend.py +0 -0
  230. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_baseline_persistence.py +0 -0
  231. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_baseline_probe.py +0 -0
  232. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_baseline_provenance.py +0 -0
  233. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_baseline_tree_hash_key.py +0 -0
  234. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_bash_routing.py +0 -0
  235. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_bats_suites_gated.py +0 -0
  236. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_binary_verdict.py +0 -0
  237. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_binding_block_advisory.py +0 -0
  238. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_binding_block_allocation.py +0 -0
  239. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_binding_block_budget.py +0 -0
  240. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_bookkeeping_commit_crash_run.py +0 -0
  241. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_bookkeeping_commit_hook_crash.py +0 -0
  242. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_bookkeeping_no_verify.py +0 -0
  243. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_brief_covers_every_unit_escalation.py +0 -0
  244. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_bug_lane_ci_pending.py +0 -0
  245. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_bug_lane_guardrails.py +0 -0
  246. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_bug_lane_label_nonfatal.py +0 -0
  247. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_bug_lane_labels_registered.py +0 -0
  248. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_bug_lane_min_severity.py +0 -0
  249. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_bug_lane_policy_contract.py +0 -0
  250. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_bug_lane_pr_number_carried.py +0 -0
  251. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_bug_lane_preflight_and_dial.py +0 -0
  252. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_bug_lane_run.py +0 -0
  253. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_bug_lane_state.py +0 -0
  254. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_bug_lane_stopped_outcome_pr_number.py +0 -0
  255. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_bug_lane_test_paths.py +0 -0
  256. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_build_provenance.py +0 -0
  257. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_caller_check_ratchet.py +0 -0
  258. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_changed_file_test_selection.py +0 -0
  259. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_changelog_append_only.py +0 -0
  260. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_changelog_collection.py +0 -0
  261. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_changelog_release_wiring.py +0 -0
  262. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_changelog_schema.py +0 -0
  263. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_ci_conclusion_wait.py +0 -0
  264. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_ci_runs_every_gate.py +0 -0
  265. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_claude_resolution.py +0 -0
  266. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_close_b_pending_learnings.py +0 -0
  267. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_close_records_feature_oracle.py +0 -0
  268. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_close_wu_self_reference.py +0 -0
  269. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_closing_deliverable_guard.py +0 -0
  270. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_closing_guard_contracts.py +0 -0
  271. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_closing_guard_prediction.py +0 -0
  272. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_closing_learnings_destination.py +0 -0
  273. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_closing_requirements.py +0 -0
  274. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_coauthor_trailer_allowlist.py +0 -0
  275. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_conditional_reflection.py +0 -0
  276. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_console_encoding.py +0 -0
  277. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_convergent_iteration.py +0 -0
  278. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_convergent_wu_lint.py +0 -0
  279. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_correlation_id_override.py +0 -0
  280. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_correlation_id_two_surfaces.py +0 -0
  281. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_cost_lifetime.py +0 -0
  282. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_cost_tracking.py +0 -0
  283. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_criteria_per_gate_lint.py +0 -0
  284. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_criteria_state.py +0 -0
  285. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_criteria_worklist.py +0 -0
  286. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_decision_citation_lint.py +0 -0
  287. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_decision_followups_discharged.py +0 -0
  288. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_decision_override_lint.py +0 -0
  289. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_decisions_format.py +0 -0
  290. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_default_severity_aliases.py +0 -0
  291. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_degraded_oracle.py +0 -0
  292. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_deliverable_missing_note.py +0 -0
  293. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_deliverable_presence_gate.py +0 -0
  294. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_derive_agent_policy_review_mode.py +0 -0
  295. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_derive_agent_policy_skill.py +0 -0
  296. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_derive_monitoring_discovery.py +0 -0
  297. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_derive_monitoring_skill_registration.py +0 -0
  298. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_design_for_diagnosis_rule.py +0 -0
  299. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_deterministic_refusal_repeat.py +0 -0
  300. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_diagnose_headless.py +0 -0
  301. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_diagnose_issue_skill.py +0 -0
  302. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_diagnosis.py +0 -0
  303. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_diagnosis_roundtrip_live.py +0 -0
  304. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_dispatch_skeleton.py +0 -0
  305. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_distilled_accept_step.py +0 -0
  306. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_doctor.py +0 -0
  307. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_doctor_command_preflight.py +0 -0
  308. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_done_feature_gates.py +0 -0
  309. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_draft_feature_answers_mode.py +0 -0
  310. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_drafting_answer_gate.py +0 -0
  311. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_drafting_invoke.py +0 -0
  312. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_drafting_question_issue.py +0 -0
  313. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_drafting_questions.py +0 -0
  314. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_drafting_reply_shape.py +0 -0
  315. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_driver_edit_detection.py +0 -0
  316. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_driver_event_registry_covers_emitters.py +0 -0
  317. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_driver_integration.py +0 -0
  318. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_driver_lock.py +0 -0
  319. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_driver_module_surface.py +0 -0
  320. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_driver_restart_halt_surfaced.py +0 -0
  321. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_driver_restart_halt_wiring.py +0 -0
  322. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_driver_restart_hold.py +0 -0
  323. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_driver_staleness_gate_summary.py +0 -0
  324. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_driver_staleness_warning.py +0 -0
  325. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_dry_run_no_branch_mutation.py +0 -0
  326. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_duration_tracking.py +0 -0
  327. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_empty_files_escalation.py +0 -0
  328. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_ensure_feature_branch.py +0 -0
  329. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_ensure_feature_branch_base.py +0 -0
  330. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_escalation_assignee_policy.py +0 -0
  331. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_escalation_contract.py +0 -0
  332. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_escalation_crash_containment.py +0 -0
  333. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_escalation_dedup_search.py +0 -0
  334. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_escalation_emit.py +0 -0
  335. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_event_gate_full_envelope.py +0 -0
  336. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_events_redaction.py +0 -0
  337. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_events_stats.py +0 -0
  338. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_extra_gates.py +0 -0
  339. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_failure_artifact_model.py +0 -0
  340. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_failure_signature_fail_prefix.py +0 -0
  341. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_failure_signature_skips_log_noise.py +0 -0
  342. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_feature_branch_auto_sync.py +0 -0
  343. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_feature_branch_squash_merge.py +0 -0
  344. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_feature_branch_stale_base.py +0 -0
  345. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_feature_id_resolution.py +0 -0
  346. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_feature_oracle_e2e.py +0 -0
  347. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_feature_provider_drafting_dispatch.py +0 -0
  348. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_feature_review_accumulation.py +0 -0
  349. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_filelock_portable.py +0 -0
  350. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_files_changed_auto_repair.py +0 -0
  351. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_find_pr_no_search_index.py +0 -0
  352. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_fingerprint.py +0 -0
  353. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_fix_bug_diff_self_check.py +0 -0
  354. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_fix_bug_headless.py +0 -0
  355. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_fix_bug_reads_comments.py +0 -0
  356. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_fold_contract_documented.py +0 -0
  357. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_followups_artifact.py +0 -0
  358. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_force_full_close.py +0 -0
  359. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_frontmatter_scalar_quoting.py +0 -0
  360. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_gate_broad_run.py +0 -0
  361. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_gate_budget.py +0 -0
  362. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_gate_budget_post_dispatch.py +0 -0
  363. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_gate_commands_needs.py +0 -0
  364. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_gate_eval.py +0 -0
  365. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_gate_eval_calibration.py +0 -0
  366. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_gate_eval_intermediate_wiring.py +0 -0
  367. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_gate_eval_repo_flag.py +0 -0
  368. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_gate_eval_terminal_wiring.py +0 -0
  369. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_gate_report_verdict.py +0 -0
  370. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_gh_backend.py +0 -0
  371. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_gh_features.py +0 -0
  372. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_git_env_isolation.py +0 -0
  373. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_gitleaks_pinning.py +0 -0
  374. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_groom_backlog_skill.py +0 -0
  375. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_guard_refusal_failure_class.py +0 -0
  376. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_guard_refusal_failure_excerpt.py +0 -0
  377. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_guard_repair_e2e.py +0 -0
  378. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_guard_repair_prompt.py +0 -0
  379. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_hashed_denylist.py +0 -0
  380. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_hashed_denylist_ci.py +0 -0
  381. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_heartbeat.py +0 -0
  382. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_heartbeat_adapter.py +0 -0
  383. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_human_output_rule.py +0 -0
  384. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_human_work_unit.py +0 -0
  385. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_hygiene_boundary_guidance.py +0 -0
  386. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_hyphenated_gate_name.py +0 -0
  387. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_init_integration.py +0 -0
  388. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_installed_copy_driver_e2e.py +0 -0
  389. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_interpreter_normalization.py +0 -0
  390. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_judge_close_path.py +0 -0
  391. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_judge_cost.py +0 -0
  392. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_judge_cost_fold_committed.py +0 -0
  393. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_judge_module.py +0 -0
  394. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_judge_path_registry.py +0 -0
  395. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_judge_terminal_flip_state.py +0 -0
  396. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_label_provisioning_runner_contract.py +0 -0
  397. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_label_registry_covers_consumers.py +0 -0
  398. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_language_aware_narrow_selection.py +0 -0
  399. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_lazy_baseline_e2e.py +0 -0
  400. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_leak_findings_redaction.py +0 -0
  401. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_leak_scan.py +0 -0
  402. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_leak_scan_content.py +0 -0
  403. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_learnings_curate_vendored_promote.py +0 -0
  404. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_learnings_distil_cli.py +0 -0
  405. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_learnings_query.py +0 -0
  406. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_learnings_staging.py +0 -0
  407. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_learnings_suggest_signature_filter.py +0 -0
  408. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_learnings_weights.py +0 -0
  409. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_legacy_4wu_terminal_flips.py +0 -0
  410. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_lifecycle_integration.py +0 -0
  411. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_lifetime_accounting.py +0 -0
  412. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_lint_ac_observable.py +0 -0
  413. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_lint_bare_produces_path.py +0 -0
  414. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_lint_boundary_consistency.py +0 -0
  415. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_lint_boundary_extraction.py +0 -0
  416. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_lint_close_intermediate.py +0 -0
  417. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_lint_close_verdict_not_predecided.py +0 -0
  418. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_lint_close_wu.py +0 -0
  419. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_lint_closing.py +0 -0
  420. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_lint_closing_criteria.py +0 -0
  421. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_lint_closing_criteria_pristine.py +0 -0
  422. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_lint_correlation_id.py +0 -0
  423. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_lint_correlation_id_close_intermediate.py +0 -0
  424. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_lint_covers_load_wu_invariants.py +0 -0
  425. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_lint_feature_oracle_declared.py +0 -0
  426. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_lint_gate_proportionality.py +0 -0
  427. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_lint_monitoring.py +0 -0
  428. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_lint_oracle_env.py +0 -0
  429. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_lint_plan_contract_fields.py +0 -0
  430. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_lint_plan_errors.py +0 -0
  431. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_lint_plan_next_draft.py +0 -0
  432. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_lint_plan_relative_import.py +0 -0
  433. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_lint_plan_verdict_exempt.py +0 -0
  434. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_lint_planning_sections.py +0 -0
  435. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_lint_produces_driver_helper.py +0 -0
  436. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_lint_produces_satisfiability.py +0 -0
  437. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_lint_roadmap.py +0 -0
  438. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_lint_sections.py +0 -0
  439. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_lint_task_graph_yaml_selection.py +0 -0
  440. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_lint_verification_reference.py +0 -0
  441. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_live_tests_opt_in.py +0 -0
  442. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_load_bearing_close_lint.py +0 -0
  443. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_loop_attempt_evidence.py +0 -0
  444. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_loop_auto_archive.py +0 -0
  445. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_loop_caveman_preamble.py +0 -0
  446. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_loop_close_intermediate.py +0 -0
  447. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_loop_criteria_skeleton.py +0 -0
  448. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_loop_criteria_survival.py +0 -0
  449. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_loop_defaults_by_type.py +0 -0
  450. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_loop_effort.py +0 -0
  451. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_loop_failure_note_cap.py +0 -0
  452. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_loop_files_changed_guard.py +0 -0
  453. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_loop_gate_budget.py +0 -0
  454. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_loop_gate_needs.py +0 -0
  455. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_loop_model_alias.py +0 -0
  456. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_loop_orchestration.py +0 -0
  457. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_loop_post_pass_invariant.py +0 -0
  458. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_loop_reset_preserving_events.py +0 -0
  459. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_loop_smoke_runner.py +0 -0
  460. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_loop_unsandboxed.py +0 -0
  461. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_loop_worklist_injection.py +0 -0
  462. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_loop_zero_token_guard.py +0 -0
  463. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_make_repo_gpgsign_isolation.py +0 -0
  464. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_marker_dual_shape.py +0 -0
  465. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_maven_failure_excerpt.py +0 -0
  466. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_max_attempts_configurable.py +0 -0
  467. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_max_open_prs.py +0 -0
  468. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_migrate_legacy.py +0 -0
  469. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_miniyaml_equivalence.py +0 -0
  470. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_miniyaml_negative.py +0 -0
  471. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_monitor_cli.py +0 -0
  472. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_monitor_issue_lifecycle.py +0 -0
  473. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_monitor_runner_surfaces.py +0 -0
  474. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_monitoring_bootstrap_artifacts.py +0 -0
  475. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_monitoring_cron_dialect.py +0 -0
  476. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_monitoring_example.py +0 -0
  477. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_monitoring_fenced_blocks.py +0 -0
  478. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_monitoring_seed.py +0 -0
  479. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_narrow_fallback_warning.py +0 -0
  480. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_no_pytest_subprocess.py +0 -0
  481. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_notify.py +0 -0
  482. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_notify_escalation.py +0 -0
  483. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_notify_sla.py +0 -0
  484. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_operator_escalation_rule.py +0 -0
  485. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_oracle_set_declared.py +0 -0
  486. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_oracle_unrunnable_attribution.py +0 -0
  487. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_per_run_item_cap_disclosure.py +0 -0
  488. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_pick_feature_skill.py +0 -0
  489. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_pin_honesty_and_integrity.py +0 -0
  490. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_pin_marker_not_inherited.py +0 -0
  491. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_pin_marker_spawn_sites.py +0 -0
  492. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_plan_baseline.py +0 -0
  493. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_planned_cost_lint.py +0 -0
  494. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_planning_cost_floor.py +0 -0
  495. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_plugin_prose_cross_refs.py +0 -0
  496. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_plugin_qualified_dispatch.py +0 -0
  497. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_policy_budgets_applied.py +0 -0
  498. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_policy_proposals.py +0 -0
  499. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_policy_review.py +0 -0
  500. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_pr_checks_json_field.py +0 -0
  501. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_pr_reference_in_title.py +0 -0
  502. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_pre_existing_claims_cite_evidence.py +0 -0
  503. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_prep_halt_operator_surface.py +0 -0
  504. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_prepare_feature.py +0 -0
  505. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_prepare_scaffold_sync.py +0 -0
  506. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_prerun_capture.py +0 -0
  507. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_prerun_oracles.py +0 -0
  508. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_prerun_wiring.py +0 -0
  509. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_produced_document_verdict_guard.py +0 -0
  510. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_produces_bracket_literal.py +0 -0
  511. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_produces_bracket_wildcard.py +0 -0
  512. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_produces_field.py +0 -0
  513. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_produces_in_diff.py +0 -0
  514. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_produces_justification.py +0 -0
  515. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_produces_recursive_glob.py +0 -0
  516. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_produces_semantics_unified.py +0 -0
  517. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_produces_shape.py +0 -0
  518. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_progress_forward_note.py +0 -0
  519. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_progress_lines_end_to_end.py +0 -0
  520. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_promotion_ledger.py +0 -0
  521. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_provider_escalation_traces.py +0 -0
  522. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_provision_labels.py +0 -0
  523. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_queue_stalled_adapter.py +0 -0
  524. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_rearm_clears_stale_criteria_state.py +0 -0
  525. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_rearm_fold_marker.py +0 -0
  526. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_rearm_migration.py +0 -0
  527. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_red_baseline_never_reused.py +0 -0
  528. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_redaction_windows_home.py +0 -0
  529. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_reflection_required_signals.py +0 -0
  530. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_replan_end_to_end.py +0 -0
  531. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_replan_event_emission.py +0 -0
  532. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_replan_note_collision.py +0 -0
  533. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_replan_trigger.py +0 -0
  534. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_replan_turn_contract.py +0 -0
  535. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_replay_spin.py +0 -0
  536. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_required_checks.py +0 -0
  537. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_resolve_base.py +0 -0
  538. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_restart_cap_counts_progress.py +0 -0
  539. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_result_block.py +0 -0
  540. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_result_block_audience.py +0 -0
  541. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_resume_command_build.py +0 -0
  542. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_retain_on_guard_refusal_docs.py +0 -0
  543. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_roadmap_add_skill.py +0 -0
  544. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_roadmap_archive_cli.py +0 -0
  545. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_roadmap_archive_shim.py +0 -0
  546. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_roadmap_archive_skill.py +0 -0
  547. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_roadmap_archive_skill_delegates.py +0 -0
  548. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_roadmap_link_gate.py +0 -0
  549. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_roadmap_row_parser.py +0 -0
  550. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_ruff_verdict_lines.py +0 -0
  551. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_rule_import_convergence.py +0 -0
  552. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_run_level_failure_breaker.py +0 -0
  553. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_scaffold_data_in_sync.py +0 -0
  554. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_scaffold_doc_hygiene.py +0 -0
  555. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_scaffold_docs.py +0 -0
  556. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_scaffold_init.py +0 -0
  557. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_scaffold_label_provisioning.py +0 -0
  558. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_scaffold_manifest.py +0 -0
  559. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_scaffold_resources.py +0 -0
  560. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_scaffold_seed_sanity.py +0 -0
  561. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_scaffold_skew_halts.py +0 -0
  562. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_scaffold_upgrade.py +0 -0
  563. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_scaffold_wiring.py +0 -0
  564. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_schedule_dialect.py +0 -0
  565. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_seed_documents_prerun_keys.py +0 -0
  566. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_service_bus_dlq_adapter.py +0 -0
  567. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_session_env_preflight.py +0 -0
  568. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_severity_aliases.py +0 -0
  569. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_severity_backfill_apply.py +0 -0
  570. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_severity_backfill_limit.py +0 -0
  571. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_severity_backfill_marker.py +0 -0
  572. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_severity_label_projection.py +0 -0
  573. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_severity_rubric.py +0 -0
  574. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_shipped_templates_parse.py +0 -0
  575. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_skill_discovery_links.py +0 -0
  576. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_skills_vendored_in_sync.py +0 -0
  577. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_spinning_rearm_gate.py +0 -0
  578. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_spinout_brief_end_to_end.py +0 -0
  579. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_spinout_brief_replan_option.py +0 -0
  580. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_squash_commit_hook_crash.py +0 -0
  581. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_stop_rationale_captured.py +0 -0
  582. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_stranded_halt_cause.py +0 -0
  583. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_surefire_run_level_signature.py +0 -0
  584. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_surefire_verdict_lines.py +0 -0
  585. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_template_closing_shapes.py +0 -0
  586. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_terminal_flip_ownership.py +0 -0
  587. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_terminal_flips.py +0 -0
  588. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_terminal_gate_message.py +0 -0
  589. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_tiered_verification_e2e.py +0 -0
  590. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_timeout_kill_portable.py +0 -0
  591. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_triage.py +0 -0
  592. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_triage_apply.py +0 -0
  593. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_triage_severity_classify.py +0 -0
  594. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_triage_severity_end_to_end.py +0 -0
  595. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_triage_severity_write.py +0 -0
  596. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_triage_skill_contract.py +0 -0
  597. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_triage_skips_agent_escalations.py +0 -0
  598. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_tsc_verdict_lines.py +0 -0
  599. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_umbrella_version_resolution.py +0 -0
  600. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_unterminated_frontmatter.py +0 -0
  601. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_untracked_feature_folder.py +0 -0
  602. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_untracked_file_absorption.py +0 -0
  603. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_upgrade_integration.py +0 -0
  604. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_upgrade_merge_gate.py +0 -0
  605. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_validate_event.py +0 -0
  606. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_validate_event_driver_types.py +0 -0
  607. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_verdict_coupling.py +0 -0
  608. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_verify_empty_gate_set.py +0 -0
  609. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_version_consistency.py +0 -0
  610. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_version_skew.py +0 -0
  611. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_windows_gate_exec.py +0 -0
  612. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_worktree_restore.py +0 -0
  613. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_wu_execution_metadata.py +0 -0
  614. {specfuse_loop-0.22.2 → specfuse_loop-0.23.0}/tests/test_wu_template_renders_lintable.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: specfuse-loop
3
- Version: 0.22.2
3
+ Version: 0.23.0
4
4
  Summary: Local-first executor for the Specfuse Plan + Work Unit gate-cycle methodology.
5
5
  Author: Specfuse contributors
6
6
  License: Apache-2.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "specfuse-loop"
7
- version = "0.22.2"
7
+ version = "0.23.0"
8
8
  description = "Local-first executor for the Specfuse Plan + Work Unit gate-cycle methodology."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -642,6 +642,23 @@ def _timed_out_payload(issue_number: int, elapsed_seconds: float) -> EscalationP
642
642
  )
643
643
 
644
644
 
645
+ def _dispatch_profile_note(profile) -> str:
646
+ """` [dispatched opus/high via diagnosis:large]`, or empty (#3391).
647
+
648
+ Appended to every bug-lane item's `detail` so the run log states what each
649
+ item cost and why. The proposal's closing point was that without it the
650
+ effect of selecting a model by `fix_scope` is unmeasurable; a dial nobody
651
+ can evaluate is worse than no dial, because it looks like evidence.
652
+
653
+ Empty for a result carrying no profile, so a caller predating this reads
654
+ exactly as it did.
655
+ """
656
+ if not profile or len(profile) != 3:
657
+ return ""
658
+ model, effort, source = profile
659
+ return f" [dispatched {model}/{effort} via {source}]"
660
+
661
+
645
662
  class BugsProvider:
646
663
  """`ActionProvider` over the bug lane."""
647
664
 
@@ -727,6 +744,13 @@ class BugsProvider:
727
744
  spend=0,
728
745
  )
729
746
 
747
+ # #3391: state which model this item was dispatched with and where
748
+ # that came from. Without this line nobody can tell afterwards whether
749
+ # selecting by `fix_scope` helped, which makes the dial unmeasurable
750
+ # and therefore unevaluable — the one point the proposal called out as
751
+ # mattering most.
752
+ profile_note = _dispatch_profile_note(result.dispatch_profile)
753
+
730
754
  if result.outcome == OUTCOME_MERGED:
731
755
  # `run_bug_lane` (`specfuse/loop/bug_lane_run.py`, T04's file) does
732
756
  # not surface the headless `/fix-bug` session's usage envelope on
@@ -734,7 +758,9 @@ class BugsProvider:
734
758
  # change there, out of this WU's reach (`specfuse/loop/` is
735
759
  # off-limits except what T01 declares). Explicit `spend=0` rather
736
760
  # than a value this module cannot actually measure.
737
- return ActionOutcome(status=STATUS_COMPLETED, detail=result.reason or "", spend=0)
761
+ return ActionOutcome(
762
+ status=STATUS_COMPLETED,
763
+ detail=f"{result.reason or ''}{profile_note}", spend=0)
738
764
 
739
765
  detail = result.reason if result.reason is not None else result.outcome
740
766
 
@@ -796,7 +822,7 @@ class BugsProvider:
796
822
  # queue holds three feature-scoped issues, not that anything is broken.
797
823
  return ActionOutcome(
798
824
  status=STATUS_ESCALATED,
799
- detail=detail,
825
+ detail=f"{detail}{profile_note}",
800
826
  escalation=escalation,
801
827
  environmental=not result.outcome_named,
802
828
  )
@@ -43,7 +43,7 @@ from specfuse.loop.labels import (
43
43
  severity_label_projection,
44
44
  )
45
45
 
46
- from specfuse.loop import triage
46
+ from specfuse.loop import agent_policy, triage
47
47
  from specfuse.loop.build_provenance import warn_if_out_of_tree
48
48
 
49
49
  #: Stands in for T10's classification session (gate 3, walking skeleton).
@@ -105,6 +105,7 @@ def apply_severity_backfill(
105
105
  "number": number,
106
106
  "severity": severity,
107
107
  "skipped": False,
108
+ "severity_source": decision.get("severity_source", "classifier"),
108
109
  }
109
110
 
110
111
  new_body = triage.amend_marker_severity(body, severity)
@@ -121,6 +122,16 @@ def apply_severity_backfill(
121
122
  continue
122
123
  row["marker_written"] = True
123
124
 
125
+ if decision.get("severity_source") == "label":
126
+ # Reconciled (#3360): the label IS the source of this severity, so
127
+ # it is already on the issue. Writing one would at best re-apply
128
+ # what is there and at worst add a second spelling beside the
129
+ # person's, which is the collision this path exists to avoid.
130
+ row["label_written"] = False
131
+ row["label_skipped_reason"] = "severity read from the issue's own label"
132
+ results.append(row)
133
+ continue
134
+
124
135
  label = (label_projection or {}).get(
125
136
  severity, triage.severity_label_for(severity)
126
137
  )
@@ -206,6 +217,8 @@ def run_backfill(
206
217
  ),
207
218
  }
208
219
 
220
+ aliases = agent_policy.resolve_severity_aliases()
221
+
209
222
  rows = []
210
223
  decisions = []
211
224
  for issue in candidates:
@@ -213,6 +226,31 @@ def run_backfill(
213
226
  title = issue.get("title", "")
214
227
  body = issue.get("body") or ""
215
228
 
229
+ # The label is consulted BEFORE any classification session (#3360): an
230
+ # issue a person already labelled must have its marker written from
231
+ # that label, never from a fresh opinion about it. This also means a
232
+ # reconciled issue costs no `claude` invocation at all.
233
+ action, labelled_severity, reason = triage.severity_decision(
234
+ issue.get("labels"), aliases)
235
+
236
+ if action == triage.SEVERITY_SKIP:
237
+ rows.append({
238
+ "number": number, "classified": False, "severity": None,
239
+ "severity_source": "label", "skipped_reason": reason,
240
+ })
241
+ continue
242
+
243
+ if action == triage.SEVERITY_RECONCILE:
244
+ rows.append({
245
+ "number": number, "classified": True,
246
+ "severity": labelled_severity, "severity_source": "label",
247
+ })
248
+ decisions.append({
249
+ "number": number, "body": body,
250
+ "severity": labelled_severity, "severity_source": "label",
251
+ })
252
+ continue
253
+
216
254
  argv, prompt = build_invocation(
217
255
  number, title, body, repo, working_dir, model=model, effort=effort, rubric=rubric
218
256
  )
@@ -220,11 +258,14 @@ def run_backfill(
220
258
  severity = classify_severity(invoked.text, rubric)
221
259
 
222
260
  if severity is None:
223
- rows.append({"number": number, "classified": False, "severity": None})
261
+ rows.append({"number": number, "classified": False, "severity": None,
262
+ "severity_source": "classifier"})
224
263
  continue
225
264
 
226
- rows.append({"number": number, "classified": True, "severity": severity})
227
- decisions.append({"number": number, "body": body, "severity": severity})
265
+ rows.append({"number": number, "classified": True, "severity": severity,
266
+ "severity_source": "classifier"})
267
+ decisions.append({"number": number, "body": body, "severity": severity,
268
+ "severity_source": "classifier"})
228
269
 
229
270
  if apply and decisions:
230
271
  # A property of the repository's label set, not of any one issue
@@ -271,7 +312,19 @@ def _print_run_report(report: dict) -> None:
271
312
  return
272
313
  for row in report["rows"]:
273
314
  if not row["classified"]:
274
- print(f"#{row['number']}: no usable classification, skipped")
315
+ # A refusal to overwrite a person is not a classification failure,
316
+ # and #3360's re-run condition is read off these lines: it asks for
317
+ # zero rows the run would contradict, which is unanswerable if both
318
+ # print the same sentence.
319
+ reason = row.get("skipped_reason")
320
+ if reason:
321
+ print(f"#{row['number']}: left alone — {reason}")
322
+ else:
323
+ print(f"#{row['number']}: no usable classification, skipped")
324
+ continue
325
+ if row.get("severity_source") == "label":
326
+ print(f"#{row['number']}: severity={row['severity']} "
327
+ f"(reconciled from its own label, not classified)")
275
328
  continue
276
329
  print(f"#{row['number']}: severity={row['severity']}")
277
330
 
@@ -335,6 +335,95 @@ def resolve_required_checks(path: "str | Path | None" = None) -> tuple:
335
335
  return tuple(str(n).strip() for n in raw if str(n).strip())
336
336
 
337
337
 
338
+ #: What each `fix_scope` costs to dispatch, as `(model, effort)` (#3391).
339
+ #:
340
+ #: `small` is the historical default, so an issue diagnosed as a small fix is
341
+ #: dispatched exactly as every bug was before this existed. `large` is the only
342
+ #: entry that spends more: a fix whose direction has consequences is where a
343
+ #: cheaper session produced coherent, wrong work.
344
+ #:
345
+ #: `external` deliberately keeps the cheap profile. The fix lives outside this
346
+ #: repository, so the session's job is to recognise that and stop — paying for
347
+ #: a stronger model to reach the same refusal buys nothing.
348
+ DEFAULT_MODEL_BY_FIX_SCOPE = {
349
+ "small": ("sonnet", "medium"),
350
+ "large": ("opus", "high"),
351
+ "external": ("sonnet", "medium"),
352
+ }
353
+
354
+
355
+ def resolve_model_by_fix_scope(path: "str | Path | None" = None) -> dict:
356
+ """`rules.bugs.model_by_fix_scope` merged over `DEFAULT_MODEL_BY_FIX_SCOPE`.
357
+
358
+ A deployment chooses what each scope costs it: `large: [sonnet, high]` for
359
+ one that would rather not spend on Opus, and so on. Merged rather than
360
+ replaced, so naming one scope does not silently drop the others.
361
+
362
+ Every unusable value falls back to the shipped pair for that scope rather
363
+ than raising or disabling dispatch — an unreadable policy must not decide
364
+ that no bug can be worked on. An unknown scope name is ignored: it is a
365
+ typo, and inventing a profile for it would route nothing while looking
366
+ configured.
367
+ """
368
+ resolved = dict(DEFAULT_MODEL_BY_FIX_SCOPE)
369
+ try:
370
+ policy = load_policy(path)
371
+ except (FileNotFoundError, OSError):
372
+ return resolved
373
+ rules = policy.get("rules") if isinstance(policy, dict) else None
374
+ bugs = rules.get("bugs") if isinstance(rules, dict) else None
375
+ raw = bugs.get("model_by_fix_scope") if isinstance(bugs, dict) else None
376
+ if not isinstance(raw, dict):
377
+ return resolved
378
+ for scope, value in raw.items():
379
+ if scope not in resolved:
380
+ continue
381
+ if not isinstance(value, (list, tuple)) or len(value) != 2:
382
+ continue
383
+ model, effort = (str(v).strip() for v in value)
384
+ if model and effort:
385
+ resolved[scope] = (model, effort)
386
+ return resolved
387
+
388
+
389
+ def resolve_require_red_on_base(path: "str | Path | None" = None) -> bool:
390
+ """`rules.bugs.require_red_on_base` — must a new test be proven red on the
391
+ merge base before the lane may merge unattended (#3377)?
392
+
393
+ `False` unless explicitly `true`. Defaulting on would decline every merge
394
+ for any deployment that has not also declared how to run the check, which
395
+ is a silent automerge shutdown rather than a guardrail — and the reason a
396
+ deployment turns this on is precisely to bring unattended merging *back*
397
+ on evidence.
398
+ """
399
+ try:
400
+ policy = load_policy(path)
401
+ except (FileNotFoundError, OSError):
402
+ return False
403
+ rules = policy.get("rules") if isinstance(policy, dict) else None
404
+ bugs = rules.get("bugs") if isinstance(rules, dict) else None
405
+ return (bugs.get("require_red_on_base") if isinstance(bugs, dict) else None) is True
406
+
407
+
408
+ def resolve_red_on_base_command(path: "str | Path | None" = None) -> str:
409
+ """`rules.bugs.red_on_base_command` — the operator's own test command, with
410
+ a `{tests}` placeholder (#3377).
411
+
412
+ Empty when absent or unusable, which leaves the check unverifiable and so
413
+ declines the merge when it is required. That is deliberate: a guessed
414
+ command that silently matched no tests would exit non-zero, read as "red on
415
+ base", and wave every PR through — strictly worse than the gap it closes.
416
+ """
417
+ try:
418
+ policy = load_policy(path)
419
+ except (FileNotFoundError, OSError):
420
+ return ""
421
+ rules = policy.get("rules") if isinstance(policy, dict) else None
422
+ bugs = rules.get("bugs") if isinstance(rules, dict) else None
423
+ raw = bugs.get("red_on_base_command") if isinstance(bugs, dict) else None
424
+ return raw.strip() if isinstance(raw, str) else ""
425
+
426
+
338
427
  def resolve_max_open_prs(path: "str | Path | None" = None) -> "int | None":
339
428
  """`budgets.max_open_prs`, or None when unset or unusable (#3340).
340
429
 
@@ -98,6 +98,7 @@ JUDGE_MODULES = tuple(
98
98
  "bug_lane.py", # the merge-guardrail verdict
99
99
  "bug_lane_run.py", # holds the single `gh pr merge` call site
100
100
  "bug_lane_state.py", # the rolling merge cap a guardrail reads
101
+ "red_on_base.py", # produces the red-on-base verdict a merge guardrail reads (#3377)
101
102
  "upgrade_merge_gate.py", # the scaffold-upgrade merge gate
102
103
  # The dials, budgets and command sets a verdict is measured against.
103
104
  "agent_policy.py", # automerge dial, diff/merge caps, test paths
@@ -46,6 +46,15 @@ REASON_UNTRACEABLE = "untraceable_provenance"
46
46
  REASON_DAILY_CAP_REACHED = "daily_cap_reached"
47
47
  REASON_ELIGIBLE = "eligible"
48
48
  REASON_UNREADABLE_INPUT = "unreadable_input"
49
+ #: The PR's own new test passed at the merge base, so it proves nothing about
50
+ #: the fix — it asserts pre-existing behaviour, or tests something adjacent to
51
+ #: the defect (#3377).
52
+ REASON_TEST_NOT_RED_ON_BASE = "test_not_red_on_base"
53
+ #: The red-on-base check was required and could not be run. Deliberately
54
+ #: distinct from the reason above: "the check said no" and "the check never
55
+ #: ran" are different situations with different fixes, and collapsing them is
56
+ #: exactly how a guard comes to report clean because it could not look.
57
+ REASON_RED_ON_BASE_UNVERIFIED = "red_on_base_unverified"
49
58
 
50
59
  # Public label names for the declining reasons (#1420). The REASON_* values above
51
60
  # are internal identifiers and must not double as labels: they are snake_case
@@ -66,6 +75,8 @@ DECLINE_LABELS = {
66
75
  REASON_DAILY_CAP_REACHED: "bug-lane:daily-cap-reached",
67
76
  REASON_UNREADABLE_INPUT: "bug-lane:unreadable-input",
68
77
  REASON_CI_PENDING: "bug-lane:ci-pending",
78
+ REASON_TEST_NOT_RED_ON_BASE: "bug-lane:test-not-red-on-base",
79
+ REASON_RED_ON_BASE_UNVERIFIED: "bug-lane:red-on-base-unverified",
69
80
  }
70
81
 
71
82
  PROVENANCE_KINDS = ("triaged_issue", "diagnosed_finding")
@@ -155,6 +166,11 @@ def evaluate_merge_guardrails(
155
166
  # as before #1418. An explicitly malformed value still fails closed —
156
167
  # omission means "use the default", not "skip the guardrail".
157
168
  test_paths: Any = (_TESTS_PREFIX,),
169
+ # #3377. Off by default: a deployment that has not declared how to run the
170
+ # check would otherwise have every merge declined on upgrade, which is a
171
+ # silent automerge shutdown rather than a guardrail.
172
+ require_red_on_base: Any = False,
173
+ red_on_base: Any = None,
158
174
  state_reader: MergeCapStateReader,
159
175
  ) -> MergeDecision:
160
176
  """Decide whether a bug-lane PR may auto-merge.
@@ -167,6 +183,25 @@ def evaluate_merge_guardrails(
167
183
  `state_reader` answers the rolling-24h merge-count question T03 owns the
168
184
  storage for. Any failure to evaluate an input returns
169
185
  `eligible=False`.
186
+
187
+ `red_on_base` is the one guardrail that measures whether the PR *works*
188
+ rather than what shape it has (#3377). Every other check here reads the
189
+ form of the diff; a PR can satisfy all of them and still not fix the thing
190
+ the issue reported. Audited on one consumer repository: seven lane PRs
191
+ passed all six form checks and four were incomplete for the issue they
192
+ claimed to close, two of them merging unattended and closing the issue
193
+ behind a symptom that still reproduced.
194
+
195
+ The caller runs the PR's own added test at the merge base and passes the
196
+ verdict here: `True` means it failed there (so it measures something the
197
+ fix changed), `False` means it passed there and therefore proves nothing.
198
+ Evaluated last, after every cheap check, because it is the only one whose
199
+ input costs a test run to produce.
200
+
201
+ Its limit, stated rather than implied: this catches "the test proves
202
+ nothing", not "the test proves the wrong thing". A test red on base for a
203
+ reason unrelated to the reported symptom still passes. Closing that needs
204
+ the issue to carry a machine-readable reproduction.
170
205
  """
171
206
  # The two checks that precede the CI read keep their own inline form, so
172
207
  # the CI check stays exactly where it was in the sequence and no declining
@@ -212,6 +247,15 @@ def evaluate_merge_guardrails(
212
247
  if merge_count >= max_merges:
213
248
  return _decline(REASON_DAILY_CAP_REACHED)
214
249
 
250
+ # Last, so the two test runs its input costs are only ever paid by a PR
251
+ # that has already cleared everything cheaper.
252
+ if require_red_on_base is True:
253
+ if red_on_base is True:
254
+ return MergeDecision(eligible=True, reason=REASON_ELIGIBLE)
255
+ if red_on_base is False:
256
+ return _decline(REASON_TEST_NOT_RED_ON_BASE)
257
+ return _decline(REASON_RED_ON_BASE_UNVERIFIED)
258
+
215
259
  return MergeDecision(eligible=True, reason=REASON_ELIGIBLE)
216
260
 
217
261
 
@@ -31,6 +31,7 @@ failure -- leaves the PR open, optionally labelled with the declining reason.
31
31
  from __future__ import annotations
32
32
 
33
33
  import json
34
+ import tempfile
34
35
  import re
35
36
  import time
36
37
  from dataclasses import dataclass
@@ -41,6 +42,9 @@ from specfuse.loop.agent_policy import (
41
42
  resolve_required_checks,
42
43
  bug_lane_limits,
43
44
  resolve_bug_automerge,
45
+ resolve_model_by_fix_scope,
46
+ resolve_require_red_on_base,
47
+ resolve_red_on_base_command,
44
48
  )
45
49
  from specfuse.loop.bug_lane import (
46
50
  DECLINE_LABELS,
@@ -53,7 +57,10 @@ from specfuse.loop.bug_lane import (
53
57
  )
54
58
  from specfuse.loop.bug_lane_state import GitHubMergeCapState, record_merge
55
59
  from specfuse.loop.labels import provision_labels
60
+ from specfuse.loop.red_on_base import select_test_files, test_was_red_on_base
56
61
  from specfuse.loop.triage import parse_marker
62
+ from specfuse.monitor.diagnosis import DiagnosisParseError
63
+ from specfuse.monitor.diagnosis import parse as parse_diagnosis
57
64
  from specfuse.monitor.autofix_invoke import (
58
65
  build_invocation,
59
66
  classify_outcome,
@@ -84,6 +91,7 @@ CORRELATION_ID = "FEAT-2026-0048/T04"
84
91
 
85
92
  OUTCOME_REFUSED = "refused"
86
93
  OUTCOME_COULD_NOT_PROCEED = "could_not_proceed"
94
+ OUTCOME_NEEDS_DECISION = "needs_decision"
87
95
  OUTCOME_MERGED = "merged"
88
96
  OUTCOME_DECLINED = "declined"
89
97
 
@@ -96,7 +104,12 @@ OUTCOME_DECLINED = "declined"
96
104
  #: when the guardrails had passed and the dial was the only thing in the way.
97
105
  OUTCOME_AUTOMERGE_OFF = "automerge_off"
98
106
 
99
- _ESCALATING_OUTCOMES = (OUTCOME_REFUSED, OUTCOME_COULD_NOT_PROCEED)
107
+ #: `needs_decision` escalates like the other two: the session opened no PR,
108
+ #: so there is nothing for the guardrails to evaluate, and what the operator
109
+ #: owes is an answer rather than a review (#3390).
110
+ _ESCALATING_OUTCOMES = (
111
+ OUTCOME_REFUSED, OUTCOME_COULD_NOT_PROCEED, OUTCOME_NEEDS_DECISION,
112
+ )
100
113
 
101
114
  _CI_UNKNOWN = "unknown"
102
115
  #: Checks exist but have not concluded yet -- internal to this module; never
@@ -149,6 +162,12 @@ class BugLaneResult:
149
162
  #: was throwing it away, so three refusals in one run were three
150
163
  #: identical escalations saying only which word came back.
151
164
  stop_rationale: str = ""
165
+ #: `(model, effort, source)` this issue was dispatched with, and where that
166
+ #: profile came from -- `diagnosis:<fix_scope>` or `default` (#3391).
167
+ #: Carried so a run can state it per item: without that line nobody can
168
+ #: tell afterwards whether selecting the model by scope helped, which
169
+ #: would make the dial unmeasurable and therefore unevaluable.
170
+ dispatch_profile: tuple = ()
152
171
  #: Whether the session actually named one of `OUTCOMES`, as opposed to
153
172
  #: `classify_outcome` failing closed to `could_not_proceed` for output
154
173
  #: naming none (#3343). Defaults True so a caller constructing a result by
@@ -658,6 +677,7 @@ def _declined(
658
677
  working_dir: str,
659
678
  *,
660
679
  evidence: tuple = (),
680
+ dispatch_profile: tuple = (),
661
681
  ) -> BugLaneResult:
662
682
  """Label the PR with *reason* (best-effort) and return the declined result."""
663
683
  # The public label name, never the raw reason constant (#1420): the
@@ -675,9 +695,102 @@ def _declined(
675
695
  pr_number=pr_number,
676
696
  label_written=label_written,
677
697
  evidence=evidence,
698
+ dispatch_profile=dispatch_profile,
678
699
  )
679
700
 
680
701
 
702
+ def _merge_base_sha(runner: Callable, repo: str, pr_number: int) -> str:
703
+ """The commit the PR is measured against, or `""` (#3377).
704
+
705
+ `baseRefOid` is the tip of the base branch as GitHub sees it now, which is
706
+ what the PR would merge into — the tree the fix was written against, and so
707
+ the tree its new test must fail on. An empty string makes the probe decline
708
+ rather than guess a commit.
709
+ """
710
+ try:
711
+ result = runner(
712
+ ["gh", "pr", "view", str(pr_number), "--repo", repo,
713
+ "--json", "baseRefOid"],
714
+ check=False,
715
+ )
716
+ except Exception: # noqa: BLE001 - a lookup failure declines, never raises
717
+ return ""
718
+ if getattr(result, "returncode", 1) != 0 or not getattr(result, "stdout", None):
719
+ return ""
720
+ try:
721
+ payload = json.loads(result.stdout)
722
+ except ValueError:
723
+ return ""
724
+ sha = payload.get("baseRefOid") if isinstance(payload, dict) else None
725
+ return sha.strip() if isinstance(sha, str) else ""
726
+
727
+
728
+ def dispatch_profile_for(
729
+ runner: Callable, repo: str, issue_number: int, policy_path: Any = None,
730
+ ) -> tuple:
731
+ """`(model, effort, source)` for one issue's `/fix-bug` dispatch (#3391).
732
+
733
+ Every bug was dispatched at `sonnet`/`medium` — `build_invocation`'s
734
+ defaults, never overridden — whether it was a one-line template fix or a
735
+ change whose direction has architectural consequences. Of seven bugs a run
736
+ dispatched, the two that produced correct fixes had issues already carrying
737
+ a diagnosis that named the file and the shape of the fix; the two closed on
738
+ review as wrong reported a symptom and left the fix location open.
739
+
740
+ The signal read here is `fix_scope`, from the `/diagnose-issue` comment
741
+ `specfuse.monitor.diagnosis.parse` recovers. That is deliberate: the
742
+ alternative proposal was an `effort=` field triage guesses from issue text
743
+ in about eight seconds, and effort is a property of the codebase rather
744
+ than of the report — the issue in that run that *read* as most mechanical
745
+ was the one whose correct fix required knowing where a seed value came
746
+ from. `fix_scope` rests on something a tool produced.
747
+
748
+ `source` names where the profile came from (`diagnosis:<scope>` or
749
+ `default`) so the run can say it. Without that line nobody can tell
750
+ afterwards whether the dial helped, which makes the whole change
751
+ unmeasurable.
752
+
753
+ Never raises. An unreadable issue, an absent diagnosis and a malformed
754
+ marker all resolve to the historical default, so this can only ever spend
755
+ more deliberately — never refuse to dispatch.
756
+ """
757
+ profiles = resolve_model_by_fix_scope(policy_path)
758
+ default = profiles["small"]
759
+ try:
760
+ result = runner(
761
+ ["gh", "issue", "view", str(issue_number), "--repo", repo,
762
+ "--json", "body,comments"],
763
+ check=False,
764
+ )
765
+ except Exception: # noqa: BLE001 - a diagnostic read never stops a dispatch
766
+ return (*default, "default")
767
+ if getattr(result, "returncode", 1) != 0 or not getattr(result, "stdout", None):
768
+ return (*default, "default")
769
+ try:
770
+ payload = json.loads(result.stdout)
771
+ except ValueError:
772
+ return (*default, "default")
773
+ if not isinstance(payload, dict):
774
+ return (*default, "default")
775
+
776
+ bodies = [payload.get("body") or ""]
777
+ for comment in payload.get("comments") or []:
778
+ if isinstance(comment, dict):
779
+ bodies.append(comment.get("body") or "")
780
+
781
+ for body in bodies:
782
+ try:
783
+ diagnosis = parse_diagnosis(body)
784
+ except DiagnosisParseError:
785
+ continue
786
+ if diagnosis is None:
787
+ continue
788
+ scope = getattr(diagnosis, "fix_scope", None)
789
+ if scope in profiles:
790
+ return (*profiles[scope], f"diagnosis:{scope}")
791
+ return (*default, "default")
792
+
793
+
681
794
  def run_bug_lane(
682
795
  runner: Callable,
683
796
  repo: str,
@@ -700,7 +813,13 @@ def run_bug_lane(
700
813
  always accurate) and merges only when the dial is `on` *and* the
701
814
  guardrails are eligible -- the module's single merge call site.
702
815
  """
703
- argv, prompt = build_invocation(issue_number, repo, working_dir)
816
+ model, effort, profile_source = dispatch_profile_for(
817
+ runner, repo, issue_number, policy_path
818
+ )
819
+ _profile = (model, effort, profile_source)
820
+ argv, prompt = build_invocation(
821
+ issue_number, repo, working_dir, model=model, effort=effort
822
+ )
704
823
  invocation = runner(argv + [prompt], check=False)
705
824
  session_output = getattr(invocation, "stdout", "") or ""
706
825
  outcome = classify_outcome(session_output)
@@ -719,6 +838,7 @@ def run_bug_lane(
719
838
  unpushed_work=unpushed_work_for_issue(runner, issue_number),
720
839
  stop_rationale=extract_stop_rationale(session_output),
721
840
  outcome_named=outcome_was_named(session_output),
841
+ dispatch_profile=_profile
722
842
  )
723
843
 
724
844
  # The session's own account first (#3180) -- it never re-discovers what it
@@ -731,7 +851,9 @@ def run_bug_lane(
731
851
  pr_lookup_sleep(PR_LOOKUP_RETRY_SECONDS)
732
852
  pr_number = _find_pr_for_issue(runner, repo, issue_number)
733
853
  if pr_number is None:
734
- return BugLaneResult(outcome=OUTCOME_DECLINED, reason=_REASON_PR_NOT_FOUND, pr_number=None)
854
+ return BugLaneResult(
855
+ outcome=OUTCOME_DECLINED, reason=_REASON_PR_NOT_FOUND,
856
+ pr_number=None, dispatch_profile=_profile)
735
857
 
736
858
  changed_files, diff_lines = _pr_changed_files_and_diff_lines(runner, repo, pr_number)
737
859
  limits = bug_lane_limits(policy_path)
@@ -755,6 +877,7 @@ def run_bug_lane(
755
877
  evidence=_evidence_for(
756
878
  shape.reason, changed_files, diff_lines, limits,
757
879
  ),
880
+ dispatch_profile=_profile,
758
881
  )
759
882
 
760
883
  deadline_seconds = (
@@ -773,6 +896,21 @@ def run_bug_lane(
773
896
  )
774
897
  state_reader = GitHubMergeCapState(runner=runner, repo=repo, now=now)
775
898
 
899
+ # #3377: the only guardrail that asks whether the change works rather than
900
+ # what shape it has. Computed here and not inside the predicate, which
901
+ # stays pure — and only when the deployment asked for it, since it costs a
902
+ # test run.
903
+ require_red = resolve_require_red_on_base(policy_path)
904
+ red_on_base = None
905
+ if require_red:
906
+ red_on_base = test_was_red_on_base(
907
+ runner,
908
+ base_sha=_merge_base_sha(runner, repo, pr_number),
909
+ test_files=select_test_files(changed_files, limits["test_paths"]),
910
+ command_template=resolve_red_on_base_command(policy_path),
911
+ worktree_root=tempfile.gettempdir(),
912
+ )
913
+
776
914
  decision = evaluate_merge_guardrails(
777
915
  changed_files=changed_files,
778
916
  ci_conclusion=ci_conclusion,
@@ -781,6 +919,8 @@ def run_bug_lane(
781
919
  provenance=provenance,
782
920
  max_merges_per_day=limits["max_merges_per_day"],
783
921
  test_paths=limits["test_paths"],
922
+ require_red_on_base=require_red,
923
+ red_on_base=red_on_base,
784
924
  state_reader=state_reader,
785
925
  )
786
926
  dial = resolve_bug_automerge(policy_path)
@@ -791,7 +931,9 @@ def run_bug_lane(
791
931
  check=True,
792
932
  )
793
933
  record_merge(runner, repo, pr_number, at=time.time() if now is None else now)
794
- return BugLaneResult(outcome=OUTCOME_MERGED, reason=decision.reason, pr_number=pr_number)
934
+ return BugLaneResult(
935
+ outcome=OUTCOME_MERGED, reason=decision.reason,
936
+ pr_number=pr_number, dispatch_profile=_profile)
795
937
 
796
938
  if decision.eligible:
797
939
  # Eligible but not merged: the dial is the only thing in the way, and
@@ -799,7 +941,8 @@ def run_bug_lane(
799
941
  # declining reason to project, and `DECLINE_LABELS` deliberately has no
800
942
  # entry for `REASON_ELIGIBLE`.
801
943
  return BugLaneResult(
802
- outcome=OUTCOME_AUTOMERGE_OFF, reason=decision.reason, pr_number=pr_number
944
+ outcome=OUTCOME_AUTOMERGE_OFF, reason=decision.reason,
945
+ pr_number=pr_number, dispatch_profile=_profile,
803
946
  )
804
947
 
805
948
  # Reaching here means `decision.eligible` is False, so the reason is a real
@@ -808,4 +951,5 @@ def run_bug_lane(
808
951
  return _declined(
809
952
  runner, repo, pr_number, decision.reason, working_dir,
810
953
  evidence=_evidence_for(decision.reason, changed_files, diff_lines, limits),
954
+ dispatch_profile=_profile,
811
955
  )
@@ -0,0 +1 @@
1
+ 0.23.0
@@ -199,6 +199,24 @@ LABEL_REGISTRY: tuple[LabelSpec, ...] = (
199
199
  ),
200
200
  consumer="loop/bug_lane_run.py",
201
201
  ),
202
+ LabelSpec(
203
+ name=bug_lane.DECLINE_LABELS[bug_lane.REASON_TEST_NOT_RED_ON_BASE],
204
+ colour="fbca04",
205
+ description=(
206
+ "Bug-lane auto-merge declined: the PR's new test passes at the "
207
+ "merge base, so it proves nothing about the fix"
208
+ ),
209
+ consumer="loop/bug_lane_run.py",
210
+ ),
211
+ LabelSpec(
212
+ name=bug_lane.DECLINE_LABELS[bug_lane.REASON_RED_ON_BASE_UNVERIFIED],
213
+ colour="fbca04",
214
+ description=(
215
+ "Bug-lane auto-merge declined: the red-on-base check was required "
216
+ "and could not be run; declare rules.bugs.red_on_base_command"
217
+ ),
218
+ consumer="loop/bug_lane_run.py",
219
+ ),
202
220
  )
203
221
 
204
222