syncade 0.7.3__tar.gz → 0.7.5__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.
- {syncade-0.7.3/src/syncade.egg-info → syncade-0.7.5}/PKG-INFO +1 -1
- {syncade-0.7.3 → syncade-0.7.5}/pyproject.toml +1 -1
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/__init__.py +1 -1
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/cli/update_mode.py +51 -56
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/config.py +6 -1
- {syncade-0.7.3 → syncade-0.7.5/src/syncade.egg-info}/PKG-INFO +1 -1
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade.egg-info/SOURCES.txt +1 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/cli/test_update_post_condition.py +3 -123
- syncade-0.7.5/tests/cli/test_version_probe.py +158 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/conftest.py +11 -12
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_worktree_base_isolation.py +2 -2
- {syncade-0.7.3 → syncade-0.7.5}/LICENSE +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/MANIFEST.in +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/README.md +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/docs/config-reference.md +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/docs/how-to-use.md +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/setup.cfg +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/__main__.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/adapters/__init__.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/adapters/anthropic.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/adapters/base.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/adapters/fake.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/adapters/fake_common.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/adapters/fake_producer_audit_draft.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/adapters/fake_reviewer_synth.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/adapters/openai.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/adapters/openai_parsing.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/adapters/producer.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/adapters/producer_anthropic.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/adapters/producer_openai.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/adapters/registry.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/auth_check.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/auth_preflight.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/base_resolution.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/billing.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/checks_config.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/cli/__init__.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/cli/auth_gate.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/cli/config_keys.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/cli/config_list.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/cli/config_menu_rows.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/cli/config_mode.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/cli/config_overrides.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/cli/config_tui.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/cli/doctor_mode.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/cli/gc_mode.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/cli/install_skill.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/cli/metrics_mode.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/cli/modes.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/cli/parser.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/cli/parser_types.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/cli/paths.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/cli/preflight_paths.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/cli/resolve.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/cli/resume_mode.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/cli/skill_status.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/cli/toml_writer.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/cli/update_notice.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/cli/validate.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/config_auth.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/config_cold.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/config_gc.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/config_loader.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/config_loop.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/config_producer.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/config_retry.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/config_types.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/config_update.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/diff_filter.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/dispatcher.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/doctor.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/doctor_env.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/doctor_preview.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/doctor_types.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/exit_codes.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/findings.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/findings_json.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/gc.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/gc_execute.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/gc_protection.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/gc_types.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/gc_worktrees.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/git_object_id.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/git_preconditions.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/logging.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/metrics/__init__.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/metrics/aggregate.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/metrics/findings.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/metrics/schema.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/orchestrator/__init__.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/orchestrator/_runs_dir.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/orchestrator/branch_advance.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/orchestrator/branch_guard.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/orchestrator/budget.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/orchestrator/escalation_coverage.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/orchestrator/loop.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/orchestrator/loop_dispatch_check.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/orchestrator/loop_finalize.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/orchestrator/loop_preflight.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/orchestrator/loop_resume.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/orchestrator/loop_rmtree.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/orchestrator/loop_round_step.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/orchestrator/prior_round.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/orchestrator/producer_phase.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/orchestrator/results.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/orchestrator/resume.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/orchestrator/resume_load.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/orchestrator/resume_plan.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/orchestrator/resume_target.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/orchestrator/resume_types.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/orchestrator/reviewer_template_failure.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/orchestrator/round.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/orchestrator/round_checks.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/orchestrator/round_no_changes.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/orchestrator/round_predispatch.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/orchestrator/verdict.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/persistence/__init__.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/persistence/_atomic.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/persistence/_clusters.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/persistence/_findings_verdict.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/persistence/_markdown.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/persistence/_validation.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/persistence/checks.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/persistence/decision_needed.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/persistence/findings_md.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/persistence/handoff.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/persistence/handoff_classify.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/persistence/last_reviewed.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/persistence/loop_manifest.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/persistence/loop_summary.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/persistence/loop_summary_text.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/persistence/producer.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/persistence/reviewer.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/persistence/round_manifest.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/persistence/run_init.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/persistence/run_summary.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/persistence/run_summary_next_steps.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/persistence/synth.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/persistence/test_run.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/presets.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/pricing_config.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/process.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/producer.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/producer_attempt.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/producer_escalation.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/producer_git.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/producer_result.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/prompts.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/prompts_loader.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/retry.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/run_inputs.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/run_status.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/selfcheck.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/skills/claude/README.md +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/skills/claude/SKILL.md +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/skills/codex/README.md +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/skills/codex/SKILL.md +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/snapshot.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/spec_audit.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/spec_audit_schema.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/spec_draft.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/spec_source.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/synthesis.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/synthesis_clusters.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/synthesis_repair.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/synthesis_schema.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/synthesizer/__init__.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/synthesizer/constants.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/synthesizer/driver.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/synthesizer/rendering.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/synthesizer/result.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/synthesizer/validation.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/synthesizer/workspace.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/templates/presets/balanced.toml +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/templates/presets/cheap.toml +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/templates/presets/thorough.toml +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/templates/producer.md +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/templates/reviewer.md +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/templates/reviewer_adversarial.md +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/templates/reviewer_codex.md +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/templates/spec_audit.md +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/templates/spec_draft.md +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/templates/synthesizer.md +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/test_runner.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/test_runner_classify.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/transcript.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/update_check.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/usage.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/worktree.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/worktree_env.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade/worktree_paths.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade.egg-info/dependency_links.txt +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade.egg-info/entry_points.txt +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade.egg-info/requires.txt +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/src/syncade.egg-info/top_level.txt +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/__init__.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/adapters/__init__.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/adapters/_anthropic_helpers.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/adapters/_openai_helpers.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/adapters/test_anthropic.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/adapters/test_anthropic_more.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/adapters/test_openai.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/adapters/test_openai_more.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/adapters/test_producer.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/adapters/test_producer_more.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/adapters/test_prompt_transport.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/auth_check/__init__.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/auth_check/test_probes.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/auth_check/test_run_auth_check.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/cli/__init__.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/cli/_helpers.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/cli/_undo_helpers.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/cli/test_basic_and_mutex.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/cli/test_cli_library_divergence.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/cli/test_config_keys.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/cli/test_config_list.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/cli/test_config_menu_rows.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/cli/test_config_mode.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/cli/test_config_overrides.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/cli/test_config_tui.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/cli/test_config_tui_pty.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/cli/test_documented_exit_codes.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/cli/test_documented_flags_exist.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/cli/test_flags.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/cli/test_gc.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/cli/test_help_retention_contract.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/cli/test_mode_exclusivity_matrix.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/cli/test_mode_flag_guards.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/cli/test_resume_reports_hard_kill.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/cli/test_run_and_config.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/cli/test_run_status_cli.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/cli/test_scope_openspec_draft.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/cli/test_toml_writer.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/cli/test_undo_auto_init.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/cli/test_undo_scope.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/cli/test_update_mode.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/cli/test_update_notice.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/cli/test_validate_before_mutate.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/config/__init__.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/config/test_auth_enforcement.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/config/test_auth_modes.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/config/test_auth_preflight.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/config/test_auth_report_scope.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/config/test_budget_config.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/config/test_cold_actors.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/config/test_config_checks.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/config/test_config_reference_drift.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/config/test_config_schema.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/config/test_gc_config.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/config/test_global_config_layer.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/config/test_presets.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/config/test_retry_config.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/config/test_reviewer_config.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/config/test_reviewer_permissions.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/config/test_worktree_base_config.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/dispatcher/__init__.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/dispatcher/_helpers.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/dispatcher/test_dispatch_core.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/dispatcher/test_dispatch_misc.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/dispatcher/test_dispatch_usage.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/doctor/__init__.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/doctor/_helpers.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/doctor/conftest.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/doctor/test_doctor.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/doctor/test_doctor_diff_size.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/doctor/test_doctor_malformed_diff.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/doctor/test_doctor_preview.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/findings/__init__.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/findings/_helpers.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/findings/test_parser_and_fields.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/findings/test_parser_resilience.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/findings/test_verdict_block_selection.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/gc/__init__.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/gc/_helpers.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/gc/test_autoprune.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/gc/test_execute.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/gc/test_plan.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/gc/test_plan_execution_races.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/gc/test_reaping_scope.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/gc/test_retention_tiers.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/gc/test_robustness.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/gc/test_self_orphan_protection.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/gc/test_smoke.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/gc/test_two_tier_retention.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/gc/test_worktree_age_bound.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/logging/__init__.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/logging/_helpers.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/logging/test_logging.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/logging/test_logging_summary.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/metrics/conftest.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/metrics/test_actor_spend.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/metrics/test_aggregate.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/metrics/test_billing_reconciliation.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/metrics/test_billing_surfaces_agree.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/metrics/test_budget_metrics.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/metrics/test_consensus_report.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/metrics/test_cost_honesty.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/metrics/test_findings_capture.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/metrics/test_metrics_cli.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/metrics/test_metrics_mode.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/metrics/test_schema.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/metrics/test_unknown_panel.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/__init__.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/_helpers.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/_resume_fixtures.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/conftest.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_budget.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_budget_resume_e2e.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_budget_stop_message.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_budget_warning.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_checks_and_recording.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_contracts.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_cross_round.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_cross_round_more.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_default_branch_guard.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_diff_size_cap.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_dispatch_record.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_empty_diff.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_empty_diff_no_side_effects.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_escalation.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_last_reviewed_truthful.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_loop_audit_remediation.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_matrix_and_e2e.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_multiround.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_multiround_dirty.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_multiround_more.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_paraphrase_escalation.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_pipeline.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_provider_usage_limit.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_rename_boundary.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_resume.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_resume_base_oid.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_reviewer_blindness_is_structural.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_round_input_boundaries.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_run_layout.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_run_status_wiring.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_safety_warning_survives_quiet.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_snapshot_and_base.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_synth_phase.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_synth_phase_more.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_synth_provenance.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_synth_validation.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_template_robustness.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_termination_reason_drift.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_testleg.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_timeout_is_per_subprocess.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_usage_limit_message.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/orchestrator/test_worktree_cleanup.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/persistence/__init__.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/persistence/_helpers.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/persistence/test_atomic_writes.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/persistence/test_checks_surfacing.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/persistence/test_diff_size_recording.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/persistence/test_findings_md.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/persistence/test_findings_md_more.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/persistence/test_findings_md_verdict.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/persistence/test_handoff.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/persistence/test_loop_manifest_checks.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/persistence/test_loop_summary.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/persistence/test_loop_summary_more.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/persistence/test_md_safety.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/persistence/test_next_steps.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/persistence/test_producer_persist.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/persistence/test_reviewer.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/persistence/test_round_manifest.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/persistence/test_run_init.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/persistence/test_run_summary.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/persistence/test_run_summary_more.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/persistence/test_run_summary_producer.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/persistence/test_synth_persist.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/persistence/test_test_run.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/producer/__init__.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/producer/_helpers.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/producer/test_producer.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/producer/test_producer_confinement.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/producer/test_producer_retry.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/producer/test_producer_run.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/producer/test_producer_timeout.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/producer/test_producer_usage.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/prompts/__init__.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/prompts/test_load_template.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/prompts/test_misc.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/prompts/test_producer.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/prompts/test_render_synthesizer.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/prompts/test_synthesizer.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/resume/__init__.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/resume/_helpers.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/resume/test_budget_resume.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/resume/test_checks_phase_failure.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/resume/test_cli_malformed_rehydration.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/resume/test_find_resolve_plan.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/resume/test_plan_resume.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/resume/test_round_trip.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/resume/test_tree_drift_and_escalation.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/skills/test_config_section.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/skills/test_install_skill.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/skills/test_install_skill_multi_harness.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/skills/test_install_skill_ownership.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/skills/test_install_skill_refuses.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/skills/test_skill_drift.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/skills/test_skill_status.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/smoke/__init__.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/smoke/test_anthropic_smoke.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/smoke/test_auth_check_smoke.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/smoke/test_codex_smoke.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/smoke/test_loop_smoke.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/smoke/test_orchestrator_smoke.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/smoke/test_orchestrator_smoke_more.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/smoke/test_producer_smoke.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/smoke/test_resume_reap_smoke.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/smoke/test_selfcheck_smoke.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/smoke/test_synthesizer_smoke.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/smoke/test_test_runner_smoke.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/snapshot/__init__.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/snapshot/_helpers.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/snapshot/conftest.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/snapshot/test_snapshot.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/snapshot/test_snapshot_diff_identity.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/snapshot/test_snapshot_dirty.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/snapshot/test_snapshot_warning.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/snapshot/test_three_dot_diff.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/synthesis/__init__.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/synthesis/_helpers.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/synthesis/test_cluster_repair.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/synthesis/test_clusters.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/synthesis/test_models.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/synthesis/test_models_distinct_keying.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/synthesis/test_parsing.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/synthesis/test_unanimous_blocker_downgrade.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/synthesizer/test_provenance_fabrication.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/synthesizer/test_provenance_repair.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_adapter_registry.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_adapters_fake.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_auto_init_policy.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_base_resolution.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_check_loc_script.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_checks.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_config_loader.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_cross_model_claim.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_diff_filter.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_diff_filter_binary.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_diff_filter_quoted_paths.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_escalation_coverage.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_exit_codes.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_git_choke_point.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_git_config_isolation.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_git_preconditions.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_last_reviewed.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_parse_repair_extra_keys.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_pricing.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_private_identifier_gate.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_process.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_process_teardown.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_producer_escalation.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_regression_acme.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_retry.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_run_status.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_run_status_signals.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_runner/__init__.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_runner/test_test_runner.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_runner/test_test_runner_more.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_safety_disclosures_survive_quiet.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_selfcheck.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_spec_audit.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_spec_draft.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_spec_source.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_streamed_capture.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_strip_list_semantics.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_synthesizer.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_synthesizer_passthrough.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_synthesizer_provenance.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_transcript.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_update_check.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_usage.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_usage_persistence.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_workflow_action_pins.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_worktree_env.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/test_worktree_strip_guard.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/worktree/__init__.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/worktree/_helpers.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/worktree/conftest.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/worktree/test_worktree.py +0 -0
- {syncade-0.7.3 → syncade-0.7.5}/tests/worktree/test_worktree_cleanup.py +0 -0
|
@@ -21,6 +21,7 @@ import site
|
|
|
21
21
|
import subprocess
|
|
22
22
|
import sys
|
|
23
23
|
from dataclasses import dataclass
|
|
24
|
+
from importlib.metadata import Distribution
|
|
24
25
|
from pathlib import Path
|
|
25
26
|
|
|
26
27
|
import syncade
|
|
@@ -29,9 +30,6 @@ from syncade.process import SubprocessError, run_subprocess
|
|
|
29
30
|
from syncade.update_check import _parse, _text, manifest_once
|
|
30
31
|
|
|
31
32
|
_UPGRADE_TIMEOUT = 300.0
|
|
32
|
-
#: Reading one version string out of a fresh interpreter. Generous, because a cold venv
|
|
33
|
-
#: import is slower than it looks; short, because we already hold the operator's terminal.
|
|
34
|
-
_VERSION_TIMEOUT = 60.0
|
|
35
33
|
#: How far up from ``syncade/__init__.py`` an install marker can sit. A venv layout is
|
|
36
34
|
#: ``<root>/lib/python3.x/site-packages/syncade/__init__.py`` — five parents to the root; the
|
|
37
35
|
#: extra headroom costs nothing and covers layouts that nest one deeper.
|
|
@@ -59,6 +57,21 @@ def _in_user_site() -> bool:
|
|
|
59
57
|
return user_site in Path(syncade.__file__).resolve().parents
|
|
60
58
|
|
|
61
59
|
|
|
60
|
+
def _scoped_dist_infos() -> list[Path]:
|
|
61
|
+
"""The dist-info directories beside the package that supplied this process's code."""
|
|
62
|
+
try:
|
|
63
|
+
site_packages = Path(syncade.__file__).resolve().parent.parent
|
|
64
|
+
return sorted(
|
|
65
|
+
entry
|
|
66
|
+
for entry in site_packages.iterdir()
|
|
67
|
+
if entry.name.casefold().startswith("syncade-")
|
|
68
|
+
and entry.name.casefold().endswith(".dist-info")
|
|
69
|
+
and entry.is_dir()
|
|
70
|
+
)
|
|
71
|
+
except (OSError, RuntimeError, TypeError):
|
|
72
|
+
return []
|
|
73
|
+
|
|
74
|
+
|
|
62
75
|
def _read_scoped_installer() -> str:
|
|
63
76
|
"""Read INSTALLER from the dist-info directory that BELONGS to this syncade install.
|
|
64
77
|
|
|
@@ -71,27 +84,44 @@ def _read_scoped_installer() -> str:
|
|
|
71
84
|
is empty string — the same as not found. First-match would misclassify in that state,
|
|
72
85
|
which contradicts the PROVE-or-refuse posture the rest of this module maintains.
|
|
73
86
|
"""
|
|
74
|
-
site_packages = Path(syncade.__file__).resolve().parent.parent
|
|
75
87
|
found: list[str] = []
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
and entry.is_dir()
|
|
82
|
-
):
|
|
83
|
-
try:
|
|
84
|
-
found.append((entry / "INSTALLER").read_text(encoding="utf-8").strip())
|
|
85
|
-
except OSError:
|
|
86
|
-
pass # No INSTALLER file — this dist-info cannot prove anything.
|
|
87
|
-
except OSError:
|
|
88
|
-
pass
|
|
88
|
+
for entry in _scoped_dist_infos():
|
|
89
|
+
try:
|
|
90
|
+
found.append((entry / "INSTALLER").read_text(encoding="utf-8").strip())
|
|
91
|
+
except OSError:
|
|
92
|
+
pass # No INSTALLER file — this dist-info cannot prove anything.
|
|
89
93
|
if not found:
|
|
90
94
|
return ""
|
|
91
95
|
# Contradictory entries in a broken packaging state are not proof of anything.
|
|
92
96
|
return found[0] if len(set(found)) == 1 else ""
|
|
93
97
|
|
|
94
98
|
|
|
99
|
+
def _read_scoped_version() -> str | None:
|
|
100
|
+
"""Read ``Version`` from this syncade install's one adjacent dist-info.
|
|
101
|
+
|
|
102
|
+
The package directory is already the authoritative identity: it supplied the code running
|
|
103
|
+
this function. Reading its sibling metadata after the package manager returns sees the new
|
|
104
|
+
on-disk version without resolving the name ``syncade`` through ``sys.path`` again. A missing,
|
|
105
|
+
malformed, or duplicate dist-info is ambiguous and therefore unverifiable, never first-match.
|
|
106
|
+
"""
|
|
107
|
+
candidates = _scoped_dist_infos()
|
|
108
|
+
if len(candidates) != 1:
|
|
109
|
+
return None
|
|
110
|
+
try:
|
|
111
|
+
if not (candidates[0] / "METADATA").is_file():
|
|
112
|
+
return None
|
|
113
|
+
metadata = Distribution.at(candidates[0]).metadata
|
|
114
|
+
names = metadata.get_all("Name") or []
|
|
115
|
+
versions = metadata.get_all("Version") or []
|
|
116
|
+
except Exception: # noqa: BLE001 — broken install metadata degrades to "unverifiable".
|
|
117
|
+
return None
|
|
118
|
+
if len(names) != 1 or names[0].strip().casefold() != "syncade":
|
|
119
|
+
return None
|
|
120
|
+
if len(versions) != 1:
|
|
121
|
+
return None
|
|
122
|
+
return versions[0].strip()
|
|
123
|
+
|
|
124
|
+
|
|
95
125
|
def _pip_or_unknown(where: Path | None) -> InstallMethod:
|
|
96
126
|
"""``pip`` when the distribution records pip as its installer, else ``unknown``.
|
|
97
127
|
|
|
@@ -239,49 +269,14 @@ def _installed_version() -> str | None:
|
|
|
239
269
|
"""The version on disk NOW, or ``None`` when it cannot be established.
|
|
240
270
|
|
|
241
271
|
It CANNOT be ``syncade.__version__``: this process imported that before the upgrade ran, so
|
|
242
|
-
it is by definition the old number.
|
|
243
|
-
|
|
244
|
-
|
|
272
|
+
it is by definition the old number. The value is read from the dist-info tree that belongs
|
|
273
|
+
to this install, so the answer follows the package on disk instead of any ambient metadata
|
|
274
|
+
discovery or subprocess import state.
|
|
245
275
|
|
|
246
276
|
``None`` means "could not verify", which is deliberately NOT the same as "did not change":
|
|
247
277
|
the caller must not turn an unreadable version into a failure report.
|
|
248
278
|
"""
|
|
249
|
-
|
|
250
|
-
result = run_subprocess(
|
|
251
|
-
# `-I` (isolated) is load-bearing, not tidiness. Without it the probe inherits
|
|
252
|
-
# PYTHONPATH and the user site dir, so a `sitecustomize` on that path runs inside
|
|
253
|
-
# the answer and can emit stdout before OR after the version print.
|
|
254
|
-
# `-I` keeps the venv's own site-packages, so `importlib.metadata` still finds
|
|
255
|
-
# the install (verified) — but it does NOT suppress `sitecustomize.py` that lives
|
|
256
|
-
# in those site-packages. An atexit hook registered there (syncade ships such a
|
|
257
|
-
# shim for worktree imports) can append a version-shaped line AFTER the real one.
|
|
258
|
-
# `os._exit(0)` bypasses all atexit handlers: the process terminates immediately
|
|
259
|
-
# after `os.write`, so nothing can append to our stdout (reproduced: a hostile
|
|
260
|
-
# sitecustomize turned "0.7.0" into "0.7.0\n99.0.0" before this fix).
|
|
261
|
-
# `os.write` is used instead of `print` because `os._exit` skips stdio flushing;
|
|
262
|
-
# an unbuffered syscall guarantees the version byte reaches the pipe.
|
|
263
|
-
[
|
|
264
|
-
sys.executable,
|
|
265
|
-
"-I",
|
|
266
|
-
"-c",
|
|
267
|
-
"import importlib.metadata as m, os; "
|
|
268
|
-
"os.write(1, (m.version('syncade') + '\\n').encode()); "
|
|
269
|
-
"os._exit(0)",
|
|
270
|
-
],
|
|
271
|
-
timeout=_VERSION_TIMEOUT,
|
|
272
|
-
)
|
|
273
|
-
except SubprocessError:
|
|
274
|
-
return None
|
|
275
|
-
if result.returncode != 0:
|
|
276
|
-
return None
|
|
277
|
-
raw = result.stdout.strip()
|
|
278
|
-
if not raw:
|
|
279
|
-
return None
|
|
280
|
-
# sitecustomize from PYTHONPATH (suppressed by -I) could emit noise before -c runs;
|
|
281
|
-
# the venv's own sitecustomize can still do so. Take the LAST line — atexit is now
|
|
282
|
-
# impossible (os._exit), so the last line is what os.write put there — and validate it
|
|
283
|
-
# parses as a version. Noise that accidentally ends in a dotted string still fails _parse.
|
|
284
|
-
candidate = raw.splitlines()[-1].strip()
|
|
279
|
+
candidate = _read_scoped_version()
|
|
285
280
|
return candidate if _parse(candidate) is not None else None
|
|
286
281
|
|
|
287
282
|
|
|
@@ -327,6 +327,11 @@ def _default_reviewers() -> list[ReviewerConfig]:
|
|
|
327
327
|
# model="claude-opus-4-6", adversarial_lens=True)
|
|
328
328
|
|
|
329
329
|
|
|
330
|
+
def _default_worktree_base() -> Path:
|
|
331
|
+
"""Resolve the worktree default when a config instance is created."""
|
|
332
|
+
return DEFAULT_WORKTREE_BASE
|
|
333
|
+
|
|
334
|
+
|
|
330
335
|
class SyncadeConfig(BaseModel):
|
|
331
336
|
"""Top-level ``.syncade/config.toml`` schema.
|
|
332
337
|
|
|
@@ -363,7 +368,7 @@ class SyncadeConfig(BaseModel):
|
|
|
363
368
|
# ``[worktree]`` block — Q5); default reproduces worktree.DEFAULT_WORKTREE_BASE.
|
|
364
369
|
# ``--worktree-base`` overrides per-invocation; threaded into the review run and doctor preview.
|
|
365
370
|
worktree_base: Path = Field(
|
|
366
|
-
|
|
371
|
+
default_factory=_default_worktree_base,
|
|
367
372
|
description=(
|
|
368
373
|
"Base directory under which each run's per-reviewer/producer/test git worktrees are "
|
|
369
374
|
f"created (default ``{DEFAULT_WORKTREE_BASE}``). Overridable per-run with "
|
|
@@ -277,6 +277,7 @@ tests/cli/test_update_mode.py
|
|
|
277
277
|
tests/cli/test_update_notice.py
|
|
278
278
|
tests/cli/test_update_post_condition.py
|
|
279
279
|
tests/cli/test_validate_before_mutate.py
|
|
280
|
+
tests/cli/test_version_probe.py
|
|
280
281
|
tests/config/__init__.py
|
|
281
282
|
tests/config/test_auth_enforcement.py
|
|
282
283
|
tests/config/test_auth_modes.py
|
|
@@ -15,7 +15,6 @@ that finally held is to validate a value as the thing it claims to be, at the po
|
|
|
15
15
|
|
|
16
16
|
from __future__ import annotations
|
|
17
17
|
|
|
18
|
-
import sys
|
|
19
18
|
from pathlib import Path
|
|
20
19
|
|
|
21
20
|
import pytest
|
|
@@ -23,7 +22,6 @@ import pytest
|
|
|
23
22
|
import syncade
|
|
24
23
|
from syncade.cli import update_mode
|
|
25
24
|
from syncade.exit_codes import SUCCESS, WORKTREE_ERROR
|
|
26
|
-
from syncade.process import SubprocessError, SubprocessResult
|
|
27
25
|
from tests.cli.test_update_mode import (
|
|
28
26
|
_install_tree,
|
|
29
27
|
_point_syncade_at,
|
|
@@ -90,62 +88,6 @@ def test_an_unverifiable_version_is_not_reported_as_failure(tmp_path, monkeypatc
|
|
|
90
88
|
assert "updated to" not in out, "no version may be named when none was established"
|
|
91
89
|
|
|
92
90
|
|
|
93
|
-
def test_the_version_probe_reads_a_fresh_interpreter_not_this_one(monkeypatch) -> None:
|
|
94
|
-
"""It must not answer from `syncade.__version__`, which this process imported BEFORE the
|
|
95
|
-
upgrade and is therefore the old number by construction."""
|
|
96
|
-
seen: list[list[str]] = []
|
|
97
|
-
|
|
98
|
-
def spy(cmd, **kw):
|
|
99
|
-
seen.append(cmd)
|
|
100
|
-
return SubprocessResult(returncode=0, stdout="1.2.3\n", stderr="", duration_seconds=0.0)
|
|
101
|
-
|
|
102
|
-
monkeypatch.setattr(update_mode, "run_subprocess", spy)
|
|
103
|
-
assert update_mode._installed_version() == "1.2.3"
|
|
104
|
-
assert seen and seen[0][0] == sys.executable, "must re-invoke the interpreter, not import"
|
|
105
|
-
assert "importlib.metadata" in seen[0][-1]
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
def test_an_unreadable_probe_degrades_to_none(monkeypatch) -> None:
|
|
109
|
-
for outcome in (
|
|
110
|
-
lambda *a, **k: (_ for _ in ()).throw(SubprocessError("no python")),
|
|
111
|
-
lambda *a, **k: SubprocessResult(returncode=1, stdout="", stderr="x", duration_seconds=0.0),
|
|
112
|
-
lambda *a, **k: SubprocessResult(
|
|
113
|
-
returncode=0, stdout=" \n", stderr="", duration_seconds=0.0
|
|
114
|
-
),
|
|
115
|
-
):
|
|
116
|
-
monkeypatch.setattr(update_mode, "run_subprocess", outcome)
|
|
117
|
-
assert update_mode._installed_version() is None
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
def test_startup_noise_before_version_line_is_stripped(monkeypatch) -> None:
|
|
121
|
-
"""sitecustomize imported from PYTHONPATH can emit stdout before the version print.
|
|
122
|
-
|
|
123
|
-
The old code returned the full stdout verbatim, so "noise\\n0.7.0" != "0.7.0" and
|
|
124
|
-
run_update treated the install as having moved — a false success on an unchanged version.
|
|
125
|
-
"""
|
|
126
|
-
monkeypatch.setattr(
|
|
127
|
-
update_mode,
|
|
128
|
-
"run_subprocess",
|
|
129
|
-
lambda *a, **k: SubprocessResult(
|
|
130
|
-
returncode=0, stdout="startup noise\n1.2.3\n", stderr="", duration_seconds=0.0
|
|
131
|
-
),
|
|
132
|
-
)
|
|
133
|
-
assert update_mode._installed_version() == "1.2.3"
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
def test_non_version_stdout_degrades_to_none(monkeypatch) -> None:
|
|
137
|
-
"""stdout that doesn't end with a parseable version string means 'could not verify'."""
|
|
138
|
-
for stdout in ("not a version\n", "noise only\n", "1.2\n", "1.2.3.4\n"):
|
|
139
|
-
monkeypatch.setattr(
|
|
140
|
-
update_mode,
|
|
141
|
-
"run_subprocess",
|
|
142
|
-
lambda *a, stdout=stdout, **k: SubprocessResult(
|
|
143
|
-
returncode=0, stdout=stdout, stderr="", duration_seconds=0.0
|
|
144
|
-
),
|
|
145
|
-
)
|
|
146
|
-
assert update_mode._installed_version() is None, f"should be None for {stdout!r}"
|
|
147
|
-
|
|
148
|
-
|
|
149
91
|
def test_an_already_current_install_is_not_reported_as_a_failed_upgrade(
|
|
150
92
|
tmp_path, monkeypatch, capsys
|
|
151
93
|
) -> None:
|
|
@@ -358,71 +300,6 @@ def test_is_newest_cannot_certify_an_unparseable_installed_version(monkeypatch)
|
|
|
358
300
|
assert update_mode._is_newest("not-a-version") is None
|
|
359
301
|
|
|
360
302
|
|
|
361
|
-
def test_the_probe_is_isolated_from_ambient_interpreter_state(monkeypatch) -> None:
|
|
362
|
-
"""`-I` is load-bearing: without it the probe inherits PYTHONPATH and the user site dir.
|
|
363
|
-
|
|
364
|
-
A `sitecustomize` on that path runs INSIDE the answer — an atexit hook printing a version
|
|
365
|
-
number becomes the last stdout line, so a no-op upgrade reads as a move. Reproduced live: a
|
|
366
|
-
hostile PYTHONPATH turned the probe's `"0.7.0"` into `"0.7.0\\n99.0.0"`. Syncade ships such a
|
|
367
|
-
shim itself for worktree imports, so this is not purely adversarial.
|
|
368
|
-
|
|
369
|
-
`-I` suppresses PYTHONPATH but NOT the venv's own site-packages, where a `sitecustomize.py`
|
|
370
|
-
can still register atexit hooks. The probe must call `os._exit(0)` to bypass atexit entirely —
|
|
371
|
-
the only guarantee against a venv-local hook appending to stdout after the version line.
|
|
372
|
-
`os.write` is asserted alongside it: `os._exit` skips stdio flushing, so `print()` would lose
|
|
373
|
-
the version; the unbuffered syscall guarantees delivery before the process exits.
|
|
374
|
-
|
|
375
|
-
Pinned as flag assertions because the behavioural twin below cannot run the failure case
|
|
376
|
-
without installing a real adversarial sitecustomize.
|
|
377
|
-
"""
|
|
378
|
-
seen: list[list[str]] = []
|
|
379
|
-
monkeypatch.setattr(
|
|
380
|
-
update_mode,
|
|
381
|
-
"run_subprocess",
|
|
382
|
-
lambda cmd, **kw: (
|
|
383
|
-
seen.append(cmd)
|
|
384
|
-
or SubprocessResult(returncode=0, stdout="1.2.3\n", stderr="", duration_seconds=0.0)
|
|
385
|
-
),
|
|
386
|
-
)
|
|
387
|
-
update_mode._installed_version()
|
|
388
|
-
assert seen and "-I" in seen[0], (
|
|
389
|
-
"the version probe must run isolated; without -I a sitecustomize on PYTHONPATH can "
|
|
390
|
-
"append a version number to its stdout and fake a successful upgrade"
|
|
391
|
-
)
|
|
392
|
-
assert seen[0].index("-I") < seen[0].index("-c"), "-I must precede -c to take effect"
|
|
393
|
-
c_arg = seen[0][seen[0].index("-c") + 1]
|
|
394
|
-
assert "os._exit(0)" in c_arg, (
|
|
395
|
-
"the probe must call os._exit(0) to bypass atexit handlers; a venv-local "
|
|
396
|
-
"sitecustomize can register atexit hooks that append a fake version line after the real one"
|
|
397
|
-
)
|
|
398
|
-
assert "os.write" in c_arg, (
|
|
399
|
-
"the probe must use os.write (unbuffered) not print; os._exit skips stdio flushing "
|
|
400
|
-
"so print output would be lost before reaching the pipe"
|
|
401
|
-
)
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
def test_the_isolated_probe_still_resolves_the_real_install() -> None:
|
|
405
|
-
"""The behavioural twin: `-I` must not break what the probe is FOR.
|
|
406
|
-
|
|
407
|
-
It drops PYTHONPATH and the user site dir but keeps the environment's own site-packages, so
|
|
408
|
-
`importlib.metadata` still finds the package. Runs the real subprocess — a flag assertion
|
|
409
|
-
alone would happily pin an isolation level that returns None for everyone.
|
|
410
|
-
|
|
411
|
-
We assert the result is a parseable, non-None version, NOT that it equals
|
|
412
|
-
`syncade.__version__`. In a dev worktree the source version and the installed distribution
|
|
413
|
-
version legitimately differ (e.g. 0.7.0 in source, 0.1.0 in site-packages); asserting
|
|
414
|
-
equality would make this test prove the ambient installation, not the probe's behaviour.
|
|
415
|
-
"""
|
|
416
|
-
from syncade.update_check import _parse
|
|
417
|
-
|
|
418
|
-
result = update_mode._installed_version()
|
|
419
|
-
assert result is not None, (
|
|
420
|
-
"the probe must find the installed package under -I "
|
|
421
|
-
"(drops PYTHONPATH but keeps the environment's own site-packages)"
|
|
422
|
-
)
|
|
423
|
-
assert _parse(result) is not None, f"result {result!r} must be a parseable version string"
|
|
424
|
-
|
|
425
|
-
|
|
426
303
|
# ------------------------------------------------- unreachable manifest is not a pin (field-09)
|
|
427
304
|
|
|
428
305
|
|
|
@@ -654,3 +531,6 @@ def test_doctor_skips_the_check_under_ci(monkeypatch) -> None:
|
|
|
654
531
|
check = _check_update_manifest(enabled=True)
|
|
655
532
|
assert check.status == "skip"
|
|
656
533
|
assert "CI" in check.detail
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
# ------------------------------------- the probe must see a user-site install (pr-h-field-10)
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""The post-update version comes from the exact install tree running syncade.
|
|
2
|
+
|
|
3
|
+
The old probe spawned another interpreter and asked which distribution the name ``syncade``
|
|
4
|
+
resolved to. Isolation could hide a user-site install; relaxing it let CWD, user-site metadata,
|
|
5
|
+
``sitecustomize``, and shutdown hooks influence the answer. These tests pin the re-derived
|
|
6
|
+
property instead: read fresh metadata beside ``syncade.__file__`` and never resolve the name.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
import pytest
|
|
15
|
+
|
|
16
|
+
from syncade.cli import update_mode
|
|
17
|
+
from syncade.exit_codes import SUCCESS
|
|
18
|
+
from syncade.process import SubprocessResult
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _metadata(version: str, *, name: str = "syncade") -> str:
|
|
22
|
+
return f"Metadata-Version: 2.1\nName: {name}\nVersion: {version}\n"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _install_tree(
|
|
26
|
+
tmp_path: Path, version: str = "1.2.3", *, installer: str | None = None
|
|
27
|
+
) -> tuple[Path, Path]:
|
|
28
|
+
site_packages = tmp_path / "site-packages"
|
|
29
|
+
package_file = site_packages / "syncade" / "__init__.py"
|
|
30
|
+
package_file.parent.mkdir(parents=True)
|
|
31
|
+
package_file.write_text("__version__ = 'loaded-before-the-update'\n", encoding="utf-8")
|
|
32
|
+
dist_info = site_packages / f"syncade-{version}.dist-info"
|
|
33
|
+
dist_info.mkdir()
|
|
34
|
+
(dist_info / "METADATA").write_text(_metadata(version), encoding="utf-8")
|
|
35
|
+
if installer is not None:
|
|
36
|
+
(dist_info / "INSTALLER").write_text(installer, encoding="utf-8")
|
|
37
|
+
return package_file, dist_info
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _point_syncade_at(monkeypatch: pytest.MonkeyPatch, package_file: Path) -> None:
|
|
41
|
+
monkeypatch.setattr(update_mode.syncade, "__file__", str(package_file))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_reads_fresh_version_from_the_running_install_tree(tmp_path, monkeypatch) -> None:
|
|
45
|
+
package_file, dist_info = _install_tree(tmp_path, "1.2.3")
|
|
46
|
+
_point_syncade_at(monkeypatch, package_file)
|
|
47
|
+
|
|
48
|
+
assert update_mode._installed_version() == "1.2.3"
|
|
49
|
+
|
|
50
|
+
replacement = dist_info.with_name("syncade-1.2.4.dist-info")
|
|
51
|
+
dist_info.rename(replacement)
|
|
52
|
+
(replacement / "METADATA").write_text(_metadata("1.2.4"), encoding="utf-8")
|
|
53
|
+
assert update_mode._installed_version() == "1.2.4", "the post-condition must not be cached"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_ambient_python_state_and_shutdown_hooks_are_not_probe_channels(
|
|
57
|
+
tmp_path, monkeypatch
|
|
58
|
+
) -> None:
|
|
59
|
+
package_file, _ = _install_tree(tmp_path / "real", "1.2.3")
|
|
60
|
+
_point_syncade_at(monkeypatch, package_file)
|
|
61
|
+
|
|
62
|
+
hostile_path = tmp_path / "hostile-pythonpath"
|
|
63
|
+
hostile_path.mkdir()
|
|
64
|
+
(hostile_path / "sitecustomize.py").write_text(
|
|
65
|
+
"import atexit\nprint('98.0.0')\natexit.register(lambda: print('99.0.0'))\n",
|
|
66
|
+
encoding="utf-8",
|
|
67
|
+
)
|
|
68
|
+
cwd = tmp_path / "cwd"
|
|
69
|
+
cwd.mkdir()
|
|
70
|
+
cwd_dist = cwd / "syncade-97.0.0.dist-info"
|
|
71
|
+
cwd_dist.mkdir()
|
|
72
|
+
(cwd_dist / "METADATA").write_text(_metadata("97.0.0"), encoding="utf-8")
|
|
73
|
+
monkeypatch.chdir(cwd)
|
|
74
|
+
monkeypatch.setenv("PYTHONPATH", str(hostile_path))
|
|
75
|
+
monkeypatch.setenv("PYTHONUSERBASE", str(tmp_path / "unrelated-user-base"))
|
|
76
|
+
monkeypatch.setattr(
|
|
77
|
+
update_mode,
|
|
78
|
+
"run_subprocess",
|
|
79
|
+
lambda *a, **k: pytest.fail("reading an installed version must not spawn a subprocess"),
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
assert update_mode._installed_version() == "1.2.3"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@pytest.mark.parametrize(
|
|
86
|
+
"metadata_bytes",
|
|
87
|
+
[
|
|
88
|
+
None,
|
|
89
|
+
b"\xff",
|
|
90
|
+
b"Metadata-Version: 2.1\nVersion: 1.2.3\n",
|
|
91
|
+
b"Metadata-Version: 2.1\nName: other\nVersion: 1.2.3\n",
|
|
92
|
+
b"Metadata-Version: 2.1\nName: syncade\n",
|
|
93
|
+
b"Metadata-Version: 2.1\nName: syncade\nVersion: 1.2\n",
|
|
94
|
+
b"Metadata-Version: 2.1\nName: syncade\nName: syncade\nVersion: 1.2.3\n",
|
|
95
|
+
b"Metadata-Version: 2.1\nName: syncade\nVersion: 1.2.3\nVersion: 9.9.9\n",
|
|
96
|
+
],
|
|
97
|
+
)
|
|
98
|
+
def test_missing_or_malformed_metadata_is_unverifiable(
|
|
99
|
+
tmp_path, monkeypatch, metadata_bytes
|
|
100
|
+
) -> None:
|
|
101
|
+
package_file, dist_info = _install_tree(tmp_path)
|
|
102
|
+
metadata_path = dist_info / "METADATA"
|
|
103
|
+
if metadata_bytes is None:
|
|
104
|
+
metadata_path.unlink()
|
|
105
|
+
else:
|
|
106
|
+
metadata_path.write_bytes(metadata_bytes)
|
|
107
|
+
_point_syncade_at(monkeypatch, package_file)
|
|
108
|
+
|
|
109
|
+
assert update_mode._installed_version() is None
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def test_duplicate_adjacent_dist_info_is_unverifiable(tmp_path, monkeypatch) -> None:
|
|
113
|
+
package_file, _ = _install_tree(tmp_path, "1.2.3")
|
|
114
|
+
site_packages = package_file.parent.parent
|
|
115
|
+
stale = site_packages / "syncade-9.9.9.dist-info"
|
|
116
|
+
stale.mkdir()
|
|
117
|
+
(stale / "METADATA").write_text(_metadata("9.9.9"), encoding="utf-8")
|
|
118
|
+
_point_syncade_at(monkeypatch, package_file)
|
|
119
|
+
|
|
120
|
+
assert update_mode._installed_version() is None
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def test_non_adjacent_metadata_and_editable_metadata_are_ignored(tmp_path, monkeypatch) -> None:
|
|
124
|
+
package_file = tmp_path / "checkout" / "src" / "syncade" / "__init__.py"
|
|
125
|
+
package_file.parent.mkdir(parents=True)
|
|
126
|
+
package_file.write_text("__version__ = '0.0.0'\n", encoding="utf-8")
|
|
127
|
+
egg_info = package_file.parent.parent / "syncade.egg-info"
|
|
128
|
+
egg_info.mkdir()
|
|
129
|
+
(egg_info / "PKG-INFO").write_text(_metadata("8.8.8"), encoding="utf-8")
|
|
130
|
+
_install_tree(tmp_path / "other-install", "9.9.9")
|
|
131
|
+
_point_syncade_at(monkeypatch, package_file)
|
|
132
|
+
|
|
133
|
+
assert update_mode._installed_version() is None
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def test_user_site_noop_update_reports_already_current(tmp_path, monkeypatch, capsys) -> None:
|
|
137
|
+
current = update_mode.syncade.__version__
|
|
138
|
+
package_file, _ = _install_tree(tmp_path, current, installer="pip")
|
|
139
|
+
site_packages = package_file.parent.parent.resolve()
|
|
140
|
+
_point_syncade_at(monkeypatch, package_file)
|
|
141
|
+
monkeypatch.setattr(update_mode.site, "getusersitepackages", lambda: str(site_packages))
|
|
142
|
+
monkeypatch.setattr(update_mode, "live_run", lambda cwd: None)
|
|
143
|
+
monkeypatch.setattr(update_mode, "_resync_skills", lambda out: True)
|
|
144
|
+
monkeypatch.setattr(update_mode, "manifest_once", lambda *, enabled=True: {"latest": current})
|
|
145
|
+
monkeypatch.delenv("CI", raising=False)
|
|
146
|
+
commands: list[list[str]] = []
|
|
147
|
+
|
|
148
|
+
def successful_package_manager(cmd, **kwargs):
|
|
149
|
+
commands.append(cmd)
|
|
150
|
+
return SubprocessResult(returncode=0, stdout="", stderr="", duration_seconds=0.0)
|
|
151
|
+
|
|
152
|
+
monkeypatch.setattr(update_mode, "run_subprocess", successful_package_manager)
|
|
153
|
+
|
|
154
|
+
assert update_mode.run_update(cwd=tmp_path) == SUCCESS
|
|
155
|
+
assert commands == [[sys.executable, "-m", "pip", "install", "-U", "--user", "syncade"]]
|
|
156
|
+
out = capsys.readouterr().err
|
|
157
|
+
assert f"already up to date ({current})" in out
|
|
158
|
+
assert "[syncade] updated." not in out
|
|
@@ -79,7 +79,7 @@ def _isolate_global_config(monkeypatch, tmp_path_factory):
|
|
|
79
79
|
|
|
80
80
|
|
|
81
81
|
@pytest.fixture(autouse=True)
|
|
82
|
-
def _isolated_worktree_base(request, tmp_path):
|
|
82
|
+
def _isolated_worktree_base(request, tmp_path, monkeypatch):
|
|
83
83
|
"""No test may touch the SHARED worktree base — PR-h-12 item 1b.
|
|
84
84
|
|
|
85
85
|
Moved here from ``tests/orchestrator/conftest.py``, where it was package-scoped. Its own
|
|
@@ -89,12 +89,12 @@ def _isolated_worktree_base(request, tmp_path):
|
|
|
89
89
|
worktrees leaked — so the brief's original 4.4 GB attribution was wrong, exactly as it later
|
|
90
90
|
corrected itself to say. The gap was real; the magnitude was not.
|
|
91
91
|
|
|
92
|
-
``run_review`` falls back to ``config.worktree_base`` (PR-v2-9), so the
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
92
|
+
``run_review`` falls back to ``config.worktree_base`` (PR-v2-9), so the config default is what
|
|
93
|
+
must move: a bare ``SyncadeConfig()`` has to inherit the per-test path too. The field uses a
|
|
94
|
+
default factory so patching its module-level source works across the supported Pydantic range
|
|
95
|
+
without mutating Pydantic's compiled schema.
|
|
96
96
|
"""
|
|
97
|
-
from syncade
|
|
97
|
+
from syncade import config as config_module
|
|
98
98
|
|
|
99
99
|
# A DRIFT TEST must be able to see the real default. Two of them assert exactly that
|
|
100
100
|
# (`SyncadeConfig().worktree_base == DEFAULT_WORKTREE_BASE`, "byte-identical"), and this
|
|
@@ -105,13 +105,12 @@ def _isolated_worktree_base(request, tmp_path):
|
|
|
105
105
|
yield
|
|
106
106
|
return
|
|
107
107
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
108
|
+
monkeypatch.setattr(
|
|
109
|
+
config_module,
|
|
110
|
+
"DEFAULT_WORKTREE_BASE",
|
|
111
|
+
tmp_path / "syncade-worktrees",
|
|
112
|
+
)
|
|
112
113
|
yield
|
|
113
|
-
field.default = old_default
|
|
114
|
-
SyncadeConfig.model_rebuild(force=True)
|
|
115
114
|
|
|
116
115
|
|
|
117
116
|
@pytest.fixture(autouse=True)
|
|
@@ -23,8 +23,8 @@ _ROOT = Path(__file__).resolve().parent
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
def test_a_bare_config_never_resolves_to_the_shared_base(tmp_path: Path) -> None:
|
|
26
|
-
"""The fixture patches the
|
|
27
|
-
`config.worktree_base` (PR-v2-9). Patching only an explicit argument would leave
|
|
26
|
+
"""The fixture patches the default factory's source, because `run_review` falls back to
|
|
27
|
+
`config.worktree_base` (PR-v2-9). Patching only an explicit argument would leave a bare
|
|
28
28
|
`SyncadeConfig()` pointing at the shared base — which is how six packages kept reaching it.
|
|
29
29
|
"""
|
|
30
30
|
assert SyncadeConfig().worktree_base != DEFAULT_WORKTREE_BASE
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|