specfuse-loop 0.16.0__tar.gz → 0.18.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {specfuse_loop-0.16.0/specfuse_loop.egg-info → specfuse_loop-0.18.0}/PKG-INFO +1 -1
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/pyproject.toml +14 -1
- specfuse_loop-0.18.0/specfuse/loop/build_provenance.py +332 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/closing_requirements.py +30 -2
- specfuse_loop-0.18.0/specfuse/loop/data/VERSION +1 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/data/docs/methodology.md +30 -6
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/data/rules/close-discipline.md +23 -1
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/data/rules/operator-escalation.md +4 -4
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/data/rules/result-contract.md +16 -5
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/data/rules/verification-discipline.md +6 -3
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/data/schemas/driver-event.schema.json +5 -2
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/data/templates/GATE.template.md +4 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/data/templates/WU.template.md +3 -3
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/data/verification.yml.example +23 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/escalation.py +13 -6
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/lint_closing.py +41 -1
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/lint_plan.py +172 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/loop.py +1658 -130
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0/specfuse_loop.egg-info}/PKG-INFO +1 -1
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse_loop.egg-info/SOURCES.txt +19 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_arm_wiring.py +38 -14
- specfuse_loop-0.18.0/tests/test_attempt_note_untracked_files.py +126 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_baseline_escalation_message.py +6 -1
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_baseline_persistence.py +86 -35
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_baseline_probe.py +6 -66
- specfuse_loop-0.18.0/tests/test_baseline_provenance.py +135 -0
- specfuse_loop-0.18.0/tests/test_baseline_tree_hash_key.py +173 -0
- specfuse_loop-0.18.0/tests/test_changed_file_test_selection.py +207 -0
- specfuse_loop-0.18.0/tests/test_close_records_feature_oracle.py +142 -0
- specfuse_loop-0.18.0/tests/test_criteria_per_gate_lint.py +161 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_driver_restart_halt_wiring.py +22 -0
- specfuse_loop-0.18.0/tests/test_feature_oracle_e2e.py +110 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_followups_artifact.py +160 -8
- specfuse_loop-0.18.0/tests/test_gate_broad_run.py +475 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_human_work_unit.py +4 -1
- specfuse_loop-0.18.0/tests/test_installed_copy_driver_e2e.py +450 -0
- specfuse_loop-0.18.0/tests/test_lazy_baseline_e2e.py +509 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_lint_bare_produces_path.py +3 -1
- specfuse_loop-0.18.0/tests/test_lint_feature_oracle_declared.py +271 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_lint_planning_sections.py +2 -1
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_lint_produces_driver_helper.py +1 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_loop_criteria_skeleton.py +6 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_loop_gate_budget.py +2 -1
- specfuse_loop-0.18.0/tests/test_oracle_unrunnable_attribution.py +116 -0
- specfuse_loop-0.18.0/tests/test_pin_honesty_and_integrity.py +245 -0
- specfuse_loop-0.18.0/tests/test_pin_marker_not_inherited.py +99 -0
- specfuse_loop-0.18.0/tests/test_pin_marker_spawn_sites.py +128 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_produces_field.py +1 -0
- specfuse_loop-0.18.0/tests/test_produces_justification.py +324 -0
- specfuse_loop-0.18.0/tests/test_rearm_clears_stale_criteria_state.py +143 -0
- specfuse_loop-0.18.0/tests/test_red_baseline_never_reused.py +268 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_scaffold_doc_hygiene.py +44 -0
- specfuse_loop-0.18.0/tests/test_tiered_verification_e2e.py +366 -0
- specfuse_loop-0.16.0/specfuse/loop/build_provenance.py +0 -106
- specfuse_loop-0.16.0/specfuse/loop/data/VERSION +0 -1
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/LICENSE +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/NOTICE +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/README.md +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/setup.cfg +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/agent/__init__.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/agent/budget.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/agent/diagnose_invoke.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/agent/drafting_answers.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/agent/drafting_invoke.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/agent/drafting_questions.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/agent/driver_command.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/agent/driver_invoke.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/agent/invoke.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/agent/monitoring_read.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/agent/providers/__init__.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/agent/providers/answers.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/agent/providers/bugs.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/agent/providers/feature.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/agent/providers/findings_autofix.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/agent/providers/findings_diagnose.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/agent/providers/triage.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/agent/queue_read.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/agent/repo_detect.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/agent/run.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/agent/state.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/agent/triage_invoke.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/agent/worktree.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/__init__.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/_filelock.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/_miniyaml.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/_wu_sections.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/adopt_feature.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/agent_policy.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/arm_eval.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/arm_sweep.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/arm_txn.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/bug_lane.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/bug_lane_run.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/bug_lane_state.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/changelog.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/cost.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/criteria_state.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/data/CHANGELOG.seed.md +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/data/LEARNINGS.template.md +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/data/docs/concepts/adopting-auto-mode.md +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/data/docs/concepts/architecture-addendum-gates-and-iterative-planning.md +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/data/docs/concepts/autonomy-stop-classes.md +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/data/docs/concepts/ralph-lineage.md +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/data/docs/getting-started.md +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/data/docs/glossary.md +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/data/docs/skills.md +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/data/gitignore.snippet +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/data/monitoring-secrets-checklist.md +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/data/monitoring.overrides.yml.example +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/data/monitoring.yml.example +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/data/roadmap.template.md +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/data/rules/correlation-ids.md +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/data/rules/design-for-diagnosis.md +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/data/rules/human-output.md +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/data/rules/never-touch.md +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/data/rules/planning-discipline.md +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/data/rules/security-boundaries.md +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/data/rules-local/README.md +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/data/schemas/event.schema.json +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/data/schemas/events/initiative_created.schema.json +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/data/schemas/events/spec_issue_resolved.schema.json +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/data/schemas/events/spec_issue_routed.schema.json +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/data/schemas/events/spec_validated.schema.json +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/data/templates/DECISIONS.template.md +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/data/templates/LEARNINGS-pending.template.md +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/data/templates/PLAN.template.md +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/data/workflows/specfuse-monitor.yml +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/decisions_format.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/driver_edit.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/events_stats.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/gate_commands.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/gate_eval.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/gh_backend.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/gh_features.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/heartbeat.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/judge.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/labels.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/learnings_query.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/lint_monitoring.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/lint_roadmap.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/notify.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/notify_escalation.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/notify_sla.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/plan_baseline.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/policy_proposals.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/policy_review.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/prerun.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/prerun_capture.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/promotion.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/rearm_migration.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/replay_spin.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/roadmap_archive.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/scaffold.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/triage.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/upgrade_merge_gate.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/validate_event.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/monitor/__init__.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/monitor/adapters.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/monitor/artifact.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/monitor/autofix.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/monitor/autofix_invoke.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/monitor/autofix_run.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/monitor/autofix_state.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/monitor/cli.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/monitor/diagnose_cli.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/monitor/diagnosis.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/monitor/fingerprint.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/monitor/issues.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/monitor/providers/__init__.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/monitor/providers/_azure_auth.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/monitor/providers/azure_app_insights.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/monitor/providers/azure_service_bus.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/monitor/redaction.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/monitor/schedule.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse_loop.egg-info/dependency_links.txt +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse_loop.egg-info/entry_points.txt +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse_loop.egg-info/requires.txt +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse_loop.egg-info/top_level.txt +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_abandon_feature_skill.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_abandoned_work_reported.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_abandoned_wu_strands_gate.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_adopt_feature.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_agent_budget.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_agent_bug_selection_dedup.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_agent_default_features_root.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_agent_driver_command.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_agent_driver_invoke.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_agent_driver_restart.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_agent_escalation_on_issue.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_agent_escalation_run_state.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_agent_findings_seam.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_agent_invoke_timeout.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_agent_invoke_usage.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_agent_item_worktree.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_agent_lock.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_agent_policy_key_ownership.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_agent_policy_queue.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_agent_policy_schema.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_agent_policy_triage_dial.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_agent_provider_answers.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_agent_provider_bugs.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_agent_provider_feature.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_agent_provider_findings_autofix.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_agent_provider_findings_diagnose.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_agent_provider_isolation.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_agent_provider_triage.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_agent_queue_read.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_agent_relays_driver_output.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_agent_repo_detect.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_agent_run.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_agent_run_progress.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_agent_seam.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_agent_state.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_answer_escalation_skill.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_app_insights_adapters.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_archive_ref_reconciliation.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_arm_eval.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_arm_eval_lint_class.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_arm_eval_wiring.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_arm_gate_edits_uncommitted.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_arm_sweep.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_arm_sweep_gate.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_arm_txn.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_artifact_redaction.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_attempt_outcome_contract.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_attempt_outcome_emission.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_attention_nonwriting_guard.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_attention_silence_section.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_attention_skill_structure.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_autoclose_deferral_visibility.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_autoclose_gate_note.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_autoclose_stub_states_what_passed.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_autofix_invoke.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_autofix_live.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_autofix_predicate.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_autofix_run.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_autofix_state.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_autosync.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_autosync_consent.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_autosync_firstrun.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_autosync_no_cwd_leak.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_autosync_plugin.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_azure_credential_resolution.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_backend.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_bash_routing.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_bats_suites_gated.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_binary_verdict.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_bookkeeping_commit_crash_run.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_bookkeeping_commit_hook_crash.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_bookkeeping_no_verify.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_bug_lane_ci_pending.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_bug_lane_guardrails.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_bug_lane_label_nonfatal.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_bug_lane_labels_registered.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_bug_lane_policy_contract.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_bug_lane_pr_number_carried.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_bug_lane_preflight_and_dial.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_bug_lane_run.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_bug_lane_state.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_bug_lane_stopped_outcome_pr_number.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_bug_lane_test_paths.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_build_provenance.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_changelog_append_only.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_changelog_collection.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_changelog_release_wiring.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_changelog_schema.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_ci_conclusion_wait.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_ci_runs_every_gate.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_claude_resolution.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_close_b_pending_learnings.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_close_wu_self_reference.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_closing_deliverable_guard.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_closing_guard_contracts.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_closing_guard_prediction.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_closing_learnings_destination.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_closing_requirements.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_coauthor_trailer_allowlist.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_console_encoding.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_convergent_iteration.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_convergent_wu_lint.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_correlation_id_override.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_correlation_id_two_surfaces.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_cost_lifetime.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_cost_tracking.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_criteria_state.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_criteria_worklist.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_decision_citation_lint.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_decision_followups_discharged.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_decision_override_lint.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_decisions_format.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_degraded_oracle.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_deliverable_missing_note.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_deliverable_presence_gate.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_derive_agent_policy_review_mode.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_derive_agent_policy_skill.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_derive_monitoring_discovery.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_derive_monitoring_skill_registration.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_design_for_diagnosis_rule.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_deterministic_refusal_repeat.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_diagnose_headless.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_diagnose_issue_skill.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_diagnosis.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_diagnosis_roundtrip_live.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_dispatch_skeleton.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_doctor.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_done_feature_gates.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_draft_feature_answers_mode.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_drafting_answer_gate.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_drafting_invoke.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_drafting_question_issue.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_drafting_questions.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_drafting_reply_shape.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_driver_edit_detection.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_driver_event_registry_covers_emitters.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_driver_integration.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_driver_lock.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_driver_module_surface.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_driver_restart_halt_surfaced.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_driver_restart_hold.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_driver_staleness_gate_summary.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_driver_staleness_warning.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_dry_run_no_branch_mutation.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_duration_tracking.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_empty_files_escalation.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_ensure_feature_branch.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_ensure_feature_branch_base.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_escalation_assignee_policy.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_escalation_contract.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_escalation_crash_containment.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_escalation_dedup_search.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_escalation_emit.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_event_gate_full_envelope.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_events_redaction.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_events_stats.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_extra_gates.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_failure_artifact_model.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_failure_signature_fail_prefix.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_failure_signature_skips_log_noise.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_feature_branch_auto_sync.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_feature_branch_squash_merge.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_feature_branch_stale_base.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_feature_id_resolution.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_feature_provider_drafting_dispatch.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_feature_review_accumulation.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_filelock_portable.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_find_pr_no_search_index.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_fingerprint.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_fix_bug_diff_self_check.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_fix_bug_headless.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_fix_bug_reads_comments.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_fold_contract_documented.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_force_full_close.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_frontmatter_scalar_quoting.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_gate_budget.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_gate_budget_post_dispatch.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_gate_commands_needs.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_gate_eval.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_gate_eval_calibration.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_gate_eval_intermediate_wiring.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_gate_eval_repo_flag.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_gate_eval_terminal_wiring.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_gate_report_verdict.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_gh_backend.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_gh_features.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_git_env_isolation.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_gitleaks_pinning.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_groom_backlog_skill.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_guard_refusal_failure_class.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_guard_refusal_failure_excerpt.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_hashed_denylist.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_hashed_denylist_ci.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_heartbeat.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_heartbeat_adapter.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_human_output_rule.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_hyphenated_gate_name.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_init_integration.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_interpreter_normalization.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_judge_close_path.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_judge_cost.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_judge_module.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_judge_path_registry.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_label_provisioning_runner_contract.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_label_registry.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_label_registry_covers_consumers.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_leak_findings_redaction.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_leak_scan.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_leak_scan_content.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_learnings_curate_vendored_promote.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_learnings_query.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_learnings_staging.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_learnings_suggest_signature_filter.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_legacy_4wu_terminal_flips.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_lifecycle_integration.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_lifetime_accounting.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_lint_ac_observable.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_lint_boundary_consistency.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_lint_boundary_extraction.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_lint_close_intermediate.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_lint_close_verdict_not_predecided.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_lint_close_wu.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_lint_closing.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_lint_closing_criteria.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_lint_closing_criteria_pristine.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_lint_correlation_id.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_lint_correlation_id_close_intermediate.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_lint_gate_proportionality.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_lint_monitoring.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_lint_oracle_env.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_lint_plan_contract_fields.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_lint_plan_errors.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_lint_plan_next_draft.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_lint_plan_relative_import.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_lint_plan_verdict_exempt.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_lint_produces_satisfiability.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_lint_roadmap.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_lint_sections.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_lint_task_graph_yaml_selection.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_lint_verification_reference.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_live_tests_opt_in.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_load_bearing_close_lint.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_loop_attempt_evidence.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_loop_auto_archive.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_loop_caveman_preamble.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_loop_close_intermediate.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_loop_criteria_survival.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_loop_defaults_by_type.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_loop_effort.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_loop_failure_note_cap.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_loop_files_changed_guard.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_loop_gate_needs.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_loop_model_alias.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_loop_orchestration.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_loop_post_pass_invariant.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_loop_reset_preserving_events.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_loop_smoke_runner.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_loop_unsandboxed.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_loop_worklist_injection.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_loop_zero_token_guard.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_make_repo_gpgsign_isolation.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_max_attempts_configurable.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_migrate_legacy.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_miniyaml_equivalence.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_miniyaml_negative.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_monitor_cli.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_monitor_issue_lifecycle.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_monitor_runner_surfaces.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_monitoring_bootstrap_artifacts.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_monitoring_cron_dialect.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_monitoring_example.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_monitoring_fenced_blocks.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_monitoring_seed.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_no_pytest_subprocess.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_notify.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_notify_escalation.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_notify_sla.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_operator_escalation_rule.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_oracle_set_declared.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_pick_feature_skill.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_plan_baseline.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_planned_cost_lint.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_planning_cost_floor.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_policy_proposals.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_policy_review.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_pr_checks_json_field.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_pre_existing_claims_cite_evidence.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_prep_halt_operator_surface.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_prepare_feature.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_prepare_scaffold_sync.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_prerun_capture.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_prerun_oracles.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_prerun_wiring.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_produces_bracket_literal.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_produces_bracket_wildcard.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_produces_in_diff.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_produces_recursive_glob.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_produces_semantics_unified.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_produces_shape.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_promotion_ledger.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_provider_escalation_traces.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_provision_labels.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_queue_stalled_adapter.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_rearm_fold_marker.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_rearm_migration.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_redaction_windows_home.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_replay_spin.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_resolve_base.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_restart_cap_counts_progress.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_result_block.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_result_block_audience.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_resume_command_build.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_roadmap_add_skill.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_roadmap_archive_cli.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_roadmap_archive_shim.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_roadmap_archive_skill.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_roadmap_archive_skill_delegates.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_roadmap_link_gate.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_roadmap_row_parser.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_ruff_verdict_lines.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_scaffold_data_in_sync.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_scaffold_docs.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_scaffold_init.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_scaffold_label_provisioning.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_scaffold_manifest.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_scaffold_resources.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_scaffold_seed_sanity.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_scaffold_skew_halts.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_scaffold_upgrade.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_scaffold_wiring.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_schedule_dialect.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_seed_documents_prerun_keys.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_service_bus_dlq_adapter.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_session_env_preflight.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_shipped_templates_parse.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_skill_discovery_links.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_skills_vendored_in_sync.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_spinning_rearm_gate.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_squash_commit_hook_crash.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_stop_rationale_captured.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_surefire_run_level_signature.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_surefire_verdict_lines.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_template_closing_shapes.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_terminal_flip_ownership.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_terminal_flips.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_terminal_gate_message.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_timeout_kill_portable.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_triage.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_triage_apply.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_triage_skill_contract.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_triage_skips_agent_escalations.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_tsc_verdict_lines.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_umbrella_version_resolution.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_unterminated_frontmatter.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_untracked_feature_folder.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_untracked_file_absorption.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_upgrade_integration.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_upgrade_merge_gate.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_validate_event.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_validate_event_driver_types.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_verdict_coupling.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_verify_empty_gate_set.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_version_consistency.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_version_skew.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_windows_gate_exec.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_worktree_restore.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_wu_execution_metadata.py +0 -0
- {specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/tests/test_wu_template_renders_lintable.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.
|
|
7
|
+
version = "0.18.0"
|
|
8
8
|
description = "Local-first executor for the Specfuse Plan + Work Unit gate-cycle methodology."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -88,3 +88,16 @@ namespaces = true
|
|
|
88
88
|
# templates, rules, and other seed files out of the wheel without needing the
|
|
89
89
|
# loop source repo on disk.
|
|
90
90
|
"specfuse.loop" = ["data/**/*", "data/*"]
|
|
91
|
+
|
|
92
|
+
# FEAT-2026-0109/T05: per-test dynamic contexts turn the `tests` gate's
|
|
93
|
+
# existing `coverage run --source=specfuse` (`.specfuse/verification.yml`)
|
|
94
|
+
# into a measured file/line -> test map for free — see
|
|
95
|
+
# `GATE-02-REVIEW.md` § "Tests touching changed files" for why this beat an
|
|
96
|
+
# import-graph or path-convention rule. Lives here rather than a new
|
|
97
|
+
# `.coveragerc` because none exists today and the `tests` gate already
|
|
98
|
+
# passes `--source` on the command line; adding a second config surface
|
|
99
|
+
# would split coverage configuration in two. Read by `build_changed_file_test_map`
|
|
100
|
+
# (`specfuse/loop/loop.py`) via the `coverage` package's own config loading —
|
|
101
|
+
# no code here parses this section directly.
|
|
102
|
+
[tool.coverage.run]
|
|
103
|
+
dynamic_context = "test_function"
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
# Copyright 2026 Specfuse contributors
|
|
2
|
+
# Licensed under the Apache License, Version 2.0. See LICENSE.
|
|
3
|
+
"""Which build am I actually running? (#1040)
|
|
4
|
+
|
|
5
|
+
An installed console script (`specfuse-loop`, `specfuse-lint`, …) resolves
|
|
6
|
+
`specfuse.loop` from `site-packages`. In a checkout of the driver's own source
|
|
7
|
+
whose tree has moved ahead of the installed wheel, that means the command runs
|
|
8
|
+
**a different program than the session believes it is running** -- and reports
|
|
9
|
+
confidently.
|
|
10
|
+
|
|
11
|
+
The failure mode is the worst kind: it does not error, it returns a plausible
|
|
12
|
+
number. Three observed occurrences, two at real cost:
|
|
13
|
+
|
|
14
|
+
* A gate-1 arming probe swept `specfuse-lint --closing` to confirm a new
|
|
15
|
+
requirement added no findings. It resolved to the installed 0.7.1 wheel,
|
|
16
|
+
which contained neither the new rules nor the module implementing them --
|
|
17
|
+
"the sweep would have reported identically had T03 shipped nothing."
|
|
18
|
+
* A terminal close produced **14 spurious red results** before the run was
|
|
19
|
+
repeated from the repo root.
|
|
20
|
+
|
|
21
|
+
Every affected surface had been patched by convention -- work-unit bodies
|
|
22
|
+
saying "use the `.specfuse/scripts/` shim, not the installed console script"
|
|
23
|
+
-- which is exactly the shape `a-rule-a-human-must-execute-is-not-a-control`
|
|
24
|
+
says does not hold: a rule a session must remember, a silent wrong answer when
|
|
25
|
+
it forgets, and no signal telling the two apart.
|
|
26
|
+
|
|
27
|
+
This module is that signal. It warns; it does not refuse. #1040 lists refusal
|
|
28
|
+
as a stronger option and recommends warning first, on the grounds that the
|
|
29
|
+
warning's own evidence is what tells you whether refusal is needed.
|
|
30
|
+
|
|
31
|
+
**Silent for downstream projects by construction.** The check fires only when
|
|
32
|
+
the directory being operated on contains a `specfuse/loop/` source tree of its
|
|
33
|
+
own -- i.e. a checkout of the driver. A project that installed Specfuse has no
|
|
34
|
+
such path, so the console script is the correct thing to run there and this
|
|
35
|
+
module never says anything.
|
|
36
|
+
|
|
37
|
+
**A third state (FEAT-2026-0109/T08).** An out-of-tree build used to be
|
|
38
|
+
uniformly "confidently wrong" -- an arbitrary installed wheel, indistinguishable
|
|
39
|
+
from a driver that forgot to rebuild. It no longer is: `main()` now
|
|
40
|
+
materializes its own pin of `specfuse/` outside the working tree, keyed on
|
|
41
|
+
`HEAD^{tree}`, and re-enters execution from it (`loop.py`'s `_reexec_pinned`).
|
|
42
|
+
That pin's provenance is recorded (`_PIN_MARKER_NAME`), so `out_of_tree_warning`
|
|
43
|
+
can tell the two apart: a *recorded* pin reports both tree hashes and says
|
|
44
|
+
which one it executed; an *unidentified* out-of-tree build keeps the alarming
|
|
45
|
+
text verbatim, because it is still exactly as dangerous as the wheel that
|
|
46
|
+
motivated this module.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
from __future__ import annotations
|
|
50
|
+
|
|
51
|
+
import os
|
|
52
|
+
import shutil
|
|
53
|
+
import subprocess
|
|
54
|
+
import sys
|
|
55
|
+
import tempfile
|
|
56
|
+
from pathlib import Path
|
|
57
|
+
from typing import Optional
|
|
58
|
+
|
|
59
|
+
#: The shim that always resolves to the working tree, named in the warning as
|
|
60
|
+
#: the concrete fix. `.specfuse/scripts/` is vendored per project.
|
|
61
|
+
_SHIM_HINT = "python3 -m specfuse.loop.loop (or .specfuse/scripts/<script>.py)"
|
|
62
|
+
|
|
63
|
+
#: Relative path identifying a driver source checkout.
|
|
64
|
+
_SOURCE_MARKER = Path("specfuse") / "loop" / "loop.py"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def running_package_dir() -> Path:
|
|
68
|
+
"""The `specfuse/loop/` directory this process actually imported.
|
|
69
|
+
|
|
70
|
+
Derived from this module's own location rather than by importing
|
|
71
|
+
`loop.py`: the check runs at CLI startup, and importing the driver to ask
|
|
72
|
+
where the driver is would be both slow and circular.
|
|
73
|
+
"""
|
|
74
|
+
return Path(__file__).resolve().parent
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def source_tree_package_dir(start: Optional[Path] = None) -> Optional[Path]:
|
|
78
|
+
"""The `specfuse/loop/` of the source checkout *start* sits in, if any.
|
|
79
|
+
|
|
80
|
+
Walks upward from *start* (default: the current directory) looking for a
|
|
81
|
+
`specfuse/loop/loop.py`. Returns `None` when there is none -- the normal
|
|
82
|
+
case for a project that installed Specfuse, and the reason this check is
|
|
83
|
+
silent there.
|
|
84
|
+
"""
|
|
85
|
+
current = (start if start is not None else Path.cwd()).resolve()
|
|
86
|
+
for candidate in (current, *current.parents):
|
|
87
|
+
if (candidate / _SOURCE_MARKER).is_file():
|
|
88
|
+
return (candidate / "specfuse" / "loop").resolve()
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
#: Repo root, relative to which the pin cache lives -- overridable so tests
|
|
93
|
+
#: (and an operator with no writable platform cache) can redirect it without
|
|
94
|
+
#: env-var-shaped magic sprinkled through the pinning logic itself.
|
|
95
|
+
_PIN_CACHE_ENV_VAR = "SPECFUSE_PIN_CACHE_DIR"
|
|
96
|
+
|
|
97
|
+
#: Marker file written into a materialized pin, naming the tree hash it was
|
|
98
|
+
#: built from. Its presence (and matching content) is what lets
|
|
99
|
+
#: `out_of_tree_warning` tell a recorded pin apart from an arbitrary
|
|
100
|
+
#: out-of-tree build -- anything under the pin cache root WITHOUT this marker
|
|
101
|
+
#: (or whose content disagrees with its own directory name) is treated as
|
|
102
|
+
#: unidentified, never trusted silently.
|
|
103
|
+
_PIN_MARKER_NAME = ".specfuse-pin-tree"
|
|
104
|
+
|
|
105
|
+
#: Manifest of every file `materialize_pin` copied into a pin, written
|
|
106
|
+
#: alongside the marker. The marker alone only proves a directory CLAIMS to be
|
|
107
|
+
#: the pin for a tree hash; an age-based tmp reaper (or anything else) can
|
|
108
|
+
#: delete files from under it without touching the marker, and a copy missing
|
|
109
|
+
#: files must never be reported as the running build (FEAT-2026-0109/T09,
|
|
110
|
+
#: FOLLOW-UPS.md "A materialized pin can lose its files ..."). Reuse checks
|
|
111
|
+
#: every path this manifest lists before trusting an existing pin.
|
|
112
|
+
_PIN_MANIFEST_NAME = ".specfuse-pin-manifest"
|
|
113
|
+
|
|
114
|
+
#: The launcher `main()` re-execs through once a pin is materialized. Its own
|
|
115
|
+
#: directory becomes `sys.path[0]` for a script invocation (probed fact #2 in
|
|
116
|
+
#: FEAT-2026-0109/T08's work-unit body), so it lives beside the pinned
|
|
117
|
+
#: `specfuse/` copy rather than importing it via `-m` or `-c`, both of which
|
|
118
|
+
#: the probe showed the working tree's copy shadows.
|
|
119
|
+
PIN_LAUNCHER_NAME = "_run_pinned.py"
|
|
120
|
+
|
|
121
|
+
_PIN_LAUNCHER_SOURCE = '''\
|
|
122
|
+
"""Generated by specfuse.loop.build_provenance.materialize_pin — do not edit.
|
|
123
|
+
|
|
124
|
+
Re-enters `specfuse.loop.loop:main` from this pin. This file's own directory
|
|
125
|
+
is `sys.path[0]` for a script invocation, ahead of the cwd, so the `specfuse`
|
|
126
|
+
package importable next to it resolves before the working tree's copy does.
|
|
127
|
+
"""
|
|
128
|
+
import sys
|
|
129
|
+
from pathlib import Path
|
|
130
|
+
|
|
131
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
132
|
+
|
|
133
|
+
from specfuse.loop.loop import main # noqa: E402
|
|
134
|
+
|
|
135
|
+
if __name__ == "__main__":
|
|
136
|
+
sys.exit(main())
|
|
137
|
+
'''
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def repo_root_for(start: Optional[Path] = None) -> Optional[Path]:
|
|
141
|
+
"""The repo root of the source checkout *start* sits in, if any."""
|
|
142
|
+
tree = source_tree_package_dir(start)
|
|
143
|
+
return None if tree is None else tree.parent.parent
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def head_tree_hash(repo_root: Path) -> Optional[str]:
|
|
147
|
+
"""`git rev-parse HEAD^{tree}` for *repo_root*, or None if unavailable.
|
|
148
|
+
|
|
149
|
+
None (rather than raising) covers every reason a tree hash might not be
|
|
150
|
+
resolvable -- no git binary, not a git repo, no commits yet -- and each
|
|
151
|
+
one means the same thing to a caller: pinning cannot proceed, run
|
|
152
|
+
unpinned. `never-touch.md`'s "stop at a boundary" posture, not a bug to
|
|
153
|
+
chase down.
|
|
154
|
+
"""
|
|
155
|
+
try:
|
|
156
|
+
result = subprocess.run(
|
|
157
|
+
["git", "-C", str(repo_root), "rev-parse", "HEAD^{tree}"],
|
|
158
|
+
capture_output=True, text=True, check=True,
|
|
159
|
+
)
|
|
160
|
+
except (subprocess.CalledProcessError, OSError):
|
|
161
|
+
return None
|
|
162
|
+
tree_hash = result.stdout.strip()
|
|
163
|
+
return tree_hash or None
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def pin_cache_root() -> Path:
|
|
167
|
+
"""Where materialized pins live -- always outside any working tree.
|
|
168
|
+
|
|
169
|
+
A platform cache directory keyed by tree hash, per the work unit's
|
|
170
|
+
flag-scope table: putting it inside the tree it names would make its own
|
|
171
|
+
key (`HEAD^{tree}`) move every time the pin is written. `$TMPDIR` (or its
|
|
172
|
+
override) rather than a dotfile under the repo for the same reason.
|
|
173
|
+
"""
|
|
174
|
+
override = os.environ.get(_PIN_CACHE_ENV_VAR)
|
|
175
|
+
if override:
|
|
176
|
+
return Path(override)
|
|
177
|
+
return Path(tempfile.gettempdir()) / "specfuse-pins"
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def pin_dir_for(tree_hash: str) -> Path:
|
|
181
|
+
return pin_cache_root() / tree_hash
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _pin_manifest_lines(pin_specfuse_dir: Path) -> list:
|
|
185
|
+
"""Relative paths of every file under *pin_specfuse_dir*, sorted."""
|
|
186
|
+
return sorted(
|
|
187
|
+
str(p.relative_to(pin_specfuse_dir))
|
|
188
|
+
for p in pin_specfuse_dir.rglob("*") if p.is_file()
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _pin_is_complete(pin_dir: Path, tree_hash: str) -> bool:
|
|
193
|
+
"""Whether *pin_dir* is a matching, fully-present pin for *tree_hash*.
|
|
194
|
+
|
|
195
|
+
Checks the marker's content (as before) AND that every file the manifest
|
|
196
|
+
recorded at materialize time is still present -- a manifest recorded once
|
|
197
|
+
and never re-derived from the possibly-reaped directory, so a pin missing
|
|
198
|
+
files fails this check instead of being reported as complete because most
|
|
199
|
+
of it happens to still be there.
|
|
200
|
+
"""
|
|
201
|
+
marker = pin_dir / _PIN_MARKER_NAME
|
|
202
|
+
if not (marker.is_file()
|
|
203
|
+
and marker.read_text(encoding="utf-8").strip() == tree_hash):
|
|
204
|
+
return False
|
|
205
|
+
manifest = pin_dir / _PIN_MANIFEST_NAME
|
|
206
|
+
if not manifest.is_file():
|
|
207
|
+
return False
|
|
208
|
+
try:
|
|
209
|
+
expected = [line for line in
|
|
210
|
+
manifest.read_text(encoding="utf-8").splitlines() if line]
|
|
211
|
+
except OSError:
|
|
212
|
+
return False
|
|
213
|
+
specfuse_dir = pin_dir / "specfuse"
|
|
214
|
+
return bool(expected) and all(
|
|
215
|
+
(specfuse_dir / rel).is_file() for rel in expected)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def materialize_pin(repo_root: Path, tree_hash: str) -> Path:
|
|
219
|
+
"""Copy *repo_root*'s `specfuse/` package to a pin keyed on *tree_hash*.
|
|
220
|
+
|
|
221
|
+
Idempotent: a pin already materialized for this tree hash -- marker
|
|
222
|
+
matches AND every manifested file is still present, `_pin_is_complete`
|
|
223
|
+
-- is reused as-is, so a second process pinning the same commit does not
|
|
224
|
+
re-copy. A pin that is missing files (an age-based tmp reaper, or
|
|
225
|
+
anything else, deleting from under an intact marker) fails that check
|
|
226
|
+
and is rebuilt from scratch here rather than reused half-complete
|
|
227
|
+
(FEAT-2026-0109/T09). Builds into a sibling temp directory first and
|
|
228
|
+
installs it with a single `os.replace` so a reader never observes a
|
|
229
|
+
half-copied pin.
|
|
230
|
+
"""
|
|
231
|
+
pin_dir = pin_dir_for(tree_hash)
|
|
232
|
+
if _pin_is_complete(pin_dir, tree_hash):
|
|
233
|
+
return pin_dir
|
|
234
|
+
|
|
235
|
+
cache_root = pin_cache_root()
|
|
236
|
+
cache_root.mkdir(parents=True, exist_ok=True)
|
|
237
|
+
staging = Path(tempfile.mkdtemp(prefix=f".{tree_hash}-", dir=str(cache_root)))
|
|
238
|
+
try:
|
|
239
|
+
shutil.copytree(
|
|
240
|
+
repo_root / "specfuse", staging / "specfuse",
|
|
241
|
+
ignore=shutil.ignore_patterns("__pycache__", "*.pyc"),
|
|
242
|
+
)
|
|
243
|
+
(staging / PIN_LAUNCHER_NAME).write_text(_PIN_LAUNCHER_SOURCE, encoding="utf-8")
|
|
244
|
+
(staging / _PIN_MARKER_NAME).write_text(tree_hash, encoding="utf-8")
|
|
245
|
+
manifest = _pin_manifest_lines(staging / "specfuse")
|
|
246
|
+
(staging / _PIN_MANIFEST_NAME).write_text(
|
|
247
|
+
"\n".join(manifest) + ("\n" if manifest else ""), encoding="utf-8")
|
|
248
|
+
if pin_dir.exists():
|
|
249
|
+
if _pin_is_complete(pin_dir, tree_hash):
|
|
250
|
+
# Another process won the race and finished first; its copy
|
|
251
|
+
# is equally valid (same tree hash, fully present), so keep
|
|
252
|
+
# it and discard ours.
|
|
253
|
+
shutil.rmtree(staging, ignore_errors=True)
|
|
254
|
+
else:
|
|
255
|
+
# Stale marker, or a prior pin that lost files -- replace it.
|
|
256
|
+
shutil.rmtree(pin_dir, ignore_errors=True)
|
|
257
|
+
os.replace(staging, pin_dir)
|
|
258
|
+
else:
|
|
259
|
+
os.replace(staging, pin_dir)
|
|
260
|
+
finally:
|
|
261
|
+
if staging.exists():
|
|
262
|
+
shutil.rmtree(staging, ignore_errors=True)
|
|
263
|
+
return pin_dir
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def pinned_build_info(running: Path) -> "Optional[tuple[str, Path]]":
|
|
267
|
+
"""If *running* is a recorded pin, return `(tree_hash, pin_root)`; else None.
|
|
268
|
+
|
|
269
|
+
Trusts only a marker directly under a pin whose directory name equals the
|
|
270
|
+
marker's own content -- an out-of-tree build that merely happens to sit
|
|
271
|
+
under the cache root without a matching marker is still unidentified, not
|
|
272
|
+
a free pass.
|
|
273
|
+
"""
|
|
274
|
+
pin_root = running.parent.parent # running == pin_root/specfuse/loop
|
|
275
|
+
marker = pin_root / _PIN_MARKER_NAME
|
|
276
|
+
if not marker.is_file():
|
|
277
|
+
return None
|
|
278
|
+
try:
|
|
279
|
+
recorded = marker.read_text(encoding="utf-8").strip()
|
|
280
|
+
except OSError:
|
|
281
|
+
return None
|
|
282
|
+
if not recorded or pin_root.name != recorded:
|
|
283
|
+
return None
|
|
284
|
+
return recorded, pin_root
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def out_of_tree_warning(start: Optional[Path] = None) -> Optional[str]:
|
|
288
|
+
"""Return the warning text, or `None` when there is nothing to warn about.
|
|
289
|
+
|
|
290
|
+
Separated from the printing so the message can be asserted on directly.
|
|
291
|
+
|
|
292
|
+
Three states (FEAT-2026-0109/T08): in-tree (`None`), a **recorded pin**
|
|
293
|
+
(reports both tree hashes, no alarming language -- this driver
|
|
294
|
+
materialized it, from a named commit, this run), and an **unidentified**
|
|
295
|
+
out-of-tree build (the original, unchanged "confidently wrong" text --
|
|
296
|
+
still exactly as dangerous as the stale wheel that motivated this module).
|
|
297
|
+
"""
|
|
298
|
+
tree = source_tree_package_dir(start)
|
|
299
|
+
if tree is None:
|
|
300
|
+
return None
|
|
301
|
+
running = running_package_dir()
|
|
302
|
+
if running == tree:
|
|
303
|
+
return None
|
|
304
|
+
pin_info = pinned_build_info(running)
|
|
305
|
+
if pin_info is not None:
|
|
306
|
+
pinned_hash, pin_root = pin_info
|
|
307
|
+
working_root = tree.parent.parent
|
|
308
|
+
working_hash = head_tree_hash(working_root)
|
|
309
|
+
return (
|
|
310
|
+
f"running a recorded pin of specfuse from {pin_root} "
|
|
311
|
+
f"(tree {pinned_hash}); the working tree at {working_root} is "
|
|
312
|
+
f"now at tree {working_hash or 'unknown'}."
|
|
313
|
+
)
|
|
314
|
+
return (
|
|
315
|
+
f"warning: running specfuse from {running}, but the working tree at "
|
|
316
|
+
f"{tree.parent.parent} carries its own source.\n"
|
|
317
|
+
f" This command is measuring the INSTALLED build, not your "
|
|
318
|
+
f"checkout — results can be confidently wrong rather than failing.\n"
|
|
319
|
+
f" Run instead: {_SHIM_HINT}"
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def warn_if_out_of_tree(start: Optional[Path] = None, stream=None) -> Optional[str]:
|
|
324
|
+
"""Print the warning to stderr when one applies; return what was printed.
|
|
325
|
+
|
|
326
|
+
stderr, not stdout: several of these commands have parseable stdout that a
|
|
327
|
+
caller consumes, and a diagnostic must not land in it.
|
|
328
|
+
"""
|
|
329
|
+
message = out_of_tree_warning(start)
|
|
330
|
+
if message is not None:
|
|
331
|
+
print(message, file=stream if stream is not None else sys.stderr)
|
|
332
|
+
return message
|
|
@@ -53,6 +53,7 @@ RETROSPECTIVE_FILENAME = "RETROSPECTIVE.md"
|
|
|
53
53
|
#: creates the artifact and the requirement that a `not_met` close carry it.
|
|
54
54
|
FOLLOW_UPS_FILENAME = "FOLLOW-UPS.md"
|
|
55
55
|
FOLLOW_UP_ENTRY_RE = re.compile(r"^### ", re.MULTILINE)
|
|
56
|
+
FOLLOW_UP_ENTRY_END_RE = re.compile(r"^#{1,3}\s", re.MULTILINE)
|
|
56
57
|
|
|
57
58
|
#: `## Post-merge checklist` — the optional PLAN.md section a `met` close
|
|
58
59
|
#: files as one `specfuse:post-merge` issue (FEAT-2026-0085/T03).
|
|
@@ -74,8 +75,12 @@ def parse_followup_entries(text: str) -> list[str]:
|
|
|
74
75
|
"""
|
|
75
76
|
matches = list(FOLLOW_UP_ENTRY_RE.finditer(text))
|
|
76
77
|
entries = []
|
|
77
|
-
for
|
|
78
|
-
|
|
78
|
+
for m in matches:
|
|
79
|
+
# An entry ends at the next heading of any level up to `###`, not
|
|
80
|
+
# only the next `### `: a closing `## Discharged` section used to be
|
|
81
|
+
# filed as the tail of the last entry's issue body (#1729).
|
|
82
|
+
nxt = FOLLOW_UP_ENTRY_END_RE.search(text, m.end())
|
|
83
|
+
end = nxt.start() if nxt else len(text)
|
|
79
84
|
entries.append(text[m.start():end].strip("\n") + "\n")
|
|
80
85
|
return entries
|
|
81
86
|
|
|
@@ -140,6 +145,16 @@ def gate_section_heading_re(gate_n: int) -> re.Pattern:
|
|
|
140
145
|
)
|
|
141
146
|
|
|
142
147
|
|
|
148
|
+
#: The feature-oracle verdict line a close's `## Measurements` section must
|
|
149
|
+
#: carry when the gate declares `feature_oracle` (FEAT-2026-0101/T02). The
|
|
150
|
+
#: gate's `feature_oracle` *is* the feature-level re-run
|
|
151
|
+
#: `close-discipline.md` §1 requires — see
|
|
152
|
+
#: [FEAT-2026-0057/G1-CLOSE/feature-oracle-is-a-different-question].
|
|
153
|
+
FEATURE_ORACLE_KEY = "feature_oracle"
|
|
154
|
+
FEATURE_ORACLE_VERDICT_RE = re.compile(
|
|
155
|
+
rf"^###\s+{re.escape(FEATURE_ORACLE_KEY)}:\s*(PASS|FAIL)\s*$", re.MULTILINE,
|
|
156
|
+
)
|
|
157
|
+
|
|
143
158
|
GATE_REVIEW_FILENAME_TEMPLATE = "GATE-{next_gate:02d}-REVIEW.md"
|
|
144
159
|
|
|
145
160
|
|
|
@@ -339,6 +354,19 @@ CLOSING_REQUIREMENTS: dict[str, list[Requirement]] = {
|
|
|
339
354
|
applies_when="criteria_artifact_present",
|
|
340
355
|
enforced_by="check_criteria_state_well_formed",
|
|
341
356
|
),
|
|
357
|
+
Requirement(
|
|
358
|
+
id="close-n", wu_type="close", phase="pre-squash",
|
|
359
|
+
description=(
|
|
360
|
+
f"When the gate's GATE-NN.md declares {FEATURE_ORACLE_KEY}, "
|
|
361
|
+
"RETROSPECTIVE.md's Measurements section records a "
|
|
362
|
+
"PASS/FAIL verdict line naming it — the gate's "
|
|
363
|
+
f"{FEATURE_ORACLE_KEY} is the feature-level re-run "
|
|
364
|
+
"close-discipline.md §1 requires "
|
|
365
|
+
"[FEAT-2026-0057/G1-CLOSE/feature-oracle-is-a-different-question]"
|
|
366
|
+
),
|
|
367
|
+
file=RETROSPECTIVE_FILENAME,
|
|
368
|
+
enforced_by="check_feature_oracle_verdict_recorded",
|
|
369
|
+
),
|
|
342
370
|
],
|
|
343
371
|
"close-intermediate": [
|
|
344
372
|
Requirement(
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.18.0
|
|
@@ -86,6 +86,16 @@ it mostly does not need. Author-set unless marked driver-owned.
|
|
|
86
86
|
- `generated_surfaces` — OPTIONAL. Generated files this unit's acceptance depends on.
|
|
87
87
|
- `oracle_env` — OPTIONAL. Where the verifying oracle runs: `macos_local`,
|
|
88
88
|
`linux_docker`, `github_actions_ci`, or an operator-named string.
|
|
89
|
+
- `feature_oracle` — GATE-level, not WU-level: lives in `GATE-NN.md`
|
|
90
|
+
frontmatter, one per gate. The executable proof of that gate's definition of
|
|
91
|
+
done, exercising the user-visible outcome end to end. Required to start
|
|
92
|
+
**red** and go **green** at the gate's walking skeleton (its first
|
|
93
|
+
implementation unit, permitted to stub anything off the thinnest end-to-end
|
|
94
|
+
path — `.specfuse/skills/authoring-work-units/SKILL.md` §14). The driver
|
|
95
|
+
runs it as part of every unit's verification in that gate and again at the
|
|
96
|
+
close, which records the verdict. `GATE.template.md` carries the key;
|
|
97
|
+
`/draft-feature` refuses to draft a gate without one
|
|
98
|
+
(`.specfuse/rules/close-discipline.md` §5, FEAT-2026-0101).
|
|
89
99
|
- `produces` — OPTIONAL. Path(s) or glob(s) this unit must yield; the driver's
|
|
90
100
|
presence gate refuses `complete` when one is missing or empty.
|
|
91
101
|
- `produces_driver_helper` — OPTIONAL. Symbol(s) this unit adds to the driver.
|
|
@@ -295,11 +305,16 @@ line.
|
|
|
295
305
|
|
|
296
306
|
**Unfinished work becomes tracked issues, not prose.** After a `not_met`
|
|
297
307
|
close's squash, `file_followup_issues` files one GitHub issue per
|
|
298
|
-
`FOLLOW-UPS.md` entry under `specfuse:follow-up`,
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
308
|
+
`FOLLOW-UPS.md` entry under `specfuse:follow-up`, titled from the entry's
|
|
309
|
+
heading text (`[FEAT-2026-0155 follow-up] <heading>`), carrying the entry body
|
|
310
|
+
verbatim behind a `<!-- specfuse:followup id=… -->` marker, and writing
|
|
311
|
+
`**Tracked as #N.**` back under the heading so the entry is filed once across
|
|
312
|
+
every later attempt — an entry that already carries that line, by the driver's
|
|
313
|
+
hand or a person's, is skipped; on a `met` close it files the optional
|
|
314
|
+
`## Post-merge checklist` section as one `specfuse:post-merge` issue and writes
|
|
315
|
+
the same line under that heading in `PLAN.md`. `gh` absent or failing leaves
|
|
316
|
+
`FOLLOW-UPS.md` itself as the record — the driver never deletes or rewrites an
|
|
317
|
+
entry, it only adds the tracked line — and emits one `followups_recorded` event naming
|
|
303
318
|
the `filed` and `unfiled` counts.
|
|
304
319
|
|
|
305
320
|
**Re-firing the flips out of band** (FEAT-2026-0070) — `fire_terminal_flips`
|
|
@@ -359,7 +374,7 @@ bound to the emitter by `tests/test_attempt_outcome_contract.py`:
|
|
|
359
374
|
| `closing_deliverable_missing` | a closing-WU guard refused (see `close-discipline.md` §4) |
|
|
360
375
|
| `deliverable_missing` | a declared `produces:` path is absent |
|
|
361
376
|
| `no_deliverable_files` | the squash names only the WU file / events |
|
|
362
|
-
| `produces_not_in_diff` | `produces:` path exists but is not in the squash |
|
|
377
|
+
| `produces_not_in_diff` | `produces:` path exists but is not in the squash, and the RESULT's `produces_unchanged:` did not justify it |
|
|
363
378
|
| `squash_commit_failed` | `git commit` for the squash was rejected |
|
|
364
379
|
| `smoke_import_failed` | a declared smoke-import line failed post-squash |
|
|
365
380
|
| `learnings_not_staged` | a closing WU under `autonomy_default: auto` touched `.specfuse/LEARNINGS.md` directly instead of staging to `LEARNINGS-pending.md` (FEAT-2026-0053/T09) |
|
|
@@ -555,6 +570,15 @@ a roadmap-anchor check (with a loud flag if the goal itself seems to be drifting
|
|
|
555
570
|
and open questions — each mapped to the draft WU it affects. The summary is
|
|
556
571
|
advisory and owns no state.
|
|
557
572
|
|
|
573
|
+
**The oracle-progression obligation.** `plan-next` also drafts the next gate's
|
|
574
|
+
`feature_oracle`, and its review summary must state how the drafted oracle
|
|
575
|
+
advances the prior gate's oracle — in prose, as a judgment call, not as a
|
|
576
|
+
lint rule. Nothing can decide mechanically whether one shell command is a
|
|
577
|
+
"stronger" proof than another, so this is the one place an
|
|
578
|
+
unenforceable-but-important question belongs: the summary is already weighted
|
|
579
|
+
toward doubt and already carries the roadmap-anchor check above it
|
|
580
|
+
(FEAT-2026-0101).
|
|
581
|
+
|
|
558
582
|
## 8. LEARNINGS — the cross-feature feedback loop
|
|
559
583
|
|
|
560
584
|
`LEARNINGS.md` is an append-only, cross-feature log of durable, reusable rules
|
|
@@ -24,10 +24,32 @@ WU's frontmatter so the auto-close predicate cannot optimize it away (#189).
|
|
|
24
24
|
The close re-runs every oracle the feature's acceptance criteria name — the
|
|
25
25
|
full test command(s), plus any compile/execution gates — fresh, in this
|
|
26
26
|
session, exit codes read directly. Never inherit a producing WU's
|
|
27
|
-
self-report: `done` is a claim, the re-run is the verification.
|
|
27
|
+
self-report: `done` is a claim, the re-run is the verification. What may be
|
|
28
|
+
inherited is narrower and stated in §5: on a **re-close** of the same gate, a
|
|
29
|
+
`narrow` criterion's green recorded in `GATE-NN-CRITERIA.md` by an earlier
|
|
30
|
+
close attempt is carried forward; every `broad` oracle and the gate's
|
|
31
|
+
`feature_oracle` re-run on every attempt. "Fresh" binds the first close of a
|
|
32
|
+
gate in full, and the broad half of every close after it — never a producing
|
|
33
|
+
unit's own report. When the
|
|
28
34
|
feature's criteria assert on generated artifacts, regenerate into a clean
|
|
29
35
|
output directory before asserting — stale output satisfies any assertion.
|
|
30
36
|
|
|
37
|
+
**The gate's `feature_oracle` is the feature-level re-run, not one more unit
|
|
38
|
+
oracle.** Re-running every producing unit's own oracle is a different, weaker
|
|
39
|
+
question: `[FEAT-2026-0057/G1-CLOSE/feature-oracle-is-a-different-question]`
|
|
40
|
+
records a close where fifteen unit oracles re-ran green and none could
|
|
41
|
+
observe the defect, because none asserted on the path the feature was meant
|
|
42
|
+
to change. A gate's `GATE-NN.md` may declare one `feature_oracle` — the
|
|
43
|
+
composite question the units were each too small to ask. When it does, the
|
|
44
|
+
close re-runs it fresh, same as any other oracle above, and records its
|
|
45
|
+
verdict as a `### feature_oracle: PASS` or `### feature_oracle: FAIL` line in
|
|
46
|
+
`## Measurements`. `specfuse lint --closing` fails a close that omits this
|
|
47
|
+
line when the gate declares an oracle (`close-n`,
|
|
48
|
+
`check_feature_oracle_verdict_recorded` in `closing_requirements.py` /
|
|
49
|
+
`lint_closing.py`); a gate that declares no `feature_oracle` imposes no such
|
|
50
|
+
requirement here — see this rule's "What the driver checks" section and T03
|
|
51
|
+
for whether an absent declaration is itself a finding.
|
|
52
|
+
|
|
31
53
|
> **Provenance.** A WU reported `done` while its source was untouched and its
|
|
32
54
|
> oracle never ran; the driver-side produces-vs-diff guard now refuses that
|
|
33
55
|
> pass (specfuse-loop >= 0.3.21), but only the close's own fresh re-run
|
{specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/data/rules/operator-escalation.md
RENAMED
|
@@ -6,7 +6,7 @@ Licensed under the Apache License, Version 2.0. See LICENSE.
|
|
|
6
6
|
# Rule: operator escalation framing
|
|
7
7
|
|
|
8
8
|
Binding whenever work **stops and needs a human decision** — a `blocked` work
|
|
9
|
-
unit, a gate at `awaiting_review`, a
|
|
9
|
+
unit, a gate at `awaiting_review`, a `not_met` verdict, a scope question, a refusal,
|
|
10
10
|
or any skill that asks before writing.
|
|
11
11
|
|
|
12
12
|
The audience is someone **deciding**, not someone executing. They may not have
|
|
@@ -60,7 +60,7 @@ so in that line rather than dropping it silently.
|
|
|
60
60
|
## The feature briefing — required when the halt is at feature scope
|
|
61
61
|
|
|
62
62
|
The six parts frame a **decision**. When the halt is at feature or gate scope —
|
|
63
|
-
a
|
|
63
|
+
a `not_met` terminal verdict, a feature that stopped partway and needs a human —
|
|
64
64
|
the decision is not the whole question. The operator also needs to know whether
|
|
65
65
|
the work *achieved what it was funded to achieve*, and that is a separate
|
|
66
66
|
question from whether the tasks passed.
|
|
@@ -124,11 +124,11 @@ success is the failure this section prevents.
|
|
|
124
124
|
|
|
125
125
|
- **Options without a recommendation.** Surfacing choices and stopping pushes the
|
|
126
126
|
analysis back onto the person who asked. Always make the call; they can override.
|
|
127
|
-
- **A refusal reported as a malfunction.** A withheld terminal flip on a
|
|
127
|
+
- **A refusal reported as a malfunction.** A withheld terminal flip on a `not_met`
|
|
128
128
|
verdict is the contract working. Reporting it in the same tone as a crash makes
|
|
129
129
|
a correct system look broken and invites someone to "fix" the safety catch.
|
|
130
130
|
- **Writing the human's own justification for them.** Where a field records *why a
|
|
131
|
-
human
|
|
131
|
+
human decided something* — a `type: human` unit's `evidence:` line, `/unblock-wu`'s
|
|
132
132
|
rationale, a contract-change acknowledgment — that text must come from the human.
|
|
133
133
|
An agent that drafts it has removed the signature it was collecting. Ask; do not
|
|
134
134
|
supply.
|
|
@@ -35,14 +35,19 @@ cycle; this file is normative on how the loop surface reports step 4.
|
|
|
35
35
|
the work unit's **Do not touch** section is binding.
|
|
36
36
|
3. **Verify.** Re-read what you produced — the Write/Edit tool reports the
|
|
37
37
|
action it took, not the property you wanted — and run the work unit's own
|
|
38
|
-
verification commands, in declared order, with full output.
|
|
38
|
+
verification commands, in declared order, with full output. On the loop
|
|
39
|
+
that set is the **per-attempt (narrow) tier**: the unit type's gates minus
|
|
40
|
+
any declaring `tier: broad`, with `tests` through its `narrow_command` over
|
|
41
|
+
the unit's `produces:` test modules. The full suite, coverage and every
|
|
42
|
+
`tier: broad` gate are the driver's, once per gate — running them in-session
|
|
43
|
+
buys nothing the driver does not re-run. "I assume the
|
|
39
44
|
tests still pass" is not a verification. A behavioural claim needs a run, not
|
|
40
45
|
a reading of the source; a rule-or-severity claim needs a **negative
|
|
41
46
|
observation**, the rule seen rejecting a purpose-built bad input.
|
|
42
47
|
4. **Report.** Report only what verification confirmed.
|
|
43
48
|
|
|
44
49
|
A failing check leaves you in one of three situations: correctable locally (fix
|
|
45
|
-
the cause, re-run the **
|
|
50
|
+
the cause, re-run the **whole narrow tier** from the top), spinning (three fresh
|
|
46
51
|
attempts is the driver's budget — emit `status: blocked` with the evidence
|
|
47
52
|
rather than guessing), or fundamentally blocked (a spec ambiguity, generated
|
|
48
53
|
code that must change, a missing dependency — emit `status: blocked` naming the
|
|
@@ -61,6 +66,9 @@ acceptance_criteria:
|
|
|
61
66
|
met: true | false
|
|
62
67
|
evidence: <how you know — a test name, a behavior, a line reference>
|
|
63
68
|
blocked_reason: <present only when status is blocked>
|
|
69
|
+
produces_unchanged: # optional — closing obligation 1 below
|
|
70
|
+
- path: <a produces: entry, verbatim>
|
|
71
|
+
justification: <the command you ran and its output showing the deliverable already holds>
|
|
64
72
|
```
|
|
65
73
|
````
|
|
66
74
|
|
|
@@ -96,9 +104,12 @@ blocked_reason: <present only when status is blocked>
|
|
|
96
104
|
|
|
97
105
|
1. **Diff against `produces:` first.** Every path in the WU's `produces:` list
|
|
98
106
|
must show a working-tree change, or the RESULT must justify each unchanged
|
|
99
|
-
path
|
|
100
|
-
|
|
101
|
-
|
|
107
|
+
path under `produces_unchanged:` — the entry spelled as `produces:` spells
|
|
108
|
+
it, plus the command and output showing the deliverable already holds. The
|
|
109
|
+
driver reads that list: a justified entry passes and is recorded on the
|
|
110
|
+
attempt as `produces_justified`; an unjustified one, or a blank
|
|
111
|
+
justification, is refused (#198, #3268, outcome `produces_not_in_diff`).
|
|
112
|
+
Silence on an unchanged deliverable is not a valid close.
|
|
102
113
|
2. **A plan-level contradiction is `blocked`, not `complete`.** If the plan
|
|
103
114
|
cannot be delivered as written, put the finding in `blocked_reason`; never
|
|
104
115
|
write it into a gate document and close `complete`.
|
{specfuse_loop-0.16.0 → specfuse_loop-0.18.0}/specfuse/loop/data/rules/verification-discipline.md
RENAMED
|
@@ -38,7 +38,10 @@ Two generic meanings, both apply unless the unit narrows the scope:
|
|
|
38
38
|
- **Run the declared verification commands.** The unit names the checks that
|
|
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
|
-
The commands are the verification.
|
|
41
|
+
The commands are the verification. Where a surface tiers its checks — the
|
|
42
|
+
loop's per-attempt narrow tier against the driver's once-per-gate broad run —
|
|
43
|
+
the declared set is the tier the surface assigns to the session; running the
|
|
44
|
+
broader tier in-session proves nothing the surface does not re-run itself.
|
|
42
45
|
- **Observe behavior, not source.** A claim that code does X is verified by making
|
|
43
46
|
X happen and reading the output — never by reading the source that looks like it
|
|
44
47
|
does X. A rule read as `ERROR` in source may be gated off and never fire; only a
|
|
@@ -50,8 +53,8 @@ Two generic meanings, both apply unless the unit narrows the scope:
|
|
|
50
53
|
decided by the checks passing, not by the agent's belief. If a check fails you are
|
|
51
54
|
in one of three situations:
|
|
52
55
|
|
|
53
|
-
1. **Correctable locally.** Fix the cause, re-run the **
|
|
54
|
-
top, then continue. A re-run that fixes the original failure but introduces a
|
|
56
|
+
1. **Correctable locally.** Fix the cause, re-run the **whole declared** check set
|
|
57
|
+
from the top, then continue. A re-run that fixes the original failure but introduces a
|
|
55
58
|
new one is still a failed cycle.
|
|
56
59
|
2. **Spinning threshold reached** (typically three fresh attempts on one unit).
|
|
57
60
|
Signal blocked with precise evidence rather than spending the cycle guessing.
|