specfuse-loop 0.23.2__tar.gz → 0.24.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.2/specfuse_loop.egg-info → specfuse_loop-0.24.0}/PKG-INFO +1 -1
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/pyproject.toml +1 -1
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/providers/triage.py +41 -6
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/run.py +72 -1
- specfuse_loop-0.24.0/specfuse/loop/data/VERSION +1 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/docs/methodology.md +11 -1
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/loop.py +36 -1
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/red_on_base.py +19 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/triage.py +23 -3
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0/specfuse_loop.egg-info}/PKG-INFO +1 -1
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse_loop.egg-info/SOURCES.txt +4 -0
- specfuse_loop-0.24.0/tests/test_blind_run_is_not_drained.py +105 -0
- specfuse_loop-0.24.0/tests/test_judged_event_points_at_findings.py +137 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_red_on_base_probe.py +42 -0
- specfuse_loop-0.24.0/tests/test_spec_work_unit_rules.py +173 -0
- specfuse_loop-0.24.0/tests/test_triage_projects_severity_label.py +190 -0
- specfuse_loop-0.23.2/specfuse/loop/data/VERSION +0 -1
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/LICENSE +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/NOTICE +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/README.md +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/setup.cfg +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/__init__.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/budget.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/diagnose_invoke.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/drafting_answers.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/drafting_invoke.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/drafting_questions.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/driver_command.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/driver_invoke.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/invoke.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/monitoring_read.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/providers/__init__.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/providers/answers.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/providers/bugs.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/providers/feature.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/providers/findings_autofix.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/providers/findings_diagnose.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/queue_read.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/repo_detect.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/severity_backfill.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/state.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/triage_invoke.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/agent/worktree.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/__init__.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/_filelock.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/_miniyaml.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/_wu_sections.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/adopt_feature.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/agent_policy.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/arm_eval.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/arm_sweep.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/arm_txn.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/bug_lane.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/bug_lane_run.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/bug_lane_state.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/build_provenance.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/caller_check.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/changelog.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/closing_requirements.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/cost.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/criteria_state.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/CHANGELOG.seed.md +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/LEARNINGS.template.md +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/docs/concepts/adopting-auto-mode.md +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/docs/concepts/architecture-addendum-gates-and-iterative-planning.md +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/docs/concepts/autonomy-stop-classes.md +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/docs/concepts/ralph-lineage.md +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/docs/getting-started.md +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/docs/glossary.md +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/docs/skills.md +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/gitignore.snippet +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/monitoring-secrets-checklist.md +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/monitoring.overrides.yml.example +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/monitoring.yml.example +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/roadmap.template.md +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/rules/close-discipline.md +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/rules/correlation-ids.md +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/rules/design-for-diagnosis.md +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/rules/human-output.md +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/rules/never-touch.md +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/rules/operator-escalation.md +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/rules/planning-discipline.md +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/rules/result-contract.md +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/rules/security-boundaries.md +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/rules/verification-discipline.md +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/rules-local/README.md +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/schemas/driver-event.schema.json +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/schemas/event.schema.json +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/schemas/events/initiative_created.schema.json +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/schemas/events/spec_issue_resolved.schema.json +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/schemas/events/spec_issue_routed.schema.json +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/schemas/events/spec_validated.schema.json +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/templates/DECISIONS.template.md +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/templates/GATE.template.md +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/templates/LEARNINGS-pending.template.md +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/templates/PLAN.template.md +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/templates/WU.template.md +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/verification.yml.example +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/data/workflows/specfuse-monitor.yml +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/decisions_format.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/driver_edit.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/escalation.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/events_stats.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/gate_commands.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/gate_eval.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/gh_backend.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/gh_features.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/heartbeat.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/judge.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/labels.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/learnings_distil.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/learnings_query.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/lint_closing.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/lint_monitoring.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/lint_plan.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/lint_roadmap.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/notify.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/notify_escalation.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/notify_sla.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/plan_baseline.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/policy_proposals.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/policy_review.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/prerun.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/prerun_capture.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/promotion.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/rearm_migration.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/replay_spin.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/roadmap_archive.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/scaffold.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/upgrade_merge_gate.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/loop/validate_event.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/monitor/__init__.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/monitor/adapters.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/monitor/artifact.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/monitor/autofix.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/monitor/autofix_invoke.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/monitor/autofix_run.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/monitor/autofix_state.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/monitor/cli.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/monitor/diagnose_cli.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/monitor/diagnosis.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/monitor/fingerprint.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/monitor/issues.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/monitor/providers/__init__.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/monitor/providers/_azure_auth.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/monitor/providers/azure_app_insights.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/monitor/providers/azure_service_bus.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/monitor/redaction.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse/monitor/schedule.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse_loop.egg-info/dependency_links.txt +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse_loop.egg-info/entry_points.txt +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse_loop.egg-info/requires.txt +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/specfuse_loop.egg-info/top_level.txt +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_abandon_feature_skill.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_abandoned_work_reported.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_abandoned_wu_strands_gate.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_adopt_feature.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_budget.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_bug_selection_dedup.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_default_features_root.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_driver_command.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_driver_invoke.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_driver_restart.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_escalation_on_issue.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_escalation_run_state.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_findings_seam.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_invoke_timeout.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_invoke_usage.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_item_worktree.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_lock.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_policy_key_ownership.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_policy_queue.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_policy_schema.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_policy_triage_dial.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_provider_answers.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_provider_bugs.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_provider_feature.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_provider_findings_autofix.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_provider_findings_diagnose.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_provider_isolation.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_provider_triage.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_queue_read.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_relays_driver_output.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_repo_detect.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_run.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_run_progress.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_seam.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_snapshot_refresh.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_agent_state.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_answer_escalation_skill.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_app_insights_adapters.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_archive_ref_reconciliation.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_arm_eval.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_arm_eval_lint_class.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_arm_eval_wiring.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_arm_gate_edits_uncommitted.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_arm_sweep.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_arm_sweep_gate.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_arm_txn.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_arm_wiring.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_artifact_redaction.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_attempt_note_untracked_files.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_attempt_outcome_contract.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_attempt_outcome_emission.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_attention_nonwriting_guard.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_attention_silence_section.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_attention_skill_structure.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_attribution_halt_claims.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_autoclose_deferral_visibility.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_autoclose_gate_note.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_autoclose_stub_states_what_passed.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_autofix_invoke.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_autofix_live.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_autofix_predicate.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_autofix_run.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_autofix_state.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_autosync.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_autosync_consent.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_autosync_firstrun.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_autosync_no_cwd_leak.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_autosync_plugin.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_azure_credential_resolution.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_backend.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_baseline_escalation_message.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_baseline_persistence.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_baseline_probe.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_baseline_provenance.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_baseline_tree_hash_key.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_bash_routing.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_bats_suites_gated.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_binary_verdict.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_binding_block_advisory.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_binding_block_allocation.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_binding_block_budget.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_bookkeeping_commit_crash_run.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_bookkeeping_commit_hook_crash.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_bookkeeping_no_verify.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_brief_covers_every_unit_escalation.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_bug_lane_ci_pending.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_bug_lane_guardrails.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_bug_lane_label_nonfatal.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_bug_lane_labels_registered.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_bug_lane_min_severity.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_bug_lane_policy_contract.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_bug_lane_pr_number_carried.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_bug_lane_preflight_and_dial.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_bug_lane_run.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_bug_lane_state.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_bug_lane_stopped_outcome_pr_number.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_bug_lane_test_paths.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_build_provenance.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_caller_check_ratchet.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_changed_file_test_selection.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_changelog_append_only.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_changelog_collection.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_changelog_release_wiring.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_changelog_schema.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_ci_conclusion_wait.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_ci_runs_every_gate.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_claude_resolution.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_close_b_pending_learnings.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_close_records_feature_oracle.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_close_wu_self_reference.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_closing_deliverable_guard.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_closing_guard_contracts.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_closing_guard_prediction.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_closing_learnings_destination.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_closing_requirements.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_coauthor_trailer_allowlist.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_conditional_reflection.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_console_encoding.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_convergent_iteration.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_convergent_wu_lint.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_correlation_id_override.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_correlation_id_two_surfaces.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_cost_lifetime.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_cost_tracking.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_criteria_per_gate_lint.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_criteria_state.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_criteria_worklist.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_decision_citation_lint.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_decision_followups_discharged.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_decision_override_lint.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_decisions_format.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_default_severity_aliases.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_degraded_oracle.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_deliverable_missing_note.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_deliverable_presence_gate.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_derive_agent_policy_review_mode.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_derive_agent_policy_skill.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_derive_monitoring_discovery.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_derive_monitoring_skill_registration.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_design_for_diagnosis_rule.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_deterministic_refusal_repeat.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_diagnose_headless.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_diagnose_issue_skill.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_diagnosis.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_diagnosis_roundtrip_live.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_dispatch_model_from_fix_scope.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_dispatch_skeleton.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_distilled_accept_step.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_doctor.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_doctor_command_preflight.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_done_feature_gates.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_draft_feature_answers_mode.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_drafting_answer_gate.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_drafting_invoke.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_drafting_question_issue.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_drafting_questions.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_drafting_reply_shape.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_driver_edit_detection.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_driver_event_registry_covers_emitters.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_driver_integration.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_driver_lock.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_driver_module_surface.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_driver_restart_halt_surfaced.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_driver_restart_halt_wiring.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_driver_restart_hold.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_driver_staleness_gate_summary.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_driver_staleness_warning.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_dry_run_no_branch_mutation.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_duration_tracking.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_empty_files_escalation.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_ensure_feature_branch.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_ensure_feature_branch_base.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_escalation_assignee_policy.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_escalation_contract.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_escalation_crash_containment.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_escalation_dedup_search.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_escalation_emit.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_event_gate_full_envelope.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_events_redaction.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_events_stats.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_extra_gates.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_failure_artifact_model.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_failure_signature_fail_prefix.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_failure_signature_skips_log_noise.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_feature_branch_auto_sync.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_feature_branch_squash_merge.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_feature_branch_stale_base.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_feature_id_resolution.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_feature_oracle_e2e.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_feature_provider_drafting_dispatch.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_feature_review_accumulation.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_filelock_portable.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_files_changed_auto_repair.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_find_pr_no_search_index.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_fingerprint.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_fix_bug_diff_self_check.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_fix_bug_headless.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_fix_bug_reads_comments.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_fold_contract_documented.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_followups_artifact.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_force_full_close.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_frontmatter_scalar_quoting.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_gate_broad_run.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_gate_budget.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_gate_budget_post_dispatch.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_gate_commands_needs.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_gate_eval.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_gate_eval_calibration.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_gate_eval_intermediate_wiring.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_gate_eval_repo_flag.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_gate_eval_terminal_wiring.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_gate_report_verdict.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_gh_backend.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_gh_features.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_git_env_isolation.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_gitleaks_pinning.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_groom_backlog_skill.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_guard_refusal_failure_class.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_guard_refusal_failure_excerpt.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_guard_repair_e2e.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_guard_repair_prompt.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_hashed_denylist.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_hashed_denylist_ci.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_heartbeat.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_heartbeat_adapter.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_human_output_rule.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_human_work_unit.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_hygiene_boundary_guidance.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_hyphenated_gate_name.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_ignored_cleanup_before_probe.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_init_integration.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_installed_copy_driver_e2e.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_interpreter_normalization.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_judge_close_path.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_judge_cost.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_judge_cost_fold_committed.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_judge_module.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_judge_path_registry.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_judge_terminal_flip_state.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_label_description_length.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_label_provisioning_runner_contract.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_label_registry.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_label_registry_covers_consumers.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_language_aware_narrow_selection.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lazy_baseline_e2e.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_leak_findings_redaction.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_leak_scan.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_leak_scan_content.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_learnings_curate_vendored_promote.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_learnings_distil_cli.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_learnings_query.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_learnings_staging.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_learnings_suggest_signature_filter.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_learnings_weights.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_legacy_4wu_terminal_flips.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lifecycle_integration.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lifetime_accounting.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_ac_observable.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_bare_produces_path.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_boundary_consistency.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_boundary_extraction.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_close_intermediate.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_close_verdict_not_predecided.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_close_wu.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_closing.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_closing_criteria.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_closing_criteria_pristine.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_correlation_id.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_correlation_id_close_intermediate.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_covers_load_wu_invariants.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_feature_oracle_declared.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_gate_proportionality.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_monitoring.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_oracle_env.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_plan_contract_fields.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_plan_errors.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_plan_next_draft.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_plan_relative_import.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_plan_verdict_exempt.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_planning_sections.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_produces_driver_helper.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_produces_satisfiability.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_roadmap.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_sections.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_task_graph_yaml_selection.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_lint_verification_reference.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_live_tests_opt_in.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_load_bearing_close_lint.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_loop_attempt_evidence.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_loop_auto_archive.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_loop_caveman_preamble.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_loop_close_intermediate.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_loop_criteria_skeleton.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_loop_criteria_survival.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_loop_defaults_by_type.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_loop_effort.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_loop_failure_note_cap.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_loop_files_changed_guard.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_loop_gate_budget.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_loop_gate_needs.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_loop_model_alias.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_loop_orchestration.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_loop_post_pass_invariant.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_loop_reset_preserving_events.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_loop_smoke_runner.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_loop_unsandboxed.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_loop_worklist_injection.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_loop_zero_token_guard.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_make_repo_gpgsign_isolation.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_marker_dual_shape.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_maven_failure_excerpt.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_max_attempts_configurable.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_max_open_prs.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_migrate_legacy.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_miniyaml_equivalence.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_miniyaml_negative.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_monitor_cli.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_monitor_issue_lifecycle.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_monitor_runner_surfaces.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_monitoring_bootstrap_artifacts.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_monitoring_cron_dialect.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_monitoring_example.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_monitoring_fenced_blocks.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_monitoring_seed.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_narrow_fallback_warning.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_needs_decision_outcome.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_no_pytest_subprocess.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_notify.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_notify_escalation.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_notify_sla.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_operator_escalation_rule.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_oracle_set_declared.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_oracle_unrunnable_attribution.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_per_run_item_cap_disclosure.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_pick_feature_skill.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_pin_honesty_and_integrity.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_pin_marker_not_inherited.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_pin_marker_spawn_sites.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_plan_baseline.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_planned_cost_lint.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_planning_cost_floor.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_plugin_prose_cross_refs.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_plugin_qualified_dispatch.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_policy_budgets_applied.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_policy_proposals.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_policy_review.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_pr_checks_json_field.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_pr_reference_in_title.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_pre_existing_claims_cite_evidence.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_prep_halt_operator_surface.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_prepare_feature.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_prepare_scaffold_sync.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_prerun_capture.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_prerun_oracles.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_prerun_wiring.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_produced_document_verdict_guard.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_produces_bracket_literal.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_produces_bracket_wildcard.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_produces_field.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_produces_in_diff.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_produces_justification.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_produces_recursive_glob.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_produces_semantics_unified.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_produces_shape.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_progress_forward_note.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_progress_lines_end_to_end.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_promotion_ledger.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_provider_escalation_traces.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_provision_labels.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_queue_stalled_adapter.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_rearm_clears_stale_criteria_state.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_rearm_fold_marker.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_rearm_migration.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_red_baseline_never_reused.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_red_on_base_guardrail.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_redaction_windows_home.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_reflection_required_signals.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_replan_end_to_end.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_replan_event_emission.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_replan_note_collision.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_replan_trigger.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_replan_turn_contract.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_replay_spin.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_required_checks.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_resolve_base.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_restart_cap_counts_progress.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_result_block.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_result_block_audience.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_resume_command_build.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_retain_on_guard_refusal_docs.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_roadmap_add_skill.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_roadmap_archive_cli.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_roadmap_archive_shim.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_roadmap_archive_skill.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_roadmap_archive_skill_delegates.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_roadmap_link_gate.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_roadmap_row_parser.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_ruff_verdict_lines.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_rule_import_convergence.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_run_level_failure_breaker.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_scaffold_data_in_sync.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_scaffold_doc_hygiene.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_scaffold_docs.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_scaffold_init.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_scaffold_label_provisioning.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_scaffold_manifest.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_scaffold_resources.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_scaffold_seed_sanity.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_scaffold_skew_halts.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_scaffold_upgrade.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_scaffold_wiring.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_schedule_dialect.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_seed_documents_prerun_keys.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_service_bus_dlq_adapter.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_session_env_preflight.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_severity_aliases.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_severity_backfill_apply.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_severity_backfill_end_to_end.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_severity_backfill_limit.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_severity_backfill_marker.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_severity_backfill_run.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_severity_label_projection.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_severity_reconciliation.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_severity_rubric.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_shipped_templates_parse.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_skill_discovery_links.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_skills_vendored_in_sync.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_spinning_rearm_gate.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_spinout_brief_end_to_end.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_spinout_brief_replan_option.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_squash_commit_hook_crash.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_stop_rationale_captured.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_stranded_halt_cause.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_surefire_run_level_signature.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_surefire_verdict_lines.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_template_closing_shapes.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_terminal_flip_ownership.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_terminal_flips.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_terminal_gate_message.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_tiered_verification_e2e.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_timeout_kill_portable.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_triage.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_triage_apply.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_triage_severity_classify.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_triage_severity_end_to_end.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_triage_severity_write.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_triage_skill_contract.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_triage_skips_agent_escalations.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_tsc_verdict_lines.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_umbrella_version_resolution.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_unterminated_frontmatter.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_untracked_feature_folder.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_untracked_file_absorption.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_upgrade_integration.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_upgrade_merge_gate.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_validate_event.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_validate_event_driver_types.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_verdict_coupling.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_verify_empty_gate_set.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_version_consistency.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_version_skew.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_windows_gate_exec.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_worktree_restore.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.0}/tests/test_wu_execution_metadata.py +0 -0
- {specfuse_loop-0.23.2 → specfuse_loop-0.24.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.24.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.24.0
|
|
@@ -759,7 +759,17 @@ when and how these get created.
|
|
|
759
759
|
| State backend | WU / GATE file frontmatter | GitHub issue labels + feature registry |
|
|
760
760
|
| Dispatch | driver shells out (`claude -p`) | inbox files + polling loop |
|
|
761
761
|
| Branch / merge | one branch, squash per WU | branch + PR per task, merge watcher |
|
|
762
|
-
| Spec front-end |
|
|
762
|
+
| 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 |
|
|
763
|
+
|
|
764
|
+
The loop's spec front-end is three-valued because a repository whose entire
|
|
765
|
+
deliverable **is** the spec tree fits neither of the two values this row
|
|
766
|
+
carried before (#3407): it is not "optional", and the task graph is not
|
|
767
|
+
"authored directly" — the work units author spec files. One consumer carried
|
|
768
|
+
that third case in about forty lines of project `CLAUDE.md` because the row
|
|
769
|
+
could not express it. In the **authored** mode the work-unit contract picks up
|
|
770
|
+
two constraints that do not apply to code units — see
|
|
771
|
+
`authoring-work-units` §6 (the whole tree validates) and §12 (a structural
|
|
772
|
+
assertion substitutes for the red test).
|
|
763
773
|
|
|
764
774
|
Everything above those rows — the unit hierarchy, ownership split, WU contract,
|
|
765
775
|
verification-as-oracle, the gate cycle, plan-next, LEARNINGS, and autonomy — is
|
|
@@ -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.24.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
|
|
@@ -9488,6 +9488,15 @@ def capture_gate_diff(
|
|
|
9488
9488
|
return "\n\n".join(parts)
|
|
9489
9489
|
|
|
9490
9490
|
|
|
9491
|
+
def _is_relative_to(path: Path, other: Path) -> bool:
|
|
9492
|
+
"""`Path.is_relative_to`, tolerant of paths that do not resolve."""
|
|
9493
|
+
try:
|
|
9494
|
+
path.relative_to(other)
|
|
9495
|
+
except ValueError:
|
|
9496
|
+
return False
|
|
9497
|
+
return True
|
|
9498
|
+
|
|
9499
|
+
|
|
9491
9500
|
def write_judge_followups(
|
|
9492
9501
|
feature_dir: Path, gate_number: int, findings: list,
|
|
9493
9502
|
) -> Path:
|
|
@@ -9644,6 +9653,32 @@ def judge_close(
|
|
|
9644
9653
|
feature_dir, gate_number, result.findings)
|
|
9645
9654
|
payload["lowered"] = True
|
|
9646
9655
|
payload["verdict"] = "not_met"
|
|
9656
|
+
# Say WHERE the findings went (#3406). They are written verbatim to
|
|
9657
|
+
# FOLLOW-UPS.md above and committed below, but the event said only
|
|
9658
|
+
# `findings: N` with `reason: null` — so a reader of events.jsonl had
|
|
9659
|
+
# nothing pointing out of it. One operator searched events.jsonl,
|
|
9660
|
+
# `work/` and for a repository-level judge artifact, concluded the text
|
|
9661
|
+
# did not exist, and priced a third close at ~$12 to re-derive a
|
|
9662
|
+
# finding that was sitting in a committed file.
|
|
9663
|
+
#
|
|
9664
|
+
# The criteria go in; the finding TEXT deliberately does not. It is
|
|
9665
|
+
# already recorded verbatim in one place, and copying it into a second
|
|
9666
|
+
# invites the two to disagree — `write_judge_followups` exists so the
|
|
9667
|
+
# judge's words are not reformatted by the driver.
|
|
9668
|
+
payload["findings_path"] = str(
|
|
9669
|
+
Path(followups).relative_to(Path(feature_dir).parent.parent)
|
|
9670
|
+
if _is_relative_to(Path(followups), Path(feature_dir).parent.parent)
|
|
9671
|
+
else Path(followups)
|
|
9672
|
+
)
|
|
9673
|
+
payload["finding_criteria"] = [
|
|
9674
|
+
f.criterion for f in result.findings if getattr(f, "criterion", None)
|
|
9675
|
+
]
|
|
9676
|
+
_named = ", ".join(payload["finding_criteria"]) or "no named criterion"
|
|
9677
|
+
payload["reason"] = (
|
|
9678
|
+
f"judge lowered the verdict on {payload['findings']} finding(s) "
|
|
9679
|
+
f"({_named}); the judge's own words are in "
|
|
9680
|
+
f"{payload['findings_path']}"
|
|
9681
|
+
)
|
|
9647
9682
|
commit_bookkeeping(
|
|
9648
9683
|
[wu.file, followups],
|
|
9649
9684
|
f"chore(loop): {wu.wu_id} judge lowered verdict to not_met"
|
|
@@ -52,6 +52,12 @@ from pathlib import Path
|
|
|
52
52
|
from typing import Any, Callable, Optional
|
|
53
53
|
|
|
54
54
|
|
|
55
|
+
#: POSIX reserves these for the shell's own failure to run the command: 126 is
|
|
56
|
+
#: "found but not executable", 127 is "not found". Neither says anything about
|
|
57
|
+
#: the test, so neither may be read as a verdict on it (#3403).
|
|
58
|
+
_COULD_NOT_EXECUTE = frozenset({126, 127})
|
|
59
|
+
|
|
60
|
+
|
|
55
61
|
def select_test_files(changed_files: Any, test_paths: Any) -> list:
|
|
56
62
|
"""The changed files that live under a declared test root.
|
|
57
63
|
|
|
@@ -126,6 +132,19 @@ def test_was_red_on_base(
|
|
|
126
132
|
code = getattr(result, "returncode", None)
|
|
127
133
|
if not isinstance(code, int):
|
|
128
134
|
return None
|
|
135
|
+
if code in _COULD_NOT_EXECUTE:
|
|
136
|
+
# The shell could not run the command at all. That is "the check
|
|
137
|
+
# never ran", not "the test failed" -- and the difference is the
|
|
138
|
+
# whole point of keeping those two declining reasons apart.
|
|
139
|
+
#
|
|
140
|
+
# Measured: an operator whose command is a committed script hits
|
|
141
|
+
# this on every PR whose merge base predates that script. The
|
|
142
|
+
# worktree is the BASE tree, so the script is simply absent there,
|
|
143
|
+
# `sh` exits 127, and reading that as red would let the merge
|
|
144
|
+
# through on the strength of a command that never ran. Fail-open,
|
|
145
|
+
# in the one guardrail whose entire job is to be harder to satisfy
|
|
146
|
+
# than the others.
|
|
147
|
+
return None
|
|
129
148
|
# Non-zero at the base is the proof we want: the test the PR adds does
|
|
130
149
|
# not hold on the tree the fix was written against.
|
|
131
150
|
return code != 0
|
|
@@ -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(
|
|
@@ -224,6 +224,7 @@ tests/test_binary_verdict.py
|
|
|
224
224
|
tests/test_binding_block_advisory.py
|
|
225
225
|
tests/test_binding_block_allocation.py
|
|
226
226
|
tests/test_binding_block_budget.py
|
|
227
|
+
tests/test_blind_run_is_not_drained.py
|
|
227
228
|
tests/test_bookkeeping_commit_crash_run.py
|
|
228
229
|
tests/test_bookkeeping_commit_hook_crash.py
|
|
229
230
|
tests/test_bookkeeping_no_verify.py
|
|
@@ -382,6 +383,7 @@ tests/test_judge_cost_fold_committed.py
|
|
|
382
383
|
tests/test_judge_module.py
|
|
383
384
|
tests/test_judge_path_registry.py
|
|
384
385
|
tests/test_judge_terminal_flip_state.py
|
|
386
|
+
tests/test_judged_event_points_at_findings.py
|
|
385
387
|
tests/test_label_description_length.py
|
|
386
388
|
tests/test_label_provisioning_runner_contract.py
|
|
387
389
|
tests/test_label_registry.py
|
|
@@ -571,6 +573,7 @@ tests/test_severity_rubric.py
|
|
|
571
573
|
tests/test_shipped_templates_parse.py
|
|
572
574
|
tests/test_skill_discovery_links.py
|
|
573
575
|
tests/test_skills_vendored_in_sync.py
|
|
576
|
+
tests/test_spec_work_unit_rules.py
|
|
574
577
|
tests/test_spinning_rearm_gate.py
|
|
575
578
|
tests/test_spinout_brief_end_to_end.py
|
|
576
579
|
tests/test_spinout_brief_replan_option.py
|
|
@@ -587,6 +590,7 @@ tests/test_tiered_verification_e2e.py
|
|
|
587
590
|
tests/test_timeout_kill_portable.py
|
|
588
591
|
tests/test_triage.py
|
|
589
592
|
tests/test_triage_apply.py
|
|
593
|
+
tests/test_triage_projects_severity_label.py
|
|
590
594
|
tests/test_triage_severity_classify.py
|
|
591
595
|
tests/test_triage_severity_end_to_end.py
|
|
592
596
|
tests/test_triage_severity_write.py
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright 2026 Specfuse contributors
|
|
3
|
+
# Licensed under the Apache License, Version 2.0. See LICENSE.
|
|
4
|
+
"""A run that could not see the queue does not report `drained` (#3392).
|
|
5
|
+
|
|
6
|
+
When the snapshot's issue listing failed, `run_agent` reported the failure,
|
|
7
|
+
treated the listing as empty, found no candidates and stopped with `drained` —
|
|
8
|
+
exit 0, zero attempted, zero escalated.
|
|
9
|
+
|
|
10
|
+
Observed with nine dispatchable bugs in the queue, two of them deliberately
|
|
11
|
+
ranked first for that run:
|
|
12
|
+
|
|
13
|
+
snapshot: 0 open issues, 0 open PRs, 115 features, queue=0
|
|
14
|
+
snapshot: issues unreadable — gh issue list ... tls: failed to verify certificate
|
|
15
|
+
run finished — drained after 0.00 minutes
|
|
16
|
+
stop reason: drained
|
|
17
|
+
|
|
18
|
+
`drained` means "worked through everything there was". Here it meant "could not
|
|
19
|
+
see anything". The two are opposite, and the summary — the part an operator
|
|
20
|
+
reads, and the only part automation parses — rendered them identically.
|
|
21
|
+
|
|
22
|
+
This is the shape this project keeps finding: **a failure reported in the
|
|
23
|
+
grammar of success.** An unattended runner is where it costs most; a scheduled
|
|
24
|
+
run reporting `drained` every night while its credentials are broken looks like
|
|
25
|
+
a quiet backlog.
|
|
26
|
+
|
|
27
|
+
Refusing rather than draining follows `STOP_DIRTY_TREE`'s precedent: nothing was
|
|
28
|
+
budgeted because nothing was dispatched, and every provider that matters reads
|
|
29
|
+
`snapshot.issues`, so a run without it can do nothing except mislead.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
|
|
34
|
+
import unittest
|
|
35
|
+
|
|
36
|
+
from specfuse.agent.run import (
|
|
37
|
+
STOP_DRAINED,
|
|
38
|
+
STOP_SNAPSHOT_UNREADABLE,
|
|
39
|
+
_refuse_unreadable_snapshot,
|
|
40
|
+
exit_code_for,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class _Snapshot:
|
|
45
|
+
def __init__(self, issues_error=None, prs_error=None):
|
|
46
|
+
self.issues_error = issues_error
|
|
47
|
+
self.prs_error = prs_error
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class TheRefusal(unittest.TestCase):
|
|
51
|
+
|
|
52
|
+
def _refuse(self, reason="gh issue list exited 1: tls: failed to verify"):
|
|
53
|
+
lines = []
|
|
54
|
+
summary = _refuse_unreadable_snapshot(reason, report=lines.append)
|
|
55
|
+
return summary, lines
|
|
56
|
+
|
|
57
|
+
def test_it_does_not_report_drained(self):
|
|
58
|
+
summary, _ = self._refuse()
|
|
59
|
+
|
|
60
|
+
self.assertEqual(STOP_SNAPSHOT_UNREADABLE, summary.stop_reason)
|
|
61
|
+
self.assertNotEqual(STOP_DRAINED, summary.stop_reason)
|
|
62
|
+
|
|
63
|
+
def test_nothing_is_attempted_or_completed(self):
|
|
64
|
+
summary, _ = self._refuse()
|
|
65
|
+
|
|
66
|
+
self.assertEqual(0, summary.items_attempted)
|
|
67
|
+
self.assertEqual(0, summary.items_completed)
|
|
68
|
+
|
|
69
|
+
def test_it_escalates_so_the_summary_is_not_silent(self):
|
|
70
|
+
summary, _ = self._refuse()
|
|
71
|
+
|
|
72
|
+
self.assertEqual(1, summary.items_escalated)
|
|
73
|
+
self.assertEqual(1, len(summary.escalations))
|
|
74
|
+
|
|
75
|
+
def test_the_escalation_carries_which_listing_failed(self):
|
|
76
|
+
summary, _ = self._refuse("gh issue list exited 1: tls: bad cert")
|
|
77
|
+
|
|
78
|
+
self.assertIn("tls: bad cert", summary.escalations[0].reason)
|
|
79
|
+
|
|
80
|
+
def test_it_says_so_on_the_way_out(self):
|
|
81
|
+
_, lines = self._refuse()
|
|
82
|
+
|
|
83
|
+
self.assertTrue(
|
|
84
|
+
any("refused" in line for line in lines),
|
|
85
|
+
f"the run must say it refused, not finish quietly: {lines}",
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class TheExitCode(unittest.TestCase):
|
|
90
|
+
"""A scheduled run's output must be self-describing without its log."""
|
|
91
|
+
|
|
92
|
+
def test_a_blind_run_exits_non_zero(self):
|
|
93
|
+
self.assertNotEqual(0, exit_code_for(STOP_SNAPSHOT_UNREADABLE))
|
|
94
|
+
|
|
95
|
+
def test_a_genuinely_drained_run_still_exits_zero(self):
|
|
96
|
+
self.assertEqual(0, exit_code_for(STOP_DRAINED))
|
|
97
|
+
|
|
98
|
+
def test_an_unknown_reason_does_not_invent_a_failure(self):
|
|
99
|
+
# Every other stop reason kept exit 0; this narrows to the one case
|
|
100
|
+
# that is a refusal rather than an outcome.
|
|
101
|
+
self.assertEqual(0, exit_code_for("some_future_reason"))
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
if __name__ == "__main__": # pragma: no cover
|
|
105
|
+
unittest.main()
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright 2026 Specfuse contributors
|
|
3
|
+
# Licensed under the Apache License, Version 2.0. See LICENSE.
|
|
4
|
+
"""The `judged` event says where the judge's findings went (#3406).
|
|
5
|
+
|
|
6
|
+
The issue reported that a judge lowering a terminal verdict "persists no
|
|
7
|
+
findings". It does persist them: `write_judge_followups` writes each finding
|
|
8
|
+
verbatim into `FOLLOW-UPS.md` and `commit_bookkeeping` commits it. Verified in
|
|
9
|
+
the reporting repository's own history — `chore(loop): FEAT-2026-0168/G1-CLOSE
|
|
10
|
+
judge lowered verdict to not_met` contains "Filed by the judge for gate 1. Each
|
|
11
|
+
entry is the judge's own words."
|
|
12
|
+
|
|
13
|
+
What is missing is the pointer. The `judged` event records `findings: 1` and
|
|
14
|
+
`reason: null`, and names nothing. The reporter looked in `events.jsonl`, in
|
|
15
|
+
`<feature>/work/`, and for a repository-level judge artifact — the three places
|
|
16
|
+
the event gave them no reason to leave — and concluded the text was nowhere.
|
|
17
|
+
They then priced a third close at ~$12 to re-derive a finding that was sitting
|
|
18
|
+
in a committed file.
|
|
19
|
+
|
|
20
|
+
So the event becomes self-describing: it names the file the findings were
|
|
21
|
+
written to and the criteria they were raised against, and its `reason` stops
|
|
22
|
+
being `null` on the one path where something specific was found.
|
|
23
|
+
|
|
24
|
+
The finding TEXT deliberately stays out of the event. It is already recorded
|
|
25
|
+
verbatim in one place; copying it into a second invites the two to disagree,
|
|
26
|
+
and `write_judge_followups` exists precisely so the judge's words are not
|
|
27
|
+
reformatted by the driver.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import unittest
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
from tempfile import TemporaryDirectory
|
|
35
|
+
from types import SimpleNamespace
|
|
36
|
+
from unittest import mock
|
|
37
|
+
|
|
38
|
+
from tests._loop_loader import load_loop
|
|
39
|
+
|
|
40
|
+
loop = load_loop()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class _Finding:
|
|
44
|
+
def __init__(self, criterion, text):
|
|
45
|
+
self.criterion = criterion
|
|
46
|
+
self.text = text
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class TheLoweredEventNamesTheRecord(unittest.TestCase):
|
|
50
|
+
|
|
51
|
+
def _payload_for(self, findings):
|
|
52
|
+
"""Drive judge_close's lowered branch with the file writes stubbed."""
|
|
53
|
+
tmp = TemporaryDirectory()
|
|
54
|
+
self.addCleanup(tmp.cleanup)
|
|
55
|
+
feature_dir = Path(tmp.name)
|
|
56
|
+
(feature_dir / "PLAN.md").write_text("---\nfeature_id: FEAT-2026-9999\n---\n")
|
|
57
|
+
wu_file = feature_dir / "WU-90-close.md"
|
|
58
|
+
wu_file.write_text("---\nid: FEAT-2026-9999/G1-CLOSE\nverdict: met\n---\n")
|
|
59
|
+
wu = SimpleNamespace(
|
|
60
|
+
file=wu_file, wu_id="FEAT-2026-9999/G1-CLOSE", verdict="met")
|
|
61
|
+
|
|
62
|
+
result = SimpleNamespace(verdict="not_met", findings=findings, reason=None)
|
|
63
|
+
|
|
64
|
+
with mock.patch.object(loop, "_gate_identity",
|
|
65
|
+
return_value=(1, feature_dir / "GATE-01.md")), \
|
|
66
|
+
mock.patch.object(loop, "_judge_disabled", return_value=False), \
|
|
67
|
+
mock.patch.object(loop, "resolve_gate_start_sha",
|
|
68
|
+
return_value=("abc123", "gate frontmatter")), \
|
|
69
|
+
mock.patch.object(loop, "capture_gate_diff", return_value="diff"), \
|
|
70
|
+
mock.patch.object(loop, "build_judge_bundle", return_value={}), \
|
|
71
|
+
mock.patch.object(loop, "render_judge_prompt", return_value="prompt"), \
|
|
72
|
+
mock.patch.object(loop, "parse_judge_result", return_value=result), \
|
|
73
|
+
mock.patch.object(loop, "write_frontmatter_field"), \
|
|
74
|
+
mock.patch.object(loop, "commit_bookkeeping"):
|
|
75
|
+
return loop.judge_close(
|
|
76
|
+
wu, feature_dir, feature_dir,
|
|
77
|
+
runner=lambda prompt, timeout=None: ("raw", {}),
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
def test_it_names_the_file_the_findings_were_written_to(self):
|
|
81
|
+
payload = self._payload_for([_Finding("T11#3", "### T11#3\n\nThe thing.")])
|
|
82
|
+
|
|
83
|
+
self.assertTrue(payload["lowered"])
|
|
84
|
+
self.assertIn("FOLLOW-UPS.md", payload.get("findings_path", ""))
|
|
85
|
+
|
|
86
|
+
def test_it_names_the_criteria(self):
|
|
87
|
+
payload = self._payload_for([
|
|
88
|
+
_Finding("T11#3", "### T11#3\n\nOne."),
|
|
89
|
+
_Finding("T06#2", "### T06#2\n\nTwo."),
|
|
90
|
+
])
|
|
91
|
+
|
|
92
|
+
self.assertEqual(["T11#3", "T06#2"], payload.get("finding_criteria"))
|
|
93
|
+
self.assertEqual(2, payload["findings"])
|
|
94
|
+
|
|
95
|
+
def test_the_reason_is_no_longer_null_when_something_was_found(self):
|
|
96
|
+
payload = self._payload_for([_Finding("T11#3", "### T11#3\n\nThe thing.")])
|
|
97
|
+
|
|
98
|
+
self.assertIsNotNone(
|
|
99
|
+
payload["reason"],
|
|
100
|
+
"`findings: 1` with `reason: null` is what sent a reader looking "
|
|
101
|
+
"in three places that do not hold the text (#3406)",
|
|
102
|
+
)
|
|
103
|
+
self.assertIn("FOLLOW-UPS.md", payload["reason"])
|
|
104
|
+
self.assertIn("T11#3", payload["reason"])
|
|
105
|
+
|
|
106
|
+
def test_the_finding_text_is_not_copied_into_the_event(self):
|
|
107
|
+
# One verbatim record, not two that can disagree.
|
|
108
|
+
payload = self._payload_for([
|
|
109
|
+
_Finding("T11#3", "### T11#3\n\nA sentence only in the file.")])
|
|
110
|
+
|
|
111
|
+
self.assertNotIn("A sentence only in the file", repr(payload))
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class ThePathsThatFoundNothingAreUnchanged(unittest.TestCase):
|
|
115
|
+
|
|
116
|
+
def test_a_disabled_judge_still_explains_itself(self):
|
|
117
|
+
tmp = TemporaryDirectory()
|
|
118
|
+
self.addCleanup(tmp.cleanup)
|
|
119
|
+
feature_dir = Path(tmp.name)
|
|
120
|
+
(feature_dir / "PLAN.md").write_text(
|
|
121
|
+
"---\nfeature_id: FEAT-2026-9999\njudge_disabled: true\n---\n")
|
|
122
|
+
wu_file = feature_dir / "WU-90-close.md"
|
|
123
|
+
wu_file.write_text("---\nid: X/G1-CLOSE\nverdict: met\n---\n")
|
|
124
|
+
wu = SimpleNamespace(file=wu_file, wu_id="X/G1-CLOSE", verdict="met")
|
|
125
|
+
|
|
126
|
+
with mock.patch.object(loop, "_gate_identity",
|
|
127
|
+
return_value=(1, feature_dir / "GATE-01.md")):
|
|
128
|
+
payload = loop.judge_close(wu, feature_dir, feature_dir)
|
|
129
|
+
|
|
130
|
+
self.assertFalse(payload["lowered"])
|
|
131
|
+
self.assertEqual(0, payload["findings"])
|
|
132
|
+
self.assertIsNotNone(payload["reason"])
|
|
133
|
+
self.assertNotIn("findings_path", payload)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
if __name__ == "__main__": # pragma: no cover
|
|
137
|
+
unittest.main()
|