specfuse-loop 0.31.0__tar.gz → 0.32.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 (712) hide show
  1. {specfuse_loop-0.31.0/specfuse_loop.egg-info → specfuse_loop-0.32.0}/PKG-INFO +1 -1
  2. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/pyproject.toml +1 -1
  3. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/arm_eval.py +2 -1
  4. specfuse_loop-0.32.0/specfuse/loop/changelog_fragments.py +232 -0
  5. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/closing_requirements.py +32 -2
  6. specfuse_loop-0.32.0/specfuse/loop/data/VERSION +1 -0
  7. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/docs/lifecycles.md +12 -0
  8. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/rules/close-discipline.md +7 -6
  9. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/ids.py +16 -2
  10. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/layout.py +54 -3
  11. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/lint_closing.py +7 -12
  12. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/lint_roadmap.py +65 -0
  13. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/loop.py +16 -25
  14. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0/specfuse_loop.egg-info}/PKG-INFO +1 -1
  15. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse_loop.egg-info/SOURCES.txt +5 -0
  16. specfuse_loop-0.32.0/tests/test_blocked_by_entry_anchor.py +163 -0
  17. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_caller_check_ratchet.py +0 -1
  18. specfuse_loop-0.32.0/tests/test_changelog_fragments.py +420 -0
  19. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_ids_migrate_all.py +21 -0
  20. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_judge_path_registry.py +2 -2
  21. specfuse_loop-0.32.0/tests/test_migrate_all_scales.py +56 -0
  22. specfuse_loop-0.32.0/tests/test_repo_changelog_opt_in.py +28 -0
  23. specfuse_loop-0.31.0/specfuse/loop/data/VERSION +0 -1
  24. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/LICENSE +0 -0
  25. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/NOTICE +0 -0
  26. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/README.md +0 -0
  27. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/setup.cfg +0 -0
  28. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/__init__.py +0 -0
  29. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/budget.py +0 -0
  30. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/diagnose_invoke.py +0 -0
  31. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/drafting_answers.py +0 -0
  32. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/drafting_invoke.py +0 -0
  33. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/drafting_questions.py +0 -0
  34. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/driver_command.py +0 -0
  35. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/driver_invoke.py +0 -0
  36. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/invoke.py +0 -0
  37. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/monitoring_read.py +0 -0
  38. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/providers/__init__.py +0 -0
  39. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/providers/answers.py +0 -0
  40. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/providers/bugs.py +0 -0
  41. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/providers/feature.py +0 -0
  42. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/providers/findings_autofix.py +0 -0
  43. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/providers/findings_diagnose.py +0 -0
  44. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/providers/triage.py +0 -0
  45. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/queue_read.py +0 -0
  46. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/repo_detect.py +0 -0
  47. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/run.py +0 -0
  48. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/severity_backfill.py +0 -0
  49. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/state.py +0 -0
  50. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/triage_invoke.py +0 -0
  51. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/worktree.py +0 -0
  52. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/__init__.py +0 -0
  53. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/_filelock.py +0 -0
  54. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/_miniyaml.py +0 -0
  55. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/_wu_sections.py +0 -0
  56. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/adopt_feature.py +0 -0
  57. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/agent_policy.py +0 -0
  58. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/arm_sweep.py +0 -0
  59. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/arm_txn.py +0 -0
  60. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/bug_lane.py +0 -0
  61. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/bug_lane_run.py +0 -0
  62. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/bug_lane_state.py +0 -0
  63. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/build_provenance.py +0 -0
  64. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/caller_check.py +0 -0
  65. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/changelog.py +0 -0
  66. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/cost.py +0 -0
  67. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/criteria_state.py +0 -0
  68. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/CHANGELOG.seed.md +0 -0
  69. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/LEARNINGS.template.md +0 -0
  70. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/docs/concepts/adopting-auto-mode.md +0 -0
  71. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/docs/concepts/architecture-addendum-gates-and-iterative-planning.md +0 -0
  72. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/docs/concepts/autonomy-stop-classes.md +0 -0
  73. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/docs/concepts/monitoring-runners.md +0 -0
  74. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/docs/concepts/monitoring-schema.md +0 -0
  75. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/docs/concepts/monitoring-to-fix.md +0 -0
  76. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/docs/concepts/ralph-lineage.md +0 -0
  77. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/docs/flows.html +0 -0
  78. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/docs/getting-started.md +0 -0
  79. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/docs/glossary.md +0 -0
  80. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/docs/methodology.md +0 -0
  81. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/docs/skills.md +0 -0
  82. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/gitignore.snippet +0 -0
  83. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/monitoring-secrets-checklist.md +0 -0
  84. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/monitoring.overrides.yml.example +0 -0
  85. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/monitoring.yml.example +0 -0
  86. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/roadmap.template.md +0 -0
  87. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/rules/correlation-ids.md +0 -0
  88. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/rules/design-for-diagnosis.md +0 -0
  89. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/rules/human-output.md +0 -0
  90. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/rules/never-touch.md +0 -0
  91. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/rules/operator-escalation.md +0 -0
  92. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/rules/planning-discipline.md +0 -0
  93. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/rules/result-contract.md +0 -0
  94. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/rules/security-boundaries.md +0 -0
  95. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/rules/verification-discipline.md +0 -0
  96. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/rules-local/README.md +0 -0
  97. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/schemas/driver-event.schema.json +0 -0
  98. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/schemas/event.schema.json +0 -0
  99. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/schemas/events/initiative_created.schema.json +0 -0
  100. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/schemas/events/spec_issue_resolved.schema.json +0 -0
  101. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/schemas/events/spec_issue_routed.schema.json +0 -0
  102. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/schemas/events/spec_validated.schema.json +0 -0
  103. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/templates/DECISIONS.template.md +0 -0
  104. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/templates/GATE.template.md +0 -0
  105. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/templates/LEARNINGS-pending.template.md +0 -0
  106. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/templates/PLAN.template.md +0 -0
  107. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/templates/WU.template.md +0 -0
  108. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/verification.yml.example +0 -0
  109. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/workflows/specfuse-monitor.yml +0 -0
  110. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/decisions_format.py +0 -0
  111. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/driver_edit.py +0 -0
  112. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/escalation.py +0 -0
  113. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/events_stats.py +0 -0
  114. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/gate_commands.py +0 -0
  115. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/gate_eval.py +0 -0
  116. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/gen_contract.py +0 -0
  117. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/gh_backend.py +0 -0
  118. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/gh_features.py +0 -0
  119. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/heartbeat.py +0 -0
  120. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/judge.py +0 -0
  121. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/labels.py +0 -0
  122. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/learnings_distil.py +0 -0
  123. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/learnings_query.py +0 -0
  124. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/lint_monitoring.py +0 -0
  125. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/lint_plan.py +0 -0
  126. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/notify.py +0 -0
  127. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/notify_escalation.py +0 -0
  128. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/notify_sla.py +0 -0
  129. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/plan_baseline.py +0 -0
  130. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/policy_proposals.py +0 -0
  131. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/policy_review.py +0 -0
  132. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/prerun.py +0 -0
  133. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/prerun_capture.py +0 -0
  134. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/promotion.py +0 -0
  135. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/rearm_migration.py +0 -0
  136. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/red_on_base.py +0 -0
  137. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/replay_spin.py +0 -0
  138. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/roadmap_archive.py +0 -0
  139. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/scaffold.py +0 -0
  140. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/triage.py +0 -0
  141. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/upgrade_merge_gate.py +0 -0
  142. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/validate_event.py +0 -0
  143. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/monitor/__init__.py +0 -0
  144. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/monitor/adapters.py +0 -0
  145. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/monitor/artifact.py +0 -0
  146. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/monitor/autofix.py +0 -0
  147. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/monitor/autofix_invoke.py +0 -0
  148. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/monitor/autofix_run.py +0 -0
  149. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/monitor/autofix_state.py +0 -0
  150. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/monitor/cli.py +0 -0
  151. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/monitor/diagnose_cli.py +0 -0
  152. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/monitor/diagnosis.py +0 -0
  153. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/monitor/durations.py +0 -0
  154. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/monitor/fingerprint.py +0 -0
  155. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/monitor/issues.py +0 -0
  156. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/monitor/providers/__init__.py +0 -0
  157. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/monitor/providers/_azure_auth.py +0 -0
  158. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/monitor/providers/azure_app_insights.py +0 -0
  159. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/monitor/providers/azure_service_bus.py +0 -0
  160. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/monitor/redaction.py +0 -0
  161. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/monitor/schedule.py +0 -0
  162. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse_loop.egg-info/dependency_links.txt +0 -0
  163. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse_loop.egg-info/entry_points.txt +0 -0
  164. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse_loop.egg-info/requires.txt +0 -0
  165. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse_loop.egg-info/top_level.txt +0 -0
  166. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_abandon_feature_skill.py +0 -0
  167. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_abandoned_work_reported.py +0 -0
  168. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_abandoned_wu_strands_gate.py +0 -0
  169. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_adopt_feature.py +0 -0
  170. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_budget.py +0 -0
  171. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_bug_selection_dedup.py +0 -0
  172. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_default_features_root.py +0 -0
  173. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_driver_command.py +0 -0
  174. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_driver_invoke.py +0 -0
  175. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_driver_restart.py +0 -0
  176. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_escalation_on_issue.py +0 -0
  177. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_escalation_run_state.py +0 -0
  178. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_findings_seam.py +0 -0
  179. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_invoke_timeout.py +0 -0
  180. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_invoke_usage.py +0 -0
  181. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_item_worktree.py +0 -0
  182. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_lock.py +0 -0
  183. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_policy_key_ownership.py +0 -0
  184. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_policy_queue.py +0 -0
  185. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_policy_schema.py +0 -0
  186. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_policy_triage_dial.py +0 -0
  187. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_provider_answers.py +0 -0
  188. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_provider_bugs.py +0 -0
  189. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_provider_feature.py +0 -0
  190. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_provider_findings_autofix.py +0 -0
  191. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_provider_findings_diagnose.py +0 -0
  192. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_provider_isolation.py +0 -0
  193. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_provider_triage.py +0 -0
  194. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_queue_read.py +0 -0
  195. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_relays_driver_output.py +0 -0
  196. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_repo_detect.py +0 -0
  197. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_run.py +0 -0
  198. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_run_progress.py +0 -0
  199. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_seam.py +0 -0
  200. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_snapshot_refresh.py +0 -0
  201. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_state.py +0 -0
  202. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_answer_escalation_skill.py +0 -0
  203. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_app_insights_adapters.py +0 -0
  204. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_app_insights_harvest_window.py +0 -0
  205. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_app_insights_query_schema_check.py +0 -0
  206. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_app_insights_workspace_schema.py +0 -0
  207. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_archive_ref_reconciliation.py +0 -0
  208. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_arm_eval.py +0 -0
  209. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_arm_eval_lint_class.py +0 -0
  210. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_arm_eval_terminal.py +0 -0
  211. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_arm_eval_wiring.py +0 -0
  212. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_arm_gate_edits_uncommitted.py +0 -0
  213. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_arm_sweep.py +0 -0
  214. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_arm_sweep_gate.py +0 -0
  215. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_arm_txn.py +0 -0
  216. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_arm_wiring.py +0 -0
  217. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_artifact_redaction.py +0 -0
  218. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_attempt_note_untracked_files.py +0 -0
  219. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_attempt_outcome_contract.py +0 -0
  220. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_attempt_outcome_emission.py +0 -0
  221. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_attention_nonwriting_guard.py +0 -0
  222. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_attention_silence_section.py +0 -0
  223. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_attention_skill_structure.py +0 -0
  224. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_attribution_halt_claims.py +0 -0
  225. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_autoclose_deferral_visibility.py +0 -0
  226. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_autoclose_gate_note.py +0 -0
  227. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_autoclose_stub_states_what_passed.py +0 -0
  228. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_autofix_invoke.py +0 -0
  229. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_autofix_live.py +0 -0
  230. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_autofix_predicate.py +0 -0
  231. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_autofix_run.py +0 -0
  232. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_autofix_state.py +0 -0
  233. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_autosync.py +0 -0
  234. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_autosync_consent.py +0 -0
  235. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_autosync_firstrun.py +0 -0
  236. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_autosync_no_cwd_leak.py +0 -0
  237. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_autosync_plugin.py +0 -0
  238. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_azure_credential_resolution.py +0 -0
  239. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_backend.py +0 -0
  240. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_baseline_escalation_message.py +0 -0
  241. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_baseline_persistence.py +0 -0
  242. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_baseline_probe.py +0 -0
  243. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_baseline_provenance.py +0 -0
  244. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_baseline_tree_hash_key.py +0 -0
  245. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_bash_routing.py +0 -0
  246. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_bats_suites_gated.py +0 -0
  247. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_binary_verdict.py +0 -0
  248. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_binding_block_advisory.py +0 -0
  249. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_binding_block_allocation.py +0 -0
  250. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_binding_block_budget.py +0 -0
  251. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_blind_run_is_not_drained.py +0 -0
  252. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_block_feature_layout.py +0 -0
  253. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_bookkeeping_commit_crash_run.py +0 -0
  254. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_bookkeeping_commit_hook_crash.py +0 -0
  255. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_bookkeeping_no_verify.py +0 -0
  256. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_brief_covers_every_unit_escalation.py +0 -0
  257. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_bug_lane_ci_pending.py +0 -0
  258. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_bug_lane_guardrails.py +0 -0
  259. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_bug_lane_label_nonfatal.py +0 -0
  260. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_bug_lane_labels_registered.py +0 -0
  261. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_bug_lane_min_severity.py +0 -0
  262. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_bug_lane_no_checks_pending.py +0 -0
  263. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_bug_lane_policy_contract.py +0 -0
  264. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_bug_lane_pr_number_carried.py +0 -0
  265. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_bug_lane_preflight_and_dial.py +0 -0
  266. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_bug_lane_run.py +0 -0
  267. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_bug_lane_spend.py +0 -0
  268. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_bug_lane_state.py +0 -0
  269. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_bug_lane_stopped_outcome_pr_number.py +0 -0
  270. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_bug_lane_superseded.py +0 -0
  271. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_bug_lane_test_paths.py +0 -0
  272. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_build_provenance.py +0 -0
  273. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_carried_green_invalidated_by_diff.py +0 -0
  274. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_carry_accounting_close_and_judge.py +0 -0
  275. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_changed_file_test_selection.py +0 -0
  276. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_changelog_append_only.py +0 -0
  277. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_changelog_collection.py +0 -0
  278. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_changelog_release_wiring.py +0 -0
  279. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_changelog_schema.py +0 -0
  280. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_ci_conclusion_wait.py +0 -0
  281. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_ci_runs_every_gate.py +0 -0
  282. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_claude_resolution.py +0 -0
  283. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_close_b_pending_learnings.py +0 -0
  284. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_close_records_feature_oracle.py +0 -0
  285. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_close_wu_self_reference.py +0 -0
  286. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_closed_escalation_labels.py +0 -0
  287. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_closing_deliverable_guard.py +0 -0
  288. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_closing_guard_contracts.py +0 -0
  289. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_closing_guard_prediction.py +0 -0
  290. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_closing_learnings_destination.py +0 -0
  291. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_closing_requirements.py +0 -0
  292. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_closing_sessions_on_sonnet.py +0 -0
  293. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_coauthor_trailer_allowlist.py +0 -0
  294. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_conditional_reflection.py +0 -0
  295. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_console_encoding.py +0 -0
  296. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_convergent_iteration.py +0 -0
  297. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_convergent_wu_lint.py +0 -0
  298. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_correlation_id_override.py +0 -0
  299. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_correlation_id_two_surfaces.py +0 -0
  300. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_cost_lifetime.py +0 -0
  301. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_cost_tracking.py +0 -0
  302. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_criteria_per_gate_lint.py +0 -0
  303. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_criteria_state.py +0 -0
  304. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_criteria_worklist.py +0 -0
  305. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_decision_citation_lint.py +0 -0
  306. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_decision_followups_discharged.py +0 -0
  307. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_decision_override_lint.py +0 -0
  308. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_decisions_format.py +0 -0
  309. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_default_severity_aliases.py +0 -0
  310. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_degraded_oracle.py +0 -0
  311. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_deliverable_missing_note.py +0 -0
  312. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_deliverable_presence_gate.py +0 -0
  313. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_derive_agent_policy_review_mode.py +0 -0
  314. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_derive_agent_policy_skill.py +0 -0
  315. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_derive_monitoring_discovery.py +0 -0
  316. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_derive_monitoring_skill_registration.py +0 -0
  317. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_design_for_diagnosis_rule.py +0 -0
  318. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_deterministic_refusal_repeat.py +0 -0
  319. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_diagnose_fenced_analysis.py +0 -0
  320. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_diagnose_headless.py +0 -0
  321. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_diagnose_issue_skill.py +0 -0
  322. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_diagnosis.py +0 -0
  323. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_diagnosis_roundtrip_live.py +0 -0
  324. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_diagnosis_severity_end_to_end.py +0 -0
  325. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_diagnosis_severity_rubric.py +0 -0
  326. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_dispatch_model_from_fix_scope.py +0 -0
  327. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_dispatch_preamble_rules_pointer.py +0 -0
  328. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_dispatch_skeleton.py +0 -0
  329. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_dispatch_skills_index.py +0 -0
  330. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_distilled_accept_step.py +0 -0
  331. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_doctor.py +0 -0
  332. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_doctor_command_preflight.py +0 -0
  333. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_done_feature_gates.py +0 -0
  334. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_draft_feature_answers_mode.py +0 -0
  335. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_drafting_answer_gate.py +0 -0
  336. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_drafting_invoke.py +0 -0
  337. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_drafting_question_issue.py +0 -0
  338. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_drafting_questions.py +0 -0
  339. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_drafting_reply_shape.py +0 -0
  340. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_driver_edit_detection.py +0 -0
  341. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_driver_event_registry_covers_emitters.py +0 -0
  342. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_driver_integration.py +0 -0
  343. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_driver_lock.py +0 -0
  344. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_driver_module_surface.py +0 -0
  345. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_driver_restart_halt_surfaced.py +0 -0
  346. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_driver_restart_halt_wiring.py +0 -0
  347. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_driver_restart_hold.py +0 -0
  348. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_driver_staleness_gate_summary.py +0 -0
  349. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_driver_staleness_warning.py +0 -0
  350. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_dry_run_no_branch_mutation.py +0 -0
  351. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_duration_tracking.py +0 -0
  352. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_empty_files_escalation.py +0 -0
  353. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_ensure_feature_branch.py +0 -0
  354. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_ensure_feature_branch_base.py +0 -0
  355. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_escalation_assignee_policy.py +0 -0
  356. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_escalation_contract.py +0 -0
  357. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_escalation_crash_containment.py +0 -0
  358. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_escalation_dedup_search.py +0 -0
  359. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_escalation_emit.py +0 -0
  360. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_event_gate_full_envelope.py +0 -0
  361. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_events_redaction.py +0 -0
  362. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_events_stats.py +0 -0
  363. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_extra_gates.py +0 -0
  364. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_failure_artifact_model.py +0 -0
  365. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_failure_signature_fail_prefix.py +0 -0
  366. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_failure_signature_names_the_test.py +0 -0
  367. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_failure_signature_skips_log_noise.py +0 -0
  368. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_feature_branch_auto_sync.py +0 -0
  369. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_feature_branch_squash_merge.py +0 -0
  370. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_feature_branch_stale_base.py +0 -0
  371. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_feature_id_resolution.py +0 -0
  372. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_feature_oracle_e2e.py +0 -0
  373. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_feature_provider_drafting_dispatch.py +0 -0
  374. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_feature_review_accumulation.py +0 -0
  375. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_filelock_portable.py +0 -0
  376. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_files_changed_auto_repair.py +0 -0
  377. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_find_pr_no_search_index.py +0 -0
  378. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_fingerprint.py +0 -0
  379. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_fix_bug_cwd_worktree.py +0 -0
  380. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_fix_bug_diff_self_check.py +0 -0
  381. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_fix_bug_headless.py +0 -0
  382. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_fix_bug_reads_comments.py +0 -0
  383. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_fix_unit_insertion_bookkeeping.py +0 -0
  384. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_fix_unit_insertion_e2e.py +0 -0
  385. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_fix_unit_insertion_refused.py +0 -0
  386. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_floor_skip_summary.py +0 -0
  387. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_flows_page.py +0 -0
  388. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_fold_contract_documented.py +0 -0
  389. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_followups_artifact.py +0 -0
  390. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_force_full_close.py +0 -0
  391. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_frontmatter_scalar_quoting.py +0 -0
  392. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_gate_broad_run.py +0 -0
  393. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_gate_budget.py +0 -0
  394. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_gate_budget_post_dispatch.py +0 -0
  395. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_gate_commands_needs.py +0 -0
  396. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_gate_eval.py +0 -0
  397. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_gate_eval_calibration.py +0 -0
  398. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_gate_eval_intermediate_wiring.py +0 -0
  399. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_gate_eval_repo_flag.py +0 -0
  400. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_gate_eval_terminal_wiring.py +0 -0
  401. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_gate_log_size_budget.py +0 -0
  402. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_gate_report_verdict.py +0 -0
  403. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_gen_contract.py +0 -0
  404. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_gh_backend.py +0 -0
  405. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_gh_features.py +0 -0
  406. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_git_env_isolation.py +0 -0
  407. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_gitleaks_pinning.py +0 -0
  408. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_groom_backlog_skill.py +0 -0
  409. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_guard_refusal_failure_class.py +0 -0
  410. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_guard_refusal_failure_excerpt.py +0 -0
  411. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_guard_repair_e2e.py +0 -0
  412. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_guard_repair_prompt.py +0 -0
  413. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_hashed_denylist.py +0 -0
  414. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_hashed_denylist_ci.py +0 -0
  415. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_headless_no_background_tasks.py +0 -0
  416. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_heartbeat.py +0 -0
  417. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_heartbeat_adapter.py +0 -0
  418. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_heartbeat_emission_contract.py +0 -0
  419. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_http_status_regression_adapter.py +0 -0
  420. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_http_status_regression_end_to_end.py +0 -0
  421. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_human_output_rule.py +0 -0
  422. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_human_work_unit.py +0 -0
  423. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_hygiene_boundary_guidance.py +0 -0
  424. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_hyphenated_gate_name.py +0 -0
  425. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_ids.py +0 -0
  426. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_ids_aliases.py +0 -0
  427. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_ids_call_sites.py +0 -0
  428. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_ids_migrate.py +0 -0
  429. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_ignored_cleanup_before_probe.py +0 -0
  430. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_init_integration.py +0 -0
  431. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_installed_copy_driver_e2e.py +0 -0
  432. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_interpreter_normalization.py +0 -0
  433. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_judge_close_path.py +0 -0
  434. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_judge_cost.py +0 -0
  435. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_judge_cost_fold_committed.py +0 -0
  436. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_judge_editing_terminal_docs.py +0 -0
  437. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_judge_module.py +0 -0
  438. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_judge_sees_dropped_produces.py +0 -0
  439. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_judge_terminal_flip_state.py +0 -0
  440. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_judged_event_points_at_findings.py +0 -0
  441. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_keyed_feature_ids_docs.py +0 -0
  442. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_keyed_feature_ids_end_to_end.py +0 -0
  443. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_label_description_length.py +0 -0
  444. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_label_provisioning_runner_contract.py +0 -0
  445. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_label_registry.py +0 -0
  446. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_label_registry_covers_consumers.py +0 -0
  447. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_language_aware_narrow_selection.py +0 -0
  448. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_layout.py +0 -0
  449. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_layout_end_to_end.py +0 -0
  450. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_layout_link.py +0 -0
  451. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_layout_no_layout_goldens.py +0 -0
  452. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_layout_validation.py +0 -0
  453. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lazy_baseline_e2e.py +0 -0
  454. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_leak_findings_redaction.py +0 -0
  455. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_leak_scan.py +0 -0
  456. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_leak_scan_content.py +0 -0
  457. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_learnings_curate_vendored_promote.py +0 -0
  458. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_learnings_distil_cli.py +0 -0
  459. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_learnings_query.py +0 -0
  460. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_learnings_staging.py +0 -0
  461. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_learnings_suggest_signature_filter.py +0 -0
  462. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_learnings_weights.py +0 -0
  463. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_legacy_4wu_terminal_flips.py +0 -0
  464. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lifecycle_integration.py +0 -0
  465. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lifetime_accounting.py +0 -0
  466. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_ac_observable.py +0 -0
  467. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_bare_produces_path.py +0 -0
  468. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_boundary_consistency.py +0 -0
  469. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_boundary_extraction.py +0 -0
  470. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_close_intermediate.py +0 -0
  471. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_close_verdict_not_predecided.py +0 -0
  472. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_close_wu.py +0 -0
  473. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_closing.py +0 -0
  474. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_closing_criteria.py +0 -0
  475. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_closing_criteria_pristine.py +0 -0
  476. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_correlation_id.py +0 -0
  477. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_correlation_id_close_intermediate.py +0 -0
  478. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_covers_load_wu_invariants.py +0 -0
  479. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_feature_oracle_declared.py +0 -0
  480. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_gate_proportionality.py +0 -0
  481. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_judge_edit_acknowledged.py +0 -0
  482. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_monitoring.py +0 -0
  483. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_oracle_env.py +0 -0
  484. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_plan_contract_fields.py +0 -0
  485. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_plan_errors.py +0 -0
  486. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_plan_next_draft.py +0 -0
  487. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_plan_relative_import.py +0 -0
  488. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_plan_verdict_exempt.py +0 -0
  489. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_planning_sections.py +0 -0
  490. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_produces_driver_helper.py +0 -0
  491. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_produces_satisfiability.py +0 -0
  492. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_roadmap.py +0 -0
  493. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_roadmap_unkeyed_rows.py +0 -0
  494. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_sections.py +0 -0
  495. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_task_graph_yaml_selection.py +0 -0
  496. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_verification_reference.py +0 -0
  497. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_live_tests_opt_in.py +0 -0
  498. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_load_bearing_close_lint.py +0 -0
  499. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_loop_attempt_evidence.py +0 -0
  500. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_loop_auto_archive.py +0 -0
  501. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_loop_caveman_preamble.py +0 -0
  502. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_loop_close_intermediate.py +0 -0
  503. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_loop_criteria_skeleton.py +0 -0
  504. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_loop_criteria_survival.py +0 -0
  505. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_loop_defaults_by_type.py +0 -0
  506. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_loop_effort.py +0 -0
  507. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_loop_failure_note_cap.py +0 -0
  508. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_loop_files_changed_guard.py +0 -0
  509. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_loop_gate_budget.py +0 -0
  510. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_loop_gate_needs.py +0 -0
  511. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_loop_model_alias.py +0 -0
  512. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_loop_orchestration.py +0 -0
  513. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_loop_post_pass_invariant.py +0 -0
  514. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_loop_reset_preserving_events.py +0 -0
  515. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_loop_smoke_runner.py +0 -0
  516. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_loop_unsandboxed.py +0 -0
  517. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_loop_worklist_injection.py +0 -0
  518. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_loop_zero_token_guard.py +0 -0
  519. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_make_repo_gpgsign_isolation.py +0 -0
  520. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_marker_dual_shape.py +0 -0
  521. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_maven_failure_excerpt.py +0 -0
  522. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_max_attempts_configurable.py +0 -0
  523. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_max_open_prs.py +0 -0
  524. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_migrate_all_repo_stays_valid.py +0 -0
  525. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_migrate_legacy.py +0 -0
  526. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_miniyaml_equivalence.py +0 -0
  527. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_miniyaml_negative.py +0 -0
  528. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_monitor_check_isolation.py +0 -0
  529. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_monitor_cli.py +0 -0
  530. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_monitor_diagnose_auto_warning.py +0 -0
  531. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_monitor_durations.py +0 -0
  532. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_monitor_finding_context_end_to_end.py +0 -0
  533. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_monitor_finding_lifecycle_end_to_end.py +0 -0
  534. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_monitor_finding_meta.py +0 -0
  535. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_monitor_issue_lifecycle.py +0 -0
  536. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_monitor_one_issue_per_fingerprint.py +0 -0
  537. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_monitor_quiet_annotation.py +0 -0
  538. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_monitor_runner_surfaces.py +0 -0
  539. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_monitoring_bootstrap_artifacts.py +0 -0
  540. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_monitoring_cron_dialect.py +0 -0
  541. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_monitoring_example.py +0 -0
  542. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_monitoring_fenced_blocks.py +0 -0
  543. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_monitoring_seed.py +0 -0
  544. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_narrow_fallback_warning.py +0 -0
  545. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_narrow_non_py_produces.py +0 -0
  546. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_needs_decision_outcome.py +0 -0
  547. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_no_pytest_subprocess.py +0 -0
  548. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_notify.py +0 -0
  549. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_notify_escalation.py +0 -0
  550. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_notify_sla.py +0 -0
  551. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_operator_escalation_rule.py +0 -0
  552. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_oracle_set_declared.py +0 -0
  553. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_oracle_unrunnable_attribution.py +0 -0
  554. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_per_run_item_cap_disclosure.py +0 -0
  555. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_pick_feature_skill.py +0 -0
  556. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_pin_honesty_and_integrity.py +0 -0
  557. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_pin_marker_not_inherited.py +0 -0
  558. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_pin_marker_spawn_sites.py +0 -0
  559. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_plan_baseline.py +0 -0
  560. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_planned_cost_lint.py +0 -0
  561. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_planning_cost_floor.py +0 -0
  562. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_plugin_prose_cross_refs.py +0 -0
  563. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_plugin_qualified_dispatch.py +0 -0
  564. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_policy_budgets_applied.py +0 -0
  565. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_policy_proposals.py +0 -0
  566. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_policy_review.py +0 -0
  567. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_pr_checks_json_field.py +0 -0
  568. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_pr_reference_in_title.py +0 -0
  569. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_pre_existing_claims_cite_evidence.py +0 -0
  570. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_prep_halt_operator_surface.py +0 -0
  571. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_prepare_feature.py +0 -0
  572. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_prepare_scaffold_sync.py +0 -0
  573. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_prerun_capture.py +0 -0
  574. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_prerun_oracles.py +0 -0
  575. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_prerun_wiring.py +0 -0
  576. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_produced_document_verdict_guard.py +0 -0
  577. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_produces_amendment_e2e.py +0 -0
  578. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_produces_bracket_literal.py +0 -0
  579. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_produces_bracket_wildcard.py +0 -0
  580. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_produces_field.py +0 -0
  581. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_produces_in_diff.py +0 -0
  582. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_produces_justification.py +0 -0
  583. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_produces_recursive_glob.py +0 -0
  584. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_produces_repair_note.py +0 -0
  585. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_produces_semantics_unified.py +0 -0
  586. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_produces_shape.py +0 -0
  587. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_progress_forward_note.py +0 -0
  588. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_progress_lines_end_to_end.py +0 -0
  589. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_promoted_issue_not_redispatched.py +0 -0
  590. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_promotion_ledger.py +0 -0
  591. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_provider_escalation_traces.py +0 -0
  592. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_provision_labels.py +0 -0
  593. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_queue_stalled_adapter.py +0 -0
  594. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_rearm_clears_stale_criteria_state.py +0 -0
  595. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_rearm_close_broad_run.py +0 -0
  596. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_rearm_fold_marker.py +0 -0
  597. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_rearm_migration.py +0 -0
  598. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_rearm_retained_new_files.py +0 -0
  599. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_reclose_carries_narrow_greens_e2e.py +0 -0
  600. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_red_baseline_never_reused.py +0 -0
  601. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_red_on_base_fetch_and_reason.py +0 -0
  602. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_red_on_base_guardrail.py +0 -0
  603. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_red_on_base_overlay.py +0 -0
  604. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_red_on_base_probe.py +0 -0
  605. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_red_on_base_real_runner.py +0 -0
  606. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_redaction_windows_home.py +0 -0
  607. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_rediagnose_on_reopen.py +0 -0
  608. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_reescalation_posts_comment.py +0 -0
  609. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_reflection_required_signals.py +0 -0
  610. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_replan_end_to_end.py +0 -0
  611. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_replan_event_emission.py +0 -0
  612. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_replan_note_collision.py +0 -0
  613. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_replan_trigger.py +0 -0
  614. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_replan_turn_contract.py +0 -0
  615. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_replay_spin.py +0 -0
  616. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_replay_spin_failing_sets.py +0 -0
  617. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_required_checks.py +0 -0
  618. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_resolve_base.py +0 -0
  619. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_restart_cap_counts_progress.py +0 -0
  620. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_result_block.py +0 -0
  621. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_result_block_audience.py +0 -0
  622. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_result_block_near_miss.py +0 -0
  623. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_result_block_scalar.py +0 -0
  624. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_resume_command_build.py +0 -0
  625. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_retain_on_guard_refusal_docs.py +0 -0
  626. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_roadmap_add_skill.py +0 -0
  627. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_roadmap_archive_cli.py +0 -0
  628. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_roadmap_archive_shim.py +0 -0
  629. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_roadmap_archive_skill.py +0 -0
  630. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_roadmap_archive_skill_delegates.py +0 -0
  631. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_roadmap_link_gate.py +0 -0
  632. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_roadmap_row_parser.py +0 -0
  633. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_ruff_verdict_lines.py +0 -0
  634. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_rule_import_convergence.py +0 -0
  635. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_run_level_failure_breaker.py +0 -0
  636. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_scaffold_data_in_sync.py +0 -0
  637. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_scaffold_doc_hygiene.py +0 -0
  638. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_scaffold_docs.py +0 -0
  639. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_scaffold_init.py +0 -0
  640. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_scaffold_label_provisioning.py +0 -0
  641. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_scaffold_manifest.py +0 -0
  642. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_scaffold_resources.py +0 -0
  643. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_scaffold_seed_sanity.py +0 -0
  644. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_scaffold_skew_halts.py +0 -0
  645. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_scaffold_upgrade.py +0 -0
  646. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_scaffold_wiring.py +0 -0
  647. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_schedule_dialect.py +0 -0
  648. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_seed_documents_prerun_keys.py +0 -0
  649. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_service_bus_dlq_adapter.py +0 -0
  650. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_session_env_preflight.py +0 -0
  651. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_severity_aliases.py +0 -0
  652. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_severity_backfill_apply.py +0 -0
  653. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_severity_backfill_bugs_only.py +0 -0
  654. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_severity_backfill_end_to_end.py +0 -0
  655. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_severity_backfill_limit.py +0 -0
  656. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_severity_backfill_marker.py +0 -0
  657. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_severity_backfill_run.py +0 -0
  658. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_severity_label_projection.py +0 -0
  659. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_severity_reconciliation.py +0 -0
  660. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_severity_rubric.py +0 -0
  661. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_shipped_templates_parse.py +0 -0
  662. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_skill_discovery_links.py +0 -0
  663. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_skills_vendored_in_sync.py +0 -0
  664. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_spec_work_unit_rules.py +0 -0
  665. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_spend_counts_cache_tokens.py +0 -0
  666. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_spinning_rearm_gate.py +0 -0
  667. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_spinning_repeat_same_failing_set.py +0 -0
  668. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_spinout_brief_end_to_end.py +0 -0
  669. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_spinout_brief_replan_option.py +0 -0
  670. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_squash_commit_hook_crash.py +0 -0
  671. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_stop_rationale_captured.py +0 -0
  672. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_stranded_halt_cause.py +0 -0
  673. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_surefire_run_level_signature.py +0 -0
  674. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_surefire_verdict_lines.py +0 -0
  675. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_template_closing_shapes.py +0 -0
  676. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_terminal_auto_close_changelog.py +0 -0
  677. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_terminal_flip_ownership.py +0 -0
  678. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_terminal_flips.py +0 -0
  679. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_terminal_gate_message.py +0 -0
  680. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_terminal_judge_editing_end_to_end.py +0 -0
  681. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_tiered_verification_e2e.py +0 -0
  682. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_timeout_kill_portable.py +0 -0
  683. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_triage.py +0 -0
  684. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_triage_apply.py +0 -0
  685. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_triage_projects_severity_label.py +0 -0
  686. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_triage_reads_comments.py +0 -0
  687. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_triage_severity_classify.py +0 -0
  688. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_triage_severity_end_to_end.py +0 -0
  689. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_triage_severity_label_conflict.py +0 -0
  690. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_triage_severity_write.py +0 -0
  691. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_triage_skill_contract.py +0 -0
  692. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_triage_skips_agent_escalations.py +0 -0
  693. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_tsc_verdict_lines.py +0 -0
  694. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_umbrella_version_resolution.py +0 -0
  695. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_unreadable_pr_listing_holds_pr_work.py +0 -0
  696. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_unterminated_frontmatter.py +0 -0
  697. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_untracked_feature_folder.py +0 -0
  698. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_untracked_file_absorption.py +0 -0
  699. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_upgrade_gitignore_lines.py +0 -0
  700. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_upgrade_integration.py +0 -0
  701. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_upgrade_merge_gate.py +0 -0
  702. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_validate_event.py +0 -0
  703. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_validate_event_driver_types.py +0 -0
  704. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_validate_feature_dirs_only.py +0 -0
  705. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_verdict_coupling.py +0 -0
  706. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_verify_empty_gate_set.py +0 -0
  707. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_version_consistency.py +0 -0
  708. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_version_skew.py +0 -0
  709. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_windows_gate_exec.py +0 -0
  710. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_worktree_restore.py +0 -0
  711. {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_wu_execution_metadata.py +0 -0
  712. {specfuse_loop-0.31.0 → specfuse_loop-0.32.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.31.0
3
+ Version: 0.32.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.31.0"
7
+ version = "0.32.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"
@@ -110,6 +110,8 @@ JUDGE_MODULES = tuple(
110
110
  "closing_requirements.py",
111
111
  "criteria_state.py",
112
112
  "lint_closing.py",
113
+ "changelog.py", # its parser is how close-k, lint --closing and the auto-close guard read Unreleased
114
+ "changelog_fragments.py", # the one reader/writer of changelog entries the close guards read
113
115
  "lint_plan.py",
114
116
  "ids.py", # supplies lint_plan's CORRELATION_ID_RE; an edit changes what the close's lint accepts
115
117
  "plan_baseline.py", # the baseline retroactive_edits compares to
@@ -144,7 +146,6 @@ NON_JUDGE_MODULES = {
144
146
  "caller_check.py": "reports public symbols whose only callers are "
145
147
  "tests; a hygiene report, grants no verdict and no arm/close/"
146
148
  "merge decision reads it",
147
- "changelog.py": "parses and stamps CHANGELOG.md; no gate reads it",
148
149
  "decisions_format.py": "parses DECISIONS.md into entries; not on the "
149
150
  "arm/close/merge judge path — T02/T03 own the lint that reads it",
150
151
  "driver_edit.py": "applies operator edits to a feature folder",
@@ -0,0 +1,232 @@
1
+ #
2
+ # Copyright 2026 Specfuse contributors
3
+ # Licensed under the Apache License, Version 2.0. See LICENSE.
4
+ #
5
+ """Changelog fragments — FEAT-LOOP-2026-0249.
6
+
7
+ One writer for changelog entries. `add` routes by `changelog.mode` in
8
+ `.specfuse/layout.yml`: `fragments` writes `changelog.d/<trace>-<slug>.md`
9
+ (so concurrent PRs never touch the same file), `single` (the default) appends
10
+ to `CHANGELOG.md` through `changelog.append_entry`.
11
+
12
+ python3 -m specfuse.loop.changelog_fragments add --class C --trace T --summary S [--repo DIR]
13
+ python3 -m specfuse.loop.changelog_fragments collate [--repo DIR]
14
+ python3 -m specfuse.loop.changelog_fragments check [--repo DIR]
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import argparse
20
+ import re
21
+ import sys
22
+ from pathlib import Path
23
+
24
+ from specfuse.loop import layout
25
+ from specfuse.loop.changelog import (
26
+ _TRACE_RE,
27
+ ENTRY_CLASSES,
28
+ ChangelogEntry,
29
+ append_entry,
30
+ parse_changelog,
31
+ )
32
+
33
+ FRAGMENT_DIR = "changelog.d"
34
+ CHANGELOG_FILE = "CHANGELOG.md"
35
+ _SLUG_MAX = 40
36
+
37
+
38
+ def normalize_trace(trace: str) -> str:
39
+ """`#3563` -> `3563`, `FEAT-X/T01` -> `FEAT-X-T01`."""
40
+ return trace.lstrip("#").replace("/", "-")
41
+
42
+
43
+ def slugify(summary: str) -> str:
44
+ slug = re.sub(r"[^a-z0-9]+", "-", summary.lower()).strip("-")[:_SLUG_MAX].strip("-")
45
+ return slug or "entry"
46
+
47
+
48
+ def _validate(entry_class: str, trace: str, summary: str) -> None:
49
+ if entry_class not in ENTRY_CLASSES:
50
+ raise ValueError(
51
+ f"unrecognised entry class {entry_class!r} — must be one of "
52
+ f"{', '.join(ENTRY_CLASSES)}"
53
+ )
54
+ if not _TRACE_RE.match(trace):
55
+ raise ValueError(f"trace {trace!r} is not a FEAT-YYYY-NNNN or #<issue-number> trace")
56
+ if not summary.strip():
57
+ raise ValueError("summary must not be empty")
58
+ if "\n" in summary or "\r" in summary:
59
+ raise ValueError("summary must be a single line")
60
+
61
+
62
+ def read_fragment(path) -> ChangelogEntry | None:
63
+ """The entry one fragment file carries, or None when it is not a valid fragment.
64
+
65
+ Frontmatter is read line by line, not through `_miniyaml`: `trace: #101`
66
+ is a comment to a YAML reader. Never raises on a malformed file.
67
+ """
68
+ try:
69
+ lines = Path(path).read_text(encoding="utf-8").splitlines()
70
+ except (OSError, UnicodeDecodeError):
71
+ return None
72
+ if not lines or lines[0].strip() != "---":
73
+ return None
74
+ try:
75
+ end = next(i for i in range(1, len(lines)) if lines[i].strip() == "---")
76
+ except StopIteration:
77
+ return None
78
+ fields: dict[str, str] = {}
79
+ for line in lines[1:end]:
80
+ key, sep, value = line.partition(":")
81
+ if not sep:
82
+ return None
83
+ fields[key.strip()] = value.strip()
84
+ entry_class, trace = fields.get("class", ""), fields.get("trace", "")
85
+ body = [ln.strip() for ln in lines[end + 1:] if ln.strip()]
86
+ if entry_class not in ENTRY_CLASSES or not _TRACE_RE.match(trace) or len(body) != 1:
87
+ return None
88
+ return ChangelogEntry(entry_class=entry_class, summary=body[0], trace=trace, line=end + 2)
89
+
90
+
91
+ def fragment_entries(repo_root) -> list:
92
+ """Every valid fragment's entry under `changelog.d/`; malformed files are skipped."""
93
+ directory = Path(repo_root) / FRAGMENT_DIR
94
+ if not directory.is_dir():
95
+ return []
96
+ found = (read_fragment(p) for p in sorted(directory.glob("*.md")))
97
+ return [entry for entry in found if entry is not None]
98
+
99
+
100
+ def add(repo_root, *, entry_class: str, trace: str, summary: str) -> Path:
101
+ """Write one entry in the project's mode; return the file written."""
102
+ _validate(entry_class, trace, summary)
103
+ summary = summary.strip()
104
+ root = Path(repo_root)
105
+ if layout.load(root).changelog.mode == "fragments":
106
+ target = root / FRAGMENT_DIR / f"{normalize_trace(trace)}-{slugify(summary)}.md"
107
+ if target.exists():
108
+ raise ValueError(f"{target.relative_to(root).as_posix()} already exists")
109
+ target.parent.mkdir(parents=True, exist_ok=True)
110
+ target.write_text(
111
+ f"---\nclass: {entry_class}\ntrace: {trace}\n---\n{summary}\n", encoding="utf-8"
112
+ )
113
+ return target
114
+ path = root / CHANGELOG_FILE
115
+ if not path.is_file():
116
+ raise ValueError(f"{CHANGELOG_FILE} not found in {root}")
117
+ text = path.read_text(encoding="utf-8")
118
+ path.write_text(
119
+ append_entry(text, entry_class=entry_class, summary=summary, trace=trace),
120
+ encoding="utf-8",
121
+ )
122
+ return path
123
+
124
+
125
+ def invalid_fragments(repo_root) -> list:
126
+ """One message per malformed fragment under `changelog.d/`, each naming the file."""
127
+ directory = Path(repo_root) / FRAGMENT_DIR
128
+ paths = sorted(directory.glob("*.md")) if directory.is_dir() else []
129
+ return [
130
+ f"{FRAGMENT_DIR}/{path.name} is not a valid fragment — expected "
131
+ "`class:` and `trace:` frontmatter and a single-line summary"
132
+ for path in paths
133
+ if read_fragment(path) is None
134
+ ]
135
+
136
+
137
+ def collate_text(repo_root, text: str) -> tuple:
138
+ """Fold every fragment into `text`'s `Unreleased`; return `(new_text, fragment_paths)`.
139
+
140
+ Fragments fold in sorted filename order through `append_entry`. Writes and
141
+ deletes nothing: any malformed fragment, or a result that does not parse
142
+ clean, raises `ValueError` naming the file, so the caller's tree is untouched.
143
+ """
144
+ directory = Path(repo_root) / FRAGMENT_DIR
145
+ paths = sorted(directory.glob("*.md")) if directory.is_dir() else []
146
+ problems = invalid_fragments(repo_root)
147
+ if problems:
148
+ raise ValueError(problems[0])
149
+ for path in paths:
150
+ entry = read_fragment(path)
151
+ text = append_entry(
152
+ text, entry_class=entry.entry_class, summary=entry.summary, trace=entry.trace
153
+ )
154
+ if paths:
155
+ result = parse_changelog(text)
156
+ if not result.ok:
157
+ raise ValueError(
158
+ f"collating {FRAGMENT_DIR}/ leaves {CHANGELOG_FILE} unparseable: "
159
+ + "; ".join(str(f) for f in result.findings)
160
+ )
161
+ return text, paths
162
+
163
+
164
+ def remove_fragments(paths) -> None:
165
+ """Delete consumed fragments; drop `changelog.d/` when it is left empty."""
166
+ parents = {Path(p).parent for p in paths}
167
+ for path in paths:
168
+ Path(path).unlink()
169
+ for parent in parents:
170
+ try:
171
+ parent.rmdir()
172
+ except OSError:
173
+ pass
174
+
175
+
176
+ def collate(repo_root) -> list:
177
+ """Fold fragments into `CHANGELOG.md` and delete them; return the fragment paths."""
178
+ path = Path(repo_root) / CHANGELOG_FILE
179
+ if not path.is_file():
180
+ raise ValueError(f"{CHANGELOG_FILE} not found in {repo_root}")
181
+ new_text, paths = collate_text(repo_root, path.read_text(encoding="utf-8"))
182
+ if paths:
183
+ path.write_text(new_text, encoding="utf-8")
184
+ remove_fragments(paths)
185
+ return paths
186
+
187
+
188
+ def main(argv=None) -> int:
189
+ parser = argparse.ArgumentParser(
190
+ prog="specfuse.loop.changelog_fragments", description="Write changelog entries."
191
+ )
192
+ parser.add_argument("--repo", default=".", help="repository root (default: cwd)")
193
+ sub = parser.add_subparsers(dest="command", required=True)
194
+ addp = sub.add_parser("add", help="write one changelog entry in the project's mode")
195
+ addp.add_argument("--class", dest="entry_class", required=True)
196
+ addp.add_argument("--trace", required=True)
197
+ addp.add_argument("--summary", required=True)
198
+ sub.add_parser("collate", help="fold changelog.d/ fragments into CHANGELOG.md and delete them")
199
+ sub.add_parser("check", help="fail when any changelog.d/ fragment is malformed")
200
+ args = parser.parse_args(argv)
201
+
202
+ if args.command == "check":
203
+ problems = invalid_fragments(args.repo)
204
+ for problem in problems:
205
+ print(problem, file=sys.stderr)
206
+ return 2 if problems else 0
207
+
208
+ if args.command == "collate":
209
+ try:
210
+ folded = collate(args.repo)
211
+ except ValueError as exc:
212
+ print(str(exc), file=sys.stderr)
213
+ return 2
214
+ print(f"collated {len(folded)} fragment(s)")
215
+ return 0
216
+
217
+ try:
218
+ target = add(
219
+ args.repo,
220
+ entry_class=args.entry_class,
221
+ trace=args.trace,
222
+ summary=args.summary,
223
+ )
224
+ except ValueError as exc:
225
+ print(str(exc), file=sys.stderr)
226
+ return 2
227
+ print(target)
228
+ return 0
229
+
230
+
231
+ if __name__ == "__main__":
232
+ sys.exit(main())
@@ -18,6 +18,7 @@ from __future__ import annotations
18
18
 
19
19
  import re
20
20
  from dataclasses import dataclass, field
21
+ from pathlib import Path
21
22
  from typing import Optional
22
23
 
23
24
  # --------------------------------------------------------------------------- #
@@ -232,6 +233,35 @@ def changelog_has_entry_for(unreleased_entries, feature_id: str) -> bool:
232
233
  )
233
234
 
234
235
 
236
+ def changelog_exists(repo_root) -> bool:
237
+ """True if the project has a changelog in either form: `CHANGELOG.md` or `changelog.d/*.md`."""
238
+ root = Path(repo_root)
239
+ return (root / CHANGELOG_PATH).exists() or any((root / "changelog.d").glob("*.md"))
240
+
241
+
242
+ def changelog_has_entry_for_feature(repo_root, feature_id: str) -> bool:
243
+ """True if *feature_id* has an entry in `CHANGELOG.md`'s Unreleased section or a valid fragment.
244
+
245
+ The one reader behind close-k, `lint --closing` and the auto-close guard
246
+ (FEAT-LOOP-2026-0249). Both forms are read in every mode. A missing or
247
+ unparseable changelog and a malformed fragment count as no entry; this
248
+ never raises.
249
+ """
250
+ from . import changelog_fragments
251
+ from .changelog import parse_changelog
252
+
253
+ root = Path(repo_root)
254
+ entries = list(changelog_fragments.fragment_entries(root))
255
+ changelog = root / CHANGELOG_PATH
256
+ if changelog.is_file():
257
+ try:
258
+ unreleased = parse_changelog(changelog.read_text(encoding="utf-8")).unreleased()
259
+ except Exception: # noqa: BLE001 - a guard reports a gap, never crashes on one
260
+ unreleased = None
261
+ entries += unreleased.entries if unreleased else []
262
+ return changelog_has_entry_for(entries, feature_id)
263
+
264
+
235
265
  # --------------------------------------------------------------------------- #
236
266
  # Requirement records #
237
267
  # --------------------------------------------------------------------------- #
@@ -354,8 +384,8 @@ CLOSING_REQUIREMENTS: dict[str, list[Requirement]] = {
354
384
  description=(
355
385
  f"When the '{CONSUMER_VISIBLE_HEADING}' section (close-discipline.md "
356
386
  f"§3) is not the '{CONSUMER_VISIBLE_NA_PHRASE}' line, "
357
- f"{CHANGELOG_PATH}'s Unreleased section gains an entry carrying "
358
- "this feature's FEAT-ID — the same enumeration §3 already "
387
+ f"{CHANGELOG_PATH}'s Unreleased section or a valid changelog.d/ "
388
+ "fragment gains an entry carrying this feature's FEAT-ID — the same enumeration §3 already "
359
389
  "requires, appended where a consumer reads it, not re-derived"
360
390
  ),
361
391
  file=RETROSPECTIVE_FILENAME,
@@ -0,0 +1 @@
1
+ 0.32.0
@@ -175,6 +175,18 @@ hit stops the branch rather than shipping feature-scoped work as a bug fix.
175
175
  **The `CHANGELOG.md` entry is written here or nowhere.** Bugs have no close
176
176
  ceremony to collect through, so `/fix-bug` is the collection point.
177
177
 
178
+ `/fix-bug` records the entry with `python3 -m specfuse.loop.changelog_fragments add`
179
+ (trace `#<issue-number>`) and commits what it wrote with the fix.
180
+
181
+ **Opting in to fragments.** By default entries append to `CHANGELOG.md`. To
182
+ stop concurrent PRs colliding on that file, set `changelog.mode: fragments` in
183
+ `.specfuse/layout.yml`; `add` then writes one `changelog.d/<trace>-<slug>.md`
184
+ per entry. Add `python3 -m specfuse.loop.changelog_fragments check` as a gate
185
+ in `.specfuse/verification.yml` to fail on malformed fragments. A custom
186
+ release script must run `python3 -m specfuse.loop.changelog_fragments collate`
187
+ before stamping the release, so fragments fold into `CHANGELOG.md` and are
188
+ deleted first.
189
+
178
190
  ---
179
191
 
180
192
  ## 3. The autonomous lifecycle
@@ -138,18 +138,19 @@ A close carrying this obligation is always load-bearing
138
138
  (`auto_close_disabled: true`).
139
139
 
140
140
  **Also append it to `CHANGELOG.md`'s `Unreleased` section (FEAT-2026-0064).**
141
- When this section is a real enumeration — not the `n/a` line — the close
142
- appends each item to `Unreleased`, classified `added` / `changed` / `fixed` /
143
- `breaking` and carrying the feature's `FEAT-YYYY-NNNN` ID, via
144
- `specfuse/loop/changelog.py`'s schema. **This is the same material the
141
+ When this section is a real enumeration — not the `n/a` line — run one
142
+ `python3 -m specfuse.loop.changelog_fragments add --class <class> --trace <FEAT-ID> --summary '<line>'`
143
+ per enumerated item, classified `added` / `changed` / `fixed` / `breaking`,
144
+ with the feature's `FEAT-YYYY-NNNN` ID as trace. Never hand-edit; `add`
145
+ routes by the project's `changelog.mode`. **This is the same material the
145
146
  paragraph above already requires you to write, put where a consumer will
146
147
  read it — not a second write.** An author who treats the changelog append as
147
148
  new work will write the enumeration twice, badly; write it once, in both
148
149
  places, from the same understanding. `closing_requirements.py`'s `close-k`
149
150
  requirement (enforced by `assert_changelog_entry_for_contract_changes` in
150
151
  `loop.py`, and pre-squash by `specfuse lint --closing`) fires when this
151
- section names a real change and `Unreleased` gained no entry tracing to this
152
- feature's FEAT-ID.
152
+ section names a real change and no entry tracing to this feature's FEAT-ID
153
+ lives in `CHANGELOG.md` or a `changelog.d/` fragment.
153
154
 
154
155
  **An `n/a` close appends nothing to `Unreleased`, and that is correct.** A
155
156
  changelog padded with "no user-facing change" entries is noise that trains
@@ -399,12 +399,17 @@ def _rewrite_file(rel, text, renames, include_prose, plan, folders=()):
399
399
  is_changelog = rel == "CHANGELOG.md"
400
400
  lines = text.splitlines(keepends=True)
401
401
  bare = [ln.rstrip("\r\n") for ln in lines]
402
- fm_end = _frontmatter_end(bare) if is_md and in_features else -1
402
+ # A changelog.d/ fragment is unreleased by definition: its `trace:` frontmatter
403
+ # is structural, so it follows the rename (the filename stays; frontmatter wins).
404
+ is_fragment = is_md and rel.startswith("changelog.d/")
405
+ fm_end = _frontmatter_end(bare) if is_md and (in_features or is_fragment) else -1
403
406
  graph = _task_graph_lines(bare) if name == "PLAN.md" and in_features else set()
404
407
  span = _unreleased_span(bare) if is_changelog else None
405
408
  if is_changelog and span is None:
406
409
  span = (len(lines), len(lines))
407
410
  pairs = [(o, n) for o, n in renames]
411
+ # Built once per file, not per ID per line (#3559).
412
+ owned = {o: [f for f in folders if f.startswith(o + "-")] for o, _ in pairs}
408
413
  out, fenced = [], False
409
414
  for no, line in enumerate(lines):
410
415
  if is_changelog and not (span[0] <= no < span[1]):
@@ -417,7 +422,15 @@ def _rewrite_file(rel, text, renames, include_prose, plan, folders=()):
417
422
  row = bool(is_roadmap and _ROW_ID_RE.match(line))
418
423
  heading = bool(is_roadmap and re.match(r"^#{1,6}\s", line))
419
424
  frontmatter = no <= fm_end or no in graph
425
+ low = line.lower()
426
+ # Every old ID is `FEAT-…` and its anchor `feat-…`: a line naming neither
427
+ # cannot match, so the per-ID loop is skipped (#3559).
428
+ if "feat-" not in low:
429
+ out.append(line)
430
+ continue
420
431
  for old, new in pairs:
432
+ if anchor(old) not in low:
433
+ continue
421
434
  line = _rewrite_line(
422
435
  line, old, new,
423
436
  structural=row or heading or frontmatter,
@@ -425,8 +438,9 @@ def _rewrite_file(rel, text, renames, include_prose, plan, folders=()):
425
438
  events=is_events,
426
439
  prose=include_prose and not fenced,
427
440
  where=(rel, no + 1, plan),
428
- folders=[f for f in folders if f.startswith(old + "-")],
441
+ folders=owned[old],
429
442
  )
443
+ low = line.lower()
430
444
  out.append(line)
431
445
  return "".join(out)
432
446
 
@@ -6,7 +6,8 @@
6
6
  """Project layout resolver — FEAT-2026-0224 (project-configurable layout).
7
7
 
8
8
  Reads an optional `.specfuse/layout.yml` and answers `layout get <key>` for
9
- `decisions.style`, `decisions.location`, `decisions.link` and `ids.key`.
9
+ `decisions.style`, `decisions.location`, `decisions.link`, `decisions.entryKey`
10
+ and `ids.key`.
10
11
 
11
12
  Contract: no file (or no `decisions:` block) means today's behaviour — style
12
13
  `adr`, location `docs/adr`, link `{path}`. An `entries` style with `location`
@@ -41,12 +42,23 @@ _STYLE_DEFAULTS = {
41
42
  }
42
43
  _FALLBACK_DEFAULTS = _STYLE_DEFAULTS["adr"]
43
44
 
44
- KEYS = ("decisions.style", "decisions.location", "decisions.link", "ids.key")
45
+ KEYS = (
46
+ "decisions.style",
47
+ "decisions.location",
48
+ "decisions.link",
49
+ "decisions.entryKey",
50
+ "ids.key",
51
+ "changelog.mode",
52
+ )
53
+
54
+ DEFAULT_CHANGELOG_MODE = "single"
55
+ VALID_CHANGELOG_MODES = ("single", "fragments")
45
56
 
46
57
  VALID_STYLES = ("adr", "entries")
47
58
  LINK_PLACEHOLDERS = ("location", "path", "id", "domain")
48
- KNOWN_TOP_LEVEL = ("decisions", "ids")
59
+ KNOWN_TOP_LEVEL = ("decisions", "ids", "changelog")
49
60
  _PLACEHOLDER_RE = re.compile(r"\{([^{}]*)\}")
61
+ _ENTRY_KEY_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_-]*$")
50
62
  _DRIVE_RE = re.compile(r"^[A-Za-z]:")
51
63
 
52
64
 
@@ -55,6 +67,7 @@ class Decisions:
55
67
  style: str
56
68
  location: str
57
69
  link: str
70
+ entry_key: Optional[str] = None
58
71
 
59
72
 
60
73
  @dataclass(frozen=True)
@@ -62,10 +75,16 @@ class Ids:
62
75
  key: Optional[str] = None
63
76
 
64
77
 
78
+ @dataclass(frozen=True)
79
+ class Changelog:
80
+ mode: str = DEFAULT_CHANGELOG_MODE
81
+
82
+
65
83
  @dataclass(frozen=True)
66
84
  class Layout:
67
85
  decisions: Decisions
68
86
  ids: Ids = Ids()
87
+ changelog: Changelog = Changelog()
69
88
 
70
89
 
71
90
  def _normalise(location: str) -> str:
@@ -81,10 +100,12 @@ def _resolve_decisions(block: Optional[dict]) -> Decisions:
81
100
  default_location, default_link = _STYLE_DEFAULTS.get(style, _FALLBACK_DEFAULTS)
82
101
  location = block.get("location")
83
102
  link = block.get("link")
103
+ entry_key = block.get("entryKey")
84
104
  return Decisions(
85
105
  style=style,
86
106
  location=default_location if location is None else _normalise(str(location)),
87
107
  link=default_link if link is None else str(link),
108
+ entry_key=None if entry_key is None else str(entry_key),
88
109
  )
89
110
 
90
111
 
@@ -104,9 +125,12 @@ def load(repo_root) -> Layout:
104
125
  block = data.get("decisions") if isinstance(data, dict) else None
105
126
  ids_block = data.get("ids") if isinstance(data, dict) else None
106
127
  key = ids_block.get("key") if isinstance(ids_block, dict) else None
128
+ cl_block = data.get("changelog") if isinstance(data, dict) else None
129
+ mode = cl_block.get("mode") if isinstance(cl_block, dict) else None
107
130
  return Layout(
108
131
  decisions=_resolve_decisions(block if isinstance(block, dict) else None),
109
132
  ids=Ids(key=None if key is None else str(key)),
133
+ changelog=Changelog(mode=DEFAULT_CHANGELOG_MODE if mode is None else str(mode)),
110
134
  )
111
135
 
112
136
 
@@ -168,6 +192,17 @@ def validate(repo_root) -> list:
168
192
  "(2-6 characters, upper-case letters and digits, starting with a letter)",
169
193
  )
170
194
 
195
+ cl_block = data.get("changelog")
196
+ if isinstance(cl_block, dict) and cl_block.get("mode") is not None:
197
+ mode = str(cl_block["mode"])
198
+ if mode not in VALID_CHANGELOG_MODES:
199
+ add(
200
+ SEVERITY_ERROR,
201
+ "mode",
202
+ f"changelog.mode {mode!r} is invalid; must be one of "
203
+ f"{VALID_CHANGELOG_MODES[0]!r} or {VALID_CHANGELOG_MODES[1]!r}",
204
+ )
205
+
171
206
  block = data.get("decisions")
172
207
  if not isinstance(block, dict):
173
208
  return findings
@@ -227,6 +262,18 @@ def validate(repo_root) -> list:
227
262
  f"decisions.link placeholder '{{{name}}}' is not allowed; "
228
263
  "use {location}, {path}, {id} or {domain}",
229
264
  )
265
+
266
+ entry_key = block.get("entryKey")
267
+ if entry_key is not None:
268
+ if not _ENTRY_KEY_RE.match(str(entry_key)):
269
+ add(
270
+ SEVERITY_ERROR,
271
+ "entryKey",
272
+ f"decisions.entryKey {str(entry_key)!r} is invalid; must match "
273
+ f"{_ENTRY_KEY_RE.pattern}",
274
+ )
275
+ if style is None or str(style) != "entries":
276
+ add(SEVERITY_ERROR, "entryKey", "decisions.entryKey applies only to style 'entries'")
230
277
  return findings
231
278
 
232
279
 
@@ -239,6 +286,10 @@ def get(layout: Layout, key: str) -> str:
239
286
  raise KeyError(key)
240
287
  if key == "ids.key":
241
288
  return layout.ids.key or ""
289
+ if key == "changelog.mode":
290
+ return layout.changelog.mode
291
+ if key == "decisions.entryKey":
292
+ return layout.decisions.entry_key or ""
242
293
  return getattr(layout.decisions, key.split(".", 1)[1])
243
294
 
244
295
 
@@ -28,7 +28,6 @@ from pathlib import Path
28
28
  from . import closing_requirements as creq
29
29
  from . import criteria_state
30
30
  from ._wu_sections import slice_wu_section
31
- from .changelog import parse_changelog
32
31
  from .judge import MEASUREMENTS_SECTION
33
32
  from .loop import (
34
33
  _gate_number_from_wu_id,
@@ -293,22 +292,18 @@ def _check_changelog_entry_for_contract_changes(req: creq.Requirement, ctx: Clos
293
292
  return True, ""
294
293
  if creq.consumer_visible_section_is_na(section):
295
294
  return True, ""
296
- changelog = ctx.repo_root / creq.CHANGELOG_PATH
297
- if not changelog.exists():
295
+ feature_id = ctx.wu_id.rsplit("/", 1)[0]
296
+ if creq.changelog_has_entry_for_feature(ctx.repo_root, feature_id):
297
+ return True, ""
298
+ if not creq.changelog_exists(ctx.repo_root):
298
299
  return False, (
299
300
  f"'{creq.CONSUMER_VISIBLE_HEADING}' names a real change but "
300
301
  f"{creq.CHANGELOG_PATH} does not exist"
301
302
  )
302
- result = parse_changelog(changelog.read_text())
303
- unreleased = result.unreleased()
304
- feature_id = ctx.wu_id.rsplit("/", 1)[0]
305
- entries = unreleased.entries if unreleased else []
306
- if creq.changelog_has_entry_for(entries, feature_id):
307
- return True, ""
308
303
  return False, (
309
- f"'{creq.CONSUMER_VISIBLE_HEADING}' names a real change but "
310
- f"{creq.CHANGELOG_PATH}'s Unreleased section has no entry tracing to "
311
- f"{feature_id}"
304
+ f"'{creq.CONSUMER_VISIBLE_HEADING}' names a real change but neither "
305
+ f"{creq.CHANGELOG_PATH}'s Unreleased section nor a valid changelog.d/ "
306
+ f"fragment has an entry tracing to {feature_id}"
312
307
  )
313
308
 
314
309