specfuse-loop 0.23.3__tar.gz → 0.25.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.23.3/specfuse_loop.egg-info → specfuse_loop-0.25.0}/PKG-INFO +1 -1
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/pyproject.toml +1 -1
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/agent/providers/triage.py +41 -6
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/agent/run.py +72 -1
- specfuse_loop-0.25.0/specfuse/loop/data/VERSION +1 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/data/docs/methodology.md +40 -2
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/data/verification.yml.example +13 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/lint_plan.py +1 -1
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/loop.py +159 -8
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/triage.py +23 -3
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0/specfuse_loop.egg-info}/PKG-INFO +1 -1
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse_loop.egg-info/SOURCES.txt +7 -0
- specfuse_loop-0.25.0/tests/test_blind_run_is_not_drained.py +105 -0
- specfuse_loop-0.25.0/tests/test_closing_sessions_on_sonnet.py +28 -0
- specfuse_loop-0.25.0/tests/test_dispatch_preamble_rules_pointer.py +32 -0
- specfuse_loop-0.25.0/tests/test_dispatch_skills_index.py +105 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_followups_artifact.py +26 -2
- specfuse_loop-0.25.0/tests/test_judged_event_points_at_findings.py +137 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_lint_gate_proportionality.py +19 -4
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_loop_auto_archive.py +29 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_loop_close_intermediate.py +2 -2
- specfuse_loop-0.25.0/tests/test_spec_work_unit_rules.py +173 -0
- specfuse_loop-0.25.0/tests/test_triage_projects_severity_label.py +190 -0
- specfuse_loop-0.23.3/specfuse/loop/data/VERSION +0 -1
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/LICENSE +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/NOTICE +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/README.md +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/setup.cfg +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/agent/__init__.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/agent/budget.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/agent/diagnose_invoke.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/agent/drafting_answers.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/agent/drafting_invoke.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/agent/drafting_questions.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/agent/driver_command.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/agent/driver_invoke.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/agent/invoke.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/agent/monitoring_read.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/agent/providers/__init__.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/agent/providers/answers.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/agent/providers/bugs.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/agent/providers/feature.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/agent/providers/findings_autofix.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/agent/providers/findings_diagnose.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/agent/queue_read.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/agent/repo_detect.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/agent/severity_backfill.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/agent/state.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/agent/triage_invoke.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/agent/worktree.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/__init__.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/_filelock.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/_miniyaml.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/_wu_sections.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/adopt_feature.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/agent_policy.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/arm_eval.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/arm_sweep.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/arm_txn.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/bug_lane.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/bug_lane_run.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/bug_lane_state.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/build_provenance.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/caller_check.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/changelog.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/closing_requirements.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/cost.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/criteria_state.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/data/CHANGELOG.seed.md +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/data/LEARNINGS.template.md +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/data/docs/concepts/adopting-auto-mode.md +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/data/docs/concepts/architecture-addendum-gates-and-iterative-planning.md +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/data/docs/concepts/autonomy-stop-classes.md +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/data/docs/concepts/ralph-lineage.md +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/data/docs/getting-started.md +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/data/docs/glossary.md +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/data/docs/skills.md +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/data/gitignore.snippet +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/data/monitoring-secrets-checklist.md +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/data/monitoring.overrides.yml.example +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/data/monitoring.yml.example +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/data/roadmap.template.md +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/data/rules/close-discipline.md +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/data/rules/correlation-ids.md +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/data/rules/design-for-diagnosis.md +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/data/rules/human-output.md +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/data/rules/never-touch.md +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/data/rules/operator-escalation.md +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/data/rules/planning-discipline.md +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/data/rules/result-contract.md +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/data/rules/security-boundaries.md +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/data/rules/verification-discipline.md +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/data/rules-local/README.md +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/data/schemas/driver-event.schema.json +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/data/schemas/event.schema.json +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/data/schemas/events/initiative_created.schema.json +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/data/schemas/events/spec_issue_resolved.schema.json +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/data/schemas/events/spec_issue_routed.schema.json +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/data/schemas/events/spec_validated.schema.json +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/data/templates/DECISIONS.template.md +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/data/templates/GATE.template.md +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/data/templates/LEARNINGS-pending.template.md +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/data/templates/PLAN.template.md +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/data/templates/WU.template.md +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/data/workflows/specfuse-monitor.yml +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/decisions_format.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/driver_edit.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/escalation.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/events_stats.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/gate_commands.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/gate_eval.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/gh_backend.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/gh_features.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/heartbeat.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/judge.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/labels.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/learnings_distil.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/learnings_query.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/lint_closing.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/lint_monitoring.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/lint_roadmap.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/notify.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/notify_escalation.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/notify_sla.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/plan_baseline.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/policy_proposals.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/policy_review.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/prerun.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/prerun_capture.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/promotion.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/rearm_migration.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/red_on_base.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/replay_spin.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/roadmap_archive.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/scaffold.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/upgrade_merge_gate.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/loop/validate_event.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/monitor/__init__.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/monitor/adapters.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/monitor/artifact.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/monitor/autofix.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/monitor/autofix_invoke.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/monitor/autofix_run.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/monitor/autofix_state.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/monitor/cli.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/monitor/diagnose_cli.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/monitor/diagnosis.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/monitor/fingerprint.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/monitor/issues.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/monitor/providers/__init__.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/monitor/providers/_azure_auth.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/monitor/providers/azure_app_insights.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/monitor/providers/azure_service_bus.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/monitor/redaction.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse/monitor/schedule.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse_loop.egg-info/dependency_links.txt +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse_loop.egg-info/entry_points.txt +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse_loop.egg-info/requires.txt +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/specfuse_loop.egg-info/top_level.txt +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_abandon_feature_skill.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_abandoned_work_reported.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_abandoned_wu_strands_gate.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_adopt_feature.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_agent_budget.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_agent_bug_selection_dedup.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_agent_default_features_root.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_agent_driver_command.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_agent_driver_invoke.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_agent_driver_restart.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_agent_escalation_on_issue.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_agent_escalation_run_state.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_agent_findings_seam.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_agent_invoke_timeout.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_agent_invoke_usage.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_agent_item_worktree.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_agent_lock.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_agent_policy_key_ownership.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_agent_policy_queue.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_agent_policy_schema.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_agent_policy_triage_dial.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_agent_provider_answers.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_agent_provider_bugs.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_agent_provider_feature.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_agent_provider_findings_autofix.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_agent_provider_findings_diagnose.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_agent_provider_isolation.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_agent_provider_triage.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_agent_queue_read.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_agent_relays_driver_output.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_agent_repo_detect.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_agent_run.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_agent_run_progress.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_agent_seam.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_agent_snapshot_refresh.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_agent_state.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_answer_escalation_skill.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_app_insights_adapters.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_archive_ref_reconciliation.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_arm_eval.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_arm_eval_lint_class.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_arm_eval_wiring.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_arm_gate_edits_uncommitted.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_arm_sweep.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_arm_sweep_gate.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_arm_txn.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_arm_wiring.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_artifact_redaction.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_attempt_note_untracked_files.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_attempt_outcome_contract.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_attempt_outcome_emission.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_attention_nonwriting_guard.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_attention_silence_section.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_attention_skill_structure.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_attribution_halt_claims.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_autoclose_deferral_visibility.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_autoclose_gate_note.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_autoclose_stub_states_what_passed.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_autofix_invoke.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_autofix_live.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_autofix_predicate.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_autofix_run.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_autofix_state.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_autosync.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_autosync_consent.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_autosync_firstrun.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_autosync_no_cwd_leak.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_autosync_plugin.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_azure_credential_resolution.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_backend.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_baseline_escalation_message.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_baseline_persistence.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_baseline_probe.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_baseline_provenance.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_baseline_tree_hash_key.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_bash_routing.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_bats_suites_gated.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_binary_verdict.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_binding_block_advisory.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_binding_block_allocation.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_binding_block_budget.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_bookkeeping_commit_crash_run.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_bookkeeping_commit_hook_crash.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_bookkeeping_no_verify.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_brief_covers_every_unit_escalation.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_bug_lane_ci_pending.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_bug_lane_guardrails.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_bug_lane_label_nonfatal.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_bug_lane_labels_registered.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_bug_lane_min_severity.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_bug_lane_policy_contract.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_bug_lane_pr_number_carried.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_bug_lane_preflight_and_dial.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_bug_lane_run.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_bug_lane_state.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_bug_lane_stopped_outcome_pr_number.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_bug_lane_test_paths.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_build_provenance.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_caller_check_ratchet.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_changed_file_test_selection.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_changelog_append_only.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_changelog_collection.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_changelog_release_wiring.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_changelog_schema.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_ci_conclusion_wait.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_ci_runs_every_gate.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_claude_resolution.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_close_b_pending_learnings.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_close_records_feature_oracle.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_close_wu_self_reference.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_closing_deliverable_guard.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_closing_guard_contracts.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_closing_guard_prediction.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_closing_learnings_destination.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_closing_requirements.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_coauthor_trailer_allowlist.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_conditional_reflection.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_console_encoding.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_convergent_iteration.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_convergent_wu_lint.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_correlation_id_override.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_correlation_id_two_surfaces.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_cost_lifetime.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_cost_tracking.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_criteria_per_gate_lint.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_criteria_state.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_criteria_worklist.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_decision_citation_lint.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_decision_followups_discharged.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_decision_override_lint.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_decisions_format.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_default_severity_aliases.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_degraded_oracle.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_deliverable_missing_note.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_deliverable_presence_gate.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_derive_agent_policy_review_mode.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_derive_agent_policy_skill.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_derive_monitoring_discovery.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_derive_monitoring_skill_registration.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_design_for_diagnosis_rule.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_deterministic_refusal_repeat.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_diagnose_headless.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_diagnose_issue_skill.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_diagnosis.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_diagnosis_roundtrip_live.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_dispatch_model_from_fix_scope.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_dispatch_skeleton.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_distilled_accept_step.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_doctor.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_doctor_command_preflight.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_done_feature_gates.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_draft_feature_answers_mode.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_drafting_answer_gate.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_drafting_invoke.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_drafting_question_issue.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_drafting_questions.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_drafting_reply_shape.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_driver_edit_detection.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_driver_event_registry_covers_emitters.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_driver_integration.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_driver_lock.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_driver_module_surface.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_driver_restart_halt_surfaced.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_driver_restart_halt_wiring.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_driver_restart_hold.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_driver_staleness_gate_summary.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_driver_staleness_warning.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_dry_run_no_branch_mutation.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_duration_tracking.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_empty_files_escalation.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_ensure_feature_branch.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_ensure_feature_branch_base.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_escalation_assignee_policy.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_escalation_contract.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_escalation_crash_containment.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_escalation_dedup_search.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_escalation_emit.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_event_gate_full_envelope.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_events_redaction.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_events_stats.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_extra_gates.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_failure_artifact_model.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_failure_signature_fail_prefix.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_failure_signature_skips_log_noise.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_feature_branch_auto_sync.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_feature_branch_squash_merge.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_feature_branch_stale_base.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_feature_id_resolution.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_feature_oracle_e2e.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_feature_provider_drafting_dispatch.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_feature_review_accumulation.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_filelock_portable.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_files_changed_auto_repair.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_find_pr_no_search_index.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_fingerprint.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_fix_bug_diff_self_check.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_fix_bug_headless.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_fix_bug_reads_comments.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_fold_contract_documented.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_force_full_close.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_frontmatter_scalar_quoting.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_gate_broad_run.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_gate_budget.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_gate_budget_post_dispatch.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_gate_commands_needs.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_gate_eval.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_gate_eval_calibration.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_gate_eval_intermediate_wiring.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_gate_eval_repo_flag.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_gate_eval_terminal_wiring.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_gate_report_verdict.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_gh_backend.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_gh_features.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_git_env_isolation.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_gitleaks_pinning.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_groom_backlog_skill.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_guard_refusal_failure_class.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_guard_refusal_failure_excerpt.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_guard_repair_e2e.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_guard_repair_prompt.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_hashed_denylist.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_hashed_denylist_ci.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_heartbeat.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_heartbeat_adapter.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_human_output_rule.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_human_work_unit.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_hygiene_boundary_guidance.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_hyphenated_gate_name.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_ignored_cleanup_before_probe.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_init_integration.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_installed_copy_driver_e2e.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_interpreter_normalization.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_judge_close_path.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_judge_cost.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_judge_cost_fold_committed.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_judge_module.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_judge_path_registry.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_judge_terminal_flip_state.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_label_description_length.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_label_provisioning_runner_contract.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_label_registry.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_label_registry_covers_consumers.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_language_aware_narrow_selection.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_lazy_baseline_e2e.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_leak_findings_redaction.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_leak_scan.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_leak_scan_content.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_learnings_curate_vendored_promote.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_learnings_distil_cli.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_learnings_query.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_learnings_staging.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_learnings_suggest_signature_filter.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_learnings_weights.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_legacy_4wu_terminal_flips.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_lifecycle_integration.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_lifetime_accounting.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_lint_ac_observable.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_lint_bare_produces_path.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_lint_boundary_consistency.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_lint_boundary_extraction.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_lint_close_intermediate.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_lint_close_verdict_not_predecided.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_lint_close_wu.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_lint_closing.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_lint_closing_criteria.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_lint_closing_criteria_pristine.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_lint_correlation_id.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_lint_correlation_id_close_intermediate.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_lint_covers_load_wu_invariants.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_lint_feature_oracle_declared.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_lint_monitoring.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_lint_oracle_env.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_lint_plan_contract_fields.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_lint_plan_errors.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_lint_plan_next_draft.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_lint_plan_relative_import.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_lint_plan_verdict_exempt.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_lint_planning_sections.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_lint_produces_driver_helper.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_lint_produces_satisfiability.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_lint_roadmap.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_lint_sections.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_lint_task_graph_yaml_selection.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_lint_verification_reference.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_live_tests_opt_in.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_load_bearing_close_lint.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_loop_attempt_evidence.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_loop_caveman_preamble.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_loop_criteria_skeleton.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_loop_criteria_survival.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_loop_defaults_by_type.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_loop_effort.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_loop_failure_note_cap.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_loop_files_changed_guard.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_loop_gate_budget.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_loop_gate_needs.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_loop_model_alias.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_loop_orchestration.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_loop_post_pass_invariant.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_loop_reset_preserving_events.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_loop_smoke_runner.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_loop_unsandboxed.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_loop_worklist_injection.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_loop_zero_token_guard.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_make_repo_gpgsign_isolation.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_marker_dual_shape.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_maven_failure_excerpt.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_max_attempts_configurable.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_max_open_prs.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_migrate_legacy.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_miniyaml_equivalence.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_miniyaml_negative.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_monitor_cli.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_monitor_issue_lifecycle.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_monitor_runner_surfaces.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_monitoring_bootstrap_artifacts.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_monitoring_cron_dialect.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_monitoring_example.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_monitoring_fenced_blocks.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_monitoring_seed.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_narrow_fallback_warning.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_needs_decision_outcome.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_no_pytest_subprocess.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_notify.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_notify_escalation.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_notify_sla.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_operator_escalation_rule.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_oracle_set_declared.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_oracle_unrunnable_attribution.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_per_run_item_cap_disclosure.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_pick_feature_skill.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_pin_honesty_and_integrity.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_pin_marker_not_inherited.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_pin_marker_spawn_sites.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_plan_baseline.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_planned_cost_lint.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_planning_cost_floor.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_plugin_prose_cross_refs.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_plugin_qualified_dispatch.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_policy_budgets_applied.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_policy_proposals.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_policy_review.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_pr_checks_json_field.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_pr_reference_in_title.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_pre_existing_claims_cite_evidence.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_prep_halt_operator_surface.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_prepare_feature.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_prepare_scaffold_sync.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_prerun_capture.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_prerun_oracles.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_prerun_wiring.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_produced_document_verdict_guard.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_produces_bracket_literal.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_produces_bracket_wildcard.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_produces_field.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_produces_in_diff.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_produces_justification.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_produces_recursive_glob.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_produces_semantics_unified.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_produces_shape.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_progress_forward_note.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_progress_lines_end_to_end.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_promotion_ledger.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_provider_escalation_traces.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_provision_labels.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_queue_stalled_adapter.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_rearm_clears_stale_criteria_state.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_rearm_fold_marker.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_rearm_migration.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_red_baseline_never_reused.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_red_on_base_guardrail.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_red_on_base_probe.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_redaction_windows_home.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_reflection_required_signals.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_replan_end_to_end.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_replan_event_emission.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_replan_note_collision.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_replan_trigger.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_replan_turn_contract.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_replay_spin.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_required_checks.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_resolve_base.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_restart_cap_counts_progress.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_result_block.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_result_block_audience.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_resume_command_build.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_retain_on_guard_refusal_docs.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_roadmap_add_skill.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_roadmap_archive_cli.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_roadmap_archive_shim.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_roadmap_archive_skill.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_roadmap_archive_skill_delegates.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_roadmap_link_gate.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_roadmap_row_parser.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_ruff_verdict_lines.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_rule_import_convergence.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_run_level_failure_breaker.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_scaffold_data_in_sync.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_scaffold_doc_hygiene.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_scaffold_docs.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_scaffold_init.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_scaffold_label_provisioning.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_scaffold_manifest.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_scaffold_resources.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_scaffold_seed_sanity.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_scaffold_skew_halts.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_scaffold_upgrade.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_scaffold_wiring.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_schedule_dialect.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_seed_documents_prerun_keys.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_service_bus_dlq_adapter.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_session_env_preflight.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_severity_aliases.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_severity_backfill_apply.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_severity_backfill_end_to_end.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_severity_backfill_limit.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_severity_backfill_marker.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_severity_backfill_run.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_severity_label_projection.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_severity_reconciliation.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_severity_rubric.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_shipped_templates_parse.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_skill_discovery_links.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_skills_vendored_in_sync.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_spinning_rearm_gate.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_spinout_brief_end_to_end.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_spinout_brief_replan_option.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_squash_commit_hook_crash.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_stop_rationale_captured.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_stranded_halt_cause.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_surefire_run_level_signature.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_surefire_verdict_lines.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_template_closing_shapes.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_terminal_flip_ownership.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_terminal_flips.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_terminal_gate_message.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_tiered_verification_e2e.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_timeout_kill_portable.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_triage.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_triage_apply.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_triage_severity_classify.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_triage_severity_end_to_end.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_triage_severity_write.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_triage_skill_contract.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_triage_skips_agent_escalations.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_tsc_verdict_lines.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_umbrella_version_resolution.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_unterminated_frontmatter.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_untracked_feature_folder.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_untracked_file_absorption.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_upgrade_integration.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_upgrade_merge_gate.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_validate_event.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_validate_event_driver_types.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_verdict_coupling.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_verify_empty_gate_set.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_version_consistency.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_version_skew.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_windows_gate_exec.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_worktree_restore.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.0}/tests/test_wu_execution_metadata.py +0 -0
- {specfuse_loop-0.23.3 → specfuse_loop-0.25.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.25.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"
|
|
@@ -40,7 +40,11 @@ from specfuse.agent.invoke import run_claude, usage_spend
|
|
|
40
40
|
from specfuse.agent.state import AgentSnapshot
|
|
41
41
|
from specfuse.agent.triage_invoke import build_invocation, classify_result, classify_severity
|
|
42
42
|
from specfuse.loop.escalation import CATEGORY_LABELS
|
|
43
|
-
from specfuse.loop.labels import
|
|
43
|
+
from specfuse.loop.labels import (
|
|
44
|
+
list_existing_labels,
|
|
45
|
+
read_severity_rubric,
|
|
46
|
+
severity_label_projection,
|
|
47
|
+
)
|
|
44
48
|
from specfuse.loop.triage import CATEGORIES, apply_triage, list_untriaged
|
|
45
49
|
|
|
46
50
|
_ITEM_ID_PREFIX = "triage-"
|
|
@@ -188,6 +192,7 @@ class TriageProvider:
|
|
|
188
192
|
self._rows: dict = {}
|
|
189
193
|
self._auto = False
|
|
190
194
|
self._rubric: Any = _RUBRIC_UNSET
|
|
195
|
+
self._projection: Any = _RUBRIC_UNSET
|
|
191
196
|
|
|
192
197
|
def _rubric_for_run(self) -> dict:
|
|
193
198
|
"""The repository's severity rubric, read at most once per run
|
|
@@ -195,14 +200,40 @@ class TriageProvider:
|
|
|
195
200
|
`execute()` call after an `advertise()`, so a run that classifies
|
|
196
201
|
nothing issues no `gh label list` at all."""
|
|
197
202
|
if self._rubric is _RUBRIC_UNSET:
|
|
198
|
-
self.
|
|
199
|
-
self._working_dir, runner=self._runner, repo=self._repo
|
|
200
|
-
)
|
|
203
|
+
self._read_labels_once()
|
|
201
204
|
return self._rubric
|
|
202
205
|
|
|
206
|
+
def _projection_for_run(self) -> dict:
|
|
207
|
+
"""`{canonical value: the label this repository spells it with}` (#3386).
|
|
208
|
+
|
|
209
|
+
Triage wrote canonical labels a repository may never have defined; the
|
|
210
|
+
write failed, and because the bug lane reads severity from the label
|
|
211
|
+
rather than the marker, triage removed from the lane the issues it had
|
|
212
|
+
just classified for it. Derived from the same listing as the rubric —
|
|
213
|
+
one `gh label list` per run, which is the invariant that accessor
|
|
214
|
+
already carried."""
|
|
215
|
+
if self._projection is _RUBRIC_UNSET:
|
|
216
|
+
self._read_labels_once()
|
|
217
|
+
return self._projection
|
|
218
|
+
|
|
219
|
+
def _read_labels_once(self) -> None:
|
|
220
|
+
existing, _reason = list_existing_labels(
|
|
221
|
+
self._working_dir, runner=self._runner, repo=self._repo
|
|
222
|
+
)
|
|
223
|
+
self._rubric = read_severity_rubric(
|
|
224
|
+
self._working_dir, runner=self._runner, repo=self._repo,
|
|
225
|
+
existing=existing,
|
|
226
|
+
)
|
|
227
|
+
try:
|
|
228
|
+
names = [item["name"] for item in (existing or [])]
|
|
229
|
+
except (TypeError, KeyError):
|
|
230
|
+
names = []
|
|
231
|
+
self._projection = severity_label_projection(names)
|
|
232
|
+
|
|
203
233
|
def advertise(self, snapshot: AgentSnapshot) -> Sequence[ActionItem]:
|
|
204
234
|
self._auto = snapshot.triage_auto
|
|
205
235
|
self._rubric = _RUBRIC_UNSET
|
|
236
|
+
self._projection = _RUBRIC_UNSET
|
|
206
237
|
rows = list_untriaged(self._runner, self._repo)
|
|
207
238
|
|
|
208
239
|
self._rows = {}
|
|
@@ -250,7 +281,9 @@ class TriageProvider:
|
|
|
250
281
|
"labels": row.get("labels") or [],
|
|
251
282
|
}
|
|
252
283
|
]
|
|
253
|
-
results = apply_triage(
|
|
284
|
+
results = apply_triage(
|
|
285
|
+
self._runner, self._repo, decisions, auto=self._auto,
|
|
286
|
+
label_projection=self._projection_for_run())
|
|
254
287
|
row_result = results[0]
|
|
255
288
|
if row_result.get("label_written"):
|
|
256
289
|
return ActionOutcome(
|
|
@@ -337,7 +370,9 @@ class TriageProvider:
|
|
|
337
370
|
"severity": severity,
|
|
338
371
|
}
|
|
339
372
|
]
|
|
340
|
-
results = apply_triage(
|
|
373
|
+
results = apply_triage(
|
|
374
|
+
self._runner, self._repo, decisions, auto=self._auto,
|
|
375
|
+
label_projection=self._projection_for_run())
|
|
341
376
|
row_result = results[0]
|
|
342
377
|
|
|
343
378
|
if row_result.get("skipped"):
|
|
@@ -76,6 +76,13 @@ STATUS_ESCALATED = "escalated"
|
|
|
76
76
|
#: (FEAT-2026-0108/T02). Not one of `budget.py`'s stop reasons -- nothing was
|
|
77
77
|
#: budgeted, because nothing was dispatched.
|
|
78
78
|
STOP_DIRTY_TREE = "dirty_tree"
|
|
79
|
+
#: The run could not read the queue it was meant to work through (#3392).
|
|
80
|
+
#: Distinct from `STOP_DRAINED`, which means the opposite: a drained run saw
|
|
81
|
+
#: everything there was and finished it. Reported identically, they are
|
|
82
|
+
#: indistinguishable in the summary — the part an operator reads and the only
|
|
83
|
+
#: part automation parses — so a nightly run whose credentials broke looks like
|
|
84
|
+
#: a quiet backlog.
|
|
85
|
+
STOP_SNAPSHOT_UNREADABLE = "snapshot_unreadable"
|
|
79
86
|
|
|
80
87
|
KIND_BUG = "bug"
|
|
81
88
|
KIND_FEATURE = "feature"
|
|
@@ -706,6 +713,43 @@ def _refuse_dirty_tree(reason: str, report: Callable[[str], None]) -> RunSummary
|
|
|
706
713
|
)
|
|
707
714
|
|
|
708
715
|
|
|
716
|
+
def _refuse_unreadable_snapshot(reason: str, *, report) -> "RunSummary":
|
|
717
|
+
"""Refuse a run whose issue listing failed, rather than draining it (#3392).
|
|
718
|
+
|
|
719
|
+
Every provider that matters reads `snapshot.issues`, so a run without it
|
|
720
|
+
can do nothing except mislead. Shaped like `_refuse_dirty_tree`: zero
|
|
721
|
+
attempted, zero completed, one escalation carrying the reason, and a
|
|
722
|
+
distinct stop reason so the summary cannot be mistaken for a healthy run
|
|
723
|
+
against an empty queue.
|
|
724
|
+
|
|
725
|
+
Refusing is the conservative direction here for the same reason it is
|
|
726
|
+
there. A blind run that dispatches nothing has cost nothing; a blind run
|
|
727
|
+
reported as `drained` costs the operator their belief that the queue is
|
|
728
|
+
empty.
|
|
729
|
+
"""
|
|
730
|
+
report(f"run refused to start — {reason}")
|
|
731
|
+
return RunSummary(
|
|
732
|
+
items_attempted=0,
|
|
733
|
+
items_completed=0,
|
|
734
|
+
items_escalated=1,
|
|
735
|
+
stop_reason=STOP_SNAPSHOT_UNREADABLE,
|
|
736
|
+
elapsed_minutes=0.0,
|
|
737
|
+
tokens_spent=0,
|
|
738
|
+
escalations=(Escalation(item_id="run:snapshot", reason=reason),),
|
|
739
|
+
)
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
def exit_code_for(stop_reason: str) -> int:
|
|
743
|
+
"""The process exit code a run ending in *stop_reason* should report.
|
|
744
|
+
|
|
745
|
+
Non-zero only for a refusal — a run that did not happen. Every outcome,
|
|
746
|
+
including `drained`, keeps 0: those are runs that ran. Narrow on purpose,
|
|
747
|
+
so a new stop reason does not silently become a failing exit code for
|
|
748
|
+
whatever automation is watching.
|
|
749
|
+
"""
|
|
750
|
+
return 1 if stop_reason == STOP_SNAPSHOT_UNREADABLE else 0
|
|
751
|
+
|
|
752
|
+
|
|
709
753
|
def _dirty_tree_reason(paths) -> str:
|
|
710
754
|
shown = ", ".join(paths[:10])
|
|
711
755
|
if len(paths) > 10:
|
|
@@ -799,6 +843,31 @@ def run_agent(
|
|
|
799
843
|
if error:
|
|
800
844
|
report(f"snapshot: {section} unreadable — {error}")
|
|
801
845
|
|
|
846
|
+
# #3392: an unreadable issue listing is not an empty queue. Every
|
|
847
|
+
# provider that matters reads `snapshot.issues`, so continuing here
|
|
848
|
+
# finds no candidates and stops with `drained` — "worked through
|
|
849
|
+
# everything there was" reported for "could not see anything". Measured
|
|
850
|
+
# with nine dispatchable bugs in the queue at the time.
|
|
851
|
+
if snapshot.issues_error:
|
|
852
|
+
return _refuse_unreadable_snapshot(
|
|
853
|
+
f"the issue listing failed, so the queue could not be read: "
|
|
854
|
+
f"{snapshot.issues_error}",
|
|
855
|
+
report=report,
|
|
856
|
+
)
|
|
857
|
+
|
|
858
|
+
# The PR listing failing is NOT a refusal: `max_open_prs` deliberately
|
|
859
|
+
# reads an unreadable listing as zero rather than firing on a number
|
|
860
|
+
# the run could not measure. But that leaves the cap silently disabled
|
|
861
|
+
# for this run, so the summary says so rather than the operator finding
|
|
862
|
+
# it in the log. Whether it should refuse instead is a live question
|
|
863
|
+
# (#3392 item 3) and not decided here.
|
|
864
|
+
if snapshot.prs_error:
|
|
865
|
+
report(
|
|
866
|
+
"snapshot: max_open_prs is NOT enforced this run — the PR "
|
|
867
|
+
"listing could not be read, so the cap has no number to "
|
|
868
|
+
"measure against"
|
|
869
|
+
)
|
|
870
|
+
|
|
802
871
|
budget_kwargs = {}
|
|
803
872
|
if pause_marker is not None:
|
|
804
873
|
budget_kwargs["pause_marker"] = pause_marker
|
|
@@ -1294,7 +1363,9 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
|
|
|
1294
1363
|
worktree.restore_branch(started_on, report=_default_reporter)
|
|
1295
1364
|
|
|
1296
1365
|
print(_format_summary(summary))
|
|
1297
|
-
|
|
1366
|
+
# #3392: a refusal exits non-zero so a scheduled run's exit status is
|
|
1367
|
+
# self-describing without its log. Outcomes — `drained` included — keep 0.
|
|
1368
|
+
return exit_code_for(summary.stop_reason)
|
|
1298
1369
|
|
|
1299
1370
|
|
|
1300
1371
|
if __name__ == "__main__":
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.25.0
|
|
@@ -454,6 +454,27 @@ separately carry `auto_repaired_files_changed` in `extras`: the list of
|
|
|
454
454
|
declared-but-unchanged `files_changed` paths the driver dropped on the
|
|
455
455
|
WU's behalf rather than failing the attempt over.
|
|
456
456
|
|
|
457
|
+
**Dispatch without the skills index (#3423).** A dispatched session runs
|
|
458
|
+
`claude -p --disable-slash-commands` by default: a work unit invokes no
|
|
459
|
+
skill, and the project's skills index (34 skills, 1,534 words of descriptions
|
|
460
|
+
in the loop repository) is fixed context carried in every turn's cache read.
|
|
461
|
+
`defaults.dispatch_skills: true` in `verification.yml` restores the index for
|
|
462
|
+
a project whose units do call `/skill-name` in-session.
|
|
463
|
+
|
|
464
|
+
**The post-merge checklist travels with the archive (#3424).** A `met`
|
|
465
|
+
terminal close no longer files PLAN.md's `## Post-merge checklist` as a
|
|
466
|
+
`specfuse:post-merge` issue (ten filed across two repositories, eight never
|
|
467
|
+
actioned); `auto_archive_feature` appends it under the feature's archived
|
|
468
|
+
detail section in `roadmap-archive.md`. `defaults.post_merge_issue: true` in
|
|
469
|
+
`verification.yml` restores the issue.
|
|
470
|
+
|
|
471
|
+
**Closing sessions run on sonnet (#3425).** `close`, `close-intermediate`
|
|
472
|
+
and `plan-next` default to `sonnet` at effort `high`, the same model as the
|
|
473
|
+
judge that checks them (lower-only, about $0.46 per close). Opus was 56% of
|
|
474
|
+
all spend while implementation ran on sonnet. A unit's own
|
|
475
|
+
`model:` still overrides the default, and every `judged` event carries
|
|
476
|
+
`close_model` so judge agreement can be read per close model.
|
|
477
|
+
|
|
457
478
|
Retention interacts with spinning detection, not around it:
|
|
458
479
|
`detect_deterministic_refusal_repeat` (#1415) escalates a WU to
|
|
459
480
|
`blocked_human` when two consecutive attempts emit an identical refusal
|
|
@@ -579,7 +600,7 @@ completion.
|
|
|
579
600
|
|
|
580
601
|
Closing ceremony cost should scale with feature size. A feature whose
|
|
581
602
|
**planned substantive** WU count (types `implementation`, `qa_authoring`,
|
|
582
|
-
`qa_execution`, `qa_curation`) is **≤
|
|
603
|
+
`qa_execution`, `qa_curation`) is **≤ 12** drafts as a **single gate** with
|
|
583
604
|
a **single terminal `close` WU** — no `close-intermediate`, no `plan-next`.
|
|
584
605
|
This is the proportional shape: small features do not pay multi-WU closing
|
|
585
606
|
overhead sized for large ones.
|
|
@@ -597,6 +618,13 @@ reflective session. Ceremony proportionality trades reflection only on
|
|
|
597
618
|
features that stay small **and** on-plan. The `gate_eval.py` predicate is
|
|
598
619
|
the safety net; this rule does not replace it.
|
|
599
620
|
|
|
621
|
+
The line moved from 8 to 12 on 2026-09-26 (#3426): post-review features
|
|
622
|
+
average 10.7 substantive units, every extra gate costs a `plan-next` plus a
|
|
623
|
+
`close-intermediate` and an `awaiting_review` wait, and the arm predicate
|
|
624
|
+
would have armed only 18 of 74 gate boundaries it evaluated — the wait was a
|
|
625
|
+
human redraft, not a review. A feature goes multi-gate when a human decision
|
|
626
|
+
sits between its gates, not because it is large.
|
|
627
|
+
|
|
600
628
|
**Reflective prose is conditional too (FEAT-2026-0106).** The roadmap once
|
|
601
629
|
asked for a retrospective "only on `not_met` or on request" — superseded,
|
|
602
630
|
for an ordering reason: the close writes `RETROSPECTIVE.md` before the judge
|
|
@@ -759,7 +787,17 @@ when and how these get created.
|
|
|
759
787
|
| State backend | WU / GATE file frontmatter | GitHub issue labels + feature registry |
|
|
760
788
|
| Dispatch | driver shells out (`claude -p`) | inbox files + polling loop |
|
|
761
789
|
| Branch / merge | one branch, squash per WU | branch + PR per task, merge watcher |
|
|
762
|
-
| Spec front-end |
|
|
790
|
+
| Spec front-end | one of: **none** (task graph authored directly), **consumed** (specs authored upstream, the loop implements against a handoff manifest), or **authored** (the spec tree is the deliverable — work units author spec files) | spec-first (specs agent + codegen), from a dossier **or** from an upstream handoff manifest |
|
|
791
|
+
|
|
792
|
+
The loop's spec front-end is three-valued because a repository whose entire
|
|
793
|
+
deliverable **is** the spec tree fits neither of the two values this row
|
|
794
|
+
carried before (#3407): it is not "optional", and the task graph is not
|
|
795
|
+
"authored directly" — the work units author spec files. One consumer carried
|
|
796
|
+
that third case in about forty lines of project `CLAUDE.md` because the row
|
|
797
|
+
could not express it. In the **authored** mode the work-unit contract picks up
|
|
798
|
+
two constraints that do not apply to code units — see
|
|
799
|
+
`authoring-work-units` §6 (the whole tree validates) and §12 (a structural
|
|
800
|
+
assertion substitutes for the red test).
|
|
763
801
|
|
|
764
802
|
Everything above those rows — the unit hierarchy, ownership split, WU contract,
|
|
765
803
|
verification-as-oracle, the gate cycle, plan-next, LEARNINGS, and autonomy — is
|
|
@@ -118,9 +118,22 @@
|
|
|
118
118
|
# # attempt repairs the retained tree in place
|
|
119
119
|
# # instead of re-authoring from scratch. Set to
|
|
120
120
|
# # false to restore the pre-0.18 discard behaviour.
|
|
121
|
+
# dispatch_skills: false # whether a dispatched session loads the
|
|
122
|
+
# # project's skills index. Defaults to false:
|
|
123
|
+
# # a work unit invokes no skill, and the index
|
|
124
|
+
# # is fixed context paid on every turn (#3423).
|
|
125
|
+
# # Set true for a project whose units call
|
|
126
|
+
# # `/skill-name` in-session.
|
|
127
|
+
# post_merge_issue: false # whether a `met` terminal close files PLAN.md's
|
|
128
|
+
# # `## Post-merge checklist` as a tracked issue.
|
|
129
|
+
# # Defaults to false (#3424): the checklist is
|
|
130
|
+
# # appended under the feature's archived detail
|
|
131
|
+
# # section in roadmap-archive.md instead.
|
|
121
132
|
# defaults:
|
|
122
133
|
# max_attempts: 3
|
|
123
134
|
# retain_on_guard_refusal: true
|
|
135
|
+
# dispatch_skills: false
|
|
136
|
+
# post_merge_issue: false
|
|
124
137
|
#
|
|
125
138
|
# The re-plan-after-two-failures behaviour (FEAT-2026-0104) reads this same
|
|
126
139
|
# `max_attempts` resolution and adds no config key of its own — a unit's
|
|
@@ -1041,7 +1041,7 @@ def check_produces_satisfiability(feature_dir: Path, gates: list) -> None:
|
|
|
1041
1041
|
|
|
1042
1042
|
# Ceremony-proportionality threshold (docs/methodology.md §6): one fact, one
|
|
1043
1043
|
# home. Referenced, not redefined, in the draft-feature skill.
|
|
1044
|
-
GATE_PROPORTIONALITY_THRESHOLD = 8
|
|
1044
|
+
GATE_PROPORTIONALITY_THRESHOLD = 12 # was 8 until #3426
|
|
1045
1045
|
GATE_PROPORTIONALITY_SUBSTANTIVE_TYPES = frozenset(
|
|
1046
1046
|
{"implementation", "qa_authoring", "qa_execution", "qa_curation"}
|
|
1047
1047
|
)
|
|
@@ -42,6 +42,7 @@ from __future__ import annotations
|
|
|
42
42
|
import argparse
|
|
43
43
|
import datetime as dt
|
|
44
44
|
import fnmatch
|
|
45
|
+
import functools
|
|
45
46
|
import glob
|
|
46
47
|
import hashlib
|
|
47
48
|
import inspect
|
|
@@ -125,7 +126,7 @@ SPECFUSE_DIR = Path(".specfuse")
|
|
|
125
126
|
REPO_ROOT = SPECFUSE_DIR.parent
|
|
126
127
|
FEATURES_DIR = SPECFUSE_DIR / "features"
|
|
127
128
|
VERIFICATION_PATH = SPECFUSE_DIR / "verification.yml"
|
|
128
|
-
DRIVER_VERSION = "0.
|
|
129
|
+
DRIVER_VERSION = "0.25.0"
|
|
129
130
|
# Oldest scaffold layout this driver can drive. init.sh stamps the scaffold's own
|
|
130
131
|
# version into `.specfuse/VERSION`; check_scaffold_version() fails loud at startup if
|
|
131
132
|
# the consumer's scaffold is older than this, pointing at `specfuse upgrade`. Bump
|
|
@@ -635,6 +636,66 @@ def resolve_retain_on_guard_refusal(verification_cfg: dict) -> bool:
|
|
|
635
636
|
"""
|
|
636
637
|
project = (verification_cfg or {}).get("defaults") or {}
|
|
637
638
|
return bool(project.get("retain_on_guard_refusal", True))
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
def resolve_dispatch_skills(verification_cfg: dict) -> bool:
|
|
642
|
+
"""Whether a dispatched session gets the project's skills index (#3423).
|
|
643
|
+
|
|
644
|
+
Reads `defaults.dispatch_skills` from verification.yml — the same
|
|
645
|
+
`defaults` block `resolve_max_attempts` reads. Defaults to False: a
|
|
646
|
+
dispatched work unit invokes no skill (the narrow-tier instructions are in
|
|
647
|
+
the preamble and unit bodies name skill files by path, for reading), so
|
|
648
|
+
the index — 34 skills and 1,534 words of descriptions in this repository —
|
|
649
|
+
is fixed context carried in every turn's cache read for nothing. Set true
|
|
650
|
+
for a project whose units do invoke `/skill-name` in-session.
|
|
651
|
+
"""
|
|
652
|
+
project = (verification_cfg or {}).get("defaults") or {}
|
|
653
|
+
return bool(project.get("dispatch_skills", False))
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
@functools.lru_cache(maxsize=None)
|
|
657
|
+
def claude_supports_flag(flag: str) -> bool:
|
|
658
|
+
"""Whether the installed Claude Code CLI accepts *flag* (#3432).
|
|
659
|
+
|
|
660
|
+
Probed once per driver run from `claude --help`. A CLI that cannot be run
|
|
661
|
+
reads as not supporting anything: the dispatch then omits the flag and
|
|
662
|
+
fails on its own terms, not on an unknown option.
|
|
663
|
+
"""
|
|
664
|
+
try:
|
|
665
|
+
proc = subprocess.run(
|
|
666
|
+
resolve_claude_cmd(["claude", "--help"]), capture_output=True,
|
|
667
|
+
text=True, check=False, timeout=30,
|
|
668
|
+
env=child_env_without_pin_marker(),
|
|
669
|
+
)
|
|
670
|
+
except Exception: # noqa: BLE001 - a missing or broken CLI is "no"
|
|
671
|
+
return False
|
|
672
|
+
out = "".join(s for s in (proc.stdout, proc.stderr) if isinstance(s, str))
|
|
673
|
+
return flag in out
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
_FLAG_WARNED: set[str] = set()
|
|
677
|
+
|
|
678
|
+
|
|
679
|
+
def _warn_flag_unsupported(flag: str, consequence: str) -> None:
|
|
680
|
+
if flag in _FLAG_WARNED:
|
|
681
|
+
return
|
|
682
|
+
_FLAG_WARNED.add(flag)
|
|
683
|
+
print(f" WARN: this Claude Code CLI does not accept {flag}; {consequence}")
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
def resolve_post_merge_issue(verification_cfg: dict) -> bool:
|
|
687
|
+
"""Whether a `met` terminal close files the post-merge checklist as an issue (#3424).
|
|
688
|
+
|
|
689
|
+
Reads `defaults.post_merge_issue` from verification.yml. Defaults to False:
|
|
690
|
+
across this repository and its heaviest consumer, ten such issues were
|
|
691
|
+
filed and eight were never actioned — the checklist is a measurement note
|
|
692
|
+
for the operator, not a work item. The record survives instead under the
|
|
693
|
+
feature's archived detail section (`auto_archive_feature`).
|
|
694
|
+
"""
|
|
695
|
+
project = (verification_cfg or {}).get("defaults") or {}
|
|
696
|
+
return bool(project.get("post_merge_issue", False))
|
|
697
|
+
|
|
698
|
+
|
|
638
699
|
# Per-gate-command wall-clock ceiling. A gate that exceeds it is killed and the gate
|
|
639
700
|
# FAILS (not hangs) — so a deadlocked command (e.g. a test blocked on input()) can't
|
|
640
701
|
# stall the whole driver indefinitely. Generous vs real suites (this repo's is ~20s).
|
|
@@ -657,9 +718,16 @@ MODEL_BY_TYPE = {
|
|
|
657
718
|
"retrospective": "sonnet",
|
|
658
719
|
"lessons": "sonnet",
|
|
659
720
|
"docs": "sonnet",
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
721
|
+
# #3425: closing sessions on sonnet. Opus was 56% of all spend while
|
|
722
|
+
# implementation ran on sonnet; the judge (JUDGE_MODEL, itself sonnet,
|
|
723
|
+
# lower-only, ~$0.46 per close) is the independent check on a close's
|
|
724
|
+
# measurements and lowered 9 of 54 verdicts correctly, so the close does
|
|
725
|
+
# not need a bigger model than the session that checks it. A unit's own
|
|
726
|
+
# `model:` still overrides; `judged` events carry `close_model` so
|
|
727
|
+
# agreement can be split by model.
|
|
728
|
+
"plan-next": "sonnet",
|
|
729
|
+
"close": "sonnet",
|
|
730
|
+
"close-intermediate": "sonnet",
|
|
663
731
|
}
|
|
664
732
|
EFFORT_BY_TYPE = {
|
|
665
733
|
"implementation": "medium",
|
|
@@ -4439,8 +4507,9 @@ def squash_commit(
|
|
|
4439
4507
|
# --------------------------------------------------------------------------- #
|
|
4440
4508
|
|
|
4441
4509
|
PROMPT_PREAMBLE = """\
|
|
4442
|
-
You are executing a single Specfuse work unit.
|
|
4443
|
-
|
|
4510
|
+
You are executing a single Specfuse work unit. The binding rules are already in \
|
|
4511
|
+
your context through .claude/CLAUDE.md; read a further file under .specfuse/rules/ \
|
|
4512
|
+
only when your unit body names it. Do NOT run any git command — the driver owns all commits \
|
|
4444
4513
|
and bookkeeping. Edit files only. Verification is tiered: before you report, run \
|
|
4445
4514
|
only the per-attempt (narrow) tier yourself — your unit type's gate set in \
|
|
4446
4515
|
.specfuse/verification.yml minus every gate declaring `tier: broad`, with the \
|
|
@@ -4790,7 +4859,8 @@ def format_reverification_worklist(wu: WorkUnit, feature_dir: Path) -> str:
|
|
|
4790
4859
|
|
|
4791
4860
|
|
|
4792
4861
|
def dispatch(wu: WorkUnit, failure_note: str | None,
|
|
4793
|
-
cost_tracking: bool = True
|
|
4862
|
+
cost_tracking: bool = True, *,
|
|
4863
|
+
dispatch_skills: bool | None = None) -> tuple[str, dict | None]:
|
|
4794
4864
|
"""Run a fresh agent session for this WU.
|
|
4795
4865
|
|
|
4796
4866
|
When `cost_tracking` is True (default), requests JSON output from
|
|
@@ -4810,6 +4880,24 @@ def dispatch(wu: WorkUnit, failure_note: str | None,
|
|
|
4810
4880
|
cmd = [p.replace("{model}", wu.model).replace("{effort}", wu.effort)
|
|
4811
4881
|
for p in CLAUDE_CMD]
|
|
4812
4882
|
cmd = resolve_claude_cmd(cmd)
|
|
4883
|
+
if dispatch_skills is None:
|
|
4884
|
+
# Resolved here rather than threaded through execute_unit_attempt:
|
|
4885
|
+
# every test fake of dispatch() takes the three positional arguments
|
|
4886
|
+
# and nothing else, and the yaml read is nothing next to a session.
|
|
4887
|
+
try:
|
|
4888
|
+
dispatch_skills = resolve_dispatch_skills(load_verification())
|
|
4889
|
+
except Exception: # noqa: BLE001 - an unreadable config keeps the default
|
|
4890
|
+
dispatch_skills = False
|
|
4891
|
+
if not dispatch_skills:
|
|
4892
|
+
# #3423: a dispatched unit invokes no skill, so the skills index is
|
|
4893
|
+
# fixed context paid on every turn. `defaults.dispatch_skills: true`
|
|
4894
|
+
# in verification.yml restores it (resolve_dispatch_skills).
|
|
4895
|
+
if claude_supports_flag("--disable-slash-commands"):
|
|
4896
|
+
cmd.insert(2, "--disable-slash-commands")
|
|
4897
|
+
else:
|
|
4898
|
+
_warn_flag_unsupported(
|
|
4899
|
+
"--disable-slash-commands",
|
|
4900
|
+
"the skills index stays in the session prompt (#3432)")
|
|
4813
4901
|
if wu.unsandboxed:
|
|
4814
4902
|
# Per-WU sandbox-escape. Audited via the unsandboxed_dispatch event
|
|
4815
4903
|
# emitted in run()'s attempt loop; rationale lives in WU frontmatter.
|
|
@@ -7368,6 +7456,19 @@ def _reconcile_moved_section(
|
|
|
7368
7456
|
return section_text
|
|
7369
7457
|
|
|
7370
7458
|
|
|
7459
|
+
def _post_merge_checklist_for_archive(feature_id: str, repo_root: Path) -> str | None:
|
|
7460
|
+
"""PLAN.md's `## Post-merge checklist` body for *feature_id*, or None (#3424)."""
|
|
7461
|
+
features = repo_root / ".specfuse" / "features"
|
|
7462
|
+
for plan in sorted(features.glob(f"{feature_id}-*/PLAN.md")):
|
|
7463
|
+
try:
|
|
7464
|
+
_, body = read_frontmatter(plan)
|
|
7465
|
+
except Exception: # noqa: BLE001 - archive must never fail on a bad PLAN
|
|
7466
|
+
return None
|
|
7467
|
+
section = find_post_merge_checklist_section(body)
|
|
7468
|
+
return section.strip("\n") + "\n" if section else None
|
|
7469
|
+
return None
|
|
7470
|
+
|
|
7471
|
+
|
|
7371
7472
|
def auto_archive_feature(feature_id: str, repo_root: Path) -> str:
|
|
7372
7473
|
"""Re-implement roadmap-archive single-feature algorithm (Steps 1–6) in-driver.
|
|
7373
7474
|
|
|
@@ -7480,6 +7581,13 @@ def auto_archive_feature(feature_id: str, repo_root: Path) -> str:
|
|
|
7480
7581
|
f"](roadmap.md#{feat_id_lower})", f"](#{feat_id_lower})"
|
|
7481
7582
|
)
|
|
7482
7583
|
|
|
7584
|
+
# #3424: the feature's post-merge checklist travels with the archived
|
|
7585
|
+
# detail section instead of becoming an issue nobody actions. Read from
|
|
7586
|
+
# the feature folder's PLAN.md when one exists; a row-only feature has none.
|
|
7587
|
+
checklist = _post_merge_checklist_for_archive(feature_id, repo_root)
|
|
7588
|
+
if checklist and "**Post-merge checklist.**" not in section_text:
|
|
7589
|
+
section_text = section_text.rstrip("\n") + "\n\n**Post-merge checklist.**\n\n" + checklist
|
|
7590
|
+
|
|
7483
7591
|
marker_end = archive_text.index(marker) + len(marker)
|
|
7484
7592
|
new_archive = archive_text[:marker_end] + f"\n{anchor}\n{section_text}" + archive_text[marker_end:]
|
|
7485
7593
|
archive_path.write_text(new_archive)
|
|
@@ -8833,6 +8941,7 @@ def record_tracked_issue(
|
|
|
8833
8941
|
|
|
8834
8942
|
def file_followup_issues(
|
|
8835
8943
|
feature_dir: Path, repo_root: Path, runner=None, *, commit: bool | None = None,
|
|
8944
|
+
post_merge_issue: bool | None = None,
|
|
8836
8945
|
) -> dict:
|
|
8837
8946
|
"""File one tracked GitHub issue per FOLLOW-UPS.md entry, after a close's squash.
|
|
8838
8947
|
|
|
@@ -8883,7 +8992,13 @@ def file_followup_issues(
|
|
|
8883
8992
|
followups_path,
|
|
8884
8993
|
))
|
|
8885
8994
|
|
|
8886
|
-
if
|
|
8995
|
+
if post_merge_issue is None:
|
|
8996
|
+
post_merge_issue = resolve_post_merge_issue(load_verification())
|
|
8997
|
+
if post_merge_issue and (
|
|
8998
|
+
plan_fm.get("verdict") == "met" or plan_fm.get("status") == "done"
|
|
8999
|
+
):
|
|
9000
|
+
# #3424: off by default — the checklist lands under the archived
|
|
9001
|
+
# detail section instead (auto_archive_feature).
|
|
8887
9002
|
section = find_post_merge_checklist_section(plan_body)
|
|
8888
9003
|
if section:
|
|
8889
9004
|
entries.append((
|
|
@@ -9488,6 +9603,15 @@ def capture_gate_diff(
|
|
|
9488
9603
|
return "\n\n".join(parts)
|
|
9489
9604
|
|
|
9490
9605
|
|
|
9606
|
+
def _is_relative_to(path: Path, other: Path) -> bool:
|
|
9607
|
+
"""`Path.is_relative_to`, tolerant of paths that do not resolve."""
|
|
9608
|
+
try:
|
|
9609
|
+
path.relative_to(other)
|
|
9610
|
+
except ValueError:
|
|
9611
|
+
return False
|
|
9612
|
+
return True
|
|
9613
|
+
|
|
9614
|
+
|
|
9491
9615
|
def write_judge_followups(
|
|
9492
9616
|
feature_dir: Path, gate_number: int, findings: list,
|
|
9493
9617
|
) -> Path:
|
|
@@ -9579,6 +9703,7 @@ def judge_close(
|
|
|
9579
9703
|
payload: dict = {
|
|
9580
9704
|
"gate": gate_number,
|
|
9581
9705
|
"close_verdict": close_verdict,
|
|
9706
|
+
"close_model": getattr(wu, "model", None),
|
|
9582
9707
|
"judge_verdict": None,
|
|
9583
9708
|
"verdict": close_verdict,
|
|
9584
9709
|
"lowered": False,
|
|
@@ -9644,6 +9769,32 @@ def judge_close(
|
|
|
9644
9769
|
feature_dir, gate_number, result.findings)
|
|
9645
9770
|
payload["lowered"] = True
|
|
9646
9771
|
payload["verdict"] = "not_met"
|
|
9772
|
+
# Say WHERE the findings went (#3406). They are written verbatim to
|
|
9773
|
+
# FOLLOW-UPS.md above and committed below, but the event said only
|
|
9774
|
+
# `findings: N` with `reason: null` — so a reader of events.jsonl had
|
|
9775
|
+
# nothing pointing out of it. One operator searched events.jsonl,
|
|
9776
|
+
# `work/` and for a repository-level judge artifact, concluded the text
|
|
9777
|
+
# did not exist, and priced a third close at ~$12 to re-derive a
|
|
9778
|
+
# finding that was sitting in a committed file.
|
|
9779
|
+
#
|
|
9780
|
+
# The criteria go in; the finding TEXT deliberately does not. It is
|
|
9781
|
+
# already recorded verbatim in one place, and copying it into a second
|
|
9782
|
+
# invites the two to disagree — `write_judge_followups` exists so the
|
|
9783
|
+
# judge's words are not reformatted by the driver.
|
|
9784
|
+
payload["findings_path"] = str(
|
|
9785
|
+
Path(followups).relative_to(Path(feature_dir).parent.parent)
|
|
9786
|
+
if _is_relative_to(Path(followups), Path(feature_dir).parent.parent)
|
|
9787
|
+
else Path(followups)
|
|
9788
|
+
)
|
|
9789
|
+
payload["finding_criteria"] = [
|
|
9790
|
+
f.criterion for f in result.findings if getattr(f, "criterion", None)
|
|
9791
|
+
]
|
|
9792
|
+
_named = ", ".join(payload["finding_criteria"]) or "no named criterion"
|
|
9793
|
+
payload["reason"] = (
|
|
9794
|
+
f"judge lowered the verdict on {payload['findings']} finding(s) "
|
|
9795
|
+
f"({_named}); the judge's own words are in "
|
|
9796
|
+
f"{payload['findings_path']}"
|
|
9797
|
+
)
|
|
9647
9798
|
commit_bookkeeping(
|
|
9648
9799
|
[wu.file, followups],
|
|
9649
9800
|
f"chore(loop): {wu.wu_id} judge lowered verdict to not_met"
|
|
@@ -182,7 +182,10 @@ def _list_open_issues(runner: Callable, repo: str, *, limit: int) -> list:
|
|
|
182
182
|
return []
|
|
183
183
|
|
|
184
184
|
|
|
185
|
-
def apply_triage(
|
|
185
|
+
def apply_triage(
|
|
186
|
+
runner: Callable, repo: str, decisions: list, *,
|
|
187
|
+
auto: bool = False, label_projection: "dict | None" = None,
|
|
188
|
+
) -> list:
|
|
186
189
|
"""Record each decision in `decisions` against its GitHub issue.
|
|
187
190
|
|
|
188
191
|
Each decision is a mapping carrying at least `number`, `body` (the
|
|
@@ -195,6 +198,18 @@ def apply_triage(runner: Callable, repo: str, decisions: list, *, auto: bool = F
|
|
|
195
198
|
marker. But a marker with no matching label on the issue (the label
|
|
196
199
|
write failed on an earlier pass, e.g. because the label did not exist
|
|
197
200
|
yet) is not fully idempotent -- it is missing its projection, so this
|
|
201
|
+
`label_projection` maps a canonical severity value to the label this
|
|
202
|
+
repository actually spells it with (`labels.severity_label_projection`,
|
|
203
|
+
#3386). Without it `severity_aliases` was one-directional: the bug lane
|
|
204
|
+
translated a repository's own words inbound while triage wrote canonical
|
|
205
|
+
words outbound, which that repository may never have defined. The write then
|
|
206
|
+
failed, and since the lane reads severity from the LABEL rather than the
|
|
207
|
+
marker, triage removed from the lane the very issues it had just classified
|
|
208
|
+
for it. The marker always records the canonical value, so it stays
|
|
209
|
+
comparable across repositories; only the label takes the operator's word.
|
|
210
|
+
Absent, or missing a value, the canonical label is written — the behaviour
|
|
211
|
+
before the projection existed.
|
|
212
|
+
|
|
198
213
|
retries only the label write. `decision["labels"]` (as returned by
|
|
199
214
|
`gh issue list --json ...,labels`, i.e. a list of `{"name": ...}`
|
|
200
215
|
mappings) is what `labels` is checked against; a decision with no
|
|
@@ -226,7 +241,10 @@ def apply_triage(runner: Callable, repo: str, decisions: list, *, auto: bool = F
|
|
|
226
241
|
marked_severity = (parse_marker_fields(body) or {}).get("severity")
|
|
227
242
|
target_labels = labels_for(marked_category) if marked_category in CATEGORIES else ()
|
|
228
243
|
if marked_severity:
|
|
229
|
-
target_labels = tuple(target_labels) + (
|
|
244
|
+
target_labels = tuple(target_labels) + (
|
|
245
|
+
(label_projection or {}).get(
|
|
246
|
+
marked_severity, severity_label_for(marked_severity)),
|
|
247
|
+
)
|
|
230
248
|
existing_labels = {
|
|
231
249
|
label.get("name") for label in decision.get("labels") or []
|
|
232
250
|
}
|
|
@@ -288,7 +306,9 @@ def apply_triage(runner: Callable, repo: str, decisions: list, *, auto: bool = F
|
|
|
288
306
|
|
|
289
307
|
labels_to_add = list(labels_for(applied_category))
|
|
290
308
|
if severity:
|
|
291
|
-
labels_to_add.append(
|
|
309
|
+
labels_to_add.append(
|
|
310
|
+
(label_projection or {}).get(
|
|
311
|
+
severity, severity_label_for(severity)))
|
|
292
312
|
|
|
293
313
|
try:
|
|
294
314
|
runner(
|