specfuse-loop 0.22.0__tar.gz → 0.22.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (605) hide show
  1. {specfuse_loop-0.22.0/specfuse_loop.egg-info → specfuse_loop-0.22.2}/PKG-INFO +1 -1
  2. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/pyproject.toml +1 -1
  3. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/agent/providers/bugs.py +12 -1
  4. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/agent/run.py +33 -5
  5. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/agent/severity_backfill.py +38 -5
  6. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/agent_policy.py +52 -1
  7. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/bug_lane_run.py +52 -2
  8. specfuse_loop-0.22.2/specfuse/loop/data/VERSION +1 -0
  9. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/labels.py +69 -1
  10. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/loop.py +1 -1
  11. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2/specfuse_loop.egg-info}/PKG-INFO +1 -1
  12. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse_loop.egg-info/SOURCES.txt +5 -0
  13. specfuse_loop-0.22.2/tests/test_hygiene_boundary_guidance.py +88 -0
  14. specfuse_loop-0.22.2/tests/test_per_run_item_cap_disclosure.py +80 -0
  15. specfuse_loop-0.22.2/tests/test_plugin_prose_cross_refs.py +80 -0
  16. specfuse_loop-0.22.2/tests/test_required_checks.py +161 -0
  17. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_run_level_failure_breaker.py +113 -0
  18. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_severity_backfill_run.py +60 -0
  19. specfuse_loop-0.22.2/tests/test_severity_label_projection.py +138 -0
  20. specfuse_loop-0.22.0/specfuse/loop/data/VERSION +0 -1
  21. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/LICENSE +0 -0
  22. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/NOTICE +0 -0
  23. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/README.md +0 -0
  24. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/setup.cfg +0 -0
  25. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/agent/__init__.py +0 -0
  26. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/agent/budget.py +0 -0
  27. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/agent/diagnose_invoke.py +0 -0
  28. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/agent/drafting_answers.py +0 -0
  29. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/agent/drafting_invoke.py +0 -0
  30. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/agent/drafting_questions.py +0 -0
  31. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/agent/driver_command.py +0 -0
  32. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/agent/driver_invoke.py +0 -0
  33. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/agent/invoke.py +0 -0
  34. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/agent/monitoring_read.py +0 -0
  35. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/agent/providers/__init__.py +0 -0
  36. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/agent/providers/answers.py +0 -0
  37. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/agent/providers/feature.py +0 -0
  38. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/agent/providers/findings_autofix.py +0 -0
  39. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/agent/providers/findings_diagnose.py +0 -0
  40. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/agent/providers/triage.py +0 -0
  41. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/agent/queue_read.py +0 -0
  42. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/agent/repo_detect.py +0 -0
  43. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/agent/state.py +0 -0
  44. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/agent/triage_invoke.py +0 -0
  45. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/agent/worktree.py +0 -0
  46. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/__init__.py +0 -0
  47. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/_filelock.py +0 -0
  48. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/_miniyaml.py +0 -0
  49. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/_wu_sections.py +0 -0
  50. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/adopt_feature.py +0 -0
  51. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/arm_eval.py +0 -0
  52. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/arm_sweep.py +0 -0
  53. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/arm_txn.py +0 -0
  54. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/bug_lane.py +0 -0
  55. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/bug_lane_state.py +0 -0
  56. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/build_provenance.py +0 -0
  57. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/caller_check.py +0 -0
  58. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/changelog.py +0 -0
  59. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/closing_requirements.py +0 -0
  60. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/cost.py +0 -0
  61. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/criteria_state.py +0 -0
  62. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/data/CHANGELOG.seed.md +0 -0
  63. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/data/LEARNINGS.template.md +0 -0
  64. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/data/docs/concepts/adopting-auto-mode.md +0 -0
  65. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/data/docs/concepts/architecture-addendum-gates-and-iterative-planning.md +0 -0
  66. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/data/docs/concepts/autonomy-stop-classes.md +0 -0
  67. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/data/docs/concepts/ralph-lineage.md +0 -0
  68. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/data/docs/getting-started.md +0 -0
  69. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/data/docs/glossary.md +0 -0
  70. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/data/docs/methodology.md +0 -0
  71. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/data/docs/skills.md +0 -0
  72. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/data/gitignore.snippet +0 -0
  73. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/data/monitoring-secrets-checklist.md +0 -0
  74. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/data/monitoring.overrides.yml.example +0 -0
  75. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/data/monitoring.yml.example +0 -0
  76. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/data/roadmap.template.md +0 -0
  77. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/data/rules/close-discipline.md +0 -0
  78. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/data/rules/correlation-ids.md +0 -0
  79. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/data/rules/design-for-diagnosis.md +0 -0
  80. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/data/rules/human-output.md +0 -0
  81. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/data/rules/never-touch.md +0 -0
  82. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/data/rules/operator-escalation.md +0 -0
  83. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/data/rules/planning-discipline.md +0 -0
  84. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/data/rules/result-contract.md +0 -0
  85. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/data/rules/security-boundaries.md +0 -0
  86. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/data/rules/verification-discipline.md +0 -0
  87. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/data/rules-local/README.md +0 -0
  88. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/data/schemas/driver-event.schema.json +0 -0
  89. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/data/schemas/event.schema.json +0 -0
  90. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/data/schemas/events/initiative_created.schema.json +0 -0
  91. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/data/schemas/events/spec_issue_resolved.schema.json +0 -0
  92. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/data/schemas/events/spec_issue_routed.schema.json +0 -0
  93. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/data/schemas/events/spec_validated.schema.json +0 -0
  94. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/data/templates/DECISIONS.template.md +0 -0
  95. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/data/templates/GATE.template.md +0 -0
  96. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/data/templates/LEARNINGS-pending.template.md +0 -0
  97. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/data/templates/PLAN.template.md +0 -0
  98. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/data/templates/WU.template.md +0 -0
  99. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/data/verification.yml.example +0 -0
  100. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/data/workflows/specfuse-monitor.yml +0 -0
  101. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/decisions_format.py +0 -0
  102. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/driver_edit.py +0 -0
  103. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/escalation.py +0 -0
  104. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/events_stats.py +0 -0
  105. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/gate_commands.py +0 -0
  106. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/gate_eval.py +0 -0
  107. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/gh_backend.py +0 -0
  108. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/gh_features.py +0 -0
  109. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/heartbeat.py +0 -0
  110. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/judge.py +0 -0
  111. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/learnings_distil.py +0 -0
  112. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/learnings_query.py +0 -0
  113. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/lint_closing.py +0 -0
  114. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/lint_monitoring.py +0 -0
  115. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/lint_plan.py +0 -0
  116. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/lint_roadmap.py +0 -0
  117. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/notify.py +0 -0
  118. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/notify_escalation.py +0 -0
  119. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/notify_sla.py +0 -0
  120. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/plan_baseline.py +0 -0
  121. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/policy_proposals.py +0 -0
  122. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/policy_review.py +0 -0
  123. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/prerun.py +0 -0
  124. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/prerun_capture.py +0 -0
  125. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/promotion.py +0 -0
  126. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/rearm_migration.py +0 -0
  127. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/replay_spin.py +0 -0
  128. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/roadmap_archive.py +0 -0
  129. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/scaffold.py +0 -0
  130. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/triage.py +0 -0
  131. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/upgrade_merge_gate.py +0 -0
  132. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/loop/validate_event.py +0 -0
  133. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/monitor/__init__.py +0 -0
  134. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/monitor/adapters.py +0 -0
  135. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/monitor/artifact.py +0 -0
  136. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/monitor/autofix.py +0 -0
  137. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/monitor/autofix_invoke.py +0 -0
  138. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/monitor/autofix_run.py +0 -0
  139. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/monitor/autofix_state.py +0 -0
  140. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/monitor/cli.py +0 -0
  141. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/monitor/diagnose_cli.py +0 -0
  142. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/monitor/diagnosis.py +0 -0
  143. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/monitor/fingerprint.py +0 -0
  144. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/monitor/issues.py +0 -0
  145. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/monitor/providers/__init__.py +0 -0
  146. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/monitor/providers/_azure_auth.py +0 -0
  147. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/monitor/providers/azure_app_insights.py +0 -0
  148. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/monitor/providers/azure_service_bus.py +0 -0
  149. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/monitor/redaction.py +0 -0
  150. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse/monitor/schedule.py +0 -0
  151. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse_loop.egg-info/dependency_links.txt +0 -0
  152. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse_loop.egg-info/entry_points.txt +0 -0
  153. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse_loop.egg-info/requires.txt +0 -0
  154. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/specfuse_loop.egg-info/top_level.txt +0 -0
  155. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_abandon_feature_skill.py +0 -0
  156. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_abandoned_work_reported.py +0 -0
  157. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_abandoned_wu_strands_gate.py +0 -0
  158. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_adopt_feature.py +0 -0
  159. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_agent_budget.py +0 -0
  160. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_agent_bug_selection_dedup.py +0 -0
  161. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_agent_default_features_root.py +0 -0
  162. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_agent_driver_command.py +0 -0
  163. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_agent_driver_invoke.py +0 -0
  164. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_agent_driver_restart.py +0 -0
  165. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_agent_escalation_on_issue.py +0 -0
  166. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_agent_escalation_run_state.py +0 -0
  167. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_agent_findings_seam.py +0 -0
  168. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_agent_invoke_timeout.py +0 -0
  169. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_agent_invoke_usage.py +0 -0
  170. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_agent_item_worktree.py +0 -0
  171. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_agent_lock.py +0 -0
  172. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_agent_policy_key_ownership.py +0 -0
  173. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_agent_policy_queue.py +0 -0
  174. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_agent_policy_schema.py +0 -0
  175. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_agent_policy_triage_dial.py +0 -0
  176. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_agent_provider_answers.py +0 -0
  177. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_agent_provider_bugs.py +0 -0
  178. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_agent_provider_feature.py +0 -0
  179. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_agent_provider_findings_autofix.py +0 -0
  180. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_agent_provider_findings_diagnose.py +0 -0
  181. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_agent_provider_isolation.py +0 -0
  182. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_agent_provider_triage.py +0 -0
  183. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_agent_queue_read.py +0 -0
  184. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_agent_relays_driver_output.py +0 -0
  185. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_agent_repo_detect.py +0 -0
  186. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_agent_run.py +0 -0
  187. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_agent_run_progress.py +0 -0
  188. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_agent_seam.py +0 -0
  189. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_agent_snapshot_refresh.py +0 -0
  190. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_agent_state.py +0 -0
  191. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_answer_escalation_skill.py +0 -0
  192. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_app_insights_adapters.py +0 -0
  193. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_archive_ref_reconciliation.py +0 -0
  194. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_arm_eval.py +0 -0
  195. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_arm_eval_lint_class.py +0 -0
  196. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_arm_eval_wiring.py +0 -0
  197. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_arm_gate_edits_uncommitted.py +0 -0
  198. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_arm_sweep.py +0 -0
  199. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_arm_sweep_gate.py +0 -0
  200. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_arm_txn.py +0 -0
  201. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_arm_wiring.py +0 -0
  202. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_artifact_redaction.py +0 -0
  203. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_attempt_note_untracked_files.py +0 -0
  204. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_attempt_outcome_contract.py +0 -0
  205. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_attempt_outcome_emission.py +0 -0
  206. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_attention_nonwriting_guard.py +0 -0
  207. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_attention_silence_section.py +0 -0
  208. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_attention_skill_structure.py +0 -0
  209. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_autoclose_deferral_visibility.py +0 -0
  210. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_autoclose_gate_note.py +0 -0
  211. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_autoclose_stub_states_what_passed.py +0 -0
  212. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_autofix_invoke.py +0 -0
  213. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_autofix_live.py +0 -0
  214. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_autofix_predicate.py +0 -0
  215. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_autofix_run.py +0 -0
  216. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_autofix_state.py +0 -0
  217. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_autosync.py +0 -0
  218. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_autosync_consent.py +0 -0
  219. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_autosync_firstrun.py +0 -0
  220. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_autosync_no_cwd_leak.py +0 -0
  221. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_autosync_plugin.py +0 -0
  222. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_azure_credential_resolution.py +0 -0
  223. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_backend.py +0 -0
  224. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_baseline_escalation_message.py +0 -0
  225. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_baseline_persistence.py +0 -0
  226. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_baseline_probe.py +0 -0
  227. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_baseline_provenance.py +0 -0
  228. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_baseline_tree_hash_key.py +0 -0
  229. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_bash_routing.py +0 -0
  230. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_bats_suites_gated.py +0 -0
  231. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_binary_verdict.py +0 -0
  232. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_binding_block_advisory.py +0 -0
  233. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_binding_block_allocation.py +0 -0
  234. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_binding_block_budget.py +0 -0
  235. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_bookkeeping_commit_crash_run.py +0 -0
  236. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_bookkeeping_commit_hook_crash.py +0 -0
  237. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_bookkeeping_no_verify.py +0 -0
  238. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_brief_covers_every_unit_escalation.py +0 -0
  239. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_bug_lane_ci_pending.py +0 -0
  240. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_bug_lane_guardrails.py +0 -0
  241. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_bug_lane_label_nonfatal.py +0 -0
  242. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_bug_lane_labels_registered.py +0 -0
  243. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_bug_lane_min_severity.py +0 -0
  244. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_bug_lane_policy_contract.py +0 -0
  245. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_bug_lane_pr_number_carried.py +0 -0
  246. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_bug_lane_preflight_and_dial.py +0 -0
  247. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_bug_lane_run.py +0 -0
  248. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_bug_lane_state.py +0 -0
  249. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_bug_lane_stopped_outcome_pr_number.py +0 -0
  250. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_bug_lane_test_paths.py +0 -0
  251. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_build_provenance.py +0 -0
  252. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_caller_check_ratchet.py +0 -0
  253. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_changed_file_test_selection.py +0 -0
  254. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_changelog_append_only.py +0 -0
  255. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_changelog_collection.py +0 -0
  256. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_changelog_release_wiring.py +0 -0
  257. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_changelog_schema.py +0 -0
  258. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_ci_conclusion_wait.py +0 -0
  259. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_ci_runs_every_gate.py +0 -0
  260. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_claude_resolution.py +0 -0
  261. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_close_b_pending_learnings.py +0 -0
  262. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_close_records_feature_oracle.py +0 -0
  263. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_close_wu_self_reference.py +0 -0
  264. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_closing_deliverable_guard.py +0 -0
  265. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_closing_guard_contracts.py +0 -0
  266. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_closing_guard_prediction.py +0 -0
  267. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_closing_learnings_destination.py +0 -0
  268. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_closing_requirements.py +0 -0
  269. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_coauthor_trailer_allowlist.py +0 -0
  270. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_conditional_reflection.py +0 -0
  271. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_console_encoding.py +0 -0
  272. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_convergent_iteration.py +0 -0
  273. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_convergent_wu_lint.py +0 -0
  274. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_correlation_id_override.py +0 -0
  275. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_correlation_id_two_surfaces.py +0 -0
  276. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_cost_lifetime.py +0 -0
  277. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_cost_tracking.py +0 -0
  278. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_criteria_per_gate_lint.py +0 -0
  279. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_criteria_state.py +0 -0
  280. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_criteria_worklist.py +0 -0
  281. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_decision_citation_lint.py +0 -0
  282. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_decision_followups_discharged.py +0 -0
  283. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_decision_override_lint.py +0 -0
  284. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_decisions_format.py +0 -0
  285. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_default_severity_aliases.py +0 -0
  286. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_degraded_oracle.py +0 -0
  287. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_deliverable_missing_note.py +0 -0
  288. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_deliverable_presence_gate.py +0 -0
  289. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_derive_agent_policy_review_mode.py +0 -0
  290. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_derive_agent_policy_skill.py +0 -0
  291. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_derive_monitoring_discovery.py +0 -0
  292. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_derive_monitoring_skill_registration.py +0 -0
  293. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_design_for_diagnosis_rule.py +0 -0
  294. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_deterministic_refusal_repeat.py +0 -0
  295. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_diagnose_headless.py +0 -0
  296. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_diagnose_issue_skill.py +0 -0
  297. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_diagnosis.py +0 -0
  298. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_diagnosis_roundtrip_live.py +0 -0
  299. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_dispatch_skeleton.py +0 -0
  300. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_distilled_accept_step.py +0 -0
  301. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_doctor.py +0 -0
  302. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_doctor_command_preflight.py +0 -0
  303. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_done_feature_gates.py +0 -0
  304. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_draft_feature_answers_mode.py +0 -0
  305. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_drafting_answer_gate.py +0 -0
  306. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_drafting_invoke.py +0 -0
  307. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_drafting_question_issue.py +0 -0
  308. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_drafting_questions.py +0 -0
  309. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_drafting_reply_shape.py +0 -0
  310. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_driver_edit_detection.py +0 -0
  311. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_driver_event_registry_covers_emitters.py +0 -0
  312. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_driver_integration.py +0 -0
  313. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_driver_lock.py +0 -0
  314. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_driver_module_surface.py +0 -0
  315. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_driver_restart_halt_surfaced.py +0 -0
  316. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_driver_restart_halt_wiring.py +0 -0
  317. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_driver_restart_hold.py +0 -0
  318. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_driver_staleness_gate_summary.py +0 -0
  319. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_driver_staleness_warning.py +0 -0
  320. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_dry_run_no_branch_mutation.py +0 -0
  321. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_duration_tracking.py +0 -0
  322. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_empty_files_escalation.py +0 -0
  323. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_ensure_feature_branch.py +0 -0
  324. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_ensure_feature_branch_base.py +0 -0
  325. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_escalation_assignee_policy.py +0 -0
  326. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_escalation_contract.py +0 -0
  327. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_escalation_crash_containment.py +0 -0
  328. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_escalation_dedup_search.py +0 -0
  329. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_escalation_emit.py +0 -0
  330. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_event_gate_full_envelope.py +0 -0
  331. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_events_redaction.py +0 -0
  332. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_events_stats.py +0 -0
  333. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_extra_gates.py +0 -0
  334. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_failure_artifact_model.py +0 -0
  335. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_failure_signature_fail_prefix.py +0 -0
  336. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_failure_signature_skips_log_noise.py +0 -0
  337. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_feature_branch_auto_sync.py +0 -0
  338. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_feature_branch_squash_merge.py +0 -0
  339. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_feature_branch_stale_base.py +0 -0
  340. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_feature_id_resolution.py +0 -0
  341. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_feature_oracle_e2e.py +0 -0
  342. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_feature_provider_drafting_dispatch.py +0 -0
  343. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_feature_review_accumulation.py +0 -0
  344. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_filelock_portable.py +0 -0
  345. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_files_changed_auto_repair.py +0 -0
  346. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_find_pr_no_search_index.py +0 -0
  347. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_fingerprint.py +0 -0
  348. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_fix_bug_diff_self_check.py +0 -0
  349. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_fix_bug_headless.py +0 -0
  350. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_fix_bug_reads_comments.py +0 -0
  351. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_fold_contract_documented.py +0 -0
  352. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_followups_artifact.py +0 -0
  353. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_force_full_close.py +0 -0
  354. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_frontmatter_scalar_quoting.py +0 -0
  355. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_gate_broad_run.py +0 -0
  356. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_gate_budget.py +0 -0
  357. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_gate_budget_post_dispatch.py +0 -0
  358. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_gate_commands_needs.py +0 -0
  359. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_gate_eval.py +0 -0
  360. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_gate_eval_calibration.py +0 -0
  361. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_gate_eval_intermediate_wiring.py +0 -0
  362. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_gate_eval_repo_flag.py +0 -0
  363. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_gate_eval_terminal_wiring.py +0 -0
  364. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_gate_report_verdict.py +0 -0
  365. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_gh_backend.py +0 -0
  366. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_gh_features.py +0 -0
  367. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_git_env_isolation.py +0 -0
  368. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_gitleaks_pinning.py +0 -0
  369. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_groom_backlog_skill.py +0 -0
  370. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_guard_refusal_failure_class.py +0 -0
  371. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_guard_refusal_failure_excerpt.py +0 -0
  372. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_guard_repair_e2e.py +0 -0
  373. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_guard_repair_prompt.py +0 -0
  374. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_hashed_denylist.py +0 -0
  375. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_hashed_denylist_ci.py +0 -0
  376. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_heartbeat.py +0 -0
  377. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_heartbeat_adapter.py +0 -0
  378. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_human_output_rule.py +0 -0
  379. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_human_work_unit.py +0 -0
  380. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_hyphenated_gate_name.py +0 -0
  381. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_init_integration.py +0 -0
  382. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_installed_copy_driver_e2e.py +0 -0
  383. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_interpreter_normalization.py +0 -0
  384. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_judge_close_path.py +0 -0
  385. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_judge_cost.py +0 -0
  386. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_judge_cost_fold_committed.py +0 -0
  387. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_judge_module.py +0 -0
  388. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_judge_path_registry.py +0 -0
  389. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_judge_terminal_flip_state.py +0 -0
  390. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_label_provisioning_runner_contract.py +0 -0
  391. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_label_registry.py +0 -0
  392. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_label_registry_covers_consumers.py +0 -0
  393. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_language_aware_narrow_selection.py +0 -0
  394. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_lazy_baseline_e2e.py +0 -0
  395. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_leak_findings_redaction.py +0 -0
  396. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_leak_scan.py +0 -0
  397. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_leak_scan_content.py +0 -0
  398. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_learnings_curate_vendored_promote.py +0 -0
  399. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_learnings_distil_cli.py +0 -0
  400. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_learnings_query.py +0 -0
  401. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_learnings_staging.py +0 -0
  402. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_learnings_suggest_signature_filter.py +0 -0
  403. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_learnings_weights.py +0 -0
  404. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_legacy_4wu_terminal_flips.py +0 -0
  405. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_lifecycle_integration.py +0 -0
  406. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_lifetime_accounting.py +0 -0
  407. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_lint_ac_observable.py +0 -0
  408. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_lint_bare_produces_path.py +0 -0
  409. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_lint_boundary_consistency.py +0 -0
  410. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_lint_boundary_extraction.py +0 -0
  411. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_lint_close_intermediate.py +0 -0
  412. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_lint_close_verdict_not_predecided.py +0 -0
  413. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_lint_close_wu.py +0 -0
  414. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_lint_closing.py +0 -0
  415. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_lint_closing_criteria.py +0 -0
  416. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_lint_closing_criteria_pristine.py +0 -0
  417. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_lint_correlation_id.py +0 -0
  418. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_lint_correlation_id_close_intermediate.py +0 -0
  419. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_lint_covers_load_wu_invariants.py +0 -0
  420. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_lint_feature_oracle_declared.py +0 -0
  421. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_lint_gate_proportionality.py +0 -0
  422. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_lint_monitoring.py +0 -0
  423. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_lint_oracle_env.py +0 -0
  424. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_lint_plan_contract_fields.py +0 -0
  425. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_lint_plan_errors.py +0 -0
  426. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_lint_plan_next_draft.py +0 -0
  427. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_lint_plan_relative_import.py +0 -0
  428. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_lint_plan_verdict_exempt.py +0 -0
  429. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_lint_planning_sections.py +0 -0
  430. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_lint_produces_driver_helper.py +0 -0
  431. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_lint_produces_satisfiability.py +0 -0
  432. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_lint_roadmap.py +0 -0
  433. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_lint_sections.py +0 -0
  434. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_lint_task_graph_yaml_selection.py +0 -0
  435. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_lint_verification_reference.py +0 -0
  436. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_live_tests_opt_in.py +0 -0
  437. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_load_bearing_close_lint.py +0 -0
  438. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_loop_attempt_evidence.py +0 -0
  439. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_loop_auto_archive.py +0 -0
  440. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_loop_caveman_preamble.py +0 -0
  441. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_loop_close_intermediate.py +0 -0
  442. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_loop_criteria_skeleton.py +0 -0
  443. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_loop_criteria_survival.py +0 -0
  444. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_loop_defaults_by_type.py +0 -0
  445. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_loop_effort.py +0 -0
  446. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_loop_failure_note_cap.py +0 -0
  447. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_loop_files_changed_guard.py +0 -0
  448. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_loop_gate_budget.py +0 -0
  449. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_loop_gate_needs.py +0 -0
  450. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_loop_model_alias.py +0 -0
  451. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_loop_orchestration.py +0 -0
  452. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_loop_post_pass_invariant.py +0 -0
  453. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_loop_reset_preserving_events.py +0 -0
  454. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_loop_smoke_runner.py +0 -0
  455. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_loop_unsandboxed.py +0 -0
  456. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_loop_worklist_injection.py +0 -0
  457. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_loop_zero_token_guard.py +0 -0
  458. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_make_repo_gpgsign_isolation.py +0 -0
  459. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_marker_dual_shape.py +0 -0
  460. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_maven_failure_excerpt.py +0 -0
  461. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_max_attempts_configurable.py +0 -0
  462. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_max_open_prs.py +0 -0
  463. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_migrate_legacy.py +0 -0
  464. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_miniyaml_equivalence.py +0 -0
  465. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_miniyaml_negative.py +0 -0
  466. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_monitor_cli.py +0 -0
  467. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_monitor_issue_lifecycle.py +0 -0
  468. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_monitor_runner_surfaces.py +0 -0
  469. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_monitoring_bootstrap_artifacts.py +0 -0
  470. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_monitoring_cron_dialect.py +0 -0
  471. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_monitoring_example.py +0 -0
  472. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_monitoring_fenced_blocks.py +0 -0
  473. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_monitoring_seed.py +0 -0
  474. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_narrow_fallback_warning.py +0 -0
  475. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_no_pytest_subprocess.py +0 -0
  476. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_notify.py +0 -0
  477. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_notify_escalation.py +0 -0
  478. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_notify_sla.py +0 -0
  479. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_operator_escalation_rule.py +0 -0
  480. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_oracle_set_declared.py +0 -0
  481. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_oracle_unrunnable_attribution.py +0 -0
  482. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_pick_feature_skill.py +0 -0
  483. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_pin_honesty_and_integrity.py +0 -0
  484. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_pin_marker_not_inherited.py +0 -0
  485. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_pin_marker_spawn_sites.py +0 -0
  486. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_plan_baseline.py +0 -0
  487. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_planned_cost_lint.py +0 -0
  488. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_planning_cost_floor.py +0 -0
  489. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_plugin_qualified_dispatch.py +0 -0
  490. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_policy_budgets_applied.py +0 -0
  491. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_policy_proposals.py +0 -0
  492. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_policy_review.py +0 -0
  493. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_pr_checks_json_field.py +0 -0
  494. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_pr_reference_in_title.py +0 -0
  495. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_pre_existing_claims_cite_evidence.py +0 -0
  496. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_prep_halt_operator_surface.py +0 -0
  497. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_prepare_feature.py +0 -0
  498. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_prepare_scaffold_sync.py +0 -0
  499. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_prerun_capture.py +0 -0
  500. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_prerun_oracles.py +0 -0
  501. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_prerun_wiring.py +0 -0
  502. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_produced_document_verdict_guard.py +0 -0
  503. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_produces_bracket_literal.py +0 -0
  504. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_produces_bracket_wildcard.py +0 -0
  505. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_produces_field.py +0 -0
  506. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_produces_in_diff.py +0 -0
  507. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_produces_justification.py +0 -0
  508. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_produces_recursive_glob.py +0 -0
  509. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_produces_semantics_unified.py +0 -0
  510. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_produces_shape.py +0 -0
  511. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_progress_forward_note.py +0 -0
  512. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_progress_lines_end_to_end.py +0 -0
  513. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_promotion_ledger.py +0 -0
  514. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_provider_escalation_traces.py +0 -0
  515. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_provision_labels.py +0 -0
  516. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_queue_stalled_adapter.py +0 -0
  517. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_rearm_clears_stale_criteria_state.py +0 -0
  518. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_rearm_fold_marker.py +0 -0
  519. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_rearm_migration.py +0 -0
  520. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_red_baseline_never_reused.py +0 -0
  521. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_redaction_windows_home.py +0 -0
  522. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_reflection_required_signals.py +0 -0
  523. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_replan_end_to_end.py +0 -0
  524. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_replan_event_emission.py +0 -0
  525. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_replan_note_collision.py +0 -0
  526. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_replan_trigger.py +0 -0
  527. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_replan_turn_contract.py +0 -0
  528. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_replay_spin.py +0 -0
  529. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_resolve_base.py +0 -0
  530. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_restart_cap_counts_progress.py +0 -0
  531. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_result_block.py +0 -0
  532. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_result_block_audience.py +0 -0
  533. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_resume_command_build.py +0 -0
  534. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_retain_on_guard_refusal_docs.py +0 -0
  535. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_roadmap_add_skill.py +0 -0
  536. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_roadmap_archive_cli.py +0 -0
  537. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_roadmap_archive_shim.py +0 -0
  538. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_roadmap_archive_skill.py +0 -0
  539. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_roadmap_archive_skill_delegates.py +0 -0
  540. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_roadmap_link_gate.py +0 -0
  541. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_roadmap_row_parser.py +0 -0
  542. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_ruff_verdict_lines.py +0 -0
  543. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_rule_import_convergence.py +0 -0
  544. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_scaffold_data_in_sync.py +0 -0
  545. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_scaffold_doc_hygiene.py +0 -0
  546. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_scaffold_docs.py +0 -0
  547. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_scaffold_init.py +0 -0
  548. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_scaffold_label_provisioning.py +0 -0
  549. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_scaffold_manifest.py +0 -0
  550. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_scaffold_resources.py +0 -0
  551. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_scaffold_seed_sanity.py +0 -0
  552. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_scaffold_skew_halts.py +0 -0
  553. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_scaffold_upgrade.py +0 -0
  554. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_scaffold_wiring.py +0 -0
  555. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_schedule_dialect.py +0 -0
  556. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_seed_documents_prerun_keys.py +0 -0
  557. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_service_bus_dlq_adapter.py +0 -0
  558. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_session_env_preflight.py +0 -0
  559. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_severity_aliases.py +0 -0
  560. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_severity_backfill_apply.py +0 -0
  561. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_severity_backfill_end_to_end.py +0 -0
  562. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_severity_backfill_limit.py +0 -0
  563. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_severity_backfill_marker.py +0 -0
  564. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_severity_rubric.py +0 -0
  565. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_shipped_templates_parse.py +0 -0
  566. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_skill_discovery_links.py +0 -0
  567. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_skills_vendored_in_sync.py +0 -0
  568. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_spinning_rearm_gate.py +0 -0
  569. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_spinout_brief_end_to_end.py +0 -0
  570. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_spinout_brief_replan_option.py +0 -0
  571. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_squash_commit_hook_crash.py +0 -0
  572. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_stop_rationale_captured.py +0 -0
  573. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_stranded_halt_cause.py +0 -0
  574. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_surefire_run_level_signature.py +0 -0
  575. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_surefire_verdict_lines.py +0 -0
  576. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_template_closing_shapes.py +0 -0
  577. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_terminal_flip_ownership.py +0 -0
  578. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_terminal_flips.py +0 -0
  579. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_terminal_gate_message.py +0 -0
  580. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_tiered_verification_e2e.py +0 -0
  581. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_timeout_kill_portable.py +0 -0
  582. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_triage.py +0 -0
  583. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_triage_apply.py +0 -0
  584. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_triage_severity_classify.py +0 -0
  585. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_triage_severity_end_to_end.py +0 -0
  586. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_triage_severity_write.py +0 -0
  587. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_triage_skill_contract.py +0 -0
  588. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_triage_skips_agent_escalations.py +0 -0
  589. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_tsc_verdict_lines.py +0 -0
  590. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_umbrella_version_resolution.py +0 -0
  591. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_unterminated_frontmatter.py +0 -0
  592. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_untracked_feature_folder.py +0 -0
  593. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_untracked_file_absorption.py +0 -0
  594. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_upgrade_integration.py +0 -0
  595. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_upgrade_merge_gate.py +0 -0
  596. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_validate_event.py +0 -0
  597. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_validate_event_driver_types.py +0 -0
  598. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_verdict_coupling.py +0 -0
  599. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_verify_empty_gate_set.py +0 -0
  600. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_version_consistency.py +0 -0
  601. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_version_skew.py +0 -0
  602. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_windows_gate_exec.py +0 -0
  603. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_worktree_restore.py +0 -0
  604. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/tests/test_wu_execution_metadata.py +0 -0
  605. {specfuse_loop-0.22.0 → specfuse_loop-0.22.2}/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.0
