specfuse-loop 0.23.2__tar.gz → 0.24.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 (618) hide show
  1. {specfuse_loop-0.23.2/specfuse_loop.egg-info → specfuse_loop-0.24.0}/PKG-INFO +1 -1
  2. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/pyproject.toml +1 -1
  3. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/providers/triage.py +41 -6
  4. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/run.py +72 -1
  5. specfuse_loop-0.24.0/specfuse/loop/data/VERSION +1 -0
  6. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/docs/methodology.md +11 -1
  7. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/loop.py +36 -1
  8. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/red_on_base.py +19 -0
  9. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/triage.py +23 -3
  10. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0/specfuse_loop.egg-info}/PKG-INFO +1 -1
  11. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse_loop.egg-info/SOURCES.txt +4 -0
  12. specfuse_loop-0.24.0/tests/test_blind_run_is_not_drained.py +105 -0
  13. specfuse_loop-0.24.0/tests/test_judged_event_points_at_findings.py +137 -0
  14. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_red_on_base_probe.py +42 -0
  15. specfuse_loop-0.24.0/tests/test_spec_work_unit_rules.py +173 -0
  16. specfuse_loop-0.24.0/tests/test_triage_projects_severity_label.py +190 -0
  17. specfuse_loop-0.23.2/specfuse/loop/data/VERSION +0 -1
  18. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/LICENSE +0 -0
  19. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/NOTICE +0 -0
  20. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/README.md +0 -0
  21. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/setup.cfg +0 -0
  22. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/__init__.py +0 -0
  23. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/budget.py +0 -0
  24. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/diagnose_invoke.py +0 -0
  25. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/drafting_answers.py +0 -0
  26. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/drafting_invoke.py +0 -0
  27. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/drafting_questions.py +0 -0
  28. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/driver_command.py +0 -0
  29. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/driver_invoke.py +0 -0
  30. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/invoke.py +0 -0
  31. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/monitoring_read.py +0 -0
  32. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/providers/__init__.py +0 -0
  33. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/providers/answers.py +0 -0
  34. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/providers/bugs.py +0 -0
  35. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/providers/feature.py +0 -0
  36. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/providers/findings_autofix.py +0 -0
  37. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/providers/findings_diagnose.py +0 -0
  38. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/queue_read.py +0 -0
  39. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/repo_detect.py +0 -0
  40. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/severity_backfill.py +0 -0
  41. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/state.py +0 -0
  42. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/triage_invoke.py +0 -0
  43. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/worktree.py +0 -0
  44. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/__init__.py +0 -0
  45. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/_filelock.py +0 -0
  46. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/_miniyaml.py +0 -0
  47. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/_wu_sections.py +0 -0
  48. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/adopt_feature.py +0 -0
  49. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/agent_policy.py +0 -0
  50. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/arm_eval.py +0 -0
  51. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/arm_sweep.py +0 -0
  52. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/arm_txn.py +0 -0
  53. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/bug_lane.py +0 -0
  54. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/bug_lane_run.py +0 -0
  55. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/bug_lane_state.py +0 -0
  56. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/build_provenance.py +0 -0
  57. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/caller_check.py +0 -0
  58. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/changelog.py +0 -0
  59. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/closing_requirements.py +0 -0
  60. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/cost.py +0 -0
  61. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/criteria_state.py +0 -0
  62. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/CHANGELOG.seed.md +0 -0
  63. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/LEARNINGS.template.md +0 -0
  64. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/docs/concepts/adopting-auto-mode.md +0 -0
  65. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/docs/concepts/architecture-addendum-gates-and-iterative-planning.md +0 -0
  66. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/docs/concepts/autonomy-stop-classes.md +0 -0
  67. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/docs/concepts/ralph-lineage.md +0 -0
  68. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/docs/getting-started.md +0 -0
  69. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/docs/glossary.md +0 -0
  70. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/docs/skills.md +0 -0
  71. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/gitignore.snippet +0 -0
  72. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/monitoring-secrets-checklist.md +0 -0
  73. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/monitoring.overrides.yml.example +0 -0
  74. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/monitoring.yml.example +0 -0
  75. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/roadmap.template.md +0 -0
  76. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/rules/close-discipline.md +0 -0
  77. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/rules/correlation-ids.md +0 -0
  78. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/rules/design-for-diagnosis.md +0 -0
  79. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/rules/human-output.md +0 -0
  80. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/rules/never-touch.md +0 -0
  81. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/rules/operator-escalation.md +0 -0
  82. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/rules/planning-discipline.md +0 -0
  83. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/rules/result-contract.md +0 -0
  84. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/rules/security-boundaries.md +0 -0
  85. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/rules/verification-discipline.md +0 -0
  86. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/rules-local/README.md +0 -0
  87. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/schemas/driver-event.schema.json +0 -0
  88. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/schemas/event.schema.json +0 -0
  89. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/schemas/events/initiative_created.schema.json +0 -0
  90. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/schemas/events/spec_issue_resolved.schema.json +0 -0
  91. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/schemas/events/spec_issue_routed.schema.json +0 -0
  92. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/schemas/events/spec_validated.schema.json +0 -0
  93. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/templates/DECISIONS.template.md +0 -0
  94. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/templates/GATE.template.md +0 -0
  95. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/templates/LEARNINGS-pending.template.md +0 -0
  96. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/templates/PLAN.template.md +0 -0
  97. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/templates/WU.template.md +0 -0
  98. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/verification.yml.example +0 -0
  99. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/workflows/specfuse-monitor.yml +0 -0
  100. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/decisions_format.py +0 -0
  101. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/driver_edit.py +0 -0
  102. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/escalation.py +0 -0
  103. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/events_stats.py +0 -0
  104. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/gate_commands.py +0 -0
  105. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/gate_eval.py +0 -0
  106. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/gh_backend.py +0 -0
  107. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/gh_features.py +0 -0
  108. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/heartbeat.py +0 -0
  109. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/judge.py +0 -0
  110. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/labels.py +0 -0
  111. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/learnings_distil.py +0 -0
  112. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/learnings_query.py +0 -0
  113. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/lint_closing.py +0 -0
  114. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/lint_monitoring.py +0 -0
  115. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/lint_plan.py +0 -0
  116. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/lint_roadmap.py +0 -0
  117. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/notify.py +0 -0
  118. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/notify_escalation.py +0 -0
  119. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/notify_sla.py +0 -0
  120. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/plan_baseline.py +0 -0
  121. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/policy_proposals.py +0 -0
  122. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/policy_review.py +0 -0
  123. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/prerun.py +0 -0
  124. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/prerun_capture.py +0 -0
  125. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/promotion.py +0 -0
  126. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/rearm_migration.py +0 -0
  127. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/replay_spin.py +0 -0
  128. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/roadmap_archive.py +0 -0
  129. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/scaffold.py +0 -0
  130. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/upgrade_merge_gate.py +0 -0
  131. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/validate_event.py +0 -0
  132. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/monitor/__init__.py +0 -0
  133. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/monitor/adapters.py +0 -0
  134. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/monitor/artifact.py +0 -0
  135. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/monitor/autofix.py +0 -0
  136. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/monitor/autofix_invoke.py +0 -0
  137. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/monitor/autofix_run.py +0 -0
  138. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/monitor/autofix_state.py +0 -0
  139. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/monitor/cli.py +0 -0
  140. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/monitor/diagnose_cli.py +0 -0
  141. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/monitor/diagnosis.py +0 -0
  142. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/monitor/fingerprint.py +0 -0
  143. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/monitor/issues.py +0 -0
  144. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/monitor/providers/__init__.py +0 -0
  145. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/monitor/providers/_azure_auth.py +0 -0
  146. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/monitor/providers/azure_app_insights.py +0 -0
  147. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/monitor/providers/azure_service_bus.py +0 -0
  148. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/monitor/redaction.py +0 -0
  149. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/monitor/schedule.py +0 -0
  150. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse_loop.egg-info/dependency_links.txt +0 -0
  151. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse_loop.egg-info/entry_points.txt +0 -0
  152. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse_loop.egg-info/requires.txt +0 -0
  153. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse_loop.egg-info/top_level.txt +0 -0
  154. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_abandon_feature_skill.py +0 -0
  155. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_abandoned_work_reported.py +0 -0
  156. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_abandoned_wu_strands_gate.py +0 -0
  157. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_adopt_feature.py +0 -0
  158. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_budget.py +0 -0
  159. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_bug_selection_dedup.py +0 -0
  160. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_default_features_root.py +0 -0
  161. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_driver_command.py +0 -0
  162. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_driver_invoke.py +0 -0
  163. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_driver_restart.py +0 -0
  164. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_escalation_on_issue.py +0 -0
  165. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_escalation_run_state.py +0 -0
  166. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_findings_seam.py +0 -0
  167. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_invoke_timeout.py +0 -0
  168. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_invoke_usage.py +0 -0
  169. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_item_worktree.py +0 -0
  170. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_lock.py +0 -0
  171. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_policy_key_ownership.py +0 -0
  172. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_policy_queue.py +0 -0
  173. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_policy_schema.py +0 -0
  174. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_policy_triage_dial.py +0 -0
  175. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_provider_answers.py +0 -0
  176. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_provider_bugs.py +0 -0
  177. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_provider_feature.py +0 -0
  178. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_provider_findings_autofix.py +0 -0
  179. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_provider_findings_diagnose.py +0 -0
  180. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_provider_isolation.py +0 -0
  181. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_provider_triage.py +0 -0
  182. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_queue_read.py +0 -0
  183. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_relays_driver_output.py +0 -0
  184. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_repo_detect.py +0 -0
  185. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_run.py +0 -0
  186. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_run_progress.py +0 -0
  187. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_seam.py +0 -0
  188. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_snapshot_refresh.py +0 -0
  189. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_state.py +0 -0
  190. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_answer_escalation_skill.py +0 -0
  191. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_app_insights_adapters.py +0 -0
  192. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_archive_ref_reconciliation.py +0 -0
  193. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_arm_eval.py +0 -0
  194. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_arm_eval_lint_class.py +0 -0
  195. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_arm_eval_wiring.py +0 -0
  196. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_arm_gate_edits_uncommitted.py +0 -0
  197. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_arm_sweep.py +0 -0
  198. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_arm_sweep_gate.py +0 -0
  199. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_arm_txn.py +0 -0
  200. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_arm_wiring.py +0 -0
  201. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_artifact_redaction.py +0 -0
  202. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_attempt_note_untracked_files.py +0 -0
  203. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_attempt_outcome_contract.py +0 -0
  204. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_attempt_outcome_emission.py +0 -0
  205. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_attention_nonwriting_guard.py +0 -0
  206. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_attention_silence_section.py +0 -0
  207. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_attention_skill_structure.py +0 -0
  208. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_attribution_halt_claims.py +0 -0
  209. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_autoclose_deferral_visibility.py +0 -0
  210. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_autoclose_gate_note.py +0 -0
  211. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_autoclose_stub_states_what_passed.py +0 -0
  212. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_autofix_invoke.py +0 -0
  213. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_autofix_live.py +0 -0
  214. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_autofix_predicate.py +0 -0
  215. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_autofix_run.py +0 -0
  216. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_autofix_state.py +0 -0
  217. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_autosync.py +0 -0
  218. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_autosync_consent.py +0 -0
  219. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_autosync_firstrun.py +0 -0
  220. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_autosync_no_cwd_leak.py +0 -0
  221. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_autosync_plugin.py +0 -0
  222. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_azure_credential_resolution.py +0 -0
  223. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_backend.py +0 -0
  224. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_baseline_escalation_message.py +0 -0
  225. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_baseline_persistence.py +0 -0
  226. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_baseline_probe.py +0 -0
  227. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_baseline_provenance.py +0 -0
  228. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_baseline_tree_hash_key.py +0 -0
  229. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_bash_routing.py +0 -0
  230. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_bats_suites_gated.py +0 -0
  231. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_binary_verdict.py +0 -0
  232. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_binding_block_advisory.py +0 -0
  233. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_binding_block_allocation.py +0 -0
  234. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_binding_block_budget.py +0 -0
  235. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_bookkeeping_commit_crash_run.py +0 -0
  236. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_bookkeeping_commit_hook_crash.py +0 -0
  237. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_bookkeeping_no_verify.py +0 -0
  238. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_brief_covers_every_unit_escalation.py +0 -0
  239. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_bug_lane_ci_pending.py +0 -0
  240. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_bug_lane_guardrails.py +0 -0
  241. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_bug_lane_label_nonfatal.py +0 -0
  242. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_bug_lane_labels_registered.py +0 -0
  243. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_bug_lane_min_severity.py +0 -0
  244. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_bug_lane_policy_contract.py +0 -0
  245. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_bug_lane_pr_number_carried.py +0 -0
  246. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_bug_lane_preflight_and_dial.py +0 -0
  247. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_bug_lane_run.py +0 -0
  248. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_bug_lane_state.py +0 -0
  249. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_bug_lane_stopped_outcome_pr_number.py +0 -0
  250. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_bug_lane_test_paths.py +0 -0
  251. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_build_provenance.py +0 -0
  252. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_caller_check_ratchet.py +0 -0
  253. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_changed_file_test_selection.py +0 -0
  254. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_changelog_append_only.py +0 -0
  255. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_changelog_collection.py +0 -0
  256. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_changelog_release_wiring.py +0 -0
  257. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_changelog_schema.py +0 -0
  258. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_ci_conclusion_wait.py +0 -0
  259. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_ci_runs_every_gate.py +0 -0
  260. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_claude_resolution.py +0 -0
  261. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_close_b_pending_learnings.py +0 -0
  262. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_close_records_feature_oracle.py +0 -0
  263. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_close_wu_self_reference.py +0 -0
  264. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_closing_deliverable_guard.py +0 -0
  265. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_closing_guard_contracts.py +0 -0
  266. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_closing_guard_prediction.py +0 -0
  267. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_closing_learnings_destination.py +0 -0
  268. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_closing_requirements.py +0 -0
  269. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_coauthor_trailer_allowlist.py +0 -0
  270. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_conditional_reflection.py +0 -0
  271. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_console_encoding.py +0 -0
  272. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_convergent_iteration.py +0 -0
  273. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_convergent_wu_lint.py +0 -0
  274. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_correlation_id_override.py +0 -0
  275. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_correlation_id_two_surfaces.py +0 -0
  276. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_cost_lifetime.py +0 -0
  277. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_cost_tracking.py +0 -0
  278. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_criteria_per_gate_lint.py +0 -0
  279. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_criteria_state.py +0 -0
  280. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_criteria_worklist.py +0 -0
  281. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_decision_citation_lint.py +0 -0
  282. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_decision_followups_discharged.py +0 -0
  283. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_decision_override_lint.py +0 -0
  284. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_decisions_format.py +0 -0
  285. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_default_severity_aliases.py +0 -0
  286. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_degraded_oracle.py +0 -0
  287. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_deliverable_missing_note.py +0 -0
  288. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_deliverable_presence_gate.py +0 -0
  289. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_derive_agent_policy_review_mode.py +0 -0
  290. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_derive_agent_policy_skill.py +0 -0
  291. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_derive_monitoring_discovery.py +0 -0
  292. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_derive_monitoring_skill_registration.py +0 -0
  293. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_design_for_diagnosis_rule.py +0 -0
  294. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_deterministic_refusal_repeat.py +0 -0
  295. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_diagnose_headless.py +0 -0
  296. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_diagnose_issue_skill.py +0 -0
  297. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_diagnosis.py +0 -0
  298. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_diagnosis_roundtrip_live.py +0 -0
  299. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_dispatch_model_from_fix_scope.py +0 -0
  300. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_dispatch_skeleton.py +0 -0
  301. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_distilled_accept_step.py +0 -0
  302. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_doctor.py +0 -0
  303. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_doctor_command_preflight.py +0 -0
  304. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_done_feature_gates.py +0 -0
  305. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_draft_feature_answers_mode.py +0 -0
  306. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_drafting_answer_gate.py +0 -0
  307. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_drafting_invoke.py +0 -0
  308. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_drafting_question_issue.py +0 -0
  309. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_drafting_questions.py +0 -0
  310. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_drafting_reply_shape.py +0 -0
  311. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_driver_edit_detection.py +0 -0
  312. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_driver_event_registry_covers_emitters.py +0 -0
  313. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_driver_integration.py +0 -0
  314. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_driver_lock.py +0 -0
  315. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_driver_module_surface.py +0 -0
  316. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_driver_restart_halt_surfaced.py +0 -0
  317. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_driver_restart_halt_wiring.py +0 -0
  318. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_driver_restart_hold.py +0 -0
  319. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_driver_staleness_gate_summary.py +0 -0
  320. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_driver_staleness_warning.py +0 -0
  321. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_dry_run_no_branch_mutation.py +0 -0
  322. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_duration_tracking.py +0 -0
  323. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_empty_files_escalation.py +0 -0
  324. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_ensure_feature_branch.py +0 -0
  325. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_ensure_feature_branch_base.py +0 -0
  326. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_escalation_assignee_policy.py +0 -0
  327. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_escalation_contract.py +0 -0
  328. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_escalation_crash_containment.py +0 -0
  329. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_escalation_dedup_search.py +0 -0
  330. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_escalation_emit.py +0 -0
  331. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_event_gate_full_envelope.py +0 -0
  332. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_events_redaction.py +0 -0
  333. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_events_stats.py +0 -0
  334. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_extra_gates.py +0 -0
  335. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_failure_artifact_model.py +0 -0
  336. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_failure_signature_fail_prefix.py +0 -0
  337. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_failure_signature_skips_log_noise.py +0 -0
  338. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_feature_branch_auto_sync.py +0 -0
  339. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_feature_branch_squash_merge.py +0 -0
  340. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_feature_branch_stale_base.py +0 -0
  341. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_feature_id_resolution.py +0 -0
  342. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_feature_oracle_e2e.py +0 -0
  343. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_feature_provider_drafting_dispatch.py +0 -0
  344. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_feature_review_accumulation.py +0 -0
  345. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_filelock_portable.py +0 -0
  346. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_files_changed_auto_repair.py +0 -0
  347. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_find_pr_no_search_index.py +0 -0
  348. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_fingerprint.py +0 -0
  349. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_fix_bug_diff_self_check.py +0 -0
  350. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_fix_bug_headless.py +0 -0
  351. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_fix_bug_reads_comments.py +0 -0
  352. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_fold_contract_documented.py +0 -0
  353. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_followups_artifact.py +0 -0
  354. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_force_full_close.py +0 -0
  355. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_frontmatter_scalar_quoting.py +0 -0
  356. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_gate_broad_run.py +0 -0
  357. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_gate_budget.py +0 -0
  358. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_gate_budget_post_dispatch.py +0 -0
  359. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_gate_commands_needs.py +0 -0
  360. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_gate_eval.py +0 -0
  361. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_gate_eval_calibration.py +0 -0
  362. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_gate_eval_intermediate_wiring.py +0 -0
  363. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_gate_eval_repo_flag.py +0 -0
  364. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_gate_eval_terminal_wiring.py +0 -0
  365. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_gate_report_verdict.py +0 -0
  366. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_gh_backend.py +0 -0
  367. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_gh_features.py +0 -0
  368. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_git_env_isolation.py +0 -0
  369. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_gitleaks_pinning.py +0 -0
  370. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_groom_backlog_skill.py +0 -0
  371. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_guard_refusal_failure_class.py +0 -0
  372. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_guard_refusal_failure_excerpt.py +0 -0
  373. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_guard_repair_e2e.py +0 -0
  374. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_guard_repair_prompt.py +0 -0
  375. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_hashed_denylist.py +0 -0
  376. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_hashed_denylist_ci.py +0 -0
  377. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_heartbeat.py +0 -0
  378. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_heartbeat_adapter.py +0 -0
  379. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_human_output_rule.py +0 -0
  380. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_human_work_unit.py +0 -0
  381. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_hygiene_boundary_guidance.py +0 -0
  382. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_hyphenated_gate_name.py +0 -0
  383. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_ignored_cleanup_before_probe.py +0 -0
  384. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_init_integration.py +0 -0
  385. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_installed_copy_driver_e2e.py +0 -0
  386. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_interpreter_normalization.py +0 -0
  387. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_judge_close_path.py +0 -0
  388. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_judge_cost.py +0 -0
  389. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_judge_cost_fold_committed.py +0 -0
  390. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_judge_module.py +0 -0
  391. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_judge_path_registry.py +0 -0
  392. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_judge_terminal_flip_state.py +0 -0
  393. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_label_description_length.py +0 -0
  394. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_label_provisioning_runner_contract.py +0 -0
  395. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_label_registry.py +0 -0
  396. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_label_registry_covers_consumers.py +0 -0
  397. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_language_aware_narrow_selection.py +0 -0
  398. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lazy_baseline_e2e.py +0 -0
  399. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_leak_findings_redaction.py +0 -0
  400. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_leak_scan.py +0 -0
  401. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_leak_scan_content.py +0 -0
  402. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_learnings_curate_vendored_promote.py +0 -0
  403. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_learnings_distil_cli.py +0 -0
  404. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_learnings_query.py +0 -0
  405. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_learnings_staging.py +0 -0
  406. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_learnings_suggest_signature_filter.py +0 -0
  407. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_learnings_weights.py +0 -0
  408. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_legacy_4wu_terminal_flips.py +0 -0
  409. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lifecycle_integration.py +0 -0
  410. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lifetime_accounting.py +0 -0
  411. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_ac_observable.py +0 -0
  412. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_bare_produces_path.py +0 -0
  413. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_boundary_consistency.py +0 -0
  414. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_boundary_extraction.py +0 -0
  415. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_close_intermediate.py +0 -0
  416. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_close_verdict_not_predecided.py +0 -0
  417. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_close_wu.py +0 -0
  418. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_closing.py +0 -0
  419. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_closing_criteria.py +0 -0
  420. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_closing_criteria_pristine.py +0 -0
  421. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_correlation_id.py +0 -0
  422. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_correlation_id_close_intermediate.py +0 -0
  423. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_covers_load_wu_invariants.py +0 -0
  424. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_feature_oracle_declared.py +0 -0
  425. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_gate_proportionality.py +0 -0
  426. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_monitoring.py +0 -0
  427. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_oracle_env.py +0 -0
  428. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_plan_contract_fields.py +0 -0
  429. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_plan_errors.py +0 -0
  430. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_plan_next_draft.py +0 -0
  431. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_plan_relative_import.py +0 -0
  432. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_plan_verdict_exempt.py +0 -0
  433. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_planning_sections.py +0 -0
  434. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_produces_driver_helper.py +0 -0
  435. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_produces_satisfiability.py +0 -0
  436. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_roadmap.py +0 -0
  437. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_sections.py +0 -0
  438. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_task_graph_yaml_selection.py +0 -0
  439. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_verification_reference.py +0 -0
  440. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_live_tests_opt_in.py +0 -0
  441. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_load_bearing_close_lint.py +0 -0
  442. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_loop_attempt_evidence.py +0 -0
  443. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_loop_auto_archive.py +0 -0
  444. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_loop_caveman_preamble.py +0 -0
  445. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_loop_close_intermediate.py +0 -0
  446. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_loop_criteria_skeleton.py +0 -0
  447. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_loop_criteria_survival.py +0 -0
  448. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_loop_defaults_by_type.py +0 -0
  449. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_loop_effort.py +0 -0
  450. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_loop_failure_note_cap.py +0 -0
  451. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_loop_files_changed_guard.py +0 -0
  452. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_loop_gate_budget.py +0 -0
  453. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_loop_gate_needs.py +0 -0
  454. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_loop_model_alias.py +0 -0
  455. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_loop_orchestration.py +0 -0
  456. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_loop_post_pass_invariant.py +0 -0
  457. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_loop_reset_preserving_events.py +0 -0
  458. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_loop_smoke_runner.py +0 -0
  459. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_loop_unsandboxed.py +0 -0
  460. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_loop_worklist_injection.py +0 -0
  461. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_loop_zero_token_guard.py +0 -0
  462. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_make_repo_gpgsign_isolation.py +0 -0
  463. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_marker_dual_shape.py +0 -0
  464. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_maven_failure_excerpt.py +0 -0
  465. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_max_attempts_configurable.py +0 -0
  466. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_max_open_prs.py +0 -0
  467. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_migrate_legacy.py +0 -0
  468. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_miniyaml_equivalence.py +0 -0
  469. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_miniyaml_negative.py +0 -0
  470. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_monitor_cli.py +0 -0
  471. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_monitor_issue_lifecycle.py +0 -0
  472. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_monitor_runner_surfaces.py +0 -0
  473. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_monitoring_bootstrap_artifacts.py +0 -0
  474. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_monitoring_cron_dialect.py +0 -0
  475. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_monitoring_example.py +0 -0
  476. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_monitoring_fenced_blocks.py +0 -0
  477. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_monitoring_seed.py +0 -0
  478. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_narrow_fallback_warning.py +0 -0
  479. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_needs_decision_outcome.py +0 -0
  480. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_no_pytest_subprocess.py +0 -0
  481. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_notify.py +0 -0
  482. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_notify_escalation.py +0 -0
  483. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_notify_sla.py +0 -0
  484. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_operator_escalation_rule.py +0 -0
  485. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_oracle_set_declared.py +0 -0
  486. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_oracle_unrunnable_attribution.py +0 -0
  487. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_per_run_item_cap_disclosure.py +0 -0
  488. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_pick_feature_skill.py +0 -0
  489. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_pin_honesty_and_integrity.py +0 -0
  490. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_pin_marker_not_inherited.py +0 -0
  491. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_pin_marker_spawn_sites.py +0 -0
  492. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_plan_baseline.py +0 -0
  493. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_planned_cost_lint.py +0 -0
  494. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_planning_cost_floor.py +0 -0
  495. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_plugin_prose_cross_refs.py +0 -0
  496. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_plugin_qualified_dispatch.py +0 -0
  497. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_policy_budgets_applied.py +0 -0
  498. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_policy_proposals.py +0 -0
  499. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_policy_review.py +0 -0
  500. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_pr_checks_json_field.py +0 -0
  501. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_pr_reference_in_title.py +0 -0
  502. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_pre_existing_claims_cite_evidence.py +0 -0
  503. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_prep_halt_operator_surface.py +0 -0
  504. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_prepare_feature.py +0 -0
  505. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_prepare_scaffold_sync.py +0 -0
  506. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_prerun_capture.py +0 -0
  507. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_prerun_oracles.py +0 -0
  508. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_prerun_wiring.py +0 -0
  509. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_produced_document_verdict_guard.py +0 -0
  510. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_produces_bracket_literal.py +0 -0
  511. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_produces_bracket_wildcard.py +0 -0
  512. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_produces_field.py +0 -0
  513. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_produces_in_diff.py +0 -0
  514. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_produces_justification.py +0 -0
  515. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_produces_recursive_glob.py +0 -0
  516. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_produces_semantics_unified.py +0 -0
  517. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_produces_shape.py +0 -0
  518. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_progress_forward_note.py +0 -0
  519. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_progress_lines_end_to_end.py +0 -0
  520. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_promotion_ledger.py +0 -0
  521. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_provider_escalation_traces.py +0 -0
  522. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_provision_labels.py +0 -0
  523. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_queue_stalled_adapter.py +0 -0
  524. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_rearm_clears_stale_criteria_state.py +0 -0
  525. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_rearm_fold_marker.py +0 -0
  526. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_rearm_migration.py +0 -0
  527. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_red_baseline_never_reused.py +0 -0
  528. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_red_on_base_guardrail.py +0 -0
  529. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_redaction_windows_home.py +0 -0
  530. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_reflection_required_signals.py +0 -0
  531. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_replan_end_to_end.py +0 -0
  532. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_replan_event_emission.py +0 -0
  533. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_replan_note_collision.py +0 -0
  534. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_replan_trigger.py +0 -0
  535. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_replan_turn_contract.py +0 -0
  536. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_replay_spin.py +0 -0
  537. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_required_checks.py +0 -0
  538. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_resolve_base.py +0 -0
  539. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_restart_cap_counts_progress.py +0 -0
  540. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_result_block.py +0 -0
  541. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_result_block_audience.py +0 -0
  542. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_resume_command_build.py +0 -0
  543. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_retain_on_guard_refusal_docs.py +0 -0
  544. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_roadmap_add_skill.py +0 -0
  545. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_roadmap_archive_cli.py +0 -0
  546. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_roadmap_archive_shim.py +0 -0
  547. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_roadmap_archive_skill.py +0 -0
  548. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_roadmap_archive_skill_delegates.py +0 -0
  549. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_roadmap_link_gate.py +0 -0
  550. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_roadmap_row_parser.py +0 -0
  551. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_ruff_verdict_lines.py +0 -0
  552. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_rule_import_convergence.py +0 -0
  553. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_run_level_failure_breaker.py +0 -0
  554. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_scaffold_data_in_sync.py +0 -0
  555. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_scaffold_doc_hygiene.py +0 -0
  556. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_scaffold_docs.py +0 -0
  557. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_scaffold_init.py +0 -0
  558. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_scaffold_label_provisioning.py +0 -0
  559. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_scaffold_manifest.py +0 -0
  560. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_scaffold_resources.py +0 -0
  561. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_scaffold_seed_sanity.py +0 -0
  562. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_scaffold_skew_halts.py +0 -0
  563. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_scaffold_upgrade.py +0 -0
  564. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_scaffold_wiring.py +0 -0
  565. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_schedule_dialect.py +0 -0
  566. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_seed_documents_prerun_keys.py +0 -0
  567. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_service_bus_dlq_adapter.py +0 -0
  568. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_session_env_preflight.py +0 -0
  569. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_severity_aliases.py +0 -0
  570. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_severity_backfill_apply.py +0 -0
  571. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_severity_backfill_end_to_end.py +0 -0
  572. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_severity_backfill_limit.py +0 -0
  573. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_severity_backfill_marker.py +0 -0
  574. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_severity_backfill_run.py +0 -0
  575. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_severity_label_projection.py +0 -0
  576. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_severity_reconciliation.py +0 -0
  577. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_severity_rubric.py +0 -0
  578. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_shipped_templates_parse.py +0 -0
  579. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_skill_discovery_links.py +0 -0
  580. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_skills_vendored_in_sync.py +0 -0
  581. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_spinning_rearm_gate.py +0 -0
  582. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_spinout_brief_end_to_end.py +0 -0
  583. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_spinout_brief_replan_option.py +0 -0
  584. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_squash_commit_hook_crash.py +0 -0
  585. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_stop_rationale_captured.py +0 -0
  586. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_stranded_halt_cause.py +0 -0
  587. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_surefire_run_level_signature.py +0 -0
  588. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_surefire_verdict_lines.py +0 -0
  589. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_template_closing_shapes.py +0 -0
  590. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_terminal_flip_ownership.py +0 -0
  591. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_terminal_flips.py +0 -0
  592. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_terminal_gate_message.py +0 -0
  593. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_tiered_verification_e2e.py +0 -0
  594. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_timeout_kill_portable.py +0 -0
  595. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_triage.py +0 -0
  596. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_triage_apply.py +0 -0
  597. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_triage_severity_classify.py +0 -0
  598. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_triage_severity_end_to_end.py +0 -0
  599. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_triage_severity_write.py +0 -0
  600. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_triage_skill_contract.py +0 -0
  601. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_triage_skips_agent_escalations.py +0 -0
  602. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_tsc_verdict_lines.py +0 -0
  603. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_umbrella_version_resolution.py +0 -0
  604. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_unterminated_frontmatter.py +0 -0
  605. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_untracked_feature_folder.py +0 -0
  606. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_untracked_file_absorption.py +0 -0
  607. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_upgrade_integration.py +0 -0
  608. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_upgrade_merge_gate.py +0 -0
  609. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_validate_event.py +0 -0
  610. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_validate_event_driver_types.py +0 -0
  611. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_verdict_coupling.py +0 -0
  612. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_verify_empty_gate_set.py +0 -0
  613. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_version_consistency.py +0 -0
  614. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_version_skew.py +0 -0
  615. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_windows_gate_exec.py +0 -0
  616. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_worktree_restore.py +0 -0
  617. {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_wu_execution_metadata.py +0 -0
  618. {specfuse_loop-0.23.2 → specfuse_loop-0.24.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.23.2
3
+ Version: 0.24.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.23.2"
7
+ version = "0.24.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"
@@ -40,7 +40,11 @@ from specfuse.agent.invoke import run_claude, usage_spend
40
40
  from specfuse.agent.state import AgentSnapshot
41
41
  from specfuse.agent.triage_invoke import build_invocation, classify_result, classify_severity
42
42
  from specfuse.loop.escalation import CATEGORY_LABELS
43
- from specfuse.loop.labels import read_severity_rubric
43
+ from specfuse.loop.labels import (
44
+ list_existing_labels,
45
+ read_severity_rubric,
46
+ severity_label_projection,
47
+ )
44
48
  from specfuse.loop.triage import CATEGORIES, apply_triage, list_untriaged
45
49
 
46
50
  _ITEM_ID_PREFIX = "triage-"
@@ -188,6 +192,7 @@ class TriageProvider:
188
192
  self._rows: dict = {}
189
193
  self._auto = False
190
194
  self._rubric: Any = _RUBRIC_UNSET
195
+ self._projection: Any = _RUBRIC_UNSET
191
196
 
192
197
  def _rubric_for_run(self) -> dict:
193
198
  """The repository's severity rubric, read at most once per run
@@ -195,14 +200,40 @@ class TriageProvider:
195
200
  `execute()` call after an `advertise()`, so a run that classifies
196
201
  nothing issues no `gh label list` at all."""
197
202
  if self._rubric is _RUBRIC_UNSET:
198
- self._rubric = read_severity_rubric(
199
- self._working_dir, runner=self._runner, repo=self._repo
200
- )
203
+ self._read_labels_once()
201
204
  return self._rubric
202
205
 
206
+ def _projection_for_run(self) -> dict:
207
+ """`{canonical value: the label this repository spells it with}` (#3386).
208
+
209
+ Triage wrote canonical labels a repository may never have defined; the
210
+ write failed, and because the bug lane reads severity from the label
211
+ rather than the marker, triage removed from the lane the issues it had
212
+ just classified for it. Derived from the same listing as the rubric —
213
+ one `gh label list` per run, which is the invariant that accessor
214
+ already carried."""
215
+ if self._projection is _RUBRIC_UNSET:
216
+ self._read_labels_once()
217
+ return self._projection
218
+
219
+ def _read_labels_once(self) -> None:
220
+ existing, _reason = list_existing_labels(
221
+ self._working_dir, runner=self._runner, repo=self._repo
222
+ )
223
+ self._rubric = read_severity_rubric(
224
+ self._working_dir, runner=self._runner, repo=self._repo,
225
+ existing=existing,
226
+ )
227
+ try:
228
+ names = [item["name"] for item in (existing or [])]
229
+ except (TypeError, KeyError):
230
+ names = []
231
+ self._projection = severity_label_projection(names)
232
+
203
233
  def advertise(self, snapshot: AgentSnapshot) -> Sequence[ActionItem]:
204
234
  self._auto = snapshot.triage_auto
205
235
  self._rubric = _RUBRIC_UNSET
236
+ self._projection = _RUBRIC_UNSET
206
237
  rows = list_untriaged(self._runner, self._repo)
207
238
 
208
239
  self._rows = {}
@@ -250,7 +281,9 @@ class TriageProvider:
250
281
  "labels": row.get("labels") or [],
251
282
  }
252
283
  ]
