specfuse-loop 0.31.0__tar.gz → 0.32.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.31.0/specfuse_loop.egg-info → specfuse_loop-0.32.0}/PKG-INFO +1 -1
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/pyproject.toml +1 -1
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/arm_eval.py +2 -1
- specfuse_loop-0.32.0/specfuse/loop/changelog_fragments.py +232 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/closing_requirements.py +32 -2
- specfuse_loop-0.32.0/specfuse/loop/data/VERSION +1 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/docs/lifecycles.md +12 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/rules/close-discipline.md +7 -6
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/ids.py +16 -2
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/layout.py +54 -3
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/lint_closing.py +7 -12
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/lint_roadmap.py +65 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/loop.py +16 -25
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0/specfuse_loop.egg-info}/PKG-INFO +1 -1
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse_loop.egg-info/SOURCES.txt +5 -0
- specfuse_loop-0.32.0/tests/test_blocked_by_entry_anchor.py +163 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_caller_check_ratchet.py +0 -1
- specfuse_loop-0.32.0/tests/test_changelog_fragments.py +420 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_ids_migrate_all.py +21 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_judge_path_registry.py +2 -2
- specfuse_loop-0.32.0/tests/test_migrate_all_scales.py +56 -0
- specfuse_loop-0.32.0/tests/test_repo_changelog_opt_in.py +28 -0
- specfuse_loop-0.31.0/specfuse/loop/data/VERSION +0 -1
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/LICENSE +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/NOTICE +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/README.md +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/setup.cfg +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/__init__.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/budget.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/diagnose_invoke.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/drafting_answers.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/drafting_invoke.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/drafting_questions.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/driver_command.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/driver_invoke.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/invoke.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/monitoring_read.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/providers/__init__.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/providers/answers.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/providers/bugs.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/providers/feature.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/providers/findings_autofix.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/providers/findings_diagnose.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/providers/triage.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/queue_read.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/repo_detect.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/run.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/severity_backfill.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/state.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/triage_invoke.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/agent/worktree.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/__init__.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/_filelock.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/_miniyaml.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/_wu_sections.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/adopt_feature.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/agent_policy.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/arm_sweep.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/arm_txn.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/bug_lane.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/bug_lane_run.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/bug_lane_state.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/build_provenance.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/caller_check.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/changelog.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/cost.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/criteria_state.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/CHANGELOG.seed.md +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/LEARNINGS.template.md +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/docs/concepts/adopting-auto-mode.md +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/docs/concepts/architecture-addendum-gates-and-iterative-planning.md +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/docs/concepts/autonomy-stop-classes.md +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/docs/concepts/monitoring-runners.md +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/docs/concepts/monitoring-schema.md +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/docs/concepts/monitoring-to-fix.md +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/docs/concepts/ralph-lineage.md +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/docs/flows.html +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/docs/getting-started.md +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/docs/glossary.md +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/docs/methodology.md +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/docs/skills.md +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/gitignore.snippet +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/monitoring-secrets-checklist.md +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/monitoring.overrides.yml.example +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/monitoring.yml.example +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/roadmap.template.md +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/rules/correlation-ids.md +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/rules/design-for-diagnosis.md +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/rules/human-output.md +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/rules/never-touch.md +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/rules/operator-escalation.md +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/rules/planning-discipline.md +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/rules/result-contract.md +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/rules/security-boundaries.md +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/rules/verification-discipline.md +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/rules-local/README.md +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/schemas/driver-event.schema.json +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/schemas/event.schema.json +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/schemas/events/initiative_created.schema.json +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/schemas/events/spec_issue_resolved.schema.json +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/schemas/events/spec_issue_routed.schema.json +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/schemas/events/spec_validated.schema.json +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/templates/DECISIONS.template.md +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/templates/GATE.template.md +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/templates/LEARNINGS-pending.template.md +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/templates/PLAN.template.md +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/templates/WU.template.md +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/verification.yml.example +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/data/workflows/specfuse-monitor.yml +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/decisions_format.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/driver_edit.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/escalation.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/events_stats.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/gate_commands.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/gate_eval.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/gen_contract.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/gh_backend.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/gh_features.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/heartbeat.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/judge.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/labels.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/learnings_distil.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/learnings_query.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/lint_monitoring.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/lint_plan.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/notify.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/notify_escalation.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/notify_sla.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/plan_baseline.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/policy_proposals.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/policy_review.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/prerun.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/prerun_capture.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/promotion.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/rearm_migration.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/red_on_base.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/replay_spin.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/roadmap_archive.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/scaffold.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/triage.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/upgrade_merge_gate.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/loop/validate_event.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/monitor/__init__.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/monitor/adapters.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/monitor/artifact.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/monitor/autofix.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/monitor/autofix_invoke.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/monitor/autofix_run.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/monitor/autofix_state.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/monitor/cli.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/monitor/diagnose_cli.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/monitor/diagnosis.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/monitor/durations.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/monitor/fingerprint.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/monitor/issues.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/monitor/providers/__init__.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/monitor/providers/_azure_auth.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/monitor/providers/azure_app_insights.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/monitor/providers/azure_service_bus.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/monitor/redaction.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse/monitor/schedule.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse_loop.egg-info/dependency_links.txt +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse_loop.egg-info/entry_points.txt +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse_loop.egg-info/requires.txt +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/specfuse_loop.egg-info/top_level.txt +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_abandon_feature_skill.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_abandoned_work_reported.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_abandoned_wu_strands_gate.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_adopt_feature.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_budget.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_bug_selection_dedup.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_default_features_root.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_driver_command.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_driver_invoke.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_driver_restart.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_escalation_on_issue.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_escalation_run_state.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_findings_seam.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_invoke_timeout.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_invoke_usage.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_item_worktree.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_lock.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_policy_key_ownership.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_policy_queue.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_policy_schema.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_policy_triage_dial.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_provider_answers.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_provider_bugs.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_provider_feature.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_provider_findings_autofix.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_provider_findings_diagnose.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_provider_isolation.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_provider_triage.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_queue_read.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_relays_driver_output.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_repo_detect.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_run.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_run_progress.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_seam.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_snapshot_refresh.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_agent_state.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_answer_escalation_skill.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_app_insights_adapters.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_app_insights_harvest_window.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_app_insights_query_schema_check.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_app_insights_workspace_schema.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_archive_ref_reconciliation.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_arm_eval.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_arm_eval_lint_class.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_arm_eval_terminal.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_arm_eval_wiring.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_arm_gate_edits_uncommitted.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_arm_sweep.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_arm_sweep_gate.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_arm_txn.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_arm_wiring.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_artifact_redaction.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_attempt_note_untracked_files.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_attempt_outcome_contract.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_attempt_outcome_emission.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_attention_nonwriting_guard.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_attention_silence_section.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_attention_skill_structure.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_attribution_halt_claims.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_autoclose_deferral_visibility.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_autoclose_gate_note.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_autoclose_stub_states_what_passed.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_autofix_invoke.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_autofix_live.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_autofix_predicate.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_autofix_run.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_autofix_state.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_autosync.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_autosync_consent.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_autosync_firstrun.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_autosync_no_cwd_leak.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_autosync_plugin.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_azure_credential_resolution.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_backend.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_baseline_escalation_message.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_baseline_persistence.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_baseline_probe.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_baseline_provenance.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_baseline_tree_hash_key.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_bash_routing.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_bats_suites_gated.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_binary_verdict.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_binding_block_advisory.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_binding_block_allocation.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_binding_block_budget.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_blind_run_is_not_drained.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_block_feature_layout.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_bookkeeping_commit_crash_run.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_bookkeeping_commit_hook_crash.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_bookkeeping_no_verify.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_brief_covers_every_unit_escalation.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_bug_lane_ci_pending.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_bug_lane_guardrails.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_bug_lane_label_nonfatal.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_bug_lane_labels_registered.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_bug_lane_min_severity.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_bug_lane_no_checks_pending.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_bug_lane_policy_contract.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_bug_lane_pr_number_carried.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_bug_lane_preflight_and_dial.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_bug_lane_run.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_bug_lane_spend.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_bug_lane_state.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_bug_lane_stopped_outcome_pr_number.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_bug_lane_superseded.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_bug_lane_test_paths.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_build_provenance.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_carried_green_invalidated_by_diff.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_carry_accounting_close_and_judge.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_changed_file_test_selection.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_changelog_append_only.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_changelog_collection.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_changelog_release_wiring.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_changelog_schema.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_ci_conclusion_wait.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_ci_runs_every_gate.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_claude_resolution.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_close_b_pending_learnings.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_close_records_feature_oracle.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_close_wu_self_reference.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_closed_escalation_labels.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_closing_deliverable_guard.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_closing_guard_contracts.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_closing_guard_prediction.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_closing_learnings_destination.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_closing_requirements.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_closing_sessions_on_sonnet.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_coauthor_trailer_allowlist.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_conditional_reflection.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_console_encoding.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_convergent_iteration.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_convergent_wu_lint.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_correlation_id_override.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_correlation_id_two_surfaces.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_cost_lifetime.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_cost_tracking.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_criteria_per_gate_lint.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_criteria_state.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_criteria_worklist.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_decision_citation_lint.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_decision_followups_discharged.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_decision_override_lint.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_decisions_format.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_default_severity_aliases.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_degraded_oracle.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_deliverable_missing_note.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_deliverable_presence_gate.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_derive_agent_policy_review_mode.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_derive_agent_policy_skill.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_derive_monitoring_discovery.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_derive_monitoring_skill_registration.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_design_for_diagnosis_rule.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_deterministic_refusal_repeat.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_diagnose_fenced_analysis.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_diagnose_headless.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_diagnose_issue_skill.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_diagnosis.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_diagnosis_roundtrip_live.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_diagnosis_severity_end_to_end.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_diagnosis_severity_rubric.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_dispatch_model_from_fix_scope.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_dispatch_preamble_rules_pointer.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_dispatch_skeleton.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_dispatch_skills_index.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_distilled_accept_step.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_doctor.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_doctor_command_preflight.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_done_feature_gates.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_draft_feature_answers_mode.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_drafting_answer_gate.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_drafting_invoke.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_drafting_question_issue.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_drafting_questions.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_drafting_reply_shape.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_driver_edit_detection.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_driver_event_registry_covers_emitters.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_driver_integration.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_driver_lock.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_driver_module_surface.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_driver_restart_halt_surfaced.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_driver_restart_halt_wiring.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_driver_restart_hold.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_driver_staleness_gate_summary.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_driver_staleness_warning.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_dry_run_no_branch_mutation.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_duration_tracking.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_empty_files_escalation.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_ensure_feature_branch.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_ensure_feature_branch_base.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_escalation_assignee_policy.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_escalation_contract.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_escalation_crash_containment.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_escalation_dedup_search.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_escalation_emit.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_event_gate_full_envelope.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_events_redaction.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_events_stats.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_extra_gates.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_failure_artifact_model.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_failure_signature_fail_prefix.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_failure_signature_names_the_test.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_failure_signature_skips_log_noise.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_feature_branch_auto_sync.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_feature_branch_squash_merge.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_feature_branch_stale_base.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_feature_id_resolution.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_feature_oracle_e2e.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_feature_provider_drafting_dispatch.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_feature_review_accumulation.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_filelock_portable.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_files_changed_auto_repair.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_find_pr_no_search_index.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_fingerprint.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_fix_bug_cwd_worktree.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_fix_bug_diff_self_check.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_fix_bug_headless.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_fix_bug_reads_comments.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_fix_unit_insertion_bookkeeping.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_fix_unit_insertion_e2e.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_fix_unit_insertion_refused.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_floor_skip_summary.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_flows_page.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_fold_contract_documented.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_followups_artifact.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_force_full_close.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_frontmatter_scalar_quoting.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_gate_broad_run.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_gate_budget.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_gate_budget_post_dispatch.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_gate_commands_needs.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_gate_eval.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_gate_eval_calibration.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_gate_eval_intermediate_wiring.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_gate_eval_repo_flag.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_gate_eval_terminal_wiring.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_gate_log_size_budget.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_gate_report_verdict.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_gen_contract.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_gh_backend.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_gh_features.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_git_env_isolation.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_gitleaks_pinning.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_groom_backlog_skill.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_guard_refusal_failure_class.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_guard_refusal_failure_excerpt.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_guard_repair_e2e.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_guard_repair_prompt.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_hashed_denylist.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_hashed_denylist_ci.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_headless_no_background_tasks.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_heartbeat.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_heartbeat_adapter.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_heartbeat_emission_contract.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_http_status_regression_adapter.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_http_status_regression_end_to_end.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_human_output_rule.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_human_work_unit.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_hygiene_boundary_guidance.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_hyphenated_gate_name.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_ids.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_ids_aliases.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_ids_call_sites.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_ids_migrate.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_ignored_cleanup_before_probe.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_init_integration.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_installed_copy_driver_e2e.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_interpreter_normalization.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_judge_close_path.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_judge_cost.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_judge_cost_fold_committed.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_judge_editing_terminal_docs.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_judge_module.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_judge_sees_dropped_produces.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_judge_terminal_flip_state.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_judged_event_points_at_findings.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_keyed_feature_ids_docs.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_keyed_feature_ids_end_to_end.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_label_description_length.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_label_provisioning_runner_contract.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_label_registry.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_label_registry_covers_consumers.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_language_aware_narrow_selection.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_layout.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_layout_end_to_end.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_layout_link.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_layout_no_layout_goldens.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_layout_validation.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lazy_baseline_e2e.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_leak_findings_redaction.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_leak_scan.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_leak_scan_content.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_learnings_curate_vendored_promote.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_learnings_distil_cli.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_learnings_query.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_learnings_staging.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_learnings_suggest_signature_filter.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_learnings_weights.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_legacy_4wu_terminal_flips.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lifecycle_integration.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lifetime_accounting.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_ac_observable.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_bare_produces_path.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_boundary_consistency.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_boundary_extraction.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_close_intermediate.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_close_verdict_not_predecided.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_close_wu.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_closing.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_closing_criteria.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_closing_criteria_pristine.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_correlation_id.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_correlation_id_close_intermediate.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_covers_load_wu_invariants.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_feature_oracle_declared.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_gate_proportionality.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_judge_edit_acknowledged.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_monitoring.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_oracle_env.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_plan_contract_fields.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_plan_errors.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_plan_next_draft.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_plan_relative_import.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_plan_verdict_exempt.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_planning_sections.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_produces_driver_helper.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_produces_satisfiability.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_roadmap.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_roadmap_unkeyed_rows.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_sections.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_task_graph_yaml_selection.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_lint_verification_reference.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_live_tests_opt_in.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_load_bearing_close_lint.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_loop_attempt_evidence.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_loop_auto_archive.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_loop_caveman_preamble.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_loop_close_intermediate.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_loop_criteria_skeleton.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_loop_criteria_survival.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_loop_defaults_by_type.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_loop_effort.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_loop_failure_note_cap.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_loop_files_changed_guard.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_loop_gate_budget.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_loop_gate_needs.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_loop_model_alias.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_loop_orchestration.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_loop_post_pass_invariant.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_loop_reset_preserving_events.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_loop_smoke_runner.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_loop_unsandboxed.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_loop_worklist_injection.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_loop_zero_token_guard.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_make_repo_gpgsign_isolation.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_marker_dual_shape.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_maven_failure_excerpt.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_max_attempts_configurable.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_max_open_prs.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_migrate_all_repo_stays_valid.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_migrate_legacy.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_miniyaml_equivalence.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_miniyaml_negative.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_monitor_check_isolation.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_monitor_cli.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_monitor_diagnose_auto_warning.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_monitor_durations.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_monitor_finding_context_end_to_end.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_monitor_finding_lifecycle_end_to_end.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_monitor_finding_meta.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_monitor_issue_lifecycle.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_monitor_one_issue_per_fingerprint.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_monitor_quiet_annotation.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_monitor_runner_surfaces.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_monitoring_bootstrap_artifacts.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_monitoring_cron_dialect.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_monitoring_example.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_monitoring_fenced_blocks.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_monitoring_seed.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_narrow_fallback_warning.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_narrow_non_py_produces.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_needs_decision_outcome.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_no_pytest_subprocess.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_notify.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_notify_escalation.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_notify_sla.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_operator_escalation_rule.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_oracle_set_declared.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_oracle_unrunnable_attribution.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_per_run_item_cap_disclosure.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_pick_feature_skill.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_pin_honesty_and_integrity.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_pin_marker_not_inherited.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_pin_marker_spawn_sites.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_plan_baseline.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_planned_cost_lint.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_planning_cost_floor.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_plugin_prose_cross_refs.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_plugin_qualified_dispatch.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_policy_budgets_applied.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_policy_proposals.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_policy_review.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_pr_checks_json_field.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_pr_reference_in_title.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_pre_existing_claims_cite_evidence.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_prep_halt_operator_surface.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_prepare_feature.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_prepare_scaffold_sync.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_prerun_capture.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_prerun_oracles.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_prerun_wiring.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_produced_document_verdict_guard.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_produces_amendment_e2e.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_produces_bracket_literal.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_produces_bracket_wildcard.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_produces_field.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_produces_in_diff.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_produces_justification.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_produces_recursive_glob.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_produces_repair_note.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_produces_semantics_unified.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_produces_shape.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_progress_forward_note.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_progress_lines_end_to_end.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_promoted_issue_not_redispatched.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_promotion_ledger.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_provider_escalation_traces.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_provision_labels.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_queue_stalled_adapter.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_rearm_clears_stale_criteria_state.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_rearm_close_broad_run.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_rearm_fold_marker.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_rearm_migration.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_rearm_retained_new_files.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_reclose_carries_narrow_greens_e2e.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_red_baseline_never_reused.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_red_on_base_fetch_and_reason.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_red_on_base_guardrail.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_red_on_base_overlay.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_red_on_base_probe.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_red_on_base_real_runner.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_redaction_windows_home.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_rediagnose_on_reopen.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_reescalation_posts_comment.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_reflection_required_signals.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_replan_end_to_end.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_replan_event_emission.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_replan_note_collision.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_replan_trigger.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_replan_turn_contract.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_replay_spin.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_replay_spin_failing_sets.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_required_checks.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_resolve_base.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_restart_cap_counts_progress.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_result_block.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_result_block_audience.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_result_block_near_miss.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_result_block_scalar.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_resume_command_build.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_retain_on_guard_refusal_docs.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_roadmap_add_skill.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_roadmap_archive_cli.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_roadmap_archive_shim.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_roadmap_archive_skill.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_roadmap_archive_skill_delegates.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_roadmap_link_gate.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_roadmap_row_parser.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_ruff_verdict_lines.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_rule_import_convergence.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_run_level_failure_breaker.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_scaffold_data_in_sync.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_scaffold_doc_hygiene.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_scaffold_docs.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_scaffold_init.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_scaffold_label_provisioning.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_scaffold_manifest.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_scaffold_resources.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_scaffold_seed_sanity.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_scaffold_skew_halts.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_scaffold_upgrade.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_scaffold_wiring.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_schedule_dialect.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_seed_documents_prerun_keys.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_service_bus_dlq_adapter.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_session_env_preflight.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_severity_aliases.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_severity_backfill_apply.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_severity_backfill_bugs_only.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_severity_backfill_end_to_end.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_severity_backfill_limit.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_severity_backfill_marker.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_severity_backfill_run.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_severity_label_projection.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_severity_reconciliation.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_severity_rubric.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_shipped_templates_parse.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_skill_discovery_links.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_skills_vendored_in_sync.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_spec_work_unit_rules.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_spend_counts_cache_tokens.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_spinning_rearm_gate.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_spinning_repeat_same_failing_set.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_spinout_brief_end_to_end.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_spinout_brief_replan_option.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_squash_commit_hook_crash.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_stop_rationale_captured.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_stranded_halt_cause.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_surefire_run_level_signature.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_surefire_verdict_lines.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_template_closing_shapes.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_terminal_auto_close_changelog.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_terminal_flip_ownership.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_terminal_flips.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_terminal_gate_message.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_terminal_judge_editing_end_to_end.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_tiered_verification_e2e.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_timeout_kill_portable.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_triage.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_triage_apply.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_triage_projects_severity_label.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_triage_reads_comments.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_triage_severity_classify.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_triage_severity_end_to_end.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_triage_severity_label_conflict.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_triage_severity_write.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_triage_skill_contract.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_triage_skips_agent_escalations.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_tsc_verdict_lines.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_umbrella_version_resolution.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_unreadable_pr_listing_holds_pr_work.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_unterminated_frontmatter.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_untracked_feature_folder.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_untracked_file_absorption.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_upgrade_gitignore_lines.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_upgrade_integration.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_upgrade_merge_gate.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_validate_event.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_validate_event_driver_types.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_validate_feature_dirs_only.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_verdict_coupling.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_verify_empty_gate_set.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_version_consistency.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_version_skew.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_windows_gate_exec.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_worktree_restore.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.0}/tests/test_wu_execution_metadata.py +0 -0
- {specfuse_loop-0.31.0 → specfuse_loop-0.32.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.32.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"
|
|
@@ -110,6 +110,8 @@ JUDGE_MODULES = tuple(
|
|
|
110
110
|
"closing_requirements.py",
|
|
111
111
|
"criteria_state.py",
|
|
112
112
|
"lint_closing.py",
|
|
113
|
+
"changelog.py", # its parser is how close-k, lint --closing and the auto-close guard read Unreleased
|
|
114
|
+
"changelog_fragments.py", # the one reader/writer of changelog entries the close guards read
|
|
113
115
|
"lint_plan.py",
|
|
114
116
|
"ids.py", # supplies lint_plan's CORRELATION_ID_RE; an edit changes what the close's lint accepts
|
|
115
117
|
"plan_baseline.py", # the baseline retroactive_edits compares to
|
|
@@ -144,7 +146,6 @@ NON_JUDGE_MODULES = {
|
|
|
144
146
|
"caller_check.py": "reports public symbols whose only callers are "
|
|
145
147
|
"tests; a hygiene report, grants no verdict and no arm/close/"
|
|
146
148
|
"merge decision reads it",
|
|
147
|
-
"changelog.py": "parses and stamps CHANGELOG.md; no gate reads it",
|
|
148
149
|
"decisions_format.py": "parses DECISIONS.md into entries; not on the "
|
|
149
150
|
"arm/close/merge judge path — T02/T03 own the lint that reads it",
|
|
150
151
|
"driver_edit.py": "applies operator edits to a feature folder",
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright 2026 Specfuse contributors
|
|
3
|
+
# Licensed under the Apache License, Version 2.0. See LICENSE.
|
|
4
|
+
#
|
|
5
|
+
"""Changelog fragments — FEAT-LOOP-2026-0249.
|
|
6
|
+
|
|
7
|
+
One writer for changelog entries. `add` routes by `changelog.mode` in
|
|
8
|
+
`.specfuse/layout.yml`: `fragments` writes `changelog.d/<trace>-<slug>.md`
|
|
9
|
+
(so concurrent PRs never touch the same file), `single` (the default) appends
|
|
10
|
+
to `CHANGELOG.md` through `changelog.append_entry`.
|
|
11
|
+
|
|
12
|
+
python3 -m specfuse.loop.changelog_fragments add --class C --trace T --summary S [--repo DIR]
|
|
13
|
+
python3 -m specfuse.loop.changelog_fragments collate [--repo DIR]
|
|
14
|
+
python3 -m specfuse.loop.changelog_fragments check [--repo DIR]
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import argparse
|
|
20
|
+
import re
|
|
21
|
+
import sys
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
from specfuse.loop import layout
|
|
25
|
+
from specfuse.loop.changelog import (
|
|
26
|
+
_TRACE_RE,
|
|
27
|
+
ENTRY_CLASSES,
|
|
28
|
+
ChangelogEntry,
|
|
29
|
+
append_entry,
|
|
30
|
+
parse_changelog,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
FRAGMENT_DIR = "changelog.d"
|
|
34
|
+
CHANGELOG_FILE = "CHANGELOG.md"
|
|
35
|
+
_SLUG_MAX = 40
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def normalize_trace(trace: str) -> str:
|
|
39
|
+
"""`#3563` -> `3563`, `FEAT-X/T01` -> `FEAT-X-T01`."""
|
|
40
|
+
return trace.lstrip("#").replace("/", "-")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def slugify(summary: str) -> str:
|
|
44
|
+
slug = re.sub(r"[^a-z0-9]+", "-", summary.lower()).strip("-")[:_SLUG_MAX].strip("-")
|
|
45
|
+
return slug or "entry"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _validate(entry_class: str, trace: str, summary: str) -> None:
|
|
49
|
+
if entry_class not in ENTRY_CLASSES:
|
|
50
|
+
raise ValueError(
|
|
51
|
+
f"unrecognised entry class {entry_class!r} — must be one of "
|
|
52
|
+
f"{', '.join(ENTRY_CLASSES)}"
|
|
53
|
+
)
|
|
54
|
+
if not _TRACE_RE.match(trace):
|
|
55
|
+
raise ValueError(f"trace {trace!r} is not a FEAT-YYYY-NNNN or #<issue-number> trace")
|
|
56
|
+
if not summary.strip():
|
|
57
|
+
raise ValueError("summary must not be empty")
|
|
58
|
+
if "\n" in summary or "\r" in summary:
|
|
59
|
+
raise ValueError("summary must be a single line")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def read_fragment(path) -> ChangelogEntry | None:
|
|
63
|
+
"""The entry one fragment file carries, or None when it is not a valid fragment.
|
|
64
|
+
|
|
65
|
+
Frontmatter is read line by line, not through `_miniyaml`: `trace: #101`
|
|
66
|
+
is a comment to a YAML reader. Never raises on a malformed file.
|
|
67
|
+
"""
|
|
68
|
+
try:
|
|
69
|
+
lines = Path(path).read_text(encoding="utf-8").splitlines()
|
|
70
|
+
except (OSError, UnicodeDecodeError):
|
|
71
|
+
return None
|
|
72
|
+
if not lines or lines[0].strip() != "---":
|
|
73
|
+
return None
|
|
74
|
+
try:
|
|
75
|
+
end = next(i for i in range(1, len(lines)) if lines[i].strip() == "---")
|
|
76
|
+
except StopIteration:
|
|
77
|
+
return None
|
|
78
|
+
fields: dict[str, str] = {}
|
|
79
|
+
for line in lines[1:end]:
|
|
80
|
+
key, sep, value = line.partition(":")
|
|
81
|
+
if not sep:
|
|
82
|
+
return None
|
|
83
|
+
fields[key.strip()] = value.strip()
|
|
84
|
+
entry_class, trace = fields.get("class", ""), fields.get("trace", "")
|
|
85
|
+
body = [ln.strip() for ln in lines[end + 1:] if ln.strip()]
|
|
86
|
+
if entry_class not in ENTRY_CLASSES or not _TRACE_RE.match(trace) or len(body) != 1:
|
|
87
|
+
return None
|
|
88
|
+
return ChangelogEntry(entry_class=entry_class, summary=body[0], trace=trace, line=end + 2)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def fragment_entries(repo_root) -> list:
|
|
92
|
+
"""Every valid fragment's entry under `changelog.d/`; malformed files are skipped."""
|
|
93
|
+
directory = Path(repo_root) / FRAGMENT_DIR
|
|
94
|
+
if not directory.is_dir():
|
|
95
|
+
return []
|
|
96
|
+
found = (read_fragment(p) for p in sorted(directory.glob("*.md")))
|
|
97
|
+
return [entry for entry in found if entry is not None]
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def add(repo_root, *, entry_class: str, trace: str, summary: str) -> Path:
|
|
101
|
+
"""Write one entry in the project's mode; return the file written."""
|
|
102
|
+
_validate(entry_class, trace, summary)
|
|
103
|
+
summary = summary.strip()
|
|
104
|
+
root = Path(repo_root)
|
|
105
|
+
if layout.load(root).changelog.mode == "fragments":
|
|
106
|
+
target = root / FRAGMENT_DIR / f"{normalize_trace(trace)}-{slugify(summary)}.md"
|
|
107
|
+
if target.exists():
|
|
108
|
+
raise ValueError(f"{target.relative_to(root).as_posix()} already exists")
|
|
109
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
110
|
+
target.write_text(
|
|
111
|
+
f"---\nclass: {entry_class}\ntrace: {trace}\n---\n{summary}\n", encoding="utf-8"
|
|
112
|
+
)
|
|
113
|
+
return target
|
|
114
|
+
path = root / CHANGELOG_FILE
|
|
115
|
+
if not path.is_file():
|
|
116
|
+
raise ValueError(f"{CHANGELOG_FILE} not found in {root}")
|
|
117
|
+
text = path.read_text(encoding="utf-8")
|
|
118
|
+
path.write_text(
|
|
119
|
+
append_entry(text, entry_class=entry_class, summary=summary, trace=trace),
|
|
120
|
+
encoding="utf-8",
|
|
121
|
+
)
|
|
122
|
+
return path
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def invalid_fragments(repo_root) -> list:
|
|
126
|
+
"""One message per malformed fragment under `changelog.d/`, each naming the file."""
|
|
127
|
+
directory = Path(repo_root) / FRAGMENT_DIR
|
|
128
|
+
paths = sorted(directory.glob("*.md")) if directory.is_dir() else []
|
|
129
|
+
return [
|
|
130
|
+
f"{FRAGMENT_DIR}/{path.name} is not a valid fragment — expected "
|
|
131
|
+
"`class:` and `trace:` frontmatter and a single-line summary"
|
|
132
|
+
for path in paths
|
|
133
|
+
if read_fragment(path) is None
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def collate_text(repo_root, text: str) -> tuple:
|
|
138
|
+
"""Fold every fragment into `text`'s `Unreleased`; return `(new_text, fragment_paths)`.
|
|
139
|
+
|
|
140
|
+
Fragments fold in sorted filename order through `append_entry`. Writes and
|
|
141
|
+
deletes nothing: any malformed fragment, or a result that does not parse
|
|
142
|
+
clean, raises `ValueError` naming the file, so the caller's tree is untouched.
|
|
143
|
+
"""
|
|
144
|
+
directory = Path(repo_root) / FRAGMENT_DIR
|
|
145
|
+
paths = sorted(directory.glob("*.md")) if directory.is_dir() else []
|
|
146
|
+
problems = invalid_fragments(repo_root)
|
|
147
|
+
if problems:
|
|
148
|
+
raise ValueError(problems[0])
|
|
149
|
+
for path in paths:
|
|
150
|
+
entry = read_fragment(path)
|
|
151
|
+
text = append_entry(
|
|
152
|
+
text, entry_class=entry.entry_class, summary=entry.summary, trace=entry.trace
|
|
153
|
+
)
|
|
154
|
+
if paths:
|
|
155
|
+
result = parse_changelog(text)
|
|
156
|
+
if not result.ok:
|
|
157
|
+
raise ValueError(
|
|
158
|
+
f"collating {FRAGMENT_DIR}/ leaves {CHANGELOG_FILE} unparseable: "
|
|
159
|
+
+ "; ".join(str(f) for f in result.findings)
|
|
160
|
+
)
|
|
161
|
+
return text, paths
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def remove_fragments(paths) -> None:
|
|
165
|
+
"""Delete consumed fragments; drop `changelog.d/` when it is left empty."""
|
|
166
|
+
parents = {Path(p).parent for p in paths}
|
|
167
|
+
for path in paths:
|
|
168
|
+
Path(path).unlink()
|
|
169
|
+
for parent in parents:
|
|
170
|
+
try:
|
|
171
|
+
parent.rmdir()
|
|
172
|
+
except OSError:
|
|
173
|
+
pass
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def collate(repo_root) -> list:
|
|
177
|
+
"""Fold fragments into `CHANGELOG.md` and delete them; return the fragment paths."""
|
|
178
|
+
path = Path(repo_root) / CHANGELOG_FILE
|
|
179
|
+
if not path.is_file():
|
|
180
|
+
raise ValueError(f"{CHANGELOG_FILE} not found in {repo_root}")
|
|
181
|
+
new_text, paths = collate_text(repo_root, path.read_text(encoding="utf-8"))
|
|
182
|
+
if paths:
|
|
183
|
+
path.write_text(new_text, encoding="utf-8")
|
|
184
|
+
remove_fragments(paths)
|
|
185
|
+
return paths
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def main(argv=None) -> int:
|
|
189
|
+
parser = argparse.ArgumentParser(
|
|
190
|
+
prog="specfuse.loop.changelog_fragments", description="Write changelog entries."
|
|
191
|
+
)
|
|
192
|
+
parser.add_argument("--repo", default=".", help="repository root (default: cwd)")
|
|
193
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
194
|
+
addp = sub.add_parser("add", help="write one changelog entry in the project's mode")
|
|
195
|
+
addp.add_argument("--class", dest="entry_class", required=True)
|
|
196
|
+
addp.add_argument("--trace", required=True)
|
|
197
|
+
addp.add_argument("--summary", required=True)
|
|
198
|
+
sub.add_parser("collate", help="fold changelog.d/ fragments into CHANGELOG.md and delete them")
|
|
199
|
+
sub.add_parser("check", help="fail when any changelog.d/ fragment is malformed")
|
|
200
|
+
args = parser.parse_args(argv)
|
|
201
|
+
|
|
202
|
+
if args.command == "check":
|
|
203
|
+
problems = invalid_fragments(args.repo)
|
|
204
|
+
for problem in problems:
|
|
205
|
+
print(problem, file=sys.stderr)
|
|
206
|
+
return 2 if problems else 0
|
|
207
|
+
|
|
208
|
+
if args.command == "collate":
|
|
209
|
+
try:
|
|
210
|
+
folded = collate(args.repo)
|
|
211
|
+
except ValueError as exc:
|
|
212
|
+
print(str(exc), file=sys.stderr)
|
|
213
|
+
return 2
|
|
214
|
+
print(f"collated {len(folded)} fragment(s)")
|
|
215
|
+
return 0
|
|
216
|
+
|
|
217
|
+
try:
|
|
218
|
+
target = add(
|
|
219
|
+
args.repo,
|
|
220
|
+
entry_class=args.entry_class,
|
|
221
|
+
trace=args.trace,
|
|
222
|
+
summary=args.summary,
|
|
223
|
+
)
|
|
224
|
+
except ValueError as exc:
|
|
225
|
+
print(str(exc), file=sys.stderr)
|
|
226
|
+
return 2
|
|
227
|
+
print(target)
|
|
228
|
+
return 0
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
if __name__ == "__main__":
|
|
232
|
+
sys.exit(main())
|
|
@@ -18,6 +18,7 @@ from __future__ import annotations
|
|
|
18
18
|
|
|
19
19
|
import re
|
|
20
20
|
from dataclasses import dataclass, field
|
|
21
|
+
from pathlib import Path
|
|
21
22
|
from typing import Optional
|
|
22
23
|
|
|
23
24
|
# --------------------------------------------------------------------------- #
|
|
@@ -232,6 +233,35 @@ def changelog_has_entry_for(unreleased_entries, feature_id: str) -> bool:
|
|
|
232
233
|
)
|
|
233
234
|
|
|
234
235
|
|
|
236
|
+
def changelog_exists(repo_root) -> bool:
|
|
237
|
+
"""True if the project has a changelog in either form: `CHANGELOG.md` or `changelog.d/*.md`."""
|
|
238
|
+
root = Path(repo_root)
|
|
239
|
+
return (root / CHANGELOG_PATH).exists() or any((root / "changelog.d").glob("*.md"))
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def changelog_has_entry_for_feature(repo_root, feature_id: str) -> bool:
|
|
243
|
+
"""True if *feature_id* has an entry in `CHANGELOG.md`'s Unreleased section or a valid fragment.
|
|
244
|
+
|
|
245
|
+
The one reader behind close-k, `lint --closing` and the auto-close guard
|
|
246
|
+
(FEAT-LOOP-2026-0249). Both forms are read in every mode. A missing or
|
|
247
|
+
unparseable changelog and a malformed fragment count as no entry; this
|
|
248
|
+
never raises.
|
|
249
|
+
"""
|
|
250
|
+
from . import changelog_fragments
|
|
251
|
+
from .changelog import parse_changelog
|
|
252
|
+
|
|
253
|
+
root = Path(repo_root)
|
|
254
|
+
entries = list(changelog_fragments.fragment_entries(root))
|
|
255
|
+
changelog = root / CHANGELOG_PATH
|
|
256
|
+
if changelog.is_file():
|
|
257
|
+
try:
|
|
258
|
+
unreleased = parse_changelog(changelog.read_text(encoding="utf-8")).unreleased()
|
|
259
|
+
except Exception: # noqa: BLE001 - a guard reports a gap, never crashes on one
|
|
260
|
+
unreleased = None
|
|
261
|
+
entries += unreleased.entries if unreleased else []
|
|
262
|
+
return changelog_has_entry_for(entries, feature_id)
|
|
263
|
+
|
|
264
|
+
|
|
235
265
|
# --------------------------------------------------------------------------- #
|
|
236
266
|
# Requirement records #
|
|
237
267
|
# --------------------------------------------------------------------------- #
|
|
@@ -354,8 +384,8 @@ CLOSING_REQUIREMENTS: dict[str, list[Requirement]] = {
|
|
|
354
384
|
description=(
|
|
355
385
|
f"When the '{CONSUMER_VISIBLE_HEADING}' section (close-discipline.md "
|
|
356
386
|
f"§3) is not the '{CONSUMER_VISIBLE_NA_PHRASE}' line, "
|
|
357
|
-
f"{CHANGELOG_PATH}'s Unreleased section
|
|
358
|
-
"this feature's FEAT-ID — the same enumeration §3 already "
|
|
387
|
+
f"{CHANGELOG_PATH}'s Unreleased section or a valid changelog.d/ "
|
|
388
|
+
"fragment gains an entry carrying this feature's FEAT-ID — the same enumeration §3 already "
|
|
359
389
|
"requires, appended where a consumer reads it, not re-derived"
|
|
360
390
|
),
|
|
361
391
|
file=RETROSPECTIVE_FILENAME,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.32.0
|
|
@@ -175,6 +175,18 @@ hit stops the branch rather than shipping feature-scoped work as a bug fix.
|
|
|
175
175
|
**The `CHANGELOG.md` entry is written here or nowhere.** Bugs have no close
|
|
176
176
|
ceremony to collect through, so `/fix-bug` is the collection point.
|
|
177
177
|
|
|
178
|
+
`/fix-bug` records the entry with `python3 -m specfuse.loop.changelog_fragments add`
|
|
179
|
+
(trace `#<issue-number>`) and commits what it wrote with the fix.
|
|
180
|
+
|
|
181
|
+
**Opting in to fragments.** By default entries append to `CHANGELOG.md`. To
|
|
182
|
+
stop concurrent PRs colliding on that file, set `changelog.mode: fragments` in
|
|
183
|
+
`.specfuse/layout.yml`; `add` then writes one `changelog.d/<trace>-<slug>.md`
|
|
184
|
+
per entry. Add `python3 -m specfuse.loop.changelog_fragments check` as a gate
|
|
185
|
+
in `.specfuse/verification.yml` to fail on malformed fragments. A custom
|
|
186
|
+
release script must run `python3 -m specfuse.loop.changelog_fragments collate`
|
|
187
|
+
before stamping the release, so fragments fold into `CHANGELOG.md` and are
|
|
188
|
+
deleted first.
|
|
189
|
+
|
|
178
190
|
---
|
|
179
191
|
|
|
180
192
|
## 3. The autonomous lifecycle
|
|
@@ -138,18 +138,19 @@ A close carrying this obligation is always load-bearing
|
|
|
138
138
|
(`auto_close_disabled: true`).
|
|
139
139
|
|
|
140
140
|
**Also append it to `CHANGELOG.md`'s `Unreleased` section (FEAT-2026-0064).**
|
|
141
|
-
When this section is a real enumeration — not the `n/a` line —
|
|
142
|
-
|
|
143
|
-
`
|
|
144
|
-
|
|
141
|
+
When this section is a real enumeration — not the `n/a` line — run one
|
|
142
|
+
`python3 -m specfuse.loop.changelog_fragments add --class <class> --trace <FEAT-ID> --summary '<line>'`
|
|
143
|
+
per enumerated item, classified `added` / `changed` / `fixed` / `breaking`,
|
|
144
|
+
with the feature's `FEAT-YYYY-NNNN` ID as trace. Never hand-edit; `add`
|
|
145
|
+
routes by the project's `changelog.mode`. **This is the same material the
|
|
145
146
|
paragraph above already requires you to write, put where a consumer will
|
|
146
147
|
read it — not a second write.** An author who treats the changelog append as
|
|
147
148
|
new work will write the enumeration twice, badly; write it once, in both
|
|
148
149
|
places, from the same understanding. `closing_requirements.py`'s `close-k`
|
|
149
150
|
requirement (enforced by `assert_changelog_entry_for_contract_changes` in
|
|
150
151
|
`loop.py`, and pre-squash by `specfuse lint --closing`) fires when this
|
|
151
|
-
section names a real change and
|
|
152
|
-
|
|
152
|
+
section names a real change and no entry tracing to this feature's FEAT-ID
|
|
153
|
+
lives in `CHANGELOG.md` or a `changelog.d/` fragment.
|
|
153
154
|
|
|
154
155
|
**An `n/a` close appends nothing to `Unreleased`, and that is correct.** A
|
|
155
156
|
changelog padded with "no user-facing change" entries is noise that trains
|
|
@@ -399,12 +399,17 @@ def _rewrite_file(rel, text, renames, include_prose, plan, folders=()):
|
|
|
399
399
|
is_changelog = rel == "CHANGELOG.md"
|
|
400
400
|
lines = text.splitlines(keepends=True)
|
|
401
401
|
bare = [ln.rstrip("\r\n") for ln in lines]
|
|
402
|
-
|
|
402
|
+
# A changelog.d/ fragment is unreleased by definition: its `trace:` frontmatter
|
|
403
|
+
# is structural, so it follows the rename (the filename stays; frontmatter wins).
|
|
404
|
+
is_fragment = is_md and rel.startswith("changelog.d/")
|
|
405
|
+
fm_end = _frontmatter_end(bare) if is_md and (in_features or is_fragment) else -1
|
|
403
406
|
graph = _task_graph_lines(bare) if name == "PLAN.md" and in_features else set()
|
|
404
407
|
span = _unreleased_span(bare) if is_changelog else None
|
|
405
408
|
if is_changelog and span is None:
|
|
406
409
|
span = (len(lines), len(lines))
|
|
407
410
|
pairs = [(o, n) for o, n in renames]
|
|
411
|
+
# Built once per file, not per ID per line (#3559).
|
|
412
|
+
owned = {o: [f for f in folders if f.startswith(o + "-")] for o, _ in pairs}
|
|
408
413
|
out, fenced = [], False
|
|
409
414
|
for no, line in enumerate(lines):
|
|
410
415
|
if is_changelog and not (span[0] <= no < span[1]):
|
|
@@ -417,7 +422,15 @@ def _rewrite_file(rel, text, renames, include_prose, plan, folders=()):
|
|
|
417
422
|
row = bool(is_roadmap and _ROW_ID_RE.match(line))
|
|
418
423
|
heading = bool(is_roadmap and re.match(r"^#{1,6}\s", line))
|
|
419
424
|
frontmatter = no <= fm_end or no in graph
|
|
425
|
+
low = line.lower()
|
|
426
|
+
# Every old ID is `FEAT-…` and its anchor `feat-…`: a line naming neither
|
|
427
|
+
# cannot match, so the per-ID loop is skipped (#3559).
|
|
428
|
+
if "feat-" not in low:
|
|
429
|
+
out.append(line)
|
|
430
|
+
continue
|
|
420
431
|
for old, new in pairs:
|
|
432
|
+
if anchor(old) not in low:
|
|
433
|
+
continue
|
|
421
434
|
line = _rewrite_line(
|
|
422
435
|
line, old, new,
|
|
423
436
|
structural=row or heading or frontmatter,
|
|
@@ -425,8 +438,9 @@ def _rewrite_file(rel, text, renames, include_prose, plan, folders=()):
|
|
|
425
438
|
events=is_events,
|
|
426
439
|
prose=include_prose and not fenced,
|
|
427
440
|
where=(rel, no + 1, plan),
|
|
428
|
-
folders=[
|
|
441
|
+
folders=owned[old],
|
|
429
442
|
)
|
|
443
|
+
low = line.lower()
|
|
430
444
|
out.append(line)
|
|
431
445
|
return "".join(out)
|
|
432
446
|
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
"""Project layout resolver — FEAT-2026-0224 (project-configurable layout).
|
|
7
7
|
|
|
8
8
|
Reads an optional `.specfuse/layout.yml` and answers `layout get <key>` for
|
|
9
|
-
`decisions.style`, `decisions.location`, `decisions.link
|
|
9
|
+
`decisions.style`, `decisions.location`, `decisions.link`, `decisions.entryKey`
|
|
10
|
+
and `ids.key`.
|
|
10
11
|
|
|
11
12
|
Contract: no file (or no `decisions:` block) means today's behaviour — style
|
|
12
13
|
`adr`, location `docs/adr`, link `{path}`. An `entries` style with `location`
|
|
@@ -41,12 +42,23 @@ _STYLE_DEFAULTS = {
|
|
|
41
42
|
}
|
|
42
43
|
_FALLBACK_DEFAULTS = _STYLE_DEFAULTS["adr"]
|
|
43
44
|
|
|
44
|
-
KEYS = (
|
|
45
|
+
KEYS = (
|
|
46
|
+
"decisions.style",
|
|
47
|
+
"decisions.location",
|
|
48
|
+
"decisions.link",
|
|
49
|
+
"decisions.entryKey",
|
|
50
|
+
"ids.key",
|
|
51
|
+
"changelog.mode",
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
DEFAULT_CHANGELOG_MODE = "single"
|
|
55
|
+
VALID_CHANGELOG_MODES = ("single", "fragments")
|
|
45
56
|
|
|
46
57
|
VALID_STYLES = ("adr", "entries")
|
|
47
58
|
LINK_PLACEHOLDERS = ("location", "path", "id", "domain")
|
|
48
|
-
KNOWN_TOP_LEVEL = ("decisions", "ids")
|
|
59
|
+
KNOWN_TOP_LEVEL = ("decisions", "ids", "changelog")
|
|
49
60
|
_PLACEHOLDER_RE = re.compile(r"\{([^{}]*)\}")
|
|
61
|
+
_ENTRY_KEY_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_-]*$")
|
|
50
62
|
_DRIVE_RE = re.compile(r"^[A-Za-z]:")
|
|
51
63
|
|
|
52
64
|
|
|
@@ -55,6 +67,7 @@ class Decisions:
|
|
|
55
67
|
style: str
|
|
56
68
|
location: str
|
|
57
69
|
link: str
|
|
70
|
+
entry_key: Optional[str] = None
|
|
58
71
|
|
|
59
72
|
|
|
60
73
|
@dataclass(frozen=True)
|
|
@@ -62,10 +75,16 @@ class Ids:
|
|
|
62
75
|
key: Optional[str] = None
|
|
63
76
|
|
|
64
77
|
|
|
78
|
+
@dataclass(frozen=True)
|
|
79
|
+
class Changelog:
|
|
80
|
+
mode: str = DEFAULT_CHANGELOG_MODE
|
|
81
|
+
|
|
82
|
+
|
|
65
83
|
@dataclass(frozen=True)
|
|
66
84
|
class Layout:
|
|
67
85
|
decisions: Decisions
|
|
68
86
|
ids: Ids = Ids()
|
|
87
|
+
changelog: Changelog = Changelog()
|
|
69
88
|
|
|
70
89
|
|
|
71
90
|
def _normalise(location: str) -> str:
|
|
@@ -81,10 +100,12 @@ def _resolve_decisions(block: Optional[dict]) -> Decisions:
|
|
|
81
100
|
default_location, default_link = _STYLE_DEFAULTS.get(style, _FALLBACK_DEFAULTS)
|
|
82
101
|
location = block.get("location")
|
|
83
102
|
link = block.get("link")
|
|
103
|
+
entry_key = block.get("entryKey")
|
|
84
104
|
return Decisions(
|
|
85
105
|
style=style,
|
|
86
106
|
location=default_location if location is None else _normalise(str(location)),
|
|
87
107
|
link=default_link if link is None else str(link),
|
|
108
|
+
entry_key=None if entry_key is None else str(entry_key),
|
|
88
109
|
)
|
|
89
110
|
|
|
90
111
|
|
|
@@ -104,9 +125,12 @@ def load(repo_root) -> Layout:
|
|
|
104
125
|
block = data.get("decisions") if isinstance(data, dict) else None
|
|
105
126
|
ids_block = data.get("ids") if isinstance(data, dict) else None
|
|
106
127
|
key = ids_block.get("key") if isinstance(ids_block, dict) else None
|
|
128
|
+
cl_block = data.get("changelog") if isinstance(data, dict) else None
|
|
129
|
+
mode = cl_block.get("mode") if isinstance(cl_block, dict) else None
|
|
107
130
|
return Layout(
|
|
108
131
|
decisions=_resolve_decisions(block if isinstance(block, dict) else None),
|
|
109
132
|
ids=Ids(key=None if key is None else str(key)),
|
|
133
|
+
changelog=Changelog(mode=DEFAULT_CHANGELOG_MODE if mode is None else str(mode)),
|
|
110
134
|
)
|
|
111
135
|
|
|
112
136
|
|
|
@@ -168,6 +192,17 @@ def validate(repo_root) -> list:
|
|
|
168
192
|
"(2-6 characters, upper-case letters and digits, starting with a letter)",
|
|
169
193
|
)
|
|
170
194
|
|
|
195
|
+
cl_block = data.get("changelog")
|
|
196
|
+
if isinstance(cl_block, dict) and cl_block.get("mode") is not None:
|
|
197
|
+
mode = str(cl_block["mode"])
|
|
198
|
+
if mode not in VALID_CHANGELOG_MODES:
|
|
199
|
+
add(
|
|
200
|
+
SEVERITY_ERROR,
|
|
201
|
+
"mode",
|
|
202
|
+
f"changelog.mode {mode!r} is invalid; must be one of "
|
|
203
|
+
f"{VALID_CHANGELOG_MODES[0]!r} or {VALID_CHANGELOG_MODES[1]!r}",
|
|
204
|
+
)
|
|
205
|
+
|
|
171
206
|
block = data.get("decisions")
|
|
172
207
|
if not isinstance(block, dict):
|
|
173
208
|
return findings
|
|
@@ -227,6 +262,18 @@ def validate(repo_root) -> list:
|
|
|
227
262
|
f"decisions.link placeholder '{{{name}}}' is not allowed; "
|
|
228
263
|
"use {location}, {path}, {id} or {domain}",
|
|
229
264
|
)
|
|
265
|
+
|
|
266
|
+
entry_key = block.get("entryKey")
|
|
267
|
+
if entry_key is not None:
|
|
268
|
+
if not _ENTRY_KEY_RE.match(str(entry_key)):
|
|
269
|
+
add(
|
|
270
|
+
SEVERITY_ERROR,
|
|
271
|
+
"entryKey",
|
|
272
|
+
f"decisions.entryKey {str(entry_key)!r} is invalid; must match "
|
|
273
|
+
f"{_ENTRY_KEY_RE.pattern}",
|
|
274
|
+
)
|
|
275
|
+
if style is None or str(style) != "entries":
|
|
276
|
+
add(SEVERITY_ERROR, "entryKey", "decisions.entryKey applies only to style 'entries'")
|
|
230
277
|
return findings
|
|
231
278
|
|
|
232
279
|
|
|
@@ -239,6 +286,10 @@ def get(layout: Layout, key: str) -> str:
|
|
|
239
286
|
raise KeyError(key)
|
|
240
287
|
if key == "ids.key":
|
|
241
288
|
return layout.ids.key or ""
|
|
289
|
+
if key == "changelog.mode":
|
|
290
|
+
return layout.changelog.mode
|
|
291
|
+
if key == "decisions.entryKey":
|
|
292
|
+
return layout.decisions.entry_key or ""
|
|
242
293
|
return getattr(layout.decisions, key.split(".", 1)[1])
|
|
243
294
|
|
|
244
295
|
|
|
@@ -28,7 +28,6 @@ from pathlib import Path
|
|
|
28
28
|
from . import closing_requirements as creq
|
|
29
29
|
from . import criteria_state
|
|
30
30
|
from ._wu_sections import slice_wu_section
|
|
31
|
-
from .changelog import parse_changelog
|
|
32
31
|
from .judge import MEASUREMENTS_SECTION
|
|
33
32
|
from .loop import (
|
|
34
33
|
_gate_number_from_wu_id,
|
|
@@ -293,22 +292,18 @@ def _check_changelog_entry_for_contract_changes(req: creq.Requirement, ctx: Clos
|
|
|
293
292
|
return True, ""
|
|
294
293
|
if creq.consumer_visible_section_is_na(section):
|
|
295
294
|
return True, ""
|
|
296
|
-
|
|
297
|
-
if
|
|
295
|
+
feature_id = ctx.wu_id.rsplit("/", 1)[0]
|
|
296
|
+
if creq.changelog_has_entry_for_feature(ctx.repo_root, feature_id):
|
|
297
|
+
return True, ""
|
|
298
|
+
if not creq.changelog_exists(ctx.repo_root):
|
|
298
299
|
return False, (
|
|
299
300
|
f"'{creq.CONSUMER_VISIBLE_HEADING}' names a real change but "
|
|
300
301
|
f"{creq.CHANGELOG_PATH} does not exist"
|
|
301
302
|
)
|
|
302
|
-
result = parse_changelog(changelog.read_text())
|
|
303
|
-
unreleased = result.unreleased()
|
|
304
|
-
feature_id = ctx.wu_id.rsplit("/", 1)[0]
|
|
305
|
-
entries = unreleased.entries if unreleased else []
|
|
306
|
-
if creq.changelog_has_entry_for(entries, feature_id):
|
|
307
|
-
return True, ""
|
|
308
303
|
return False, (
|
|
309
|
-
f"'{creq.CONSUMER_VISIBLE_HEADING}' names a real change but "
|
|
310
|
-
f"{creq.CHANGELOG_PATH}'s Unreleased section
|
|
311
|
-
f"{feature_id}"
|
|
304
|
+
f"'{creq.CONSUMER_VISIBLE_HEADING}' names a real change but neither "
|
|
305
|
+
f"{creq.CHANGELOG_PATH}'s Unreleased section nor a valid changelog.d/ "
|
|
306
|
+
f"fragment has an entry tracing to {feature_id}"
|
|
312
307
|
)
|
|
313
308
|
|
|
314
309
|
|