specfuse-loop 0.3.20__tar.gz → 0.3.22__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 (168) hide show
  1. {specfuse_loop-0.3.20/specfuse_loop.egg-info → specfuse_loop-0.3.22}/PKG-INFO +1 -1
  2. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/pyproject.toml +1 -1
  3. specfuse_loop-0.3.22/specfuse/loop/data/VERSION +1 -0
  4. specfuse_loop-0.3.22/specfuse/loop/data/rules/planning-discipline.md +119 -0
  5. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/rules/result-contract.md +27 -0
  6. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/rules/verification-discipline.md +6 -0
  7. specfuse_loop-0.3.22/specfuse/loop/data/rules-local/README.md +43 -0
  8. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/templates/GATE.template.md +17 -0
  9. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/templates/PLAN.template.md +26 -0
  10. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/templates/WU.template.md +15 -0
  11. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/lint_plan.py +27 -1
  12. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/loop.py +321 -34
  13. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/scaffold.py +11 -0
  14. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22/specfuse_loop.egg-info}/PKG-INFO +1 -1
  15. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse_loop.egg-info/SOURCES.txt +5 -0
  16. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_attempt_outcome_emission.py +50 -0
  17. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_init_integration.py +3 -0
  18. specfuse_loop-0.3.22/tests/test_lifetime_accounting.py +240 -0
  19. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_planned_cost_lint.py +72 -0
  20. specfuse_loop-0.3.22/tests/test_produces_in_diff.py +264 -0
  21. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_scaffold_data_in_sync.py +2 -0
  22. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_scaffold_init.py +3 -0
  23. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_scaffold_resources.py +2 -0
  24. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_scaffold_upgrade.py +34 -0
  25. specfuse_loop-0.3.22/tests/test_spinning_rearm_gate.py +308 -0
  26. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_terminal_flips.py +85 -0
  27. specfuse_loop-0.3.20/specfuse/loop/data/VERSION +0 -1
  28. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/LICENSE +0 -0
  29. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/NOTICE +0 -0
  30. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/README.md +0 -0
  31. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/setup.cfg +0 -0
  32. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/__init__.py +0 -0
  33. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/_filelock.py +0 -0
  34. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/_miniyaml.py +0 -0
  35. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/adopt_feature.py +0 -0
  36. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/LEARNINGS.template.md +0 -0
  37. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/docs/concepts/architecture-addendum-gates-and-iterative-planning.md +0 -0
  38. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/docs/concepts/ralph-lineage.md +0 -0
  39. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/docs/getting-started.md +0 -0
  40. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/docs/methodology.md +0 -0
  41. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/docs/skills.md +0 -0
  42. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/gitignore.snippet +0 -0
  43. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/roadmap.template.md +0 -0
  44. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/rules/correlation-ids.md +0 -0
  45. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/rules/never-touch.md +0 -0
  46. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/rules/security-boundaries.md +0 -0
  47. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/schemas/event.schema.json +0 -0
  48. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/schemas/events/initiative_created.schema.json +0 -0
  49. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/schemas/events/spec_issue_resolved.schema.json +0 -0
  50. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/schemas/events/spec_issue_routed.schema.json +0 -0
  51. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/schemas/events/spec_validated.schema.json +0 -0
  52. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/verification.yml.example +0 -0
  53. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/gate_eval.py +0 -0
  54. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/gh_backend.py +0 -0
  55. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/gh_features.py +0 -0
  56. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/validate_event.py +0 -0
  57. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse_loop.egg-info/dependency_links.txt +0 -0
  58. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse_loop.egg-info/entry_points.txt +0 -0
  59. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse_loop.egg-info/requires.txt +0 -0
  60. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse_loop.egg-info/top_level.txt +0 -0
  61. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_adopt_feature.py +0 -0
  62. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_arm_gate_edits_uncommitted.py +0 -0
  63. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_autoclose_deferral_visibility.py +0 -0
  64. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_autosync.py +0 -0
  65. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_autosync_consent.py +0 -0
  66. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_autosync_firstrun.py +0 -0
  67. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_autosync_plugin.py +0 -0
  68. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_backend.py +0 -0
  69. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_bash_routing.py +0 -0
  70. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_bookkeeping_commit_crash_run.py +0 -0
  71. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_bookkeeping_commit_hook_crash.py +0 -0
  72. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_bookkeeping_no_verify.py +0 -0
  73. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_claude_resolution.py +0 -0
  74. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_close_wu_self_reference.py +0 -0
  75. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_closing_deliverable_guard.py +0 -0
  76. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_console_encoding.py +0 -0
  77. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_cost_tracking.py +0 -0
  78. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_degraded_oracle.py +0 -0
  79. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_deliverable_presence_gate.py +0 -0
  80. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_doctor.py +0 -0
  81. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_driver_integration.py +0 -0
  82. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_driver_lock.py +0 -0
  83. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_duration_tracking.py +0 -0
  84. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_empty_files_escalation.py +0 -0
  85. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_ensure_feature_branch.py +0 -0
  86. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_ensure_feature_branch_base.py +0 -0
  87. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_events_redaction.py +0 -0
  88. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_extra_gates.py +0 -0
  89. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_filelock_portable.py +0 -0
  90. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_force_full_close.py +0 -0
  91. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_gate_eval.py +0 -0
  92. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_gate_eval_calibration.py +0 -0
  93. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_gate_eval_intermediate_wiring.py +0 -0
  94. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_gate_eval_terminal_wiring.py +0 -0
  95. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_gh_backend.py +0 -0
  96. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_gh_features.py +0 -0
  97. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_git_env_isolation.py +0 -0
  98. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_hashed_denylist.py +0 -0
  99. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_hashed_denylist_ci.py +0 -0
  100. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_interpreter_normalization.py +0 -0
  101. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_leak_findings_redaction.py +0 -0
  102. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_leak_scan.py +0 -0
  103. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_leak_scan_content.py +0 -0
  104. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_learnings_query.py +0 -0
  105. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_legacy_4wu_terminal_flips.py +0 -0
  106. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_lifecycle_integration.py +0 -0
  107. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_lint_bare_produces_path.py +0 -0
  108. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_lint_close_intermediate.py +0 -0
  109. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_lint_close_wu.py +0 -0
  110. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_lint_correlation_id.py +0 -0
  111. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_lint_correlation_id_close_intermediate.py +0 -0
  112. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_lint_oracle_env.py +0 -0
  113. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_lint_plan_errors.py +0 -0
  114. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_lint_plan_next_draft.py +0 -0
  115. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_lint_plan_relative_import.py +0 -0
  116. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_lint_produces_driver_helper.py +0 -0
  117. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_lint_sections.py +0 -0
  118. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_lint_task_graph_yaml_selection.py +0 -0
  119. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_lint_verification_reference.py +0 -0
  120. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_loop_attempt_evidence.py +0 -0
  121. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_loop_auto_archive.py +0 -0
  122. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_loop_caveman_preamble.py +0 -0
  123. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_loop_close_intermediate.py +0 -0
  124. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_loop_defaults_by_type.py +0 -0
  125. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_loop_effort.py +0 -0
  126. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_loop_failure_note_cap.py +0 -0
  127. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_loop_files_changed_guard.py +0 -0
  128. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_loop_gate_budget.py +0 -0
  129. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_loop_model_alias.py +0 -0
  130. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_loop_orchestration.py +0 -0
  131. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_loop_post_pass_invariant.py +0 -0
  132. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_loop_reset_preserving_events.py +0 -0
  133. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_loop_smoke_runner.py +0 -0
  134. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_loop_unsandboxed.py +0 -0
  135. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_loop_zero_token_guard.py +0 -0
  136. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_migrate_legacy.py +0 -0
  137. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_miniyaml_equivalence.py +0 -0
  138. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_miniyaml_negative.py +0 -0
  139. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_prepare_feature.py +0 -0
  140. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_prepare_scaffold_sync.py +0 -0
  141. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_produces_field.py +0 -0
  142. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_redaction_windows_home.py +0 -0
  143. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_resolve_base.py +0 -0
  144. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_result_block.py +0 -0
  145. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_roadmap_add_skill.py +0 -0
  146. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_roadmap_archive_skill.py +0 -0
  147. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_roadmap_row_parser.py +0 -0
  148. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_scaffold_doc_hygiene.py +0 -0
  149. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_scaffold_docs.py +0 -0
  150. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_scaffold_manifest.py +0 -0
  151. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_scaffold_seed_sanity.py +0 -0
  152. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_scaffold_wiring.py +0 -0
  153. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_skills_vendored_in_sync.py +0 -0
  154. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_squash_commit_hook_crash.py +0 -0
  155. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_template_closing_shapes.py +0 -0
  156. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_terminal_flip_ownership.py +0 -0
  157. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_timeout_kill_portable.py +0 -0
  158. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_untracked_feature_folder.py +0 -0
  159. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_untracked_file_absorption.py +0 -0
  160. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_upgrade_integration.py +0 -0
  161. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_upgrade_merge_gate.py +0 -0
  162. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_validate_event.py +0 -0
  163. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_verdict_coupling.py +0 -0
  164. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_verify_empty_gate_set.py +0 -0
  165. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_version_consistency.py +0 -0
  166. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_version_skew.py +0 -0
  167. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_windows_gate_exec.py +0 -0
  168. {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_wu_execution_metadata.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: specfuse-loop
3
- Version: 0.3.20
3
+ Version: 0.3.22
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.3.20"
7
+ version = "0.3.22"
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"
@@ -0,0 +1 @@
1
+ 0.3.22
@@ -0,0 +1,119 @@
1
+ <!--
2
+ Copyright 2026 Specfuse Contributors
3
+ Licensed under the Apache License, Version 2.0. See LICENSE.
4
+ -->
5
+
6
+ # Rule: planning discipline
7
+
8
+ Encodes hard-won discipline from feature retrospectives — the checks that, applied
9
+ at design and arming time, would have prevented the most expensive re-plans. Each
10
+ check below is cheap (a grep, a sentence, a table, one local run) and guards a
11
+ failure mode that cost multiple gates when skipped. Provenance is named per check so
12
+ the reasoning survives the rule (the FEAT-2026-0049 / F1–F4 references resolve in the
13
+ retrospective of the specfuse-generator dogfood feature that motivated these checks).
14
+
15
+ These are **binding at draft, plan-next, and arm time**. A gate armed in violation
16
+ of any check is not ready, regardless of what a WU's frontmatter says.
17
+
18
+ ## 1. Existing-mechanism search — before designing any enforcement or measurement
19
+
20
+ Before an ADR designs a validation rule, a severity level, an enforcement gate, or a
21
+ measurement, it must first establish that the mechanism does not already exist. The
22
+ ADR is **incomplete** without this section.
23
+
24
+ - **Grep the inventory.** Search the directory that holds the mechanism's
25
+ siblings — for a validation rule, the project's rules directory (e.g.
26
+ `grep -rl <concept> <path/to/validation/rules/>`); for an artifact, the
27
+ tool's own inventory command (e.g. `<tool> templates`); for any other
28
+ check, the directory that holds its siblings.
29
+ - **Record the grep command and its verdict** in the ADR — the exact command, and
30
+ either "no existing mechanism, building new" or "found `<X>`, reusing / extending
31
+ it". A verdict of "reusing" that then builds new anyway must say why.
32
+ - **Read the javadoc/description of anything the grep surfaces.** A rule that already
33
+ covers the property may say so in its own words.
34
+
35
+ > **Provenance (FEAT-2026-0049 / F3).** The feature spent three gates building a
36
+ > `WARNING→ERROR` ladder on a new rule while the enforcement it needed already existed
37
+ > in `RelationshipSymmetryValidationRule` — whose javadoc (F-029, 2026-05-29) literally
38
+ > said the debt "surfaces as a Saturday-night surprise when the inference rule changes."
39
+ > A one-line grep of the rules directory at ADR time would have found it. Cost: two
40
+ > gates and a full pivot.
41
+
42
+ ## 2. Escalation-predicate satisfiability — for any severity flip
43
+
44
+ Any plan that raises a check to `ERROR`, flips a `WARNING` to blocking, or asserts a
45
+ "zero issues" close predicate must answer, in writing, one question:
46
+
47
+ > **What does this rule report on a spec/input already in its intended final state?**
48
+
49
+ If the answer is anything other than **zero**, the predicate is **unsatisfiable** —
50
+ the rule fires on correct inputs, so "zero issues" can never be reached and the
51
+ severity flip forces wrong changes. **Stop and redesign** before arming: fix the rule
52
+ so a correct input reports zero, or route enforcement to a different mechanism (see §1).
53
+
54
+ > **Provenance (FEAT-2026-0049 / F3).** ADR D3 ("omission = intent — a non-owning
55
+ > reference is declared by omission") and D6 ("`ERROR` when the rule reports zero
56
+ > issues") contradicted each other inside the same document: post-flip, every correct
57
+ > omission still tripped the rule, so "zero" was unreachable. The contradiction was
58
+ > answerable by reading the ADR against itself. Separately, the *shipped* rule
59
+ > false-positived ~35 of 49 on real specs (#821) — a severity flip forcing wrong
60
+ > changes, exactly what this check prevents.
61
+
62
+ ## 3. Flag-scope table — for any WU introducing or flipping a behavior flag
63
+
64
+ A WU that introduces, gates on, or flips a behavior flag must contain a **flag-scope
65
+ table**: every code path the flag is claimed to affect, marked *gated* / *not gated*,
66
+ with a one-line *why* per row. The arming review checks the feature's headline claim
67
+ ("the flip retires inference", "the flag makes X strict") against the table — a claim
68
+ the table does not support is a scope mismatch, and scope mismatches surface gates
69
+ later as defects, not at arming.
70
+
71
+ | Code path | Gated by flag? | Why |
72
+ |---|---|---|
73
+ | `<path/method>` | yes / no | <one line> |
74
+
75
+ > **Provenance (FEAT-2026-0049 / F1).** The plan enumerated both inference paths
76
+ > (bare-FK and `$ref`) on day one, but no document ever crossed "which paths does the
77
+ > `explicitRelationshipsOnly` flag gate" against "the flip retires inference." The flag
78
+ > was built FK-path-only, deliberately and correctly — but the mismatch with the
79
+ > headline claim surfaced two gates later (F1), and again as a runtime defect (F4).
80
+
81
+ ## 4. Runtime probe before arming a default/severity flip
82
+
83
+ A gate whose WUs flip a **default value** or a **severity** may **not** be armed on
84
+ "mechanical, nothing design-open." Before arming, apply the change locally and run the
85
+ **exact command the WU's tests gate will run** (the full oracle, not a subset), and
86
+ paste the resulting failure list into the gate review. The probe is the arming
87
+ evidence; "nothing design-open" without it is a claim, not a fact.
88
+
89
+ - Run the *full* oracle. A subset hides failures in the paths the change touches that
90
+ the subset does not exercise.
91
+ - The probe's failure list becomes the WU's enumerated test surface — the WU no longer
92
+ discovers its own breakage attempt by attempt.
93
+
94
+ > **Provenance (FEAT-2026-0049 / F4).** Gate 4 was armed on "nothing is design-open"
95
+ > without ever applying the default flip and running the full suite once. The WU then
96
+ > spun three times (~$14) on a real defect that one local run exposed in seconds — and
97
+ > the first two diagnoses were made against a *subset*, missing failures the full suite
98
+ > showed. Contrast: an earlier gate's "measure before drafting" probe (a `WARNING` run
99
+ > over the real specs) worked and correctly shrank the next WU's scope.
100
+
101
+ ## 5. Planning-WU cost floor
102
+
103
+ `plan-next` and `close` / `close-intermediate` WUs are systematically under-estimated.
104
+ When drafting their `planned_cost_usd`, use a **floor of $5.00** — never the $2–3 that
105
+ "it's just bookkeeping" suggests. A gate `cost_budget_usd` set to the sum of $2–3
106
+ planning estimates is a brake that fires by construction on the first real close.
107
+
108
+ > **Provenance (FEAT-2026-0049 + LEARNINGS [FEAT-2026-0044]).** Planning/close WUs ran
109
+ > 2.8–5.2× their $2–3 estimates: `plan-next` at $5.90 and $15.65, `close-intermediate`
110
+ > at $5.67, against $2–3 drafts. LEARNINGS `[FEAT-2026-0044]` had already predicted this
111
+ > systematic under-estimation; the floor makes the prediction binding.
112
+
113
+ ## Why these live together
114
+
115
+ Each is a one-time check that trades a minute at design/arm time against a gate (or
116
+ three) of rework. They share a root: **automation and authors both trust a claim
117
+ without the cheap check that would test it** — a grep not run (§1), a contradiction not
118
+ read (§2), a scope not tabled (§3), a suite not run (§4), an estimate not calibrated
119
+ (§5). State the check, run it, record the verdict. Every plan, every arm.
@@ -107,6 +107,33 @@ blocked_reason: <present only when status is blocked>
107
107
  5. **No secret-looking values in evidence.** The RESULT block is read by the driver
108
108
  and may be archived. See [`security-boundaries.md`](security-boundaries.md).
109
109
 
110
+ ## Closing obligations for implementation WUs (FEAT-2026-0049)
111
+
112
+ Four binding obligations before `status: complete`. Each guards a way a prior WU
113
+ closed on nothing:
114
+
115
+ 1. **Diff against `produces:` first.** Every path in the WU's `produces:` list must
116
+ show a working-tree change, or the RESULT must justify each unchanged path
117
+ explicitly: "no change needed because X; here is the evidence the deliverable
118
+ already holds: `<command + output>`." Silence on an unchanged deliverable is not
119
+ a valid close. (The driver enforces this mechanically since specfuse-loop 0.3.21
120
+ — #198, outcome `produces_not_in_diff`; the obligation binds regardless of
121
+ driver version.)
122
+ 2. **A plan-level contradiction is `blocked`, not `complete`.** If mid-WU you find
123
+ the plan cannot be delivered as written (a decision contradicts another, a
124
+ deliverable cannot exist), emit `status: blocked` with the finding in
125
+ `blocked_reason`. Never write the finding into a gate document and close
126
+ `complete`. `complete` means the deliverables exist; `blocked` means they cannot
127
+ or should not.
128
+ 3. **Every `evidence:` cites an executed command** — its observed exit code/output.
129
+ Reading source, grepping for a string, or citing another WU's RESULT is not
130
+ verification. For a validation-rule or severity claim the evidence must include a
131
+ negative observation (the rule seen rejecting a purpose-built bad input); see
132
+ [`verification-discipline.md`](verification-discipline.md) §3.
133
+ 4. **Analysis without edits is not a silent attempt.** If an attempt produces
134
+ analysis but no file edits, say so explicitly and end `blocked` (or with a
135
+ concrete next-edit plan). Do not exhaust the attempt on prose.
136
+
110
137
  ## Why this matters
111
138
 
112
139
  The driver's trust model is that the RESULT block is an honest claim about what
@@ -39,6 +39,12 @@ Two generic meanings, both apply unless the unit narrows the scope:
39
39
  decide done. Run them yourself first, in declared order, with full output.
40
40
  Generic verifications — "I assume the tests still pass" — are not verifications.
41
41
  The commands are the verification.
42
+ - **Observe behavior, not source.** A claim that code does X is verified by making
43
+ X happen and reading the output — never by reading the source that looks like it
44
+ does X. A rule read as `ERROR` in source may be gated off and never fire; only a
45
+ run proves it. For any rule or severity claim, include a **negative observation**:
46
+ the rule seen rejecting a purpose-built bad input. A rule that fires on nothing
47
+ passes every test.
42
48
 
43
49
  **Verification is the exit oracle.** The agent's own report is advisory; done is
44
50
  decided by the checks passing, not by the agent's belief. If a check fails you are
@@ -0,0 +1,43 @@
1
+ <!--
2
+ Copyright 2026 Specfuse Contributors
3
+ Licensed under the Apache License, Version 2.0. See LICENSE.
4
+ -->
5
+
6
+ # Project-authored rules (`rules-local/`)
7
+
8
+ This directory belongs to **your project**, not to Specfuse. Put binding rules
9
+ here that are specific to this repository — its tools, its paths, its failure
10
+ modes — and that should survive every scaffold upgrade.
11
+
12
+ **The contract:** `specfuse upgrade` never writes, overwrites, or prunes
13
+ anything in `rules-local/`. That is the opposite of the sibling
14
+ `.specfuse/rules/` directory, which is versioned: upgrades overwrite its files
15
+ with the shipped versions and delete files the shipped scaffold no longer
16
+ carries. A project rule placed in `rules/` will be lost on upgrade; the same
17
+ rule placed here is permanent.
18
+
19
+ ## When to write a rule here vs. upstream
20
+
21
+ - **Here:** the rule names your project's directories, commands, tools, or
22
+ conventions ("grep `src/main/java/.../validation/rules/` before designing a
23
+ rule", "the generator jar's `templates` command lists artifacts"). Upstream
24
+ would have to genericize away exactly the specifics that make it useful to
25
+ you.
26
+ - **Upstream (a PR to the scaffold's `rules/`):** the failure mode is
27
+ project-agnostic and other Specfuse projects would hit it too. Keep the
28
+ provenance; genericize the examples.
29
+
30
+ ## Wiring a rule into the loop
31
+
32
+ Rules load via the `@`-references in your `.claude/CLAUDE.md` Specfuse rules
33
+ block. Add one line per rule you author:
34
+
35
+ ```markdown
36
+ @.specfuse/rules-local/<your-rule>.md
37
+ ```
38
+
39
+ Follow the shipped rules' format: one failure mode per rule, the check stated
40
+ imperatively, provenance recorded so the reasoning survives the rule (see
41
+ `.specfuse/rules/planning-discipline.md` for the model).
42
+
43
+ This README is seeded once and never overwritten — edit it freely.
@@ -30,6 +30,23 @@ here for human review-and-arm: read the review artifact, accept or edit the draf
30
30
  next-gate work units, flip the accepted ones to `pending`, set this gate's status to
31
31
  `passed`, and re-run.
32
32
 
33
+ ## Arming discipline (see `.specfuse/rules/planning-discipline.md`)
34
+
35
+ Before flipping this gate's next-gate WUs to `pending`:
36
+
37
+ - **Runtime probe for a default/severity flip (§4).** If any WU in the gate being armed
38
+ flips a **default value** or a **severity**, it may **not** be armed on "mechanical,
39
+ nothing design-open." Apply the change locally, run the **exact command the WU's tests
40
+ gate will run** (the full oracle, not a subset), and paste the failure list into
41
+ `GATE-NN-REVIEW.md`. That list becomes the WU's enumerated test surface. Arming such a
42
+ gate without a probe is what makes an implementation WU spin on a defect one local run
43
+ would have shown.
44
+ - **Flag-scope table (§3).** If a WU introduces or flips a behavior flag, confirm it
45
+ carries a flag-scope table and that the feature's headline claim matches it.
46
+ - **Escalation-predicate satisfiability (§2).** If a WU raises a check to `ERROR` or
47
+ asserts a "zero issues" predicate, confirm PLAN.md answered "what does the rule report
48
+ on a correct input" and that the answer is zero.
49
+
33
50
  ## Reflection notes
34
51
 
35
52
  <Written by the human at review time. What surprised you, what you changed in the
@@ -23,6 +23,32 @@ each WU file owns its own status, and each GATE file owns its gate's status. Det
23
23
  only as far as the next gate; plan-next drafts the gate after that from the
24
24
  retrospective and lessons.
25
25
 
26
+ ## Existing-mechanism search (mandatory — see `.specfuse/rules/planning-discipline.md` §1)
27
+
28
+ Before any ADR or gate designs a validation rule, a severity level, an enforcement
29
+ gate, or a measurement, establish that it does not already exist. **An ADR without
30
+ this section is incomplete.** If this feature designs no such mechanism, write
31
+ exactly `n/a — no enforcement or measurement designed` and stop — do not fabricate
32
+ a grep to fill the section.
33
+
34
+ - **Grep command run:** `<the exact command — e.g. grep -rl <concept>
35
+ src/main/java/.../validation/rules/>`
36
+ - **Verdict:** `<"no existing mechanism, building new" | "found <X>, reusing/extending">`
37
+ - **If reusing:** name `<X>` and quote the line of its javadoc/description that
38
+ confirms it covers the property. If building new despite a hit, say why the hit does
39
+ not suffice.
40
+
41
+ ## Escalation-predicate satisfiability (mandatory for any severity flip — §2)
42
+
43
+ If this feature raises a check to `ERROR`, flips a `WARNING` to blocking, or asserts a
44
+ "zero issues" close predicate, answer here:
45
+
46
+ - **What does the rule report on an input already in its intended final state?**
47
+ `<answer>`
48
+ - If that answer is **not zero**, the predicate is unsatisfiable — the rule fires on
49
+ correct inputs. **Redesign before drafting gates:** fix the rule so a correct input
50
+ reports zero, or route enforcement to the mechanism found in the search above.
51
+
26
52
  ## Task graph
27
53
 
28
54
  ```yaml
@@ -28,6 +28,10 @@ AUTHOR-SET FIELDS — fill or override these at draft/arm time:
28
28
  `xhigh` | `max`. Absent → type-keyed default. `low`/`medium` add a terseness directive.
29
29
  - `planned_cost_usd` — OPTIONAL. Estimated USD at draft time. Compared against actual in close
30
30
  WU's cost analysis. Lint WARN when absent on active/draft WUs (non-blocking).
31
+ **Planning-WU cost floor (`.specfuse/rules/planning-discipline.md` §5):** for `plan-next`,
32
+ `close`, and `close-intermediate` WUs, draft `planned_cost_usd` at a **floor of $5.00** — these
33
+ run 2.8–5.2× the $2–3 that "it's just bookkeeping" suggests. A gate `cost_budget_usd` set to
34
+ the sum of $2–3 planning estimates is a brake that fires by construction on the first real close.
31
35
  - `generated_surfaces` — OPTIONAL. Paths to generated files this unit's acceptance depends on.
32
36
  - `oracle_env` — OPTIONAL. Environment the verifying oracle runs in: `macos_local`,
33
37
  `linux_docker`, `github_actions_ci`, or an operator-named string. Lint WARN when AC mentions
@@ -77,6 +81,17 @@ cheapest hollow-pass guard; see `/authoring-work-units` §12 for the contract an
77
81
  the carve-outs (refactor, migration, pure-data → explicit `Red-test exempt:
78
82
  <reason>` line in the WU body).
79
83
 
84
+ **Flag-scope table** (REQUIRED only when this WU introduces, gates on, or flips a
85
+ behavior flag; omit otherwise — see `.specfuse/rules/planning-discipline.md` §3). Every
86
+ code path the flag is claimed to affect, marked gated / not gated, with a one-line why.
87
+ The arming review checks the feature's headline claim ("the flip retires X") against
88
+ this table; a claim the table does not support is a scope mismatch that surfaces as a
89
+ defect gates later.
90
+
91
+ | Code path | Gated by flag? | Why |
92
+ |---|---|---|
93
+ | `<path/method>` | yes / no | <one line> |
94
+
80
95
  **Do not touch.** Generated directories (`_generated/`, `gen-src/`, or the repo's
81
96
  declared equivalent), files owned by other work units in this gate, secrets,
82
97
  `.git/`. The driver owns all git operations — you edit files only. See
@@ -248,12 +248,23 @@ def _find_task_graph_block(body: str) -> dict | None:
248
248
  return None
249
249
 
250
250
 
251
+ # Ceremony WU types systematically under-estimated in the field: close and
252
+ # plan-next WUs ran 2.8-5.2x over on $2-3 estimates across FEAT-2026-0049
253
+ # (clabonte/generator), poisoning every calibration built on the plans (#201).
254
+ # The floor is a WARN threshold, not a cap — estimates below it are almost
255
+ # certainly wishful, not cheap.
256
+ CEREMONY_COST_FLOOR_USD = 5.0
257
+ _CEREMONY_TYPES = frozenset({"close", "close-intermediate", "plan-next"})
258
+
259
+
251
260
  def check_planned_cost(feature_dir: Path, plan_fm: dict, gates: list) -> None:
252
261
  """Emit WARN for missing planned_cost_usd on WUs and PLAN.md.
253
262
 
254
263
  Sealed WUs (wu status=done AND plan status=done) are skipped silently —
255
264
  backfilling cost estimates on history is pointless. Active or draft WUs
256
- get the WARN. PLAN.md is compared against the sum of WU planned costs;
265
+ get the WARN. Ceremony-type WUs (close/close-intermediate/plan-next)
266
+ with a planned cost below CEREMONY_COST_FLOOR_USD get a floor WARN
267
+ (#201). PLAN.md is compared against the sum of WU planned costs;
257
268
  delta > 10% emits a separate WARN naming the delta. Never raises or
258
269
  appends to an errors list — all findings are WARN-only (exit code 0).
259
270
  """
@@ -283,6 +294,21 @@ def check_planned_cost(feature_dir: Path, plan_fm: dict, gates: list) -> None:
283
294
  )
284
295
  if planned is not None:
285
296
  wu_sum += float(planned)
297
+ # Floor applies to POSITIVE estimates only: the observed
298
+ # failure is a real-but-wishful $2-3 estimate anchoring
299
+ # calibration, not an explicit 0.00 (the scaffold's
300
+ # "unestimated" placeholder, already visible as such).
301
+ if (not is_sealed
302
+ and wfm.get("type") in _CEREMONY_TYPES
303
+ and 0 < float(planned) < CEREMONY_COST_FLOOR_USD):
304
+ print(
305
+ f"WARN: {wfile}: planned_cost_usd "
306
+ f"${float(planned):.2f} is below the "
307
+ f"${CEREMONY_COST_FLOOR_USD:.2f} floor for "
308
+ f"'{wfm.get('type')}' WUs. Ceremony WUs ran 2.8-5.2x "
309
+ f"over on $2-3 estimates (FEAT-2026-0049); raise the "
310
+ f"estimate rather than anchoring calibration on it."
311
+ )
286
312
 
287
313
  wu_sum = round(wu_sum, 2)
288
314