253
- results = apply_triage(self._runner, self._repo, decisions, auto=self._auto)
284
+ results = apply_triage(
285
+ self._runner, self._repo, decisions, auto=self._auto,
286
+ label_projection=self._projection_for_run())
254
287
  row_result = results[0]
255
288
  if row_result.get("label_written"):
256
289
  return ActionOutcome(
@@ -337,7 +370,9 @@ class TriageProvider:
337
370
  "severity": severity,
338
371
  }
339
372
  ]
340
- results = apply_triage(self._runner, self._repo, decisions, auto=self._auto)
373
+ results = apply_triage(
374
+ self._runner, self._repo, decisions, auto=self._auto,
375
+ label_projection=self._projection_for_run())
341
376
  row_result = results[0]
342
377
 
343
378
  if row_result.get("skipped"):
@@ -76,6 +76,13 @@ STATUS_ESCALATED = "escalated"
76
76
  #: (FEAT-2026-0108/T02). Not one of `budget.py`'s stop reasons -- nothing was
77
77
  #: budgeted, because nothing was dispatched.
78
78
  STOP_DIRTY_TREE = "dirty_tree"
79
+ #: The run could not read the queue it was meant to work through (#3392).
80
+ #: Distinct from `STOP_DRAINED`, which means the opposite: a drained run saw
81
+ #: everything there was and finished it. Reported identically, they are
82
+ #: indistinguishable in the summary — the part an operator reads and the only
83
+ #: part automation parses — so a nightly run whose credentials broke looks like
84
+ #: a quiet backlog.
85
+ STOP_SNAPSHOT_UNREADABLE = "snapshot_unreadable"
79
86
 