3
+ Version: 0.22.2
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.0"
7
+ version = "0.22.2"
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"
@@ -788,7 +788,18 @@ class BugsProvider:
788
788
  label_written=result.label_written,
789
789
  )
790
790
 
791
- return ActionOutcome(status=STATUS_ESCALATED, detail=detail, escalation=escalation)
791
+ # #3372: only a session that produced NO outcome marker can have an
792
+ # environmental cause — a dispatch that failed, an empty result, a
793
+ # crash. `outcome_named` is exactly that distinction, already carried
794
+ # from the session's own text (#3343). A `refused` is `/fix-bug`
795
+ # reading the issue and judging it out of scope; three of those say the
796
+ # queue holds three feature-scoped issues, not that anything is broken.
797
+ return ActionOutcome(
798
+ status=STATUS_ESCALATED,
799
+ detail=detail,
800
+ escalation=escalation,
801
+ environmental=not result.outcome_named,
802
+ )
792
803
 
793
804
  def reconcile(self, item: ActionItem, outcome: ActionOutcome) -> None:
794
805
  return None
@@ -220,6 +220,24 @@ class ActionOutcome:
220
220
  #: same shape as `NON_JUDGE_MODULES` and
221
221
  #: `DEPENDENCY_MANIFEST_NAMED_UNCOVERED`.
