specfuse-loop 0.22.3__tar.gz → 0.23.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.22.3/specfuse_loop.egg-info → specfuse_loop-0.23.0}/PKG-INFO +1 -1
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/pyproject.toml +1 -1
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/agent/providers/bugs.py +28 -2
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/agent_policy.py +89 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/arm_eval.py +1 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/bug_lane.py +44 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/bug_lane_run.py +149 -5
- specfuse_loop-0.23.0/specfuse/loop/data/VERSION +1 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/labels.py +18 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/loop.py +1 -1
- specfuse_loop-0.23.0/specfuse/loop/red_on_base.py +123 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/monitor/autofix_invoke.py +12 -1
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0/specfuse_loop.egg-info}/PKG-INFO +1 -1
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse_loop.egg-info/SOURCES.txt +5 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_autofix_invoke.py +13 -4
- specfuse_loop-0.23.0/tests/test_dispatch_model_from_fix_scope.py +227 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_label_registry.py +9 -2
- specfuse_loop-0.23.0/tests/test_needs_decision_outcome.py +126 -0
- specfuse_loop-0.23.0/tests/test_red_on_base_guardrail.py +114 -0
- specfuse_loop-0.23.0/tests/test_red_on_base_probe.py +182 -0
- specfuse_loop-0.22.3/specfuse/loop/data/VERSION +0 -1
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/LICENSE +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/NOTICE +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/README.md +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/setup.cfg +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/agent/__init__.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/agent/budget.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/agent/diagnose_invoke.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/agent/drafting_answers.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/agent/drafting_invoke.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/agent/drafting_questions.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/agent/driver_command.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/agent/driver_invoke.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/agent/invoke.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/agent/monitoring_read.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/agent/providers/__init__.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/agent/providers/answers.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/agent/providers/feature.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/agent/providers/findings_autofix.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/agent/providers/findings_diagnose.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/agent/providers/triage.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/agent/queue_read.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/agent/repo_detect.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/agent/run.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/agent/severity_backfill.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/agent/state.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/agent/triage_invoke.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/agent/worktree.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/__init__.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/_filelock.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/_miniyaml.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/_wu_sections.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/adopt_feature.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/arm_sweep.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/arm_txn.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/bug_lane_state.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/build_provenance.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/caller_check.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/changelog.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/closing_requirements.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/cost.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/criteria_state.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/data/CHANGELOG.seed.md +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/data/LEARNINGS.template.md +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/data/docs/concepts/adopting-auto-mode.md +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/data/docs/concepts/architecture-addendum-gates-and-iterative-planning.md +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/data/docs/concepts/autonomy-stop-classes.md +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/data/docs/concepts/ralph-lineage.md +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/data/docs/getting-started.md +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/data/docs/glossary.md +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/data/docs/methodology.md +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/data/docs/skills.md +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/data/gitignore.snippet +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/data/monitoring-secrets-checklist.md +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/data/monitoring.overrides.yml.example +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/data/monitoring.yml.example +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/data/roadmap.template.md +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/data/rules/close-discipline.md +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/data/rules/correlation-ids.md +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/data/rules/design-for-diagnosis.md +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/data/rules/human-output.md +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/data/rules/never-touch.md +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/data/rules/operator-escalation.md +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/data/rules/planning-discipline.md +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/data/rules/result-contract.md +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/data/rules/security-boundaries.md +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/data/rules/verification-discipline.md +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/data/rules-local/README.md +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/data/schemas/driver-event.schema.json +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/data/schemas/event.schema.json +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/data/schemas/events/initiative_created.schema.json +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/data/schemas/events/spec_issue_resolved.schema.json +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/data/schemas/events/spec_issue_routed.schema.json +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/data/schemas/events/spec_validated.schema.json +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/data/templates/DECISIONS.template.md +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/data/templates/GATE.template.md +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/data/templates/LEARNINGS-pending.template.md +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/data/templates/PLAN.template.md +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/data/templates/WU.template.md +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/data/verification.yml.example +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/data/workflows/specfuse-monitor.yml +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/decisions_format.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/driver_edit.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/escalation.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/events_stats.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/gate_commands.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/gate_eval.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/gh_backend.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/gh_features.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/heartbeat.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/judge.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/learnings_distil.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/learnings_query.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/lint_closing.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/lint_monitoring.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/lint_plan.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/lint_roadmap.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/notify.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/notify_escalation.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/notify_sla.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/plan_baseline.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/policy_proposals.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/policy_review.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/prerun.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/prerun_capture.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/promotion.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/rearm_migration.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/replay_spin.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/roadmap_archive.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/scaffold.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/triage.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/upgrade_merge_gate.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/loop/validate_event.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/monitor/__init__.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/monitor/adapters.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/monitor/artifact.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/monitor/autofix.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/monitor/autofix_run.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/monitor/autofix_state.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/monitor/cli.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/monitor/diagnose_cli.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/monitor/diagnosis.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/monitor/fingerprint.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/monitor/issues.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/monitor/providers/__init__.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/monitor/providers/_azure_auth.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/monitor/providers/azure_app_insights.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/monitor/providers/azure_service_bus.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/monitor/redaction.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse/monitor/schedule.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse_loop.egg-info/dependency_links.txt +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse_loop.egg-info/entry_points.txt +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse_loop.egg-info/requires.txt +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/specfuse_loop.egg-info/top_level.txt +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_abandon_feature_skill.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_abandoned_work_reported.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_abandoned_wu_strands_gate.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_adopt_feature.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_agent_budget.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_agent_bug_selection_dedup.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_agent_default_features_root.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_agent_driver_command.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_agent_driver_invoke.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_agent_driver_restart.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_agent_escalation_on_issue.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_agent_escalation_run_state.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_agent_findings_seam.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_agent_invoke_timeout.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_agent_invoke_usage.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_agent_item_worktree.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_agent_lock.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_agent_policy_key_ownership.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_agent_policy_queue.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_agent_policy_schema.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_agent_policy_triage_dial.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_agent_provider_answers.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_agent_provider_bugs.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_agent_provider_feature.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_agent_provider_findings_autofix.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_agent_provider_findings_diagnose.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_agent_provider_isolation.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_agent_provider_triage.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_agent_queue_read.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_agent_relays_driver_output.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_agent_repo_detect.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_agent_run.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_agent_run_progress.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_agent_seam.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_agent_snapshot_refresh.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_agent_state.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_answer_escalation_skill.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_app_insights_adapters.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_archive_ref_reconciliation.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_arm_eval.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_arm_eval_lint_class.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_arm_eval_wiring.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_arm_gate_edits_uncommitted.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_arm_sweep.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_arm_sweep_gate.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_arm_txn.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_arm_wiring.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_artifact_redaction.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_attempt_note_untracked_files.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_attempt_outcome_contract.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_attempt_outcome_emission.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_attention_nonwriting_guard.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_attention_silence_section.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_attention_skill_structure.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_attribution_halt_claims.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_autoclose_deferral_visibility.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_autoclose_gate_note.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_autoclose_stub_states_what_passed.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_autofix_live.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_autofix_predicate.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_autofix_run.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_autofix_state.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_autosync.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_autosync_consent.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_autosync_firstrun.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_autosync_no_cwd_leak.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_autosync_plugin.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_azure_credential_resolution.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_backend.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_baseline_escalation_message.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_baseline_persistence.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_baseline_probe.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_baseline_provenance.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_baseline_tree_hash_key.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_bash_routing.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_bats_suites_gated.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_binary_verdict.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_binding_block_advisory.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_binding_block_allocation.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_binding_block_budget.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_bookkeeping_commit_crash_run.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_bookkeeping_commit_hook_crash.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_bookkeeping_no_verify.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_brief_covers_every_unit_escalation.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_bug_lane_ci_pending.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_bug_lane_guardrails.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_bug_lane_label_nonfatal.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_bug_lane_labels_registered.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_bug_lane_min_severity.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_bug_lane_policy_contract.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_bug_lane_pr_number_carried.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_bug_lane_preflight_and_dial.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_bug_lane_run.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_bug_lane_state.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_bug_lane_stopped_outcome_pr_number.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_bug_lane_test_paths.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_build_provenance.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_caller_check_ratchet.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_changed_file_test_selection.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_changelog_append_only.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_changelog_collection.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_changelog_release_wiring.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_changelog_schema.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_ci_conclusion_wait.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_ci_runs_every_gate.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_claude_resolution.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_close_b_pending_learnings.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_close_records_feature_oracle.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_close_wu_self_reference.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_closing_deliverable_guard.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_closing_guard_contracts.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_closing_guard_prediction.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_closing_learnings_destination.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_closing_requirements.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_coauthor_trailer_allowlist.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_conditional_reflection.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_console_encoding.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_convergent_iteration.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_convergent_wu_lint.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_correlation_id_override.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_correlation_id_two_surfaces.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_cost_lifetime.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_cost_tracking.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_criteria_per_gate_lint.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_criteria_state.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_criteria_worklist.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_decision_citation_lint.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_decision_followups_discharged.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_decision_override_lint.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_decisions_format.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_default_severity_aliases.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_degraded_oracle.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_deliverable_missing_note.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_deliverable_presence_gate.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_derive_agent_policy_review_mode.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_derive_agent_policy_skill.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_derive_monitoring_discovery.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_derive_monitoring_skill_registration.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_design_for_diagnosis_rule.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_deterministic_refusal_repeat.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_diagnose_headless.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_diagnose_issue_skill.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_diagnosis.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_diagnosis_roundtrip_live.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_dispatch_skeleton.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_distilled_accept_step.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_doctor.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_doctor_command_preflight.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_done_feature_gates.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_draft_feature_answers_mode.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_drafting_answer_gate.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_drafting_invoke.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_drafting_question_issue.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_drafting_questions.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_drafting_reply_shape.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_driver_edit_detection.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_driver_event_registry_covers_emitters.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_driver_integration.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_driver_lock.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_driver_module_surface.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_driver_restart_halt_surfaced.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_driver_restart_halt_wiring.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_driver_restart_hold.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_driver_staleness_gate_summary.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_driver_staleness_warning.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_dry_run_no_branch_mutation.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_duration_tracking.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_empty_files_escalation.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_ensure_feature_branch.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_ensure_feature_branch_base.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_escalation_assignee_policy.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_escalation_contract.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_escalation_crash_containment.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_escalation_dedup_search.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_escalation_emit.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_event_gate_full_envelope.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_events_redaction.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_events_stats.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_extra_gates.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_failure_artifact_model.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_failure_signature_fail_prefix.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_failure_signature_skips_log_noise.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_feature_branch_auto_sync.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_feature_branch_squash_merge.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_feature_branch_stale_base.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_feature_id_resolution.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_feature_oracle_e2e.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_feature_provider_drafting_dispatch.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_feature_review_accumulation.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_filelock_portable.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_files_changed_auto_repair.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_find_pr_no_search_index.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_fingerprint.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_fix_bug_diff_self_check.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_fix_bug_headless.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_fix_bug_reads_comments.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_fold_contract_documented.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_followups_artifact.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_force_full_close.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_frontmatter_scalar_quoting.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_gate_broad_run.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_gate_budget.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_gate_budget_post_dispatch.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_gate_commands_needs.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_gate_eval.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_gate_eval_calibration.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_gate_eval_intermediate_wiring.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_gate_eval_repo_flag.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_gate_eval_terminal_wiring.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_gate_report_verdict.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_gh_backend.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_gh_features.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_git_env_isolation.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_gitleaks_pinning.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_groom_backlog_skill.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_guard_refusal_failure_class.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_guard_refusal_failure_excerpt.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_guard_repair_e2e.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_guard_repair_prompt.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_hashed_denylist.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_hashed_denylist_ci.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_heartbeat.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_heartbeat_adapter.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_human_output_rule.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_human_work_unit.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_hygiene_boundary_guidance.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_hyphenated_gate_name.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_ignored_cleanup_before_probe.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_init_integration.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_installed_copy_driver_e2e.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_interpreter_normalization.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_judge_close_path.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_judge_cost.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_judge_cost_fold_committed.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_judge_module.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_judge_path_registry.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_judge_terminal_flip_state.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_label_provisioning_runner_contract.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_label_registry_covers_consumers.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_language_aware_narrow_selection.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_lazy_baseline_e2e.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_leak_findings_redaction.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_leak_scan.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_leak_scan_content.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_learnings_curate_vendored_promote.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_learnings_distil_cli.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_learnings_query.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_learnings_staging.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_learnings_suggest_signature_filter.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_learnings_weights.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_legacy_4wu_terminal_flips.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_lifecycle_integration.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_lifetime_accounting.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_lint_ac_observable.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_lint_bare_produces_path.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_lint_boundary_consistency.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_lint_boundary_extraction.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_lint_close_intermediate.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_lint_close_verdict_not_predecided.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_lint_close_wu.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_lint_closing.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_lint_closing_criteria.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_lint_closing_criteria_pristine.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_lint_correlation_id.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_lint_correlation_id_close_intermediate.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_lint_covers_load_wu_invariants.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_lint_feature_oracle_declared.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_lint_gate_proportionality.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_lint_monitoring.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_lint_oracle_env.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_lint_plan_contract_fields.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_lint_plan_errors.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_lint_plan_next_draft.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_lint_plan_relative_import.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_lint_plan_verdict_exempt.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_lint_planning_sections.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_lint_produces_driver_helper.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_lint_produces_satisfiability.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_lint_roadmap.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_lint_sections.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_lint_task_graph_yaml_selection.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_lint_verification_reference.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_live_tests_opt_in.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_load_bearing_close_lint.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_loop_attempt_evidence.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_loop_auto_archive.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_loop_caveman_preamble.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_loop_close_intermediate.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_loop_criteria_skeleton.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_loop_criteria_survival.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_loop_defaults_by_type.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_loop_effort.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_loop_failure_note_cap.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_loop_files_changed_guard.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_loop_gate_budget.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_loop_gate_needs.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_loop_model_alias.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_loop_orchestration.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_loop_post_pass_invariant.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_loop_reset_preserving_events.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_loop_smoke_runner.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_loop_unsandboxed.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_loop_worklist_injection.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_loop_zero_token_guard.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_make_repo_gpgsign_isolation.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_marker_dual_shape.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_maven_failure_excerpt.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_max_attempts_configurable.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_max_open_prs.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_migrate_legacy.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_miniyaml_equivalence.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_miniyaml_negative.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_monitor_cli.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_monitor_issue_lifecycle.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_monitor_runner_surfaces.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_monitoring_bootstrap_artifacts.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_monitoring_cron_dialect.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_monitoring_example.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_monitoring_fenced_blocks.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_monitoring_seed.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_narrow_fallback_warning.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_no_pytest_subprocess.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_notify.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_notify_escalation.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_notify_sla.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_operator_escalation_rule.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_oracle_set_declared.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_oracle_unrunnable_attribution.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_per_run_item_cap_disclosure.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_pick_feature_skill.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_pin_honesty_and_integrity.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_pin_marker_not_inherited.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_pin_marker_spawn_sites.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_plan_baseline.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_planned_cost_lint.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_planning_cost_floor.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_plugin_prose_cross_refs.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_plugin_qualified_dispatch.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_policy_budgets_applied.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_policy_proposals.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_policy_review.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_pr_checks_json_field.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_pr_reference_in_title.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_pre_existing_claims_cite_evidence.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_prep_halt_operator_surface.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_prepare_feature.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_prepare_scaffold_sync.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_prerun_capture.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_prerun_oracles.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_prerun_wiring.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_produced_document_verdict_guard.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_produces_bracket_literal.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_produces_bracket_wildcard.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_produces_field.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_produces_in_diff.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_produces_justification.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_produces_recursive_glob.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_produces_semantics_unified.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_produces_shape.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_progress_forward_note.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_progress_lines_end_to_end.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_promotion_ledger.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_provider_escalation_traces.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_provision_labels.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_queue_stalled_adapter.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_rearm_clears_stale_criteria_state.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_rearm_fold_marker.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_rearm_migration.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_red_baseline_never_reused.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_redaction_windows_home.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_reflection_required_signals.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_replan_end_to_end.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_replan_event_emission.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_replan_note_collision.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_replan_trigger.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_replan_turn_contract.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_replay_spin.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_required_checks.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_resolve_base.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_restart_cap_counts_progress.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_result_block.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_result_block_audience.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_resume_command_build.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_retain_on_guard_refusal_docs.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_roadmap_add_skill.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_roadmap_archive_cli.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_roadmap_archive_shim.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_roadmap_archive_skill.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_roadmap_archive_skill_delegates.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_roadmap_link_gate.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_roadmap_row_parser.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_ruff_verdict_lines.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_rule_import_convergence.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_run_level_failure_breaker.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_scaffold_data_in_sync.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_scaffold_doc_hygiene.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_scaffold_docs.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_scaffold_init.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_scaffold_label_provisioning.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_scaffold_manifest.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_scaffold_resources.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_scaffold_seed_sanity.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_scaffold_skew_halts.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_scaffold_upgrade.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_scaffold_wiring.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_schedule_dialect.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_seed_documents_prerun_keys.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_service_bus_dlq_adapter.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_session_env_preflight.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_severity_aliases.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_severity_backfill_apply.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_severity_backfill_end_to_end.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_severity_backfill_limit.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_severity_backfill_marker.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_severity_backfill_run.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_severity_label_projection.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_severity_reconciliation.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_severity_rubric.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_shipped_templates_parse.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_skill_discovery_links.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_skills_vendored_in_sync.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_spinning_rearm_gate.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_spinout_brief_end_to_end.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_spinout_brief_replan_option.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_squash_commit_hook_crash.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_stop_rationale_captured.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_stranded_halt_cause.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_surefire_run_level_signature.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_surefire_verdict_lines.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_template_closing_shapes.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_terminal_flip_ownership.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_terminal_flips.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_terminal_gate_message.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_tiered_verification_e2e.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_timeout_kill_portable.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_triage.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_triage_apply.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_triage_severity_classify.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_triage_severity_end_to_end.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_triage_severity_write.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_triage_skill_contract.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_triage_skips_agent_escalations.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_tsc_verdict_lines.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_umbrella_version_resolution.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_unterminated_frontmatter.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_untracked_feature_folder.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_untracked_file_absorption.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_upgrade_integration.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_upgrade_merge_gate.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_validate_event.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_validate_event_driver_types.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_verdict_coupling.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_verify_empty_gate_set.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_version_consistency.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_version_skew.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_windows_gate_exec.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_worktree_restore.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.0}/tests/test_wu_execution_metadata.py +0 -0
- {specfuse_loop-0.22.3 → specfuse_loop-0.23.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.23.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"
|
|
@@ -642,6 +642,23 @@ def _timed_out_payload(issue_number: int, elapsed_seconds: float) -> EscalationP
|
|
|
642
642
|
)
|
|
643
643
|
|
|
644
644
|
|
|
645
|
+
def _dispatch_profile_note(profile) -> str:
|
|
646
|
+
"""` [dispatched opus/high via diagnosis:large]`, or empty (#3391).
|
|
647
|
+
|
|
648
|
+
Appended to every bug-lane item's `detail` so the run log states what each
|
|
649
|
+
item cost and why. The proposal's closing point was that without it the
|
|
650
|
+
effect of selecting a model by `fix_scope` is unmeasurable; a dial nobody
|
|
651
|
+
can evaluate is worse than no dial, because it looks like evidence.
|
|
652
|
+
|
|
653
|
+
Empty for a result carrying no profile, so a caller predating this reads
|
|
654
|
+
exactly as it did.
|
|
655
|
+
"""
|
|
656
|
+
if not profile or len(profile) != 3:
|
|
657
|
+
return ""
|
|
658
|
+
model, effort, source = profile
|
|
659
|
+
return f" [dispatched {model}/{effort} via {source}]"
|
|
660
|
+
|
|
661
|
+
|
|
645
662
|
class BugsProvider:
|
|
646
663
|
"""`ActionProvider` over the bug lane."""
|
|
647
664
|
|
|
@@ -727,6 +744,13 @@ class BugsProvider:
|
|
|
727
744
|
spend=0,
|
|
728
745
|
)
|
|
729
746
|
|
|
747
|
+
# #3391: state which model this item was dispatched with and where
|
|
748
|
+
# that came from. Without this line nobody can tell afterwards whether
|
|
749
|
+
# selecting by `fix_scope` helped, which makes the dial unmeasurable
|
|
750
|
+
# and therefore unevaluable — the one point the proposal called out as
|
|
751
|
+
# mattering most.
|
|
752
|
+
profile_note = _dispatch_profile_note(result.dispatch_profile)
|
|
753
|
+
|
|
730
754
|
if result.outcome == OUTCOME_MERGED:
|
|
731
755
|
# `run_bug_lane` (`specfuse/loop/bug_lane_run.py`, T04's file) does
|
|
732
756
|
# not surface the headless `/fix-bug` session's usage envelope on
|
|
@@ -734,7 +758,9 @@ class BugsProvider:
|
|
|
734
758
|
# change there, out of this WU's reach (`specfuse/loop/` is
|
|
735
759
|
# off-limits except what T01 declares). Explicit `spend=0` rather
|
|
736
760
|
# than a value this module cannot actually measure.
|
|
737
|
-
return ActionOutcome(
|
|
761
|
+
return ActionOutcome(
|
|
762
|
+
status=STATUS_COMPLETED,
|
|
763
|
+
detail=f"{result.reason or ''}{profile_note}", spend=0)
|
|
738
764
|
|
|
739
765
|
detail = result.reason if result.reason is not None else result.outcome
|
|
740
766
|
|
|
@@ -796,7 +822,7 @@ class BugsProvider:
|
|
|
796
822
|
# queue holds three feature-scoped issues, not that anything is broken.
|
|
797
823
|
return ActionOutcome(
|
|
798
824
|
status=STATUS_ESCALATED,
|
|
799
|
-
detail=detail,
|
|
825
|
+
detail=f"{detail}{profile_note}",
|
|
800
826
|
escalation=escalation,
|
|
801
827
|
environmental=not result.outcome_named,
|
|
802
828
|
)
|
|
@@ -335,6 +335,95 @@ def resolve_required_checks(path: "str | Path | None" = None) -> tuple:
|
|
|
335
335
|
return tuple(str(n).strip() for n in raw if str(n).strip())
|
|
336
336
|
|
|
337
337
|
|
|
338
|
+
#: What each `fix_scope` costs to dispatch, as `(model, effort)` (#3391).
|
|
339
|
+
#:
|
|
340
|
+
#: `small` is the historical default, so an issue diagnosed as a small fix is
|
|
341
|
+
#: dispatched exactly as every bug was before this existed. `large` is the only
|
|
342
|
+
#: entry that spends more: a fix whose direction has consequences is where a
|
|
343
|
+
#: cheaper session produced coherent, wrong work.
|
|
344
|
+
#:
|
|
345
|
+
#: `external` deliberately keeps the cheap profile. The fix lives outside this
|
|
346
|
+
#: repository, so the session's job is to recognise that and stop — paying for
|
|
347
|
+
#: a stronger model to reach the same refusal buys nothing.
|
|
348
|
+
DEFAULT_MODEL_BY_FIX_SCOPE = {
|
|
349
|
+
"small": ("sonnet", "medium"),
|
|
350
|
+
"large": ("opus", "high"),
|
|
351
|
+
"external": ("sonnet", "medium"),
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def resolve_model_by_fix_scope(path: "str | Path | None" = None) -> dict:
|
|
356
|
+
"""`rules.bugs.model_by_fix_scope` merged over `DEFAULT_MODEL_BY_FIX_SCOPE`.
|
|
357
|
+
|
|
358
|
+
A deployment chooses what each scope costs it: `large: [sonnet, high]` for
|
|
359
|
+
one that would rather not spend on Opus, and so on. Merged rather than
|
|
360
|
+
replaced, so naming one scope does not silently drop the others.
|
|
361
|
+
|
|
362
|
+
Every unusable value falls back to the shipped pair for that scope rather
|
|
363
|
+
than raising or disabling dispatch — an unreadable policy must not decide
|
|
364
|
+
that no bug can be worked on. An unknown scope name is ignored: it is a
|
|
365
|
+
typo, and inventing a profile for it would route nothing while looking
|
|
366
|
+
configured.
|
|
367
|
+
"""
|
|
368
|
+
resolved = dict(DEFAULT_MODEL_BY_FIX_SCOPE)
|
|
369
|
+
try:
|
|
370
|
+
policy = load_policy(path)
|
|
371
|
+
except (FileNotFoundError, OSError):
|
|
372
|
+
return resolved
|
|
373
|
+
rules = policy.get("rules") if isinstance(policy, dict) else None
|
|
374
|
+
bugs = rules.get("bugs") if isinstance(rules, dict) else None
|
|
375
|
+
raw = bugs.get("model_by_fix_scope") if isinstance(bugs, dict) else None
|
|
376
|
+
if not isinstance(raw, dict):
|
|
377
|
+
return resolved
|
|
378
|
+
for scope, value in raw.items():
|
|
379
|
+
if scope not in resolved:
|
|
380
|
+
continue
|
|
381
|
+
if not isinstance(value, (list, tuple)) or len(value) != 2:
|
|
382
|
+
continue
|
|
383
|
+
model, effort = (str(v).strip() for v in value)
|
|
384
|
+
if model and effort:
|
|
385
|
+
resolved[scope] = (model, effort)
|
|
386
|
+
return resolved
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def resolve_require_red_on_base(path: "str | Path | None" = None) -> bool:
|
|
390
|
+
"""`rules.bugs.require_red_on_base` — must a new test be proven red on the
|
|
391
|
+
merge base before the lane may merge unattended (#3377)?
|
|
392
|
+
|
|
393
|
+
`False` unless explicitly `true`. Defaulting on would decline every merge
|
|
394
|
+
for any deployment that has not also declared how to run the check, which
|
|
395
|
+
is a silent automerge shutdown rather than a guardrail — and the reason a
|
|
396
|
+
deployment turns this on is precisely to bring unattended merging *back*
|
|
397
|
+
on evidence.
|
|
398
|
+
"""
|
|
399
|
+
try:
|
|
400
|
+
policy = load_policy(path)
|
|
401
|
+
except (FileNotFoundError, OSError):
|
|
402
|
+
return False
|
|
403
|
+
rules = policy.get("rules") if isinstance(policy, dict) else None
|
|
404
|
+
bugs = rules.get("bugs") if isinstance(rules, dict) else None
|
|
405
|
+
return (bugs.get("require_red_on_base") if isinstance(bugs, dict) else None) is True
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def resolve_red_on_base_command(path: "str | Path | None" = None) -> str:
|
|
409
|
+
"""`rules.bugs.red_on_base_command` — the operator's own test command, with
|
|
410
|
+
a `{tests}` placeholder (#3377).
|
|
411
|
+
|
|
412
|
+
Empty when absent or unusable, which leaves the check unverifiable and so
|
|
413
|
+
declines the merge when it is required. That is deliberate: a guessed
|
|
414
|
+
command that silently matched no tests would exit non-zero, read as "red on
|
|
415
|
+
base", and wave every PR through — strictly worse than the gap it closes.
|
|
416
|
+
"""
|
|
417
|
+
try:
|
|
418
|
+
policy = load_policy(path)
|
|
419
|
+
except (FileNotFoundError, OSError):
|
|
420
|
+
return ""
|
|
421
|
+
rules = policy.get("rules") if isinstance(policy, dict) else None
|
|
422
|
+
bugs = rules.get("bugs") if isinstance(rules, dict) else None
|
|
423
|
+
raw = bugs.get("red_on_base_command") if isinstance(bugs, dict) else None
|
|
424
|
+
return raw.strip() if isinstance(raw, str) else ""
|
|
425
|
+
|
|
426
|
+
|
|
338
427
|
def resolve_max_open_prs(path: "str | Path | None" = None) -> "int | None":
|
|
339
428
|
"""`budgets.max_open_prs`, or None when unset or unusable (#3340).
|
|
340
429
|
|
|
@@ -98,6 +98,7 @@ JUDGE_MODULES = tuple(
|
|
|
98
98
|
"bug_lane.py", # the merge-guardrail verdict
|
|
99
99
|
"bug_lane_run.py", # holds the single `gh pr merge` call site
|
|
100
100
|
"bug_lane_state.py", # the rolling merge cap a guardrail reads
|
|
101
|
+
"red_on_base.py", # produces the red-on-base verdict a merge guardrail reads (#3377)
|
|
101
102
|
"upgrade_merge_gate.py", # the scaffold-upgrade merge gate
|
|
102
103
|
# The dials, budgets and command sets a verdict is measured against.
|
|
103
104
|
"agent_policy.py", # automerge dial, diff/merge caps, test paths
|
|
@@ -46,6 +46,15 @@ REASON_UNTRACEABLE = "untraceable_provenance"
|
|
|
46
46
|
REASON_DAILY_CAP_REACHED = "daily_cap_reached"
|
|
47
47
|
REASON_ELIGIBLE = "eligible"
|
|
48
48
|
REASON_UNREADABLE_INPUT = "unreadable_input"
|
|
49
|
+
#: The PR's own new test passed at the merge base, so it proves nothing about
|
|
50
|
+
#: the fix — it asserts pre-existing behaviour, or tests something adjacent to
|
|
51
|
+
#: the defect (#3377).
|
|
52
|
+
REASON_TEST_NOT_RED_ON_BASE = "test_not_red_on_base"
|
|
53
|
+
#: The red-on-base check was required and could not be run. Deliberately
|
|
54
|
+
#: distinct from the reason above: "the check said no" and "the check never
|
|
55
|
+
#: ran" are different situations with different fixes, and collapsing them is
|
|
56
|
+
#: exactly how a guard comes to report clean because it could not look.
|
|
57
|
+
REASON_RED_ON_BASE_UNVERIFIED = "red_on_base_unverified"
|
|
49
58
|
|
|
50
59
|
# Public label names for the declining reasons (#1420). The REASON_* values above
|
|
51
60
|
# are internal identifiers and must not double as labels: they are snake_case
|
|
@@ -66,6 +75,8 @@ DECLINE_LABELS = {
|
|
|
66
75
|
REASON_DAILY_CAP_REACHED: "bug-lane:daily-cap-reached",
|
|
67
76
|
REASON_UNREADABLE_INPUT: "bug-lane:unreadable-input",
|
|
68
77
|
REASON_CI_PENDING: "bug-lane:ci-pending",
|
|
78
|
+
REASON_TEST_NOT_RED_ON_BASE: "bug-lane:test-not-red-on-base",
|
|
79
|
+
REASON_RED_ON_BASE_UNVERIFIED: "bug-lane:red-on-base-unverified",
|
|
69
80
|
}
|
|
70
81
|
|
|
71
82
|
PROVENANCE_KINDS = ("triaged_issue", "diagnosed_finding")
|
|
@@ -155,6 +166,11 @@ def evaluate_merge_guardrails(
|
|
|
155
166
|
# as before #1418. An explicitly malformed value still fails closed —
|
|
156
167
|
# omission means "use the default", not "skip the guardrail".
|
|
157
168
|
test_paths: Any = (_TESTS_PREFIX,),
|
|
169
|
+
# #3377. Off by default: a deployment that has not declared how to run the
|
|
170
|
+
# check would otherwise have every merge declined on upgrade, which is a
|
|
171
|
+
# silent automerge shutdown rather than a guardrail.
|
|
172
|
+
require_red_on_base: Any = False,
|
|
173
|
+
red_on_base: Any = None,
|
|
158
174
|
state_reader: MergeCapStateReader,
|
|
159
175
|
) -> MergeDecision:
|
|
160
176
|
"""Decide whether a bug-lane PR may auto-merge.
|
|
@@ -167,6 +183,25 @@ def evaluate_merge_guardrails(
|
|
|
167
183
|
`state_reader` answers the rolling-24h merge-count question T03 owns the
|
|
168
184
|
storage for. Any failure to evaluate an input returns
|
|
169
185
|
`eligible=False`.
|
|
186
|
+
|
|
187
|
+
`red_on_base` is the one guardrail that measures whether the PR *works*
|
|
188
|
+
rather than what shape it has (#3377). Every other check here reads the
|
|
189
|
+
form of the diff; a PR can satisfy all of them and still not fix the thing
|
|
190
|
+
the issue reported. Audited on one consumer repository: seven lane PRs
|
|
191
|
+
passed all six form checks and four were incomplete for the issue they
|
|
192
|
+
claimed to close, two of them merging unattended and closing the issue
|
|
193
|
+
behind a symptom that still reproduced.
|
|
194
|
+
|
|
195
|
+
The caller runs the PR's own added test at the merge base and passes the
|
|
196
|
+
verdict here: `True` means it failed there (so it measures something the
|
|
197
|
+
fix changed), `False` means it passed there and therefore proves nothing.
|
|
198
|
+
Evaluated last, after every cheap check, because it is the only one whose
|
|
199
|
+
input costs a test run to produce.
|
|
200
|
+
|
|
201
|
+
Its limit, stated rather than implied: this catches "the test proves
|
|
202
|
+
nothing", not "the test proves the wrong thing". A test red on base for a
|
|
203
|
+
reason unrelated to the reported symptom still passes. Closing that needs
|
|
204
|
+
the issue to carry a machine-readable reproduction.
|
|
170
205
|
"""
|
|
171
206
|
# The two checks that precede the CI read keep their own inline form, so
|
|
172
207
|
# the CI check stays exactly where it was in the sequence and no declining
|
|
@@ -212,6 +247,15 @@ def evaluate_merge_guardrails(
|
|
|
212
247
|
if merge_count >= max_merges:
|
|
213
248
|
return _decline(REASON_DAILY_CAP_REACHED)
|
|
214
249
|
|
|
250
|
+
# Last, so the two test runs its input costs are only ever paid by a PR
|
|
251
|
+
# that has already cleared everything cheaper.
|
|
252
|
+
if require_red_on_base is True:
|
|
253
|
+
if red_on_base is True:
|
|
254
|
+
return MergeDecision(eligible=True, reason=REASON_ELIGIBLE)
|
|
255
|
+
if red_on_base is False:
|
|
256
|
+
return _decline(REASON_TEST_NOT_RED_ON_BASE)
|
|
257
|
+
return _decline(REASON_RED_ON_BASE_UNVERIFIED)
|
|
258
|
+
|
|
215
259
|
return MergeDecision(eligible=True, reason=REASON_ELIGIBLE)
|
|
216
260
|
|
|
217
261
|
|
|
@@ -31,6 +31,7 @@ failure -- leaves the PR open, optionally labelled with the declining reason.
|
|
|
31
31
|
from __future__ import annotations
|
|
32
32
|
|
|
33
33
|
import json
|
|
34
|
+
import tempfile
|
|
34
35
|
import re
|
|
35
36
|
import time
|
|
36
37
|
from dataclasses import dataclass
|
|
@@ -41,6 +42,9 @@ from specfuse.loop.agent_policy import (
|
|
|
41
42
|
resolve_required_checks,
|
|
42
43
|
bug_lane_limits,
|
|
43
44
|
resolve_bug_automerge,
|
|
45
|
+
resolve_model_by_fix_scope,
|
|
46
|
+
resolve_require_red_on_base,
|
|
47
|
+
resolve_red_on_base_command,
|
|
44
48
|
)
|
|
45
49
|
from specfuse.loop.bug_lane import (
|
|
46
50
|
DECLINE_LABELS,
|
|
@@ -53,7 +57,10 @@ from specfuse.loop.bug_lane import (
|
|
|
53
57
|
)
|
|
54
58
|
from specfuse.loop.bug_lane_state import GitHubMergeCapState, record_merge
|
|
55
59
|
from specfuse.loop.labels import provision_labels
|
|
60
|
+
from specfuse.loop.red_on_base import select_test_files, test_was_red_on_base
|
|
56
61
|
from specfuse.loop.triage import parse_marker
|
|
62
|
+
from specfuse.monitor.diagnosis import DiagnosisParseError
|
|
63
|
+
from specfuse.monitor.diagnosis import parse as parse_diagnosis
|
|
57
64
|
from specfuse.monitor.autofix_invoke import (
|
|
58
65
|
build_invocation,
|
|
59
66
|
classify_outcome,
|
|
@@ -84,6 +91,7 @@ CORRELATION_ID = "FEAT-2026-0048/T04"
|
|
|
84
91
|
|
|
85
92
|
OUTCOME_REFUSED = "refused"
|
|
86
93
|
OUTCOME_COULD_NOT_PROCEED = "could_not_proceed"
|
|
94
|
+
OUTCOME_NEEDS_DECISION = "needs_decision"
|
|
87
95
|
OUTCOME_MERGED = "merged"
|
|
88
96
|
OUTCOME_DECLINED = "declined"
|
|
89
97
|
|
|
@@ -96,7 +104,12 @@ OUTCOME_DECLINED = "declined"
|
|
|
96
104
|
#: when the guardrails had passed and the dial was the only thing in the way.
|
|
97
105
|
OUTCOME_AUTOMERGE_OFF = "automerge_off"
|
|
98
106
|
|
|
99
|
-
|
|
107
|
+
#: `needs_decision` escalates like the other two: the session opened no PR,
|
|
108
|
+
#: so there is nothing for the guardrails to evaluate, and what the operator
|
|
109
|
+
#: owes is an answer rather than a review (#3390).
|
|
110
|
+
_ESCALATING_OUTCOMES = (
|
|
111
|
+
OUTCOME_REFUSED, OUTCOME_COULD_NOT_PROCEED, OUTCOME_NEEDS_DECISION,
|
|
112
|
+
)
|
|
100
113
|
|
|
101
114
|
_CI_UNKNOWN = "unknown"
|
|
102
115
|
#: Checks exist but have not concluded yet -- internal to this module; never
|
|
@@ -149,6 +162,12 @@ class BugLaneResult:
|
|
|
149
162
|
#: was throwing it away, so three refusals in one run were three
|
|
150
163
|
#: identical escalations saying only which word came back.
|
|
151
164
|
stop_rationale: str = ""
|
|
165
|
+
#: `(model, effort, source)` this issue was dispatched with, and where that
|
|
166
|
+
#: profile came from -- `diagnosis:<fix_scope>` or `default` (#3391).
|
|
167
|
+
#: Carried so a run can state it per item: without that line nobody can
|
|
168
|
+
#: tell afterwards whether selecting the model by scope helped, which
|
|
169
|
+
#: would make the dial unmeasurable and therefore unevaluable.
|
|
170
|
+
dispatch_profile: tuple = ()
|
|
152
171
|
#: Whether the session actually named one of `OUTCOMES`, as opposed to
|
|
153
172
|
#: `classify_outcome` failing closed to `could_not_proceed` for output
|
|
154
173
|
#: naming none (#3343). Defaults True so a caller constructing a result by
|
|
@@ -658,6 +677,7 @@ def _declined(
|
|
|
658
677
|
working_dir: str,
|
|
659
678
|
*,
|
|
660
679
|
evidence: tuple = (),
|
|
680
|
+
dispatch_profile: tuple = (),
|
|
661
681
|
) -> BugLaneResult:
|
|
662
682
|
"""Label the PR with *reason* (best-effort) and return the declined result."""
|
|
663
683
|
# The public label name, never the raw reason constant (#1420): the
|
|
@@ -675,9 +695,102 @@ def _declined(
|
|
|
675
695
|
pr_number=pr_number,
|
|
676
696
|
label_written=label_written,
|
|
677
697
|
evidence=evidence,
|
|
698
|
+
dispatch_profile=dispatch_profile,
|
|
678
699
|
)
|
|
679
700
|
|
|
680
701
|
|
|
702
|
+
def _merge_base_sha(runner: Callable, repo: str, pr_number: int) -> str:
|
|
703
|
+
"""The commit the PR is measured against, or `""` (#3377).
|
|
704
|
+
|
|
705
|
+
`baseRefOid` is the tip of the base branch as GitHub sees it now, which is
|
|
706
|
+
what the PR would merge into — the tree the fix was written against, and so
|
|
707
|
+
the tree its new test must fail on. An empty string makes the probe decline
|
|
708
|
+
rather than guess a commit.
|
|
709
|
+
"""
|
|
710
|
+
try:
|
|
711
|
+
result = runner(
|
|
712
|
+
["gh", "pr", "view", str(pr_number), "--repo", repo,
|
|
713
|
+
"--json", "baseRefOid"],
|
|
714
|
+
check=False,
|
|
715
|
+
)
|
|
716
|
+
except Exception: # noqa: BLE001 - a lookup failure declines, never raises
|
|
717
|
+
return ""
|
|
718
|
+
if getattr(result, "returncode", 1) != 0 or not getattr(result, "stdout", None):
|
|
719
|
+
return ""
|
|
720
|
+
try:
|
|
721
|
+
payload = json.loads(result.stdout)
|
|
722
|
+
except ValueError:
|
|
723
|
+
return ""
|
|
724
|
+
sha = payload.get("baseRefOid") if isinstance(payload, dict) else None
|
|
725
|
+
return sha.strip() if isinstance(sha, str) else ""
|
|
726
|
+
|
|
727
|
+
|
|
728
|
+
def dispatch_profile_for(
|
|
729
|
+
runner: Callable, repo: str, issue_number: int, policy_path: Any = None,
|
|
730
|
+
) -> tuple:
|
|
731
|
+
"""`(model, effort, source)` for one issue's `/fix-bug` dispatch (#3391).
|
|
732
|
+
|
|
733
|
+
Every bug was dispatched at `sonnet`/`medium` — `build_invocation`'s
|
|
734
|
+
defaults, never overridden — whether it was a one-line template fix or a
|
|
735
|
+
change whose direction has architectural consequences. Of seven bugs a run
|
|
736
|
+
dispatched, the two that produced correct fixes had issues already carrying
|
|
737
|
+
a diagnosis that named the file and the shape of the fix; the two closed on
|
|
738
|
+
review as wrong reported a symptom and left the fix location open.
|
|
739
|
+
|
|
740
|
+
The signal read here is `fix_scope`, from the `/diagnose-issue` comment
|
|
741
|
+
`specfuse.monitor.diagnosis.parse` recovers. That is deliberate: the
|
|
742
|
+
alternative proposal was an `effort=` field triage guesses from issue text
|
|
743
|
+
in about eight seconds, and effort is a property of the codebase rather
|
|
744
|
+
than of the report — the issue in that run that *read* as most mechanical
|
|
745
|
+
was the one whose correct fix required knowing where a seed value came
|
|
746
|
+
from. `fix_scope` rests on something a tool produced.
|
|
747
|
+
|
|
748
|
+
`source` names where the profile came from (`diagnosis:<scope>` or
|
|
749
|
+
`default`) so the run can say it. Without that line nobody can tell
|
|
750
|
+
afterwards whether the dial helped, which makes the whole change
|
|
751
|
+
unmeasurable.
|
|
752
|
+
|
|
753
|
+
Never raises. An unreadable issue, an absent diagnosis and a malformed
|
|
754
|
+
marker all resolve to the historical default, so this can only ever spend
|
|
755
|
+
more deliberately — never refuse to dispatch.
|
|
756
|
+
"""
|
|
757
|
+
profiles = resolve_model_by_fix_scope(policy_path)
|
|
758
|
+
default = profiles["small"]
|
|
759
|
+
try:
|
|
760
|
+
result = runner(
|
|
761
|
+
["gh", "issue", "view", str(issue_number), "--repo", repo,
|
|
762
|
+
"--json", "body,comments"],
|
|
763
|
+
check=False,
|
|
764
|
+
)
|
|
765
|
+
except Exception: # noqa: BLE001 - a diagnostic read never stops a dispatch
|
|
766
|
+
return (*default, "default")
|
|
767
|
+
if getattr(result, "returncode", 1) != 0 or not getattr(result, "stdout", None):
|
|
768
|
+
return (*default, "default")
|
|
769
|
+
try:
|
|
770
|
+
payload = json.loads(result.stdout)
|
|
771
|
+
except ValueError:
|
|
772
|
+
return (*default, "default")
|
|
773
|
+
if not isinstance(payload, dict):
|
|
774
|
+
return (*default, "default")
|
|
775
|
+
|
|
776
|
+
bodies = [payload.get("body") or ""]
|
|
777
|
+
for comment in payload.get("comments") or []:
|
|
778
|
+
if isinstance(comment, dict):
|
|
779
|
+
bodies.append(comment.get("body") or "")
|
|
780
|
+
|
|
781
|
+
for body in bodies:
|
|
782
|
+
try:
|
|
783
|
+
diagnosis = parse_diagnosis(body)
|
|
784
|
+
except DiagnosisParseError:
|
|
785
|
+
continue
|
|
786
|
+
if diagnosis is None:
|
|
787
|
+
continue
|
|
788
|
+
scope = getattr(diagnosis, "fix_scope", None)
|
|
789
|
+
if scope in profiles:
|
|
790
|
+
return (*profiles[scope], f"diagnosis:{scope}")
|
|
791
|
+
return (*default, "default")
|
|
792
|
+
|
|
793
|
+
|
|
681
794
|
def run_bug_lane(
|
|
682
795
|
runner: Callable,
|
|
683
796
|
repo: str,
|
|
@@ -700,7 +813,13 @@ def run_bug_lane(
|
|
|
700
813
|
always accurate) and merges only when the dial is `on` *and* the
|
|
701
814
|
guardrails are eligible -- the module's single merge call site.
|
|
702
815
|
"""
|
|
703
|
-
|
|
816
|
+
model, effort, profile_source = dispatch_profile_for(
|
|
817
|
+
runner, repo, issue_number, policy_path
|
|
818
|
+
)
|
|
819
|
+
_profile = (model, effort, profile_source)
|
|
820
|
+
argv, prompt = build_invocation(
|
|
821
|
+
issue_number, repo, working_dir, model=model, effort=effort
|
|
822
|
+
)
|
|
704
823
|
invocation = runner(argv + [prompt], check=False)
|
|
705
824
|
session_output = getattr(invocation, "stdout", "") or ""
|
|
706
825
|
outcome = classify_outcome(session_output)
|
|
@@ -719,6 +838,7 @@ def run_bug_lane(
|
|
|
719
838
|
unpushed_work=unpushed_work_for_issue(runner, issue_number),
|
|
720
839
|
stop_rationale=extract_stop_rationale(session_output),
|
|
721
840
|
outcome_named=outcome_was_named(session_output),
|
|
841
|
+
dispatch_profile=_profile
|
|
722
842
|
)
|
|
723
843
|
|
|
724
844
|
# The session's own account first (#3180) -- it never re-discovers what it
|
|
@@ -731,7 +851,9 @@ def run_bug_lane(
|
|
|
731
851
|
pr_lookup_sleep(PR_LOOKUP_RETRY_SECONDS)
|
|
732
852
|
pr_number = _find_pr_for_issue(runner, repo, issue_number)
|
|
733
853
|
if pr_number is None:
|
|
734
|
-
return BugLaneResult(
|
|
854
|
+
return BugLaneResult(
|
|
855
|
+
outcome=OUTCOME_DECLINED, reason=_REASON_PR_NOT_FOUND,
|
|
856
|
+
pr_number=None, dispatch_profile=_profile)
|
|
735
857
|
|
|
736
858
|
changed_files, diff_lines = _pr_changed_files_and_diff_lines(runner, repo, pr_number)
|
|
737
859
|
limits = bug_lane_limits(policy_path)
|
|
@@ -755,6 +877,7 @@ def run_bug_lane(
|
|
|
755
877
|
evidence=_evidence_for(
|
|
756
878
|
shape.reason, changed_files, diff_lines, limits,
|
|
757
879
|
),
|
|
880
|
+
dispatch_profile=_profile,
|
|
758
881
|
)
|
|
759
882
|
|
|
760
883
|
deadline_seconds = (
|
|
@@ -773,6 +896,21 @@ def run_bug_lane(
|
|
|
773
896
|
)
|
|
774
897
|
state_reader = GitHubMergeCapState(runner=runner, repo=repo, now=now)
|
|
775
898
|
|
|
899
|
+
# #3377: the only guardrail that asks whether the change works rather than
|
|
900
|
+
# what shape it has. Computed here and not inside the predicate, which
|
|
901
|
+
# stays pure — and only when the deployment asked for it, since it costs a
|
|
902
|
+
# test run.
|
|
903
|
+
require_red = resolve_require_red_on_base(policy_path)
|
|
904
|
+
red_on_base = None
|
|
905
|
+
if require_red:
|
|
906
|
+
red_on_base = test_was_red_on_base(
|
|
907
|
+
runner,
|
|
908
|
+
base_sha=_merge_base_sha(runner, repo, pr_number),
|
|
909
|
+
test_files=select_test_files(changed_files, limits["test_paths"]),
|
|
910
|
+
command_template=resolve_red_on_base_command(policy_path),
|
|
911
|
+
worktree_root=tempfile.gettempdir(),
|
|
912
|
+
)
|
|
913
|
+
|
|
776
914
|
decision = evaluate_merge_guardrails(
|
|
777
915
|
changed_files=changed_files,
|
|
778
916
|
ci_conclusion=ci_conclusion,
|
|
@@ -781,6 +919,8 @@ def run_bug_lane(
|
|
|
781
919
|
provenance=provenance,
|
|
782
920
|
max_merges_per_day=limits["max_merges_per_day"],
|
|
783
921
|
test_paths=limits["test_paths"],
|
|
922
|
+
require_red_on_base=require_red,
|
|
923
|
+
red_on_base=red_on_base,
|
|
784
924
|
state_reader=state_reader,
|
|
785
925
|
)
|
|
786
926
|
dial = resolve_bug_automerge(policy_path)
|
|
@@ -791,7 +931,9 @@ def run_bug_lane(
|
|
|
791
931
|
check=True,
|
|
792
932
|
)
|
|
793
933
|
record_merge(runner, repo, pr_number, at=time.time() if now is None else now)
|
|
794
|
-
return BugLaneResult(
|
|
934
|
+
return BugLaneResult(
|
|
935
|
+
outcome=OUTCOME_MERGED, reason=decision.reason,
|
|
936
|
+
pr_number=pr_number, dispatch_profile=_profile)
|
|
795
937
|
|
|
796
938
|
if decision.eligible:
|
|
797
939
|
# Eligible but not merged: the dial is the only thing in the way, and
|
|
@@ -799,7 +941,8 @@ def run_bug_lane(
|
|
|
799
941
|
# declining reason to project, and `DECLINE_LABELS` deliberately has no
|
|
800
942
|
# entry for `REASON_ELIGIBLE`.
|
|
801
943
|
return BugLaneResult(
|
|
802
|
-
outcome=OUTCOME_AUTOMERGE_OFF, reason=decision.reason,
|
|
944
|
+
outcome=OUTCOME_AUTOMERGE_OFF, reason=decision.reason,
|
|
945
|
+
pr_number=pr_number, dispatch_profile=_profile,
|
|
803
946
|
)
|
|
804
947
|
|
|
805
948
|
# Reaching here means `decision.eligible` is False, so the reason is a real
|
|
@@ -808,4 +951,5 @@ def run_bug_lane(
|
|
|
808
951
|
return _declined(
|
|
809
952
|
runner, repo, pr_number, decision.reason, working_dir,
|
|
810
953
|
evidence=_evidence_for(decision.reason, changed_files, diff_lines, limits),
|
|
954
|
+
dispatch_profile=_profile,
|
|
811
955
|
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.23.0
|
|
@@ -199,6 +199,24 @@ LABEL_REGISTRY: tuple[LabelSpec, ...] = (
|
|
|
199
199
|
),
|
|
200
200
|
consumer="loop/bug_lane_run.py",
|
|
201
201
|
),
|
|
202
|
+
LabelSpec(
|
|
203
|
+
name=bug_lane.DECLINE_LABELS[bug_lane.REASON_TEST_NOT_RED_ON_BASE],
|
|
204
|
+
colour="fbca04",
|
|
205
|
+
description=(
|
|
206
|
+
"Bug-lane auto-merge declined: the PR's new test passes at the "
|
|
207
|
+
"merge base, so it proves nothing about the fix"
|
|
208
|
+
),
|
|
209
|
+
consumer="loop/bug_lane_run.py",
|
|
210
|
+
),
|
|
211
|
+
LabelSpec(
|
|
212
|
+
name=bug_lane.DECLINE_LABELS[bug_lane.REASON_RED_ON_BASE_UNVERIFIED],
|
|
213
|
+
colour="fbca04",
|
|
214
|
+
description=(
|
|
215
|
+
"Bug-lane auto-merge declined: the red-on-base check was required "
|
|
216
|
+
"and could not be run; declare rules.bugs.red_on_base_command"
|
|
217
|
+
),
|
|
218
|
+
consumer="loop/bug_lane_run.py",
|
|
219
|
+
),
|
|
202
220
|
)
|
|
203
221
|
|
|
204
222
|
|
|
@@ -125,7 +125,7 @@ SPECFUSE_DIR = Path(".specfuse")
|
|
|
125
125
|
REPO_ROOT = SPECFUSE_DIR.parent
|
|
126
126
|
FEATURES_DIR = SPECFUSE_DIR / "features"
|
|
127
127
|
VERIFICATION_PATH = SPECFUSE_DIR / "verification.yml"
|
|
128
|
-
DRIVER_VERSION = "0.
|
|
128
|
+
DRIVER_VERSION = "0.23.0"
|
|
129
129
|
# Oldest scaffold layout this driver can drive. init.sh stamps the scaffold's own
|
|
130
130
|
# version into `.specfuse/VERSION`; check_scaffold_version() fails loud at startup if
|
|
131
131
|
# the consumer's scaffold is older than this, pointing at `specfuse upgrade`. Bump
|