80
87
  KIND_BUG = "bug"
81
88
  KIND_FEATURE = "feature"
@@ -706,6 +713,43 @@ def _refuse_dirty_tree(reason: str, report: Callable[[str], None]) -> RunSummary
706
713
  )
707
714
 
708
715
 
716
+ def _refuse_unreadable_snapshot(reason: str, *, report) -> "RunSummary":
717
+ """Refuse a run whose issue listing failed, rather than draining it (#3392).
718
+
719
+ Every provider that matters reads `snapshot.issues`, so a run without it
720
+ can do nothing except mislead. Shaped like `_refuse_dirty_tree`: zero
721
+ attempted, zero completed, one escalation carrying the reason, and a
722
+ distinct stop reason so the summary cannot be mistaken for a healthy run
723
+ against an empty queue.
724
+
725
+ Refusing is the conservative direction here for the same reason it is
726
+ there. A blind run that dispatches nothing has cost nothing; a blind run
727
+ reported as `drained` costs the operator their belief that the queue is
728
+ empty.
729
+ """
730
+ report(f"run refused to start — {reason}")
731
+ return RunSummary(
732
+ items_attempted=0,
733
+ items_completed=0,
734
+ items_escalated=1,
735
+ stop_reason=STOP_SNAPSHOT_UNREADABLE,
736
+ elapsed_minutes=0.0,
737
+ tokens_spent=0,
738
+ escalations=(Escalation(item_id="run:snapshot", reason=reason),),
739
+ )
740
+
741
+
742
+ def exit_code_for(stop_reason: str) -> int:
743
+ """The process exit code a run ending in *stop_reason* should report.
744
+
745
+ Non-zero only for a refusal — a run that did not happen. Every outcome,
746
+ including `drained`, keeps 0: those are runs that ran. Narrow on purpose,
747
+ so a new stop reason does not silently become a failing exit code for
748
+ whatever automation is watching.
749
+ """
750
+ return 1 if stop_reason == STOP_SNAPSHOT_UNREADABLE else 0
751
+
752
+
709
753
  def _dirty_tree_reason(paths) -> str:
