specfuse-loop 0.3.19__tar.gz → 0.3.20__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.19/specfuse_loop.egg-info → specfuse_loop-0.3.20}/PKG-INFO +1 -1
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/pyproject.toml +1 -1
- specfuse_loop-0.3.20/specfuse/loop/data/VERSION +1 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/specfuse/loop/data/docs/methodology.md +7 -1
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/specfuse/loop/loop.py +96 -1
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20/specfuse_loop.egg-info}/PKG-INFO +1 -1
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_gate_eval_intermediate_wiring.py +11 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_gate_eval_terminal_wiring.py +111 -0
- specfuse_loop-0.3.19/specfuse/loop/data/VERSION +0 -1
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/LICENSE +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/NOTICE +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/README.md +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/setup.cfg +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/specfuse/loop/__init__.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/specfuse/loop/_filelock.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/specfuse/loop/_miniyaml.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/specfuse/loop/adopt_feature.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/specfuse/loop/data/LEARNINGS.template.md +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/specfuse/loop/data/docs/concepts/architecture-addendum-gates-and-iterative-planning.md +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/specfuse/loop/data/docs/concepts/ralph-lineage.md +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/specfuse/loop/data/docs/getting-started.md +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/specfuse/loop/data/docs/skills.md +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/specfuse/loop/data/gitignore.snippet +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/specfuse/loop/data/roadmap.template.md +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/specfuse/loop/data/rules/correlation-ids.md +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/specfuse/loop/data/rules/never-touch.md +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/specfuse/loop/data/rules/result-contract.md +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/specfuse/loop/data/rules/security-boundaries.md +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/specfuse/loop/data/rules/verification-discipline.md +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/specfuse/loop/data/schemas/event.schema.json +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/specfuse/loop/data/schemas/events/initiative_created.schema.json +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/specfuse/loop/data/schemas/events/spec_issue_resolved.schema.json +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/specfuse/loop/data/schemas/events/spec_issue_routed.schema.json +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/specfuse/loop/data/schemas/events/spec_validated.schema.json +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/specfuse/loop/data/templates/GATE.template.md +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/specfuse/loop/data/templates/PLAN.template.md +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/specfuse/loop/data/templates/WU.template.md +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/specfuse/loop/data/verification.yml.example +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/specfuse/loop/gate_eval.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/specfuse/loop/gh_backend.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/specfuse/loop/gh_features.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/specfuse/loop/lint_plan.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/specfuse/loop/scaffold.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/specfuse/loop/validate_event.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/specfuse_loop.egg-info/SOURCES.txt +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/specfuse_loop.egg-info/dependency_links.txt +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/specfuse_loop.egg-info/entry_points.txt +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/specfuse_loop.egg-info/requires.txt +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/specfuse_loop.egg-info/top_level.txt +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_adopt_feature.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_arm_gate_edits_uncommitted.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_attempt_outcome_emission.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_autoclose_deferral_visibility.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_autosync.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_autosync_consent.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_autosync_firstrun.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_autosync_plugin.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_backend.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_bash_routing.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_bookkeeping_commit_crash_run.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_bookkeeping_commit_hook_crash.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_bookkeeping_no_verify.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_claude_resolution.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_close_wu_self_reference.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_closing_deliverable_guard.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_console_encoding.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_cost_tracking.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_degraded_oracle.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_deliverable_presence_gate.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_doctor.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_driver_integration.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_driver_lock.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_duration_tracking.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_empty_files_escalation.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_ensure_feature_branch.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_ensure_feature_branch_base.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_events_redaction.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_extra_gates.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_filelock_portable.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_force_full_close.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_gate_eval.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_gate_eval_calibration.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_gh_backend.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_gh_features.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_git_env_isolation.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_hashed_denylist.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_hashed_denylist_ci.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_init_integration.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_interpreter_normalization.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_leak_findings_redaction.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_leak_scan.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_leak_scan_content.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_learnings_query.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_legacy_4wu_terminal_flips.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_lifecycle_integration.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_lint_bare_produces_path.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_lint_close_intermediate.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_lint_close_wu.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_lint_correlation_id.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_lint_correlation_id_close_intermediate.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_lint_oracle_env.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_lint_plan_errors.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_lint_plan_next_draft.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_lint_plan_relative_import.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_lint_produces_driver_helper.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_lint_sections.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_lint_task_graph_yaml_selection.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_lint_verification_reference.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_loop_attempt_evidence.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_loop_auto_archive.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_loop_caveman_preamble.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_loop_close_intermediate.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_loop_defaults_by_type.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_loop_effort.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_loop_failure_note_cap.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_loop_files_changed_guard.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_loop_gate_budget.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_loop_model_alias.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_loop_orchestration.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_loop_post_pass_invariant.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_loop_reset_preserving_events.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_loop_smoke_runner.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_loop_unsandboxed.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_loop_zero_token_guard.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_migrate_legacy.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_miniyaml_equivalence.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_miniyaml_negative.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_planned_cost_lint.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_prepare_feature.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_prepare_scaffold_sync.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_produces_field.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_redaction_windows_home.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_resolve_base.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_result_block.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_roadmap_add_skill.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_roadmap_archive_skill.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_roadmap_row_parser.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_scaffold_data_in_sync.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_scaffold_doc_hygiene.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_scaffold_docs.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_scaffold_init.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_scaffold_manifest.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_scaffold_resources.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_scaffold_seed_sanity.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_scaffold_upgrade.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_scaffold_wiring.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_skills_vendored_in_sync.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_squash_commit_hook_crash.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_template_closing_shapes.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_terminal_flip_ownership.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_terminal_flips.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_timeout_kill_portable.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_untracked_feature_folder.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_untracked_file_absorption.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_upgrade_integration.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_upgrade_merge_gate.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_validate_event.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_verdict_coupling.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_verify_empty_gate_set.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_version_consistency.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_version_skew.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_windows_gate_exec.py +0 -0
- {specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/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.20"
|
|
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.20
|
|
@@ -139,7 +139,13 @@ auto-close only eliminates the reflective overhead, not the forward-design step.
|
|
|
139
139
|
full close ceremony regardless of predicate outcome. Alternatively, set
|
|
140
140
|
`auto_close_disabled: true` in a feature's `PLAN.md` frontmatter to disable
|
|
141
141
|
auto-close permanently for that feature (e.g., for features that are inherently
|
|
142
|
-
exploratory and expect off-plan behavior).
|
|
142
|
+
exploratory and expect off-plan behavior). For a single **substantive close**
|
|
143
|
+
— a `close` (or `close-intermediate`) WU whose acceptance criteria assert on the
|
|
144
|
+
source tree, reconcile prior gates, or observe runtime behaviour rather than
|
|
145
|
+
just writing the retrospective stub — set `auto_close_disabled: true` in **that
|
|
146
|
+
WU's** frontmatter. The predicate keys on the implementation WUs' cost and
|
|
147
|
+
plan-conformance and cannot see what a close verifies, so a substantive close
|
|
148
|
+
must be marked must-run or its verification is silently skipped (#189).
|
|
143
149
|
|
|
144
150
|
**Predicate-version transparency** — every `auto_close_decision` event in
|
|
145
151
|
`events.jsonl` carries a `predicate_version` field (e.g., `predicate_version:
|
|
@@ -63,7 +63,7 @@ SPECFUSE_DIR = Path(".specfuse")
|
|
|
63
63
|
REPO_ROOT = SPECFUSE_DIR.parent
|
|
64
64
|
FEATURES_DIR = SPECFUSE_DIR / "features"
|
|
65
65
|
VERIFICATION_PATH = SPECFUSE_DIR / "verification.yml"
|
|
66
|
-
DRIVER_VERSION = "0.3.
|
|
66
|
+
DRIVER_VERSION = "0.3.20"
|
|
67
67
|
# Oldest scaffold layout this driver can drive. init.sh stamps the scaffold's own
|
|
68
68
|
# version into `.specfuse/VERSION`; check_scaffold_version() fails loud at startup if
|
|
69
69
|
# the consumer's scaffold is older than this, pointing at `specfuse upgrade`. Bump
|
|
@@ -2778,6 +2778,56 @@ def _already_auto_closed(wu_file: Path) -> bool:
|
|
|
2778
2778
|
return auto in (True, "true", "True")
|
|
2779
2779
|
|
|
2780
2780
|
|
|
2781
|
+
def _gate_impl_deliverables_present(
|
|
2782
|
+
feature_dir: Path, gate: "GateNode",
|
|
2783
|
+
) -> tuple[bool, str]:
|
|
2784
|
+
"""Return (True, "") iff every implementation WU in *gate* has its declared
|
|
2785
|
+
``produces:`` deliverables present and non-empty on disk (#190).
|
|
2786
|
+
|
|
2787
|
+
Auto-close keys on WU cost/plan-conformance and trusts each WU's ``done``
|
|
2788
|
+
status; ``assert_declared_deliverables`` — the dispatch-time presence gate —
|
|
2789
|
+
never runs on an auto-closed gate, so a WU that reported ``done`` with its
|
|
2790
|
+
``produces:`` paths absent still counts as a clean gate. Running the same
|
|
2791
|
+
check here stops the predicate auto-closing over a ``done`` that did not
|
|
2792
|
+
deliver. Non-implementation WUs and WUs with no ``produces:`` are skipped
|
|
2793
|
+
(the latter is assert_declared_deliverables's own opt-out). A missing or
|
|
2794
|
+
malformed WU file is left to the predicate / dispatch path that already
|
|
2795
|
+
handles it, not crashed on here.
|
|
2796
|
+
"""
|
|
2797
|
+
for ref in gate.refs:
|
|
2798
|
+
try:
|
|
2799
|
+
wu = load_wu(feature_dir, ref)
|
|
2800
|
+
except (FileNotFoundError, KeyError, _miniyaml.MiniYAMLError):
|
|
2801
|
+
continue
|
|
2802
|
+
if wu.type != "implementation":
|
|
2803
|
+
continue
|
|
2804
|
+
ok, summary = assert_declared_deliverables(wu)
|
|
2805
|
+
if not ok:
|
|
2806
|
+
return False, f"{ref.get('id', wu.wu_id)}: {summary}"
|
|
2807
|
+
return True, ""
|
|
2808
|
+
|
|
2809
|
+
|
|
2810
|
+
def _close_wu_disables_auto_close(close_wu: "WorkUnit | None") -> bool:
|
|
2811
|
+
"""True iff the close WU's frontmatter sets ``auto_close_disabled`` truthy.
|
|
2812
|
+
|
|
2813
|
+
Per-WU counterpart to PLAN.md's feature-level ``auto_close_disabled``
|
|
2814
|
+
(#189). The auto-close predicate keys on the implementation WUs' cost and
|
|
2815
|
+
plan-conformance; it has no signal for what the CLOSE WU itself is
|
|
2816
|
+
contracted to verify. A substantive close — one whose acceptance criteria
|
|
2817
|
+
assert on the source tree, reconcile prior gates, or observe runtime
|
|
2818
|
+
behaviour — must be dispatched and run, not optimized away. The plan author
|
|
2819
|
+
marks such a close with ``auto_close_disabled: true`` in its frontmatter;
|
|
2820
|
+
the predicate then always defers to dispatch for that gate.
|
|
2821
|
+
|
|
2822
|
+
Distinct from the ``auto_close`` key, which is the auto-close OUTPUT marker
|
|
2823
|
+
(set to ``true`` by mark_close_wu_auto_closed after a gate auto-closes).
|
|
2824
|
+
"""
|
|
2825
|
+
if close_wu is None or not close_wu.file.is_file():
|
|
2826
|
+
return False
|
|
2827
|
+
fm, _ = read_frontmatter(close_wu.file)
|
|
2828
|
+
return fm.get("auto_close_disabled") in (True, "true", "True")
|
|
2829
|
+
|
|
2830
|
+
|
|
2781
2831
|
def write_stub_retrospective_terminal(
|
|
2782
2832
|
feature_dir: Path,
|
|
2783
2833
|
gate_number: int,
|
|
@@ -2881,9 +2931,32 @@ def maybe_auto_close_terminal(
|
|
|
2881
2931
|
feature_id=feature_id,
|
|
2882
2932
|
predicate_version="v1",
|
|
2883
2933
|
)
|
|
2934
|
+
if _close_wu_disables_auto_close(close_wu_for_terminal):
|
|
2935
|
+
# Substantive close marked must-run (#189): dispatch it, don't skip.
|
|
2936
|
+
return False, AutoCloseDecision(
|
|
2937
|
+
auto=False,
|
|
2938
|
+
reasons=["auto_close_disabled_per_wu"],
|
|
2939
|
+
metrics={},
|
|
2940
|
+
gate_id=gate.number,
|
|
2941
|
+
feature_id=feature_id,
|
|
2942
|
+
predicate_version="v1",
|
|
2943
|
+
)
|
|
2884
2944
|
decision = evaluate_auto_close(feature_dir, gate.number)
|
|
2885
2945
|
if not decision.auto:
|
|
2886
2946
|
return False, decision
|
|
2947
|
+
present, reason = _gate_impl_deliverables_present(feature_dir, gate)
|
|
2948
|
+
if not present:
|
|
2949
|
+
# A gate WU reported done without its declared deliverable on disk;
|
|
2950
|
+
# do not auto-close over the discrepancy — dispatch the close, which is
|
|
2951
|
+
# where it surfaces (#190).
|
|
2952
|
+
return False, AutoCloseDecision(
|
|
2953
|
+
auto=False,
|
|
2954
|
+
reasons=[f"declared_deliverable_missing: {reason}"],
|
|
2955
|
+
metrics=decision.metrics,
|
|
2956
|
+
gate_id=gate.number,
|
|
2957
|
+
feature_id=feature_id,
|
|
2958
|
+
predicate_version=decision.predicate_version,
|
|
2959
|
+
)
|
|
2887
2960
|
write_stub_retrospective_terminal(feature_dir, gate.number, decision)
|
|
2888
2961
|
mark_close_wu_auto_closed(close_wu_for_terminal, decision)
|
|
2889
2962
|
metrics = decision.metrics
|
|
@@ -2994,9 +3067,31 @@ def maybe_auto_close_intermediate(
|
|
|
2994
3067
|
feature_id=feature_id,
|
|
2995
3068
|
predicate_version="v1",
|
|
2996
3069
|
)
|
|
3070
|
+
if _close_wu_disables_auto_close(close_intermediate_wu):
|
|
3071
|
+
# Substantive close-intermediate marked must-run (#189): dispatch it.
|
|
3072
|
+
return False, AutoCloseDecision(
|
|
3073
|
+
auto=False,
|
|
3074
|
+
reasons=["auto_close_disabled_per_wu"],
|
|
3075
|
+
metrics={},
|
|
3076
|
+
gate_id=gate.number,
|
|
3077
|
+
feature_id=feature_id,
|
|
3078
|
+
predicate_version="v1",
|
|
3079
|
+
)
|
|
2997
3080
|
decision = evaluate_auto_close(feature_dir, gate.number)
|
|
2998
3081
|
if not decision.auto:
|
|
2999
3082
|
return False, decision
|
|
3083
|
+
present, reason = _gate_impl_deliverables_present(feature_dir, gate)
|
|
3084
|
+
if not present:
|
|
3085
|
+
# See maybe_auto_close_terminal: don't auto-close over a done WU whose
|
|
3086
|
+
# declared deliverable is absent — dispatch the close instead (#190).
|
|
3087
|
+
return False, AutoCloseDecision(
|
|
3088
|
+
auto=False,
|
|
3089
|
+
reasons=[f"declared_deliverable_missing: {reason}"],
|
|
3090
|
+
metrics=decision.metrics,
|
|
3091
|
+
gate_id=gate.number,
|
|
3092
|
+
feature_id=feature_id,
|
|
3093
|
+
predicate_version=decision.predicate_version,
|
|
3094
|
+
)
|
|
3000
3095
|
append_stub_retrospective_intermediate(feature_dir, gate.number, decision)
|
|
3001
3096
|
if close_intermediate_wu is not None:
|
|
3002
3097
|
write_frontmatter_field(close_intermediate_wu.file, "status", "done")
|
|
@@ -245,6 +245,17 @@ class TestAutoCloseIntermediateFiresOnCleanGate(unittest.TestCase):
|
|
|
245
245
|
self.events_path, self.root, self.ci_wu, self.plan_next_wu,
|
|
246
246
|
)
|
|
247
247
|
|
|
248
|
+
def test_per_wu_opt_out_refuses(self):
|
|
249
|
+
# #189: close-intermediate marked auto_close_disabled → not auto-closed.
|
|
250
|
+
self.ci_wu.file.write_text(
|
|
251
|
+
f"---\nid: {self.feature_id}/G1-CI\ntype: close-intermediate\n"
|
|
252
|
+
f"model: opus\nstatus: pending\nattempts: 0\n"
|
|
253
|
+
f"auto_close_disabled: true\n---\n\n# CI\n"
|
|
254
|
+
)
|
|
255
|
+
result, decision = self._call()
|
|
256
|
+
self.assertFalse(result)
|
|
257
|
+
self.assertIn("auto_close_disabled_per_wu", decision.reasons)
|
|
258
|
+
|
|
248
259
|
def test_returns_true_and_decision(self):
|
|
249
260
|
result, decision = self._call()
|
|
250
261
|
self.assertTrue(result)
|
|
@@ -256,6 +256,117 @@ class TestMaybeAutoCloseTerminalAutoPath(unittest.TestCase):
|
|
|
256
256
|
# ---------------------------------------------------------------------------
|
|
257
257
|
|
|
258
258
|
|
|
259
|
+
class TestMaybeAutoCloseTerminalPerWuOptOut(unittest.TestCase):
|
|
260
|
+
"""#189: a close WU marked auto_close_disabled must NOT be auto-closed,
|
|
261
|
+
even on an otherwise clean/on-plan gate — it is dispatched instead."""
|
|
262
|
+
|
|
263
|
+
def setUp(self):
|
|
264
|
+
self._tmp = tempfile.TemporaryDirectory(ignore_cleanup_errors=True)
|
|
265
|
+
self.root = Path(self._tmp.name)
|
|
266
|
+
self.feature_id = "FEAT-2026-9981"
|
|
267
|
+
self.gate_num = 1
|
|
268
|
+
fdir = self.root
|
|
269
|
+
self.fdir = fdir
|
|
270
|
+
_write_plan_md(fdir, self.feature_id, self.gate_num)
|
|
271
|
+
_write_wu_impl(fdir, f"{self.feature_id}/T01")
|
|
272
|
+
# Close WU carries the per-WU opt-out.
|
|
273
|
+
(fdir / "WU-close.md").write_text(
|
|
274
|
+
f"---\nid: {self.feature_id}/G{self.gate_num}-CLOSE\ntype: close\n"
|
|
275
|
+
f"model: opus\nstatus: pending\nattempts: 0\n"
|
|
276
|
+
f"auto_close_disabled: true\n---\n\n# Close\n"
|
|
277
|
+
)
|
|
278
|
+
_write_gate_file(fdir, self.gate_num)
|
|
279
|
+
_write_task_completed_event(fdir, f"{self.feature_id}/T01")
|
|
280
|
+
self.gate = _make_gate_node(fdir, self.feature_id, self.gate_num)
|
|
281
|
+
self.close_wu = _make_close_wu(
|
|
282
|
+
fdir, f"{self.feature_id}/G{self.gate_num}-CLOSE")
|
|
283
|
+
self.events_path = fdir / "events.jsonl"
|
|
284
|
+
|
|
285
|
+
def tearDown(self):
|
|
286
|
+
self._tmp.cleanup()
|
|
287
|
+
|
|
288
|
+
def test_refuses_and_names_reason(self):
|
|
289
|
+
result, decision = loop.maybe_auto_close_terminal(
|
|
290
|
+
self.fdir, self.feature_id, self.gate, [self.gate],
|
|
291
|
+
self.events_path, self.close_wu, repo_root=self.root,
|
|
292
|
+
)
|
|
293
|
+
self.assertFalse(result)
|
|
294
|
+
self.assertFalse(decision.auto)
|
|
295
|
+
self.assertIn("auto_close_disabled_per_wu", decision.reasons)
|
|
296
|
+
|
|
297
|
+
def test_close_wu_not_marked_auto_closed(self):
|
|
298
|
+
loop.maybe_auto_close_terminal(
|
|
299
|
+
self.fdir, self.feature_id, self.gate, [self.gate],
|
|
300
|
+
self.events_path, self.close_wu, repo_root=self.root,
|
|
301
|
+
)
|
|
302
|
+
fm, _ = loop.read_frontmatter(self.close_wu.file)
|
|
303
|
+
# Status untouched (still dispatchable); no auto_close output marker.
|
|
304
|
+
self.assertEqual(fm.get("status"), "pending")
|
|
305
|
+
self.assertNotEqual(fm.get("auto_close"), True)
|
|
306
|
+
|
|
307
|
+
def test_helper_reads_the_flag(self):
|
|
308
|
+
self.assertTrue(loop._close_wu_disables_auto_close(self.close_wu))
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
class TestMaybeAutoCloseTerminalDeliverableMissing(unittest.TestCase):
|
|
312
|
+
"""#190: auto-close must refuse when an implementation WU reports done but
|
|
313
|
+
its declared `produces:` deliverable is absent — dispatch the close."""
|
|
314
|
+
|
|
315
|
+
def setUp(self):
|
|
316
|
+
self._tmp = tempfile.TemporaryDirectory(ignore_cleanup_errors=True)
|
|
317
|
+
self.root = Path(self._tmp.name)
|
|
318
|
+
self.feature_id = "FEAT-2026-9982"
|
|
319
|
+
self.gate_num = 1
|
|
320
|
+
fdir = self.root
|
|
321
|
+
self.fdir = fdir
|
|
322
|
+
_write_plan_md(fdir, self.feature_id, self.gate_num)
|
|
323
|
+
# Implementation WU claims done + a produces path that does NOT exist.
|
|
324
|
+
missing = fdir / "src" / "never-created.py"
|
|
325
|
+
(fdir / "WU-01.md").write_text(
|
|
326
|
+
f"---\nid: {self.feature_id}/T01\ntype: implementation\n"
|
|
327
|
+
f"model: sonnet\nstatus: done\nattempts: 1\ncost_usd: 0.5\n"
|
|
328
|
+
f"planned_cost_usd: 0.5\nproduces: [{missing}]\n---\n\n# T01\n"
|
|
329
|
+
)
|
|
330
|
+
_write_wu_close(fdir, f"{self.feature_id}/G{self.gate_num}-CLOSE")
|
|
331
|
+
_write_gate_file(fdir, self.gate_num)
|
|
332
|
+
_write_task_completed_event(fdir, f"{self.feature_id}/T01")
|
|
333
|
+
self.gate = _make_gate_node(fdir, self.feature_id, self.gate_num)
|
|
334
|
+
self.close_wu = _make_close_wu(
|
|
335
|
+
fdir, f"{self.feature_id}/G{self.gate_num}-CLOSE")
|
|
336
|
+
self.events_path = fdir / "events.jsonl"
|
|
337
|
+
|
|
338
|
+
def tearDown(self):
|
|
339
|
+
self._tmp.cleanup()
|
|
340
|
+
|
|
341
|
+
def _call(self):
|
|
342
|
+
return loop.maybe_auto_close_terminal(
|
|
343
|
+
self.fdir, self.feature_id, self.gate, [self.gate],
|
|
344
|
+
self.events_path, self.close_wu, repo_root=self.root,
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
def test_refuses_on_missing_deliverable(self):
|
|
348
|
+
result, decision = self._call()
|
|
349
|
+
self.assertFalse(result)
|
|
350
|
+
self.assertFalse(decision.auto)
|
|
351
|
+
self.assertTrue(any("declared_deliverable_missing" in r
|
|
352
|
+
for r in decision.reasons),
|
|
353
|
+
f"reasons={decision.reasons}")
|
|
354
|
+
|
|
355
|
+
def test_present_deliverable_still_auto_closes(self):
|
|
356
|
+
# Create the declared deliverable → predicate proceeds to auto-close.
|
|
357
|
+
missing = self.fdir / "src" / "never-created.py"
|
|
358
|
+
missing.parent.mkdir(parents=True, exist_ok=True)
|
|
359
|
+
missing.write_text("x = 1\n")
|
|
360
|
+
result, decision = self._call()
|
|
361
|
+
self.assertTrue(result)
|
|
362
|
+
self.assertTrue(decision.auto)
|
|
363
|
+
|
|
364
|
+
def test_helper_reports_the_offender(self):
|
|
365
|
+
ok, reason = loop._gate_impl_deliverables_present(self.fdir, self.gate)
|
|
366
|
+
self.assertFalse(ok)
|
|
367
|
+
self.assertIn("never-created.py", reason)
|
|
368
|
+
|
|
369
|
+
|
|
259
370
|
class TestMaybeAutoCloseTerminalNonAutoPath(unittest.TestCase):
|
|
260
371
|
|
|
261
372
|
def setUp(self):
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.3.19
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/specfuse/loop/data/docs/concepts/ralph-lineage.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/specfuse/loop/data/rules/security-boundaries.md
RENAMED
|
File without changes
|
{specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/specfuse/loop/data/rules/verification-discipline.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{specfuse_loop-0.3.19 → specfuse_loop-0.3.20}/tests/test_lint_correlation_id_close_intermediate.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|