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.
- {specfuse_loop-0.3.20/specfuse_loop.egg-info → specfuse_loop-0.3.22}/PKG-INFO +1 -1
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/pyproject.toml +1 -1
- specfuse_loop-0.3.22/specfuse/loop/data/VERSION +1 -0
- specfuse_loop-0.3.22/specfuse/loop/data/rules/planning-discipline.md +119 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/rules/result-contract.md +27 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/rules/verification-discipline.md +6 -0
- specfuse_loop-0.3.22/specfuse/loop/data/rules-local/README.md +43 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/templates/GATE.template.md +17 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/templates/PLAN.template.md +26 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/templates/WU.template.md +15 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/lint_plan.py +27 -1
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/loop.py +321 -34
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/scaffold.py +11 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22/specfuse_loop.egg-info}/PKG-INFO +1 -1
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse_loop.egg-info/SOURCES.txt +5 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_attempt_outcome_emission.py +50 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_init_integration.py +3 -0
- specfuse_loop-0.3.22/tests/test_lifetime_accounting.py +240 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_planned_cost_lint.py +72 -0
- specfuse_loop-0.3.22/tests/test_produces_in_diff.py +264 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_scaffold_data_in_sync.py +2 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_scaffold_init.py +3 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_scaffold_resources.py +2 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_scaffold_upgrade.py +34 -0
- specfuse_loop-0.3.22/tests/test_spinning_rearm_gate.py +308 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_terminal_flips.py +85 -0
- specfuse_loop-0.3.20/specfuse/loop/data/VERSION +0 -1
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/LICENSE +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/NOTICE +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/README.md +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/setup.cfg +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/__init__.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/_filelock.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/_miniyaml.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/adopt_feature.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/LEARNINGS.template.md +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/docs/concepts/architecture-addendum-gates-and-iterative-planning.md +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/docs/concepts/ralph-lineage.md +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/docs/getting-started.md +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/docs/methodology.md +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/docs/skills.md +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/gitignore.snippet +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/roadmap.template.md +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/rules/correlation-ids.md +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/rules/never-touch.md +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/rules/security-boundaries.md +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/schemas/event.schema.json +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/schemas/events/initiative_created.schema.json +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/schemas/events/spec_issue_resolved.schema.json +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/schemas/events/spec_issue_routed.schema.json +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/schemas/events/spec_validated.schema.json +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/verification.yml.example +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/gate_eval.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/gh_backend.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/gh_features.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/validate_event.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse_loop.egg-info/dependency_links.txt +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse_loop.egg-info/entry_points.txt +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse_loop.egg-info/requires.txt +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse_loop.egg-info/top_level.txt +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_adopt_feature.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_arm_gate_edits_uncommitted.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_autoclose_deferral_visibility.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_autosync.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_autosync_consent.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_autosync_firstrun.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_autosync_plugin.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_backend.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_bash_routing.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_bookkeeping_commit_crash_run.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_bookkeeping_commit_hook_crash.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_bookkeeping_no_verify.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_claude_resolution.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_close_wu_self_reference.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_closing_deliverable_guard.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_console_encoding.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_cost_tracking.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_degraded_oracle.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_deliverable_presence_gate.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_doctor.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_driver_integration.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_driver_lock.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_duration_tracking.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_empty_files_escalation.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_ensure_feature_branch.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_ensure_feature_branch_base.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_events_redaction.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_extra_gates.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_filelock_portable.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_force_full_close.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_gate_eval.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_gate_eval_calibration.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_gate_eval_intermediate_wiring.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_gate_eval_terminal_wiring.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_gh_backend.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_gh_features.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_git_env_isolation.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_hashed_denylist.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_hashed_denylist_ci.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_interpreter_normalization.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_leak_findings_redaction.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_leak_scan.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_leak_scan_content.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_learnings_query.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_legacy_4wu_terminal_flips.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_lifecycle_integration.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_lint_bare_produces_path.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_lint_close_intermediate.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_lint_close_wu.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_lint_correlation_id.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_lint_correlation_id_close_intermediate.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_lint_oracle_env.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_lint_plan_errors.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_lint_plan_next_draft.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_lint_plan_relative_import.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_lint_produces_driver_helper.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_lint_sections.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_lint_task_graph_yaml_selection.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_lint_verification_reference.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_loop_attempt_evidence.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_loop_auto_archive.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_loop_caveman_preamble.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_loop_close_intermediate.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_loop_defaults_by_type.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_loop_effort.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_loop_failure_note_cap.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_loop_files_changed_guard.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_loop_gate_budget.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_loop_model_alias.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_loop_orchestration.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_loop_post_pass_invariant.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_loop_reset_preserving_events.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_loop_smoke_runner.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_loop_unsandboxed.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_loop_zero_token_guard.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_migrate_legacy.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_miniyaml_equivalence.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_miniyaml_negative.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_prepare_feature.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_prepare_scaffold_sync.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_produces_field.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_redaction_windows_home.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_resolve_base.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_result_block.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_roadmap_add_skill.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_roadmap_archive_skill.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_roadmap_row_parser.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_scaffold_doc_hygiene.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_scaffold_docs.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_scaffold_manifest.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_scaffold_seed_sanity.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_scaffold_wiring.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_skills_vendored_in_sync.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_squash_commit_hook_crash.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_template_closing_shapes.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_terminal_flip_ownership.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_timeout_kill_portable.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_untracked_feature_folder.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_untracked_file_absorption.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_upgrade_integration.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_upgrade_merge_gate.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_validate_event.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_verdict_coupling.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_verify_empty_gate_set.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_version_consistency.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_version_skew.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_windows_gate_exec.py +0 -0
- {specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/tests/test_wu_execution_metadata.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "specfuse-loop"
|
|
7
|
-
version = "0.3.
|
|
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
|
{specfuse_loop-0.3.20 → specfuse_loop-0.3.22}/specfuse/loop/data/rules/verification-discipline.md
RENAMED
|
@@ -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.
|
|
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
|
|