222
222
  escalation_waived: str = ""
223
+ #: Whether this escalation could have an environmental cause (#3372).
224
+ #:
225
+ #: Only an outcome reached WITHOUT reading the item can be about the run's
226
+ #: environment: a dispatch that failed, a session that produced no outcome
227
+ #: marker, a crash. An outcome reached BY reading the item is a judgement
228
+ #: about that item — `/fix-bug`'s `refused` says the work is not bug-sized,
229
+ #: `ci_not_green` and `pr_not_found` are facts about that issue's PR — and
230
+ #: three in a row says the queue holds three such issues, not that anything
231
+ #: is broken.
232
+ #:
233
+ #: #3343's breaker counted every escalation and stopped a run that was
234
+ #: working: two fixes merged, one held on red CI, six correctly declined,
235
+ #: 19 items never reached, and an escalation asserting an environment
236
+ #: cause a human then had to disprove.
237
+ #:
238
+ #: **Defaults False so a provider must opt in.** A provider that says
239
+ #: nothing never trips the breaker, rather than tripping it by omission.
240
+ environmental: bool = False
223
241
 
224
242
 
225
243
  class ActionProvider(Protocol):
@@ -1003,12 +1021,22 @@ def run_agent(
1003
1021
  # #3343: an escalation whose cause keeps repeating is not about
1004
1022
  # the item. Counted here rather than in a provider, because only
1005
1023
  # the run can see that the SAME thing failed N times.
1006
- signature = failure_signature(item, outcome)
1007
- if signature == last_failure_signature:
1008
- consecutive_identical_failures += 1
1024
+ #
1025
+ # #3372: but only an outcome the provider marks `environmental`
1026
+ # can have an environmental cause. A per-item judgement —
1027
+ # `refused`, `ci_not_green`, `pr_not_found` — RESETS the count
1028
+ # rather than being ignored: a lane still reaching verdicts on
1029
+ # items is not one whose environment is broken.
1030
+ if not outcome.environmental:
1031
+ last_failure_signature = None
1032
+ consecutive_identical_failures = 0
1009
1033
  else:
1010
- last_failure_signature = signature
1011
- consecutive_identical_failures = 1
1034
+ signature = failure_signature(item, outcome)
1035
+ if signature == last_failure_signature:
1036
+ consecutive_identical_failures += 1
1037
+ else:
1038
+ last_failure_signature = signature
1039
+ consecutive_identical_failures = 1
1012
1040
 
1013
1041
  if consecutive_identical_failures >= IDENTICAL_FAILURE_LIMIT:
1014
1042
  run_reason = (
@@ -37,7 +37,11 @@ from typing import Callable, Optional
37
37
 
38
38
  from specfuse.agent.invoke import run_claude
39
39
  from specfuse.agent.triage_invoke import build_invocation, classify_severity
40
- from specfuse.loop.labels import read_severity_rubric
40
+ from specfuse.loop.labels import (
41
+ list_existing_labels,
42
+ read_severity_rubric,
43
+ severity_label_projection,
44
+ )
41
45
 
42
46
  from specfuse.loop import triage
43
47
  from specfuse.loop.build_provenance import warn_if_out_of_tree
@@ -50,7 +54,13 @@ def _default_runner(argv: list, check: bool = False):
50
54
  return subprocess.run(argv, check=check, capture_output=True, text=True)
51
55
 
52
56
 
53
- def apply_severity_backfill(runner: Callable, repo: str, decisions: list) -> list:
57
+ def apply_severity_backfill(
58
+ runner: Callable,
59
+ repo: str,
60
+ decisions: list,
61
+ *,
62
+ label_projection: "dict | None" = None,
63
+ ) -> list:
54
64
  """Record each decision in `decisions` against its GitHub issue, marker
55
65
  first, label second.
56
66
 
@@ -65,6 +75,13 @@ def apply_severity_backfill(runner: Callable, repo: str, decisions: list) -> lis
65
75
  amended marker in place -- the marker is the authoritative record, the
66
76
  label a projection re-derived from it (`PLAN.md`'s "Record
67
77
  precedence").
78
+
79
+ `label_projection` maps a vocabulary value to the label this repository
80
+ spells it with (`labels.severity_label_projection`, #3353). The marker
81
+ always records the vocabulary value, so it stays comparable across
82
+ repositories; only the label takes the operator's own word. Absent or
83
+ missing a value, the vocabulary label is written -- the behaviour before
84
+ the projection existed.
68
85
  """
69
86
  results = []
70
87
  for decision in decisions:
@@ -104,7 +121,9 @@ def apply_severity_backfill(runner: Callable, repo: str, decisions: list) -> lis
104
121
  continue
105
122
  row["marker_written"] = True
106
123
 
107
- label = triage.severity_label_for(severity)
124
+ label = (label_projection or {}).get(
125
+ severity, triage.severity_label_for(severity)
126
+ )
108
127
  try:
109
128
  runner(
110
129
  ["gh", "issue", "edit", str(number), "--repo", repo, "--add-label", label],
@@ -156,7 +175,14 @@ def run_backfill(
156
175
  once `apply_severity_backfill` has run over it, that function's own
157
176
  `skipped`/`marker_written`/`label_written` fields.
158
177
  """
159
- rubric = read_severity_rubric(working_dir, runner=runner, repo=repo)
178
+ # One `gh label list` feeds both the rubric and the write-side
179
+ # projection (#3353): the once-per-run listing this run is asserted on.
180
+ existing, _listing_reason = list_existing_labels(
181
+ working_dir, runner=runner, repo=repo
182
+ )
183
+ rubric = read_severity_rubric(
184
+ working_dir, runner=runner, repo=repo, existing=existing
185
+ )
160
186
  if not rubric:
161
187
  return {
162
188
  "rubric": {},
@@ -201,7 +227,14 @@ def run_backfill(
201
227
  decisions.append({"number": number, "body": body, "severity": severity})
202
228
 
203
229
  if apply and decisions:
204
- write_results = apply_severity_backfill(runner, repo, decisions)
230
+ # A property of the repository's label set, not of any one issue
231
+ # (#3353) -- derived from the listing already read above.
232
+ projection = severity_label_projection(
233
+ [item.get("name", "") for item in (existing or [])]
234
+ )
235
+ write_results = apply_severity_backfill(
236
+ runner, repo, decisions, label_projection=projection
237
+ )
205
238
  by_number = {result["number"]: result for result in write_results}
206
239
  for row in rows:
207
240
  result = by_number.get(row["number"])
@@ -46,6 +46,7 @@ __all__ = (
46
46
  "SEVERITY_VALUES",
47
47
  "read_severity_label",
48
48
  "resolve_severity_aliases",
49
+ "resolve_required_checks",
49
50
  "DEFAULT_SEVERITY_ALIASES",
50
51
  "AUTOMERGE_VALUES",
51
52
  "GATE_REVIEW_VALUES",
@@ -295,6 +296,45 @@ def resolve_max_items(flag: "int | None",
295
296
  return _positive_int_budget(path, "max_items_per_day")
296
297
 
297
298
 
299
+ def resolve_required_checks(path: "str | Path | None" = None) -> tuple:
300
+ """`rules.bugs.required_checks` as a tuple of check names, or `()` (#3373).
301
+
302
+ The names that must be **present and passing** before the bug lane may
303
+ auto-merge. Empty when absent or unusable, so a deployment that never
304
+ declared it behaves exactly as before: any `success` conclusion is accepted.
305
+
306
+ Why it exists. `evaluate_merge_guardrails` accepted any `success`, and a
307
+ repository whose PR job is a deliberate fast lane — excluding a test group
308
+ that guards committed expectations — gave a `success` that **could not
309
+ fail** on that class of defect. A bug-lane PR merged under `automerge: "on"`
310
+ and left `main` red for an hour.
311
+
312
+ The split those repositories rely on assumes every change already passed
313
+ the full gate set before its PR opened. That holds for driver work-unit
314
+ PRs and does **not** hold for bug-lane PRs, where a headless session
315
+ chooses which tests to run. This key is how a repository points the lane at
316
+ the complete check rather than the fast one.
317
+
318
+ A string is accepted as a one-element list — the common single-check case —
319
+ and anything else unusable resolves to `()`. Requiring nothing is the
320
+ pre-existing behaviour; inventing a requirement from a malformed value
321
+ would block every merge on a typo.
322
+ """
323
+ try:
324
+ policy = load_policy(path)
325
+ except (FileNotFoundError, OSError):
326
+ return ()
327
+ rules = policy.get("rules") if isinstance(policy, dict) else None
328
+ bugs = rules.get("bugs") if isinstance(rules, dict) else None
329
+ raw = bugs.get("required_checks") if isinstance(bugs, dict) else None
330
+ if isinstance(raw, str):
331
+ name = raw.strip()
332
+ return (name,) if name else ()
333
+ if not isinstance(raw, list):
334
+ return ()
335
+ return tuple(str(n).strip() for n in raw if str(n).strip())
336
+
337
+
298
338
  def resolve_max_open_prs(path: "str | Path | None" = None) -> "int | None":
299
339
  """`budgets.max_open_prs`, or None when unset or unusable (#3340).
300
340
 
@@ -323,10 +363,21 @@ def describe_budget_sources(**caps) -> str:
323
363
  that is not capped: the operator cannot tell which of the two they have.
324
364
  Each value is a `(value, source)` pair, source being `flag`, `policy` or
325
365
  `none`.
366
+
367
+ `max_items` carries an explicit `per run` (#3340). The policy key it comes
368
+ from is named `max_items_per_day` and is applied per run -- enforcing it
369
+ across a day needs state that survives a process. Printing `max_items=62
370
+ (policy)` against that key invites the reading the name suggests, and the
371
+ wrong reading is the unsafe one: a day of runs can exceed the number the
372
+ operator believes they set. Only this cap is annotated, because only this
373
+ one has a name promising something wider than it delivers; an unset cap
374
+ prints `unbounded` and needs no qualifier.
326
375
  """
327
376
  parts = []
328
377
  for name, (value, source) in sorted(caps.items()):
329
- parts.append(f"{name}={'unbounded' if value is None else value} ({source})")
378
+ rendered = "unbounded" if value is None else str(value)
379
+ qualifier = " per run" if name == "max_items" and value is not None else ""
380
+ parts.append(f"{name}={rendered} ({source}{qualifier})")
330
381
  return "budgets: " + ", ".join(parts)
331
382
 
332
383
 
@@ -38,6 +38,7 @@ from typing import Any, Callable, Optional
38
38
 
39
39
  from specfuse.loop.agent_policy import (
40
40
  bug_lane_ci_wait_seconds,
41
+ resolve_required_checks,
41
42
  bug_lane_limits,
42
43
  resolve_bug_automerge,
43
44
  )
@@ -197,7 +198,40 @@ _BUCKET_PENDING = "pending"
197
198
  _CI_FAILING = "fail"
198
199
 
199
200
 
200
- def _read_ci_conclusion_once(runner: Callable, repo: str, pr_number: int) -> str:
201
+ def _required_checks_verdict(rows: list, required) -> str:
202
+ """`""` when every required check is present and passing, else why not.
203
+
204
+ A required check that **never ran** is the case #3373 is about: the
205
+ repository's PR job was green and the check that would have caught the
206
+ defect was not in the list at all. It is not a failure — nothing failed —
207
+ and it is equally not the green the operator asked the lane to require.
208
+
209
+ A required check that was **skipped** is treated the same way. Skipping is
210
+ not a failure in general, which is why `_BUCKETS_OK` tolerates it, but a
211
+ check an operator named as required and that did not run cannot establish
212
+ what it was required to establish.
213
+ """
214
+ if not required:
215
+ return ""
216
+ by_name = {}
217
+ for row in rows:
218
+ name = row.get("name")
219
+ if isinstance(name, str):
220
+ by_name[name] = str(row.get("bucket") or "").lower()
221
+ for name in required:
222
+ bucket = by_name.get(name)
223
+ if bucket is None:
224
+ return f"required check never ran: {name}"
225
+ if bucket == _BUCKET_PENDING:
226
+ return _CI_PENDING
227
+ if bucket != "pass":
228
+ return f"required check not passing: {name} ({bucket})"
229
+ return ""
230
+
231
+
232
+ def _read_ci_conclusion_once(
233
+ runner: Callable, repo: str, pr_number: int, required=()
234
+ ) -> str:
201
235
  """One `gh pr checks` read, mapped through `bucket`.
202
236
 
203
237
  `_CI_PENDING` when any check is queued or running, or when no check is
@@ -244,6 +278,14 @@ def _read_ci_conclusion_once(runner: Callable, repo: str, pr_number: int) -> str
244
278
 
245
279
  if _BUCKET_PENDING in buckets:
246
280
  return _CI_PENDING
281
+
282
+ # #3373: the operator's own list, checked before the aggregate verdict.
283
+ # A repository whose PR job deliberately excludes a test group can point
284
+ # the lane at the complete check instead of the fast one.
285
+ verdict = _required_checks_verdict(rows, required)
286
+ if verdict:
287
+ return verdict
288
+
247
289
  if buckets <= _BUCKETS_OK:
248
290
  return "success"
249
291
  return _CI_FAILING
@@ -258,6 +300,7 @@ def pr_ci_conclusion(
258
300
  clock: Callable = time.monotonic,
259
301
  deadline_seconds: float = CI_WAIT_SECONDS,
260
302
  poll_seconds: float = CI_POLL_SECONDS,
303
+ required_checks=(),
261
304
  ) -> str:
262
305
  """Wait for `pr_number`'s CI to reach a conclusion, then return it.
263
306
 
@@ -284,7 +327,9 @@ def pr_ci_conclusion(
284
327
  """
285
328
  started = clock()
286
329
  while True:
287
- conclusion = _read_ci_conclusion_once(runner, repo, pr_number)
330
+ conclusion = _read_ci_conclusion_once(
331
+ runner, repo, pr_number, required_checks
332
+ )
288
333
  if conclusion != _CI_PENDING:
289
334
  return conclusion
290
335
  if clock() - started >= deadline_seconds:
@@ -720,6 +765,11 @@ def run_bug_lane(
720
765
  ci_conclusion = pr_ci_conclusion(
721
766
  runner, repo, pr_number,
722
767
  sleep=ci_sleep, clock=ci_clock, deadline_seconds=deadline_seconds,
768
+ # #3373: the checks this repository says must be green before the lane
769
+ # may merge unattended. Empty unless declared, so behaviour is
770
+ # unchanged for anyone who has not pointed the lane at a complete
771
+ # check.
772
+ required_checks=resolve_required_checks(policy_path),
723
773
  )
724
774
  state_reader = GitHubMergeCapState(runner=runner, repo=repo, now=now)
725
775
 
@@ -0,0 +1 @@
1
+ 0.22.2
@@ -402,11 +402,75 @@ def _ensure_severity_labels(target, *, runner, repo=None) -> list:
402
402
  return failed
403
403
 
404
404
 
405
+ def severity_label_projection(label_names, *, aliases: Optional[dict] = None) -> dict:
406
+ """`{severity_value: label_name}` for the values a repository spells in its
407
+ own word (#3353), given the `severity:*` labels it defines.
408
+
409
+ `severity_aliases` (#3349) are honoured by the `min_severity` floor, which
410
+ reads an aliased label through `read_severity_label`. The classifier
411
+ FEAT-2026-0113 added answers in the published vocabulary instead, so the
412
+ write path projected `severity:high` at a repository whose issues all say
413
+ `severity:major` -- both spellings in circulation, and nothing reporting
414
+ the asymmetry.
415
+
416
+ This is the write-side half. The classifier is deliberately left answering
417
+ in the vocabulary so the marker's `severity=` field stays comparable across
418
+ repositories; only the *label* takes the repository's own word.
419
+
420
+ Pure over *label_names* rather than listing them itself, so the one caller
421
+ that needs both this and `read_severity_rubric` pays for a single
422
+ `gh label list` -- the once-per-run listing that run is asserted on.
423
+
424
+ Entries appear only where the projection actually differs, so an empty
425
+ result means "write the vocabulary label" and callers need no special case.
426
+ Two bounds keep it honest:
427
+
428
+ * **A label the repository does not define is never projected.** The
429
+ shipped `DEFAULT_SEVERITY_ALIASES` name words (`blocker`, `urgent`,
430
+ `trivial`) most repositories have never created; projecting one would
431
+ write a label that does not exist.
432
+ * **An in-vocabulary label always wins.** A repository defining both
433
+ `severity:high` and `severity:major` gets no entry for `high` -- the same
434
+ precedence `read_severity_rubric` applies when it builds the rubric.
435
+
436
+ Two aliases onto one value is the operator's own ambiguity. The lowest
437
+ label name sorts first, so a run is at least reproducible; defining the
438
+ vocabulary label is what removes the ambiguity.
439
+ """
440
+ prefix = agent_policy.SEVERITY_LABEL_PREFIX
441
+ try:
442
+ severity_names = sorted(
443
+ name for name in label_names if name.startswith(prefix)
444
+ )
445
+ except TypeError:
446
+ return {}
447
+
448
+ in_vocabulary = {
449
+ name[len(prefix):] for name in severity_names
450
+ if name[len(prefix):] in agent_policy.SEVERITY_VALUES
451
+ }
452
+
453
+ aliases = aliases if aliases is not None else agent_policy.resolve_severity_aliases()
454
+ projection: dict = {}
455
+ for name in severity_names:
456
+ word = name[len(prefix):]
457
+ if word in agent_policy.SEVERITY_VALUES:
458
+ continue
459
+ mapped = aliases.get(word.strip().lower())
460
+ if mapped not in agent_policy.SEVERITY_VALUES:
461
+ continue
462
+ if mapped in in_vocabulary or mapped in projection:
463
+ continue
464
+ projection[mapped] = name
465
+ return projection
466
+
467
+
405
468
  def read_severity_rubric(
406
469
  target: str | Path,
407
470
  *,
408
471
  runner: Optional[Callable] = None,
409
472
  repo: Optional[str] = None,
473
+ existing: Optional[list] = None,
410
474
  ) -> dict:
411
475
  """`{severity_value: description}` from the repo's own `severity:*` labels,
412
476
  or specfuse's `DEFAULT_SEVERITY_RUBRIC` when it has defined none (#3352).
@@ -428,7 +492,11 @@ def read_severity_rubric(
428
492
  """
429
493
  runner = runner if runner is not None else _default_runner
430
494
 
431
- existing, _reason = list_existing_labels(target, runner=runner, repo=repo)
495
+ # `existing` lets a caller that already listed the repository's labels pass
496
+ # them in rather than pay for a second `gh label list` (#3353). Absent, the
497
+ # listing happens here exactly as it always did.
498
+ if existing is None:
499
+ existing, _reason = list_existing_labels(target, runner=runner, repo=repo)
432
500
  if existing is None:
433
501
  return {}
434
502
 
@@ -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.22.0"
128
+ DRIVER_VERSION = "0.22.2"
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: specfuse-loop
3
- Version: 0.22.0
3
+ Version: 0.22.2
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
@@ -367,6 +367,7 @@ tests/test_heartbeat.py
367
367
  tests/test_heartbeat_adapter.py
368
368
  tests/test_human_output_rule.py
369
369
  tests/test_human_work_unit.py
370
+ tests/test_hygiene_boundary_guidance.py
370
371
  tests/test_hyphenated_gate_name.py
371
372
  tests/test_init_integration.py
372
373
  tests/test_installed_copy_driver_e2e.py
@@ -469,6 +470,7 @@ tests/test_notify_sla.py
469
470
  tests/test_operator_escalation_rule.py
470
471
  tests/test_oracle_set_declared.py
471
472
  tests/test_oracle_unrunnable_attribution.py
473
+ tests/test_per_run_item_cap_disclosure.py
472
474
  tests/test_pick_feature_skill.py
473
475
  tests/test_pin_honesty_and_integrity.py
474
476
  tests/test_pin_marker_not_inherited.py
@@ -476,6 +478,7 @@ tests/test_pin_marker_spawn_sites.py
476
478
  tests/test_plan_baseline.py
477
479
  tests/test_planned_cost_lint.py
478
480
  tests/test_planning_cost_floor.py
481
+ tests/test_plugin_prose_cross_refs.py
479
482
  tests/test_plugin_qualified_dispatch.py
480
483
  tests/test_policy_budgets_applied.py
481
484
  tests/test_policy_proposals.py
@@ -516,6 +519,7 @@ tests/test_replan_note_collision.py
516
519
  tests/test_replan_trigger.py
517
520
  tests/test_replan_turn_contract.py
518
521
  tests/test_replay_spin.py
522
+ tests/test_required_checks.py
519
523
  tests/test_resolve_base.py
520
524
  tests/test_restart_cap_counts_progress.py
521
525
  tests/test_result_block.py
@@ -553,6 +557,7 @@ tests/test_severity_backfill_end_to_end.py
553
557
  tests/test_severity_backfill_limit.py
554
558
  tests/test_severity_backfill_marker.py
555
559
  tests/test_severity_backfill_run.py
560
+ tests/test_severity_label_projection.py
556
561
  tests/test_severity_rubric.py
557
562
  tests/test_shipped_templates_parse.py
558
563
  tests/test_skill_discovery_links.py
@@ -0,0 +1,88 @@
1
+ # Copyright 2026 Specfuse Contributors
2
+ # Licensed under the Apache License, Version 2.0. See LICENSE.
3
+ """The authoring rule teaches the reword the lint asks for (#3357).
4
+
5
+ `lint_plan` WARNs when a work unit declares a `produces:` path that also matches
6
+ one of its own Do-not-touch patterns, and since #3357 that warning names the
7
+ fix: reword the boundary to name the *surfaces* it protects rather than the file
8
+ this unit also produces.
9
+
10
+ The shape that trips it is not an edge case. A hygiene unit exists to make a
11
+ bounded fix inside a file a previous unit built, so it has to declare that file
12
+ in `produces:` *and* protect almost all of it — and §7 of the authoring skill,
13
+ which is where an author is told how to write a hygiene unit, said to name the
14
+ broken file in `produces:` without saying anything about how to word the
15
+ boundary. The author met the warning with no guidance in the rule that
16
+ generated the shape.
17
+
18
+ Worse than unhelpful: FEAT-2026-0113's `T08H` wrote "every other function in
19
+ `specfuse/loop/triage.py`", which read literally forbids editing the one
20
+ function that unit existed to fix. It passed only because the acceptance
21
+ criteria named the target unambiguously, so the boundary and the criteria
22
+ contradicted each other and only the criteria were load-bearing.
23
+
24
+ This guards the pairing rather than the prose: whatever wording either side
25
+ uses, the authoring rule must teach surface-naming, so the lint's recommended
26
+ fix and the rule that produces the shape cannot drift apart again.
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ import re
32
+ import unittest
33
+ from pathlib import Path
34
+
35
+ REPO_ROOT = Path(__file__).resolve().parent.parent
36
+ SKILL = REPO_ROOT / "plugins/specfuse/skills/authoring-work-units/SKILL.md"
37
+ MIRROR = REPO_ROOT / ".specfuse/skills/authoring-work-units/SKILL.md"
38
+ LINT = REPO_ROOT / "specfuse/loop/lint_plan.py"
39
+
40
+
41
+ def _section_7(text: str) -> str:
42
+ """§7, the hygiene-work-unit section, up to the next top-level heading."""
43
+ match = re.search(r"^## 7\. .*?$(.*?)(?=^## \d+\. )", text, re.S | re.M)
44
+ assert match, "authoring skill has no '## 7.' section"
45
+ return match.group(1)
46
+
47
+
48
+ class HygieneBoundaryGuidance(unittest.TestCase):
49
+
50
+ def test_the_lint_still_recommends_naming_surfaces(self):
51
+ # If this fails the lint changed its advice, and the rule below is
52
+ # now teaching the wrong reword rather than merely a stale one.
53
+ self.assertIn("surfaces it protects", LINT.read_text(encoding="utf-8"))
54
+
55
+ def test_section_7_tells_the_author_how_to_word_the_boundary(self):
56
+ body = _section_7(SKILL.read_text(encoding="utf-8"))
57
+
58
+ self.assertIn(
59
+ "surface", body.lower(),
60
+ "§7 tells an author to put the broken file in `produces:`, which is "
61
+ "exactly the shape that trips the Do-not-touch collision warning. "
62
+ "It must also tell them to word the boundary by naming the "
63
+ "surfaces it protects, or the rule generates a warning it gives no "
64
+ "way to answer (#3357).",
65
+ )
66
+
67
+ def test_section_7_warns_against_the_whole_file_wording(self):
68
+ body = _section_7(SKILL.read_text(encoding="utf-8")).lower()
69
+
70
+ self.assertTrue(
71
+ "every other function in" in body or "whole file" in body,
72
+ "§7 must show the wording that actually went wrong — a boundary "
73
+ "phrased over a whole file contradicts the criteria of a unit that "
74
+ "exists to edit one function in it (FEAT-2026-0113/T08H).",
75
+ )
76
+
77
+ def test_the_mirrored_copy_agrees(self):
78
+ # plugins/ is canonical; .specfuse/ is the synced scaffold copy.
79
+ self.assertEqual(
80
+ SKILL.read_text(encoding="utf-8"),
81
+ MIRROR.read_text(encoding="utf-8"),
82
+ "plugins/ and .specfuse/ copies of the authoring skill diverged — "
83
+ "run scripts/sync-scaffold.sh",
84
+ )
85
+
86
+
87
+ if __name__ == "__main__": # pragma: no cover
88
+ unittest.main()
@@ -0,0 +1,80 @@
1
+ # Copyright 2026 Specfuse Contributors
2
+ # Licensed under the Apache License, Version 2.0. See LICENSE.
3
+ """`max_items_per_day` is enforced per run, and now says so (#3340).
4
+
5
+ Most of #3340 landed: budgets resolve from the policy when no flag is given,
6
+ `max_open_prs` gates bug-lane dispatch, and the run opens with a line naming
7
+ each effective cap and its source.
8
+
9
+ One expectation was left. The policy key is named `max_items_per_day` and is
10
+ applied *per run* -- enforcing it across runs within a day needs state that
11
+ survives a process, which is a feature, not this fix. `resolve_max_items`
12
+ records that honestly in its docstring, but a docstring is not a surface an
13
+ operator reads. The issue offered the alternative explicitly: enforce it across
14
+ runs, **or** document it as per-run.
15
+
16
+ So the operator-facing surfaces carry it. A run that prints `max_items=62
17
+ (policy)` against a policy whose key says `per_day` invites exactly the wrong
18
+ reading -- and the wrong reading is the unsafe one, since a day of runs can
19
+ exceed the number the operator believes they set.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import unittest
25
+ from pathlib import Path
26
+
27
+ from specfuse.loop import agent_policy
28
+
29
+ REPO_ROOT = Path(__file__).resolve().parent.parent
30
+ SKILL = REPO_ROOT / "plugins/specfuse/skills/derive-agent-policy/SKILL.md"
31
+
32
+
33
+ class PerRunItemCapDisclosure(unittest.TestCase):
34
+
35
+ def test_the_run_line_says_the_item_cap_is_per_run(self):
36
+ line = agent_policy.describe_budget_sources(
37
+ max_items=(62, "policy"),
38
+ max_tokens=(2800000, "policy"),
39
+ max_minutes=(None, "none"),
40
+ )
41
+
42
+ self.assertIn("per run", line)
43
+
44
+ def test_a_flag_sourced_item_cap_says_it_too(self):
45
+ # The cap is per-run whoever set it; the policy key's name is what
46
+ # misleads, but a flag run is bounded the same way.
47
+ line = agent_policy.describe_budget_sources(max_items=(5, "flag"))
48
+
49
+ self.assertIn("per run", line)
50
+
51
+ def test_other_caps_are_not_annotated(self):
52
+ # Only the item cap has a name that promises something wider than it
53
+ # delivers. Annotating the rest would be noise.
54
+ line = agent_policy.describe_budget_sources(
55
+ max_tokens=(2800000, "policy"),
56
+ max_open_prs=(3, "policy"),
57
+ )
58
+
59
+ self.assertNotIn("per run", line)
60
+
61
+ def test_an_absent_item_cap_is_not_annotated(self):
62
+ line = agent_policy.describe_budget_sources(max_items=(None, "none"))
63
+
64
+ self.assertIn("unbounded", line)
65
+ self.assertNotIn("per run", line)
66
+
67
+ def test_the_drafting_skill_discloses_the_semantics(self):
68
+ body = SKILL.read_text(encoding="utf-8")
69
+
70
+ self.assertIn(
71
+ "per run", body,
72
+ "/derive-agent-policy drafts `max_items_per_day` into an "
73
+ "operator's policy file. It must say that the key is applied per "
74
+ "run, or the operator writes a number believing it bounds a day "
75
+ "(#3340).",
76
+ )
77
+
78
+
79
+ if __name__ == "__main__": # pragma: no cover
80
+ unittest.main()