710
754
  shown = ", ".join(paths[:10])
711
755
  if len(paths) > 10:
@@ -799,6 +843,31 @@ def run_agent(
799
843
  if error:
800
844
  report(f"snapshot: {section} unreadable — {error}")
801
845
 
846
+ # #3392: an unreadable issue listing is not an empty queue. Every
847
+ # provider that matters reads `snapshot.issues`, so continuing here
848
+ # finds no candidates and stops with `drained` — "worked through
849
+ # everything there was" reported for "could not see anything". Measured
850
+ # with nine dispatchable bugs in the queue at the time.
851
+ if snapshot.issues_error:
852
+ return _refuse_unreadable_snapshot(
853
+ f"the issue listing failed, so the queue could not be read: "
854
+ f"{snapshot.issues_error}",
855
+ report=report,
856
+ )
857
+
858
+ # The PR listing failing is NOT a refusal: `max_open_prs` deliberately
859
+ # reads an unreadable listing as zero rather than firing on a number
860
+ # the run could not measure. But that leaves the cap silently disabled
861
+ # for this run, so the summary says so rather than the operator finding
862
+ # it in the log. Whether it should refuse instead is a live question
863
+ # (#3392 item 3) and not decided here.
864
+ if snapshot.prs_error:
865
+ report(
866
+ "snapshot: max_open_prs is NOT enforced this run — the PR "
867
+ "listing could not be read, so the cap has no number to "
868
+ "measure against"
869
+ )
870
+
802
871
  budget_kwargs = {}
803
872
  if pause_marker is not None:
804
873
  budget_kwargs["pause_marker"] = pause_marker
@@ -1294,7 +1363,9 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
1294
1363
  worktree.restore_branch(started_on, report=_default_reporter)
1295
1364
 
1296
1365
  print(_format_summary(summary))
1297
- return 0
1366
+ # #3392: a refusal exits non-zero so a scheduled run's exit status is
1367
+ # self-describing without its log. Outcomes — `drained` included — keep 0.
1368
+ return exit_code_for(summary.stop_reason)
1298
1369
 
1299
1370
 
1300
1371
  if __name__ == "__main__":
@@ -0,0 +1 @@
1
+ 0.24.0
@@ -759,7 +759,17 @@ when and how these get created.
759
759
  | State backend | WU / GATE file frontmatter | GitHub issue labels + feature registry |
760
760
  | Dispatch | driver shells out (`claude -p`) | inbox files + polling loop |
761
761
  | Branch / merge | one branch, squash per WU | branch + PR per task, merge watcher |
762
- | Spec front-end | optional; task graph authored directly | spec-first (specs agent + codegen) |
762
+ | Spec front-end | one of: **none** (task graph authored directly), **consumed** (specs authored upstream, the loop implements against a handoff manifest), or **authored** (the spec tree is the deliverable — work units author spec files) | spec-first (specs agent + codegen), from a dossier **or** from an upstream handoff manifest |
763
+
764
+ The loop's spec front-end is three-valued because a repository whose entire
765
+ deliverable **is** the spec tree fits neither of the two values this row
766
+ carried before (#3407): it is not "optional", and the task graph is not
767
+ "authored directly" — the work units author spec files. One consumer carried
768
+ that third case in about forty lines of project `CLAUDE.md` because the row
769
+ could not express it. In the **authored** mode the work-unit contract picks up
770
+ two constraints that do not apply to code units — see
771
+ `authoring-work-units` §6 (the whole tree validates) and §12 (a structural
772
+ assertion substitutes for the red test).
763
773
 
764
774
  Everything above those rows — the unit hierarchy, ownership split, WU contract,
765
775
  verification-as-oracle, the gate cycle, plan-next, LEARNINGS, and autonomy — is
@@ -125,7 +125,7 @@ SPECFUSE_DIR = Path(".specfuse")
125
125
  REPO_ROOT = SPECFUSE_DIR.parent
126
126
  FEATURES_DIR = SPECFUSE_DIR / "features"
127
127
  VERIFICATION_PATH = SPECFUSE_DIR / "verification.yml"
128
- DRIVER_VERSION = "0.23.2"
128
+ DRIVER_VERSION = "0.24.0"
129
129
  # Oldest scaffold layout this driver can drive. init.sh stamps the scaffold's own
130
130
  # version into `.specfuse/VERSION`; check_scaffold_version() fails loud at startup if
131
131
  # the consumer's scaffold is older than this, pointing at `specfuse upgrade`. Bump
@@ -9488,6 +9488,15 @@ def capture_gate_diff(
9488
9488
  return "\n\n".join(parts)
9489
9489
 
9490
9490
 
9491
+ def _is_relative_to(path: Path, other: Path) -> bool:
9492
+ """`Path.is_relative_to`, tolerant of paths that do not resolve."""
9493
+ try:
9494
+ path.relative_to(other)
9495
+ except ValueError:
9496
+ return False
9497
+ return True
9498
+
9499
+
9491
9500
  def write_judge_followups(
9492
9501
  feature_dir: Path, gate_number: int, findings: list,
9493
9502
  ) -> Path:
@@ -9644,6 +9653,32 @@ def judge_close(
9644
9653
  feature_dir, gate_number, result.findings)
9645
9654
  payload["lowered"] = True
9646
9655
  payload["verdict"] = "not_met"
9656
+ # Say WHERE the findings went (#3406). They are written verbatim to
9657
+ # FOLLOW-UPS.md above and committed below, but the event said only
9658
+ # `findings: N` with `reason: null` — so a reader of events.jsonl had
9659
+ # nothing pointing out of it. One operator searched events.jsonl,
9660
+ # `work/` and for a repository-level judge artifact, concluded the text
9661
+ # did not exist, and priced a third close at ~$12 to re-derive a
9662
+ # finding that was sitting in a committed file.
9663
+ #
9664
+ # The criteria go in; the finding TEXT deliberately does not. It is
9665
+ # already recorded verbatim in one place, and copying it into a second
9666
+ # invites the two to disagree — `write_judge_followups` exists so the
9667
+ # judge's words are not reformatted by the driver.
9668
+ payload["findings_path"] = str(
9669
+ Path(followups).relative_to(Path(feature_dir).parent.parent)
9670
+ if _is_relative_to(Path(followups), Path(feature_dir).parent.parent)
9671
+ else Path(followups)
9672
+ )
9673
+ payload["finding_criteria"] = [
9674
+ f.criterion for f in result.findings if getattr(f, "criterion", None)
9675
+ ]
9676
+ _named = ", ".join(payload["finding_criteria"]) or "no named criterion"
9677
+ payload["reason"] = (
9678
+ f"judge lowered the verdict on {payload['findings']} finding(s) "
9679
+ f"({_named}); the judge's own words are in "
9680
+ f"{payload['findings_path']}"
9681
+ )
9647
9682
  commit_bookkeeping(
9648
9683
  [wu.file, followups],
9649
9684
  f"chore(loop): {wu.wu_id} judge lowered verdict to not_met"
@@ -52,6 +52,12 @@ from pathlib import Path
52
52
  from typing import Any, Callable, Optional
53
53
 
54
54
 
55
+ #: POSIX reserves these for the shell's own failure to run the command: 126 is
56
+ #: "found but not executable", 127 is "not found". Neither says anything about
57
+ #: the test, so neither may be read as a verdict on it (#3403).
58
+ _COULD_NOT_EXECUTE = frozenset({126, 127})
59
+
60
+
55
61
  def select_test_files(changed_files: Any, test_paths: Any) -> list:
56
62
  """The changed files that live under a declared test root.
57
63
 
@@ -126,6 +132,19 @@ def test_was_red_on_base(
126
132
  code = getattr(result, "returncode", None)
127
133
  if not isinstance(code, int):
128
134
  return None
135
+ if code in _COULD_NOT_EXECUTE:
136
+ # The shell could not run the command at all. That is "the check
137
+ # never ran", not "the test failed" -- and the difference is the
138
+ # whole point of keeping those two declining reasons apart.
139
+ #
140
+ # Measured: an operator whose command is a committed script hits
141
+ # this on every PR whose merge base predates that script. The
142
+ # worktree is the BASE tree, so the script is simply absent there,
143
+ # `sh` exits 127, and reading that as red would let the merge
144
+ # through on the strength of a command that never ran. Fail-open,
145
+ # in the one guardrail whose entire job is to be harder to satisfy
146
+ # than the others.
147
+ return None
129
148
  # Non-zero at the base is the proof we want: the test the PR adds does
130
149
  # not hold on the tree the fix was written against.
131
150
  return code != 0
@@ -182,7 +182,10 @@ def _list_open_issues(runner: Callable, repo: str, *, limit: int) -> list:
182
182
  return []
183
183
 
184
184
 
185
- def apply_triage(runner: Callable, repo: str, decisions: list, *, auto: bool = False) -> list:
185
+ def apply_triage(
186
+ runner: Callable, repo: str, decisions: list, *,
187
+ auto: bool = False, label_projection: "dict | None" = None,
188
+ ) -> list:
186
189
  """Record each decision in `decisions` against its GitHub issue.
187
190
 
188
191
  Each decision is a mapping carrying at least `number`, `body` (the
@@ -195,6 +198,18 @@ def apply_triage(runner: Callable, repo: str, decisions: list, *, auto: bool = F
195
198
  marker. But a marker with no matching label on the issue (the label
196
199
  write failed on an earlier pass, e.g. because the label did not exist
197
200
  yet) is not fully idempotent -- it is missing its projection, so this
201
+ `label_projection` maps a canonical severity value to the label this
202
+ repository actually spells it with (`labels.severity_label_projection`,
203
+ #3386). Without it `severity_aliases` was one-directional: the bug lane
204
+ translated a repository's own words inbound while triage wrote canonical
205
+ words outbound, which that repository may never have defined. The write then
206
+ failed, and since the lane reads severity from the LABEL rather than the
207
+ marker, triage removed from the lane the very issues it had just classified
208
+ for it. The marker always records the canonical value, so it stays
209
+ comparable across repositories; only the label takes the operator's word.
210
+ Absent, or missing a value, the canonical label is written — the behaviour
211
+ before the projection existed.
212
+
198
213
  retries only the label write. `decision["labels"]` (as returned by
199
214
  `gh issue list --json ...,labels`, i.e. a list of `{"name": ...}`
200
215
  mappings) is what `labels` is checked against; a decision with no
@@ -226,7 +241,10 @@ def apply_triage(runner: Callable, repo: str, decisions: list, *, auto: bool = F
226
241
  marked_severity = (parse_marker_fields(body) or {}).get("severity")
227
242
  target_labels = labels_for(marked_category) if marked_category in CATEGORIES else ()
228
243
  if marked_severity:
229
- target_labels = tuple(target_labels) + (severity_label_for(marked_severity),)
244
+ target_labels = tuple(target_labels) + (
245
+ (label_projection or {}).get(
246
+ marked_severity, severity_label_for(marked_severity)),
247
+ )
230
248
  existing_labels = {
231
249
  label.get("name") for label in decision.get("labels") or []
232
250
  }
@@ -288,7 +306,9 @@ def apply_triage(runner: Callable, repo: str, decisions: list, *, auto: bool = F
288
306
 
289
307
  labels_to_add = list(labels_for(applied_category))
290
308
  if severity:
291
- labels_to_add.append(severity_label_for(severity))
309
+ labels_to_add.append(
310
+ (label_projection or {}).get(
311
+ severity, severity_label_for(severity)))
292
312
 
293
313
  try:
294
314
  runner(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: specfuse-loop
3
- Version: 0.23.2
3
+ Version: 0.24.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
@@ -224,6 +224,7 @@ tests/test_binary_verdict.py
224
224
  tests/test_binding_block_advisory.py
225
225
  tests/test_binding_block_allocation.py
226
226
  tests/test_binding_block_budget.py
227
+ tests/test_blind_run_is_not_drained.py
227
228
  tests/test_bookkeeping_commit_crash_run.py
228
229
  tests/test_bookkeeping_commit_hook_crash.py
229
230
  tests/test_bookkeeping_no_verify.py
@@ -382,6 +383,7 @@ tests/test_judge_cost_fold_committed.py
382
383
  tests/test_judge_module.py
383
384
  tests/test_judge_path_registry.py
384
385
  tests/test_judge_terminal_flip_state.py
386
+ tests/test_judged_event_points_at_findings.py
385
387
  tests/test_label_description_length.py
386
388
  tests/test_label_provisioning_runner_contract.py
387
389
  tests/test_label_registry.py
@@ -571,6 +573,7 @@ tests/test_severity_rubric.py
571
573
  tests/test_shipped_templates_parse.py
572
574
  tests/test_skill_discovery_links.py
573
575
  tests/test_skills_vendored_in_sync.py
576
+ tests/test_spec_work_unit_rules.py
574
577
  tests/test_spinning_rearm_gate.py
575
578
  tests/test_spinout_brief_end_to_end.py
576
579
  tests/test_spinout_brief_replan_option.py
@@ -587,6 +590,7 @@ tests/test_tiered_verification_e2e.py
587
590
  tests/test_timeout_kill_portable.py
588
591
  tests/test_triage.py
589
592
  tests/test_triage_apply.py
593
+ tests/test_triage_projects_severity_label.py
590
594
  tests/test_triage_severity_classify.py
591
595
  tests/test_triage_severity_end_to_end.py
592
596
  tests/test_triage_severity_write.py
@@ -0,0 +1,105 @@
1
+ #
2
+ # Copyright 2026 Specfuse contributors
3
+ # Licensed under the Apache License, Version 2.0. See LICENSE.
4
+ """A run that could not see the queue does not report `drained` (#3392).
5
+
6
+ When the snapshot's issue listing failed, `run_agent` reported the failure,
7
+ treated the listing as empty, found no candidates and stopped with `drained` —
8
+ exit 0, zero attempted, zero escalated.
9
+
10
+ Observed with nine dispatchable bugs in the queue, two of them deliberately
11
+ ranked first for that run:
12
+
13
+ snapshot: 0 open issues, 0 open PRs, 115 features, queue=0
14
+ snapshot: issues unreadable — gh issue list ... tls: failed to verify certificate
15
+ run finished — drained after 0.00 minutes
16
+ stop reason: drained
17
+
18
+ `drained` means "worked through everything there was". Here it meant "could not
19
+ see anything". The two are opposite, and the summary — the part an operator
20
+ reads, and the only part automation parses — rendered them identically.
21
+
22
+ This is the shape this project keeps finding: **a failure reported in the
23
+ grammar of success.** An unattended runner is where it costs most; a scheduled
24
+ run reporting `drained` every night while its credentials are broken looks like
25
+ a quiet backlog.
26
+
27
+ Refusing rather than draining follows `STOP_DIRTY_TREE`'s precedent: nothing was
28
+ budgeted because nothing was dispatched, and every provider that matters reads
29
+ `snapshot.issues`, so a run without it can do nothing except mislead.
30
+ """
31
+
32
+ from __future__ import annotations
33
+
34
+ import unittest
35
+
36
+ from specfuse.agent.run import (
37
+ STOP_DRAINED,
38
+ STOP_SNAPSHOT_UNREADABLE,
39
+ _refuse_unreadable_snapshot,
40
+ exit_code_for,
41
+ )
42
+
43
+
44
+ class _Snapshot:
45
+ def __init__(self, issues_error=None, prs_error=None):
46
+ self.issues_error = issues_error
47
+ self.prs_error = prs_error
48
+
49
+
50
+ class TheRefusal(unittest.TestCase):
51
+
52
+ def _refuse(self, reason="gh issue list exited 1: tls: failed to verify"):
53
+ lines = []
54
+ summary = _refuse_unreadable_snapshot(reason, report=lines.append)
55
+ return summary, lines
56
+
57
+ def test_it_does_not_report_drained(self):
58
+ summary, _ = self._refuse()
59
+
60
+ self.assertEqual(STOP_SNAPSHOT_UNREADABLE, summary.stop_reason)
61
+ self.assertNotEqual(STOP_DRAINED, summary.stop_reason)
62
+
63
+ def test_nothing_is_attempted_or_completed(self):
64
+ summary, _ = self._refuse()
65
+
66
+ self.assertEqual(0, summary.items_attempted)
67
+ self.assertEqual(0, summary.items_completed)
68
+
69
+ def test_it_escalates_so_the_summary_is_not_silent(self):
70
+ summary, _ = self._refuse()
71
+
72
+ self.assertEqual(1, summary.items_escalated)
73
+ self.assertEqual(1, len(summary.escalations))
74
+
75
+ def test_the_escalation_carries_which_listing_failed(self):
76
+ summary, _ = self._refuse("gh issue list exited 1: tls: bad cert")
77
+
78
+ self.assertIn("tls: bad cert", summary.escalations[0].reason)
79
+
80
+ def test_it_says_so_on_the_way_out(self):
81
+ _, lines = self._refuse()
82
+
83
+ self.assertTrue(
84
+ any("refused" in line for line in lines),
85
+ f"the run must say it refused, not finish quietly: {lines}",
86
+ )
87
+
88
+
89
+ class TheExitCode(unittest.TestCase):
90
+ """A scheduled run's output must be self-describing without its log."""
91
+
92
+ def test_a_blind_run_exits_non_zero(self):
93
+ self.assertNotEqual(0, exit_code_for(STOP_SNAPSHOT_UNREADABLE))
94
+
95
+ def test_a_genuinely_drained_run_still_exits_zero(self):
96
+ self.assertEqual(0, exit_code_for(STOP_DRAINED))
97
+
98
+ def test_an_unknown_reason_does_not_invent_a_failure(self):
99
+ # Every other stop reason kept exit 0; this narrows to the one case
100
+ # that is a refusal rather than an outcome.
101
+ self.assertEqual(0, exit_code_for("some_future_reason"))
102
+
103
+
104
+ if __name__ == "__main__": # pragma: no cover
105
+ unittest.main()
@@ -0,0 +1,137 @@
1
+ #
2
+ # Copyright 2026 Specfuse contributors
3
+ # Licensed under the Apache License, Version 2.0. See LICENSE.
4
+ """The `judged` event says where the judge's findings went (#3406).
5
+
6
+ The issue reported that a judge lowering a terminal verdict "persists no
7
+ findings". It does persist them: `write_judge_followups` writes each finding
8
+ verbatim into `FOLLOW-UPS.md` and `commit_bookkeeping` commits it. Verified in
9
+ the reporting repository's own history — `chore(loop): FEAT-2026-0168/G1-CLOSE
10
+ judge lowered verdict to not_met` contains "Filed by the judge for gate 1. Each
11
+ entry is the judge's own words."
12
+
13
+ What is missing is the pointer. The `judged` event records `findings: 1` and
14
+ `reason: null`, and names nothing. The reporter looked in `events.jsonl`, in
15
+ `<feature>/work/`, and for a repository-level judge artifact — the three places
16
+ the event gave them no reason to leave — and concluded the text was nowhere.
17
+ They then priced a third close at ~$12 to re-derive a finding that was sitting
18
+ in a committed file.
19
+
20
+ So the event becomes self-describing: it names the file the findings were
21
+ written to and the criteria they were raised against, and its `reason` stops
22
+ being `null` on the one path where something specific was found.
23
+
24
+ The finding TEXT deliberately stays out of the event. It is already recorded
25
+ verbatim in one place; copying it into a second invites the two to disagree,
26
+ and `write_judge_followups` exists precisely so the judge's words are not
27
+ reformatted by the driver.
28
+ """
29
+
30
+ from __future__ import annotations
31
+
32
+ import unittest
33
+ from pathlib import Path
34
+ from tempfile import TemporaryDirectory
35
+ from types import SimpleNamespace
36
+ from unittest import mock
37
+
38
+ from tests._loop_loader import load_loop
39
+
40
+ loop = load_loop()
41
+
42
+
43
+ class _Finding:
44
+ def __init__(self, criterion, text):
45
+ self.criterion = criterion
46
+ self.text = text
47
+
48
+
49
+ class TheLoweredEventNamesTheRecord(unittest.TestCase):
50
+
51
+ def _payload_for(self, findings):
52
+ """Drive judge_close's lowered branch with the file writes stubbed."""
53
+ tmp = TemporaryDirectory()
54
+ self.addCleanup(tmp.cleanup)
55
+ feature_dir = Path(tmp.name)
56
+ (feature_dir / "PLAN.md").write_text("---\nfeature_id: FEAT-2026-9999\n---\n")
57
+ wu_file = feature_dir / "WU-90-close.md"
58
+ wu_file.write_text("---\nid: FEAT-2026-9999/G1-CLOSE\nverdict: met\n---\n")
59
+ wu = SimpleNamespace(
60
+ file=wu_file, wu_id="FEAT-2026-9999/G1-CLOSE", verdict="met")
61
+
62
+ result = SimpleNamespace(verdict="not_met", findings=findings, reason=None)
63
+
64
+ with mock.patch.object(loop, "_gate_identity",
65
+ return_value=(1, feature_dir / "GATE-01.md")), \
66
+ mock.patch.object(loop, "_judge_disabled", return_value=False), \
67
+ mock.patch.object(loop, "resolve_gate_start_sha",
68
+ return_value=("abc123", "gate frontmatter")), \
69
+ mock.patch.object(loop, "capture_gate_diff", return_value="diff"), \
70
+ mock.patch.object(loop, "build_judge_bundle", return_value={}), \
71
+ mock.patch.object(loop, "render_judge_prompt", return_value="prompt"), \
72
+ mock.patch.object(loop, "parse_judge_result", return_value=result), \
73
+ mock.patch.object(loop, "write_frontmatter_field"), \
74
+ mock.patch.object(loop, "commit_bookkeeping"):
75
+ return loop.judge_close(
76
+ wu, feature_dir, feature_dir,
77
+ runner=lambda prompt, timeout=None: ("raw", {}),
78
+ )
79
+
80
+ def test_it_names_the_file_the_findings_were_written_to(self):
81
+ payload = self._payload_for([_Finding("T11#3", "### T11#3\n\nThe thing.")])
82
+
83
+ self.assertTrue(payload["lowered"])
84
+ self.assertIn("FOLLOW-UPS.md", payload.get("findings_path", ""))
85
+
86
+ def test_it_names_the_criteria(self):
87
+ payload = self._payload_for([
88
+ _Finding("T11#3", "### T11#3\n\nOne."),
89
+ _Finding("T06#2", "### T06#2\n\nTwo."),
90
+ ])
91
+
92
+ self.assertEqual(["T11#3", "T06#2"], payload.get("finding_criteria"))
93
+ self.assertEqual(2, payload["findings"])
94
+
95
+ def test_the_reason_is_no_longer_null_when_something_was_found(self):
96
+ payload = self._payload_for([_Finding("T11#3", "### T11#3\n\nThe thing.")])
97
+
98
+ self.assertIsNotNone(
99
+ payload["reason"],
100
+ "`findings: 1` with `reason: null` is what sent a reader looking "
101
+ "in three places that do not hold the text (#3406)",
102
+ )
103
+ self.assertIn("FOLLOW-UPS.md", payload["reason"])
104
+ self.assertIn("T11#3", payload["reason"])
105
+
106
+ def test_the_finding_text_is_not_copied_into_the_event(self):
107
+ # One verbatim record, not two that can disagree.
108
+ payload = self._payload_for([
109
+ _Finding("T11#3", "### T11#3\n\nA sentence only in the file.")])
110
+
111
+ self.assertNotIn("A sentence only in the file", repr(payload))
112
+
113
+
114
+ class ThePathsThatFoundNothingAreUnchanged(unittest.TestCase):
115
+
116
+ def test_a_disabled_judge_still_explains_itself(self):
117
+ tmp = TemporaryDirectory()
118
+ self.addCleanup(tmp.cleanup)
119
+ feature_dir = Path(tmp.name)
120
+ (feature_dir / "PLAN.md").write_text(
121
+ "---\nfeature_id: FEAT-2026-9999\njudge_disabled: true\n---\n")
122
+ wu_file = feature_dir / "WU-90-close.md"
123
+ wu_file.write_text("---\nid: X/G1-CLOSE\nverdict: met\n---\n")
124
+ wu = SimpleNamespace(file=wu_file, wu_id="X/G1-CLOSE", verdict="met")
125
+
126
+ with mock.patch.object(loop, "_gate_identity",
127
+ return_value=(1, feature_dir / "GATE-01.md")):
128
+ payload = loop.judge_close(wu, feature_dir, feature_dir)
129
+
130
+ self.assertFalse(payload["lowered"])
131
+ self.assertEqual(0, payload["findings"])
132
+ self.assertIsNotNone(payload["reason"])
133
+ self.assertNotIn("findings_path", payload)
134
+
135
+
136
+ if __name__ == "__main__": # pragma: no cover
137
+ unittest.main()