syncade 0.6.2__tar.gz → 0.7.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.
- {syncade-0.6.2/src/syncade.egg-info → syncade-0.7.0}/PKG-INFO +34 -10
- {syncade-0.6.2 → syncade-0.7.0}/README.md +33 -9
- {syncade-0.6.2 → syncade-0.7.0}/docs/config-reference.md +22 -1
- {syncade-0.6.2 → syncade-0.7.0}/docs/how-to-use.md +4 -3
- {syncade-0.6.2 → syncade-0.7.0}/pyproject.toml +2 -1
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/__init__.py +1 -1
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/cli/__init__.py +31 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/cli/gc_mode.py +9 -1
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/cli/install_skill.py +17 -4
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/cli/metrics_mode.py +81 -2
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/cli/parser.py +28 -15
- syncade-0.7.0/src/syncade/cli/skill_status.py +142 -0
- syncade-0.7.0/src/syncade/cli/update_mode.py +226 -0
- syncade-0.7.0/src/syncade/cli/update_notice.py +191 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/cli/validate.py +33 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/config.py +4 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/config_gc.py +18 -1
- syncade-0.7.0/src/syncade/config_update.py +29 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/gc.py +58 -1
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/gc_execute.py +12 -1
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/gc_types.py +13 -2
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/metrics/aggregate.py +42 -21
- syncade-0.7.0/src/syncade/metrics/findings.py +358 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/metrics/schema.py +134 -2
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/orchestrator/reviewer_template_failure.py +1 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/skills/claude/README.md +3 -2
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/skills/claude/SKILL.md +19 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/skills/codex/README.md +3 -2
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/skills/codex/SKILL.md +19 -0
- syncade-0.7.0/src/syncade/update_check.py +263 -0
- {syncade-0.6.2 → syncade-0.7.0/src/syncade.egg-info}/PKG-INFO +34 -10
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade.egg-info/SOURCES.txt +16 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/cli/test_config_menu_rows.py +5 -1
- {syncade-0.6.2 → syncade-0.7.0}/tests/cli/test_config_overrides.py +1 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/cli/test_documented_flags_exist.py +34 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/cli/test_gc.py +3 -3
- {syncade-0.6.2 → syncade-0.7.0}/tests/cli/test_help_retention_contract.py +16 -4
- {syncade-0.6.2 → syncade-0.7.0}/tests/cli/test_mode_exclusivity_matrix.py +1 -0
- syncade-0.7.0/tests/cli/test_update_mode.py +407 -0
- syncade-0.7.0/tests/cli/test_update_notice.py +505 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/config/test_worktree_base_config.py +1 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/conftest.py +36 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/gc/test_execute.py +17 -17
- {syncade-0.6.2 → syncade-0.7.0}/tests/gc/test_plan.py +13 -13
- syncade-0.7.0/tests/gc/test_retention_tiers.py +121 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/gc/test_self_orphan_protection.py +10 -6
- syncade-0.7.0/tests/gc/test_worktree_age_bound.py +300 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/metrics/test_actor_spend.py +1 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/metrics/test_aggregate.py +19 -4
- syncade-0.7.0/tests/metrics/test_consensus_report.py +428 -0
- syncade-0.7.0/tests/metrics/test_findings_capture.py +437 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/metrics/test_schema.py +70 -0
- syncade-0.7.0/tests/metrics/test_unknown_panel.py +286 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/conftest.py +8 -35
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_template_robustness.py +55 -0
- syncade-0.7.0/tests/skills/test_skill_status.py +265 -0
- syncade-0.7.0/tests/test_update_check.py +426 -0
- syncade-0.7.0/tests/test_worktree_base_isolation.py +58 -0
- {syncade-0.6.2 → syncade-0.7.0}/LICENSE +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/MANIFEST.in +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/setup.cfg +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/__main__.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/adapters/__init__.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/adapters/anthropic.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/adapters/base.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/adapters/fake.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/adapters/fake_common.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/adapters/fake_producer_audit_draft.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/adapters/fake_reviewer_synth.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/adapters/openai.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/adapters/openai_parsing.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/adapters/producer.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/adapters/producer_anthropic.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/adapters/producer_openai.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/adapters/registry.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/auth_check.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/auth_preflight.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/base_resolution.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/billing.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/checks_config.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/cli/auth_gate.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/cli/config_keys.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/cli/config_list.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/cli/config_menu_rows.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/cli/config_mode.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/cli/config_overrides.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/cli/config_tui.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/cli/doctor_mode.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/cli/modes.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/cli/parser_types.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/cli/paths.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/cli/preflight_paths.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/cli/resolve.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/cli/resume_mode.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/cli/toml_writer.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/config_auth.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/config_cold.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/config_loader.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/config_loop.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/config_producer.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/config_retry.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/config_types.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/diff_filter.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/dispatcher.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/doctor.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/doctor_env.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/doctor_preview.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/doctor_types.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/exit_codes.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/findings.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/findings_json.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/gc_protection.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/gc_worktrees.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/git_object_id.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/git_preconditions.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/logging.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/metrics/__init__.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/orchestrator/__init__.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/orchestrator/_runs_dir.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/orchestrator/branch_advance.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/orchestrator/branch_guard.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/orchestrator/budget.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/orchestrator/escalation_coverage.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/orchestrator/loop.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/orchestrator/loop_dispatch_check.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/orchestrator/loop_finalize.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/orchestrator/loop_preflight.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/orchestrator/loop_resume.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/orchestrator/loop_rmtree.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/orchestrator/loop_round_step.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/orchestrator/prior_round.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/orchestrator/producer_phase.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/orchestrator/results.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/orchestrator/resume.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/orchestrator/resume_load.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/orchestrator/resume_plan.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/orchestrator/resume_target.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/orchestrator/resume_types.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/orchestrator/round.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/orchestrator/round_checks.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/orchestrator/round_no_changes.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/orchestrator/round_predispatch.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/orchestrator/verdict.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/persistence/__init__.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/persistence/_atomic.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/persistence/_clusters.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/persistence/_findings_verdict.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/persistence/_markdown.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/persistence/_validation.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/persistence/checks.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/persistence/decision_needed.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/persistence/findings_md.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/persistence/handoff.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/persistence/handoff_classify.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/persistence/last_reviewed.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/persistence/loop_manifest.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/persistence/loop_summary.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/persistence/loop_summary_text.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/persistence/producer.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/persistence/reviewer.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/persistence/round_manifest.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/persistence/run_init.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/persistence/run_summary.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/persistence/run_summary_next_steps.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/persistence/synth.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/persistence/test_run.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/presets.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/pricing_config.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/process.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/producer.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/producer_attempt.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/producer_escalation.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/producer_git.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/producer_result.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/prompts.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/prompts_loader.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/retry.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/run_inputs.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/run_status.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/selfcheck.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/snapshot.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/spec_audit.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/spec_audit_schema.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/spec_draft.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/spec_source.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/synthesis.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/synthesis_clusters.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/synthesis_repair.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/synthesis_schema.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/synthesizer/__init__.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/synthesizer/constants.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/synthesizer/driver.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/synthesizer/rendering.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/synthesizer/result.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/synthesizer/validation.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/synthesizer/workspace.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/templates/presets/balanced.toml +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/templates/presets/cheap.toml +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/templates/presets/thorough.toml +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/templates/producer.md +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/templates/reviewer.md +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/templates/reviewer_adversarial.md +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/templates/reviewer_codex.md +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/templates/spec_audit.md +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/templates/spec_draft.md +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/templates/synthesizer.md +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/test_runner.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/test_runner_classify.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/transcript.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/usage.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/worktree.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/worktree_env.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade/worktree_paths.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade.egg-info/dependency_links.txt +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade.egg-info/entry_points.txt +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade.egg-info/requires.txt +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/src/syncade.egg-info/top_level.txt +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/__init__.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/adapters/__init__.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/adapters/_anthropic_helpers.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/adapters/_openai_helpers.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/adapters/test_anthropic.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/adapters/test_anthropic_more.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/adapters/test_openai.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/adapters/test_openai_more.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/adapters/test_producer.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/adapters/test_producer_more.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/adapters/test_prompt_transport.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/auth_check/__init__.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/auth_check/test_probes.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/auth_check/test_run_auth_check.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/cli/__init__.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/cli/_helpers.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/cli/_undo_helpers.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/cli/test_basic_and_mutex.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/cli/test_cli_library_divergence.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/cli/test_config_keys.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/cli/test_config_list.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/cli/test_config_mode.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/cli/test_config_tui.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/cli/test_config_tui_pty.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/cli/test_documented_exit_codes.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/cli/test_flags.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/cli/test_mode_flag_guards.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/cli/test_resume_reports_hard_kill.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/cli/test_run_and_config.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/cli/test_run_status_cli.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/cli/test_scope_openspec_draft.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/cli/test_toml_writer.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/cli/test_undo_auto_init.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/cli/test_undo_scope.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/cli/test_validate_before_mutate.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/config/__init__.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/config/test_auth_enforcement.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/config/test_auth_modes.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/config/test_auth_preflight.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/config/test_auth_report_scope.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/config/test_budget_config.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/config/test_cold_actors.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/config/test_config_checks.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/config/test_config_reference_drift.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/config/test_config_schema.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/config/test_gc_config.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/config/test_global_config_layer.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/config/test_presets.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/config/test_retry_config.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/config/test_reviewer_config.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/config/test_reviewer_permissions.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/dispatcher/__init__.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/dispatcher/_helpers.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/dispatcher/test_dispatch_core.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/dispatcher/test_dispatch_misc.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/dispatcher/test_dispatch_usage.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/doctor/__init__.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/doctor/_helpers.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/doctor/conftest.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/doctor/test_doctor.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/doctor/test_doctor_diff_size.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/doctor/test_doctor_malformed_diff.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/doctor/test_doctor_preview.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/findings/__init__.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/findings/_helpers.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/findings/test_parser_and_fields.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/findings/test_parser_resilience.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/findings/test_verdict_block_selection.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/gc/__init__.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/gc/_helpers.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/gc/test_autoprune.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/gc/test_plan_execution_races.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/gc/test_reaping_scope.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/gc/test_robustness.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/gc/test_smoke.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/gc/test_two_tier_retention.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/logging/__init__.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/logging/_helpers.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/logging/test_logging.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/logging/test_logging_summary.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/metrics/conftest.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/metrics/test_billing_reconciliation.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/metrics/test_billing_surfaces_agree.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/metrics/test_budget_metrics.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/metrics/test_cost_honesty.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/metrics/test_metrics_cli.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/metrics/test_metrics_mode.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/__init__.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/_helpers.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/_resume_fixtures.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_budget.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_budget_resume_e2e.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_budget_stop_message.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_budget_warning.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_checks_and_recording.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_contracts.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_cross_round.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_cross_round_more.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_default_branch_guard.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_diff_size_cap.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_dispatch_record.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_empty_diff.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_empty_diff_no_side_effects.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_escalation.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_last_reviewed_truthful.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_loop_audit_remediation.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_matrix_and_e2e.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_multiround.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_multiround_dirty.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_multiround_more.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_paraphrase_escalation.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_pipeline.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_provider_usage_limit.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_rename_boundary.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_resume.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_resume_base_oid.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_reviewer_blindness_is_structural.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_round_input_boundaries.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_run_layout.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_run_status_wiring.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_safety_warning_survives_quiet.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_snapshot_and_base.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_synth_phase.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_synth_phase_more.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_synth_provenance.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_synth_validation.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_termination_reason_drift.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_testleg.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_timeout_is_per_subprocess.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_usage_limit_message.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/orchestrator/test_worktree_cleanup.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/persistence/__init__.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/persistence/_helpers.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/persistence/test_atomic_writes.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/persistence/test_checks_surfacing.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/persistence/test_diff_size_recording.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/persistence/test_findings_md.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/persistence/test_findings_md_more.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/persistence/test_findings_md_verdict.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/persistence/test_handoff.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/persistence/test_loop_manifest_checks.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/persistence/test_loop_summary.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/persistence/test_loop_summary_more.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/persistence/test_md_safety.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/persistence/test_next_steps.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/persistence/test_producer_persist.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/persistence/test_reviewer.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/persistence/test_round_manifest.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/persistence/test_run_init.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/persistence/test_run_summary.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/persistence/test_run_summary_more.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/persistence/test_run_summary_producer.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/persistence/test_synth_persist.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/persistence/test_test_run.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/producer/__init__.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/producer/_helpers.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/producer/test_producer.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/producer/test_producer_confinement.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/producer/test_producer_retry.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/producer/test_producer_run.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/producer/test_producer_timeout.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/producer/test_producer_usage.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/prompts/__init__.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/prompts/test_load_template.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/prompts/test_misc.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/prompts/test_producer.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/prompts/test_render_synthesizer.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/prompts/test_synthesizer.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/resume/__init__.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/resume/_helpers.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/resume/test_budget_resume.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/resume/test_checks_phase_failure.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/resume/test_cli_malformed_rehydration.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/resume/test_find_resolve_plan.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/resume/test_plan_resume.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/resume/test_round_trip.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/resume/test_tree_drift_and_escalation.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/skills/test_config_section.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/skills/test_install_skill.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/skills/test_install_skill_multi_harness.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/skills/test_install_skill_ownership.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/skills/test_install_skill_refuses.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/skills/test_skill_drift.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/smoke/__init__.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/smoke/test_anthropic_smoke.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/smoke/test_auth_check_smoke.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/smoke/test_codex_smoke.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/smoke/test_loop_smoke.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/smoke/test_orchestrator_smoke.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/smoke/test_orchestrator_smoke_more.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/smoke/test_producer_smoke.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/smoke/test_resume_reap_smoke.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/smoke/test_selfcheck_smoke.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/smoke/test_synthesizer_smoke.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/smoke/test_test_runner_smoke.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/snapshot/__init__.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/snapshot/_helpers.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/snapshot/conftest.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/snapshot/test_snapshot.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/snapshot/test_snapshot_diff_identity.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/snapshot/test_snapshot_dirty.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/snapshot/test_snapshot_warning.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/snapshot/test_three_dot_diff.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/synthesis/__init__.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/synthesis/_helpers.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/synthesis/test_cluster_repair.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/synthesis/test_clusters.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/synthesis/test_models.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/synthesis/test_models_distinct_keying.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/synthesis/test_parsing.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/synthesis/test_unanimous_blocker_downgrade.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/synthesizer/test_provenance_fabrication.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/synthesizer/test_provenance_repair.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_adapter_registry.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_adapters_fake.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_auto_init_policy.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_base_resolution.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_check_loc_script.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_checks.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_config_loader.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_cross_model_claim.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_diff_filter.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_diff_filter_binary.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_diff_filter_quoted_paths.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_escalation_coverage.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_exit_codes.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_git_choke_point.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_git_config_isolation.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_git_preconditions.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_last_reviewed.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_parse_repair_extra_keys.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_pricing.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_private_identifier_gate.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_process.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_process_teardown.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_producer_escalation.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_regression_acme.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_retry.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_run_status.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_run_status_signals.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_runner/__init__.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_runner/test_test_runner.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_runner/test_test_runner_more.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_safety_disclosures_survive_quiet.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_selfcheck.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_spec_audit.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_spec_draft.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_spec_source.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_streamed_capture.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_strip_list_semantics.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_synthesizer.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_synthesizer_passthrough.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_synthesizer_provenance.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_transcript.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_usage.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_usage_persistence.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_workflow_action_pins.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_worktree_env.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/test_worktree_strip_guard.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/worktree/__init__.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/worktree/_helpers.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/worktree/conftest.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/worktree/test_worktree.py +0 -0
- {syncade-0.6.2 → syncade-0.7.0}/tests/worktree/test_worktree_cleanup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: syncade
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: External blind multi-judge review orchestrator for AI-assisted coding
|
|
5
5
|
Author-email: Syncade <build@syncade.ai>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -87,9 +87,15 @@ terminal or copy-paste between tools.
|
|
|
87
87
|
## Install
|
|
88
88
|
|
|
89
89
|
```bash
|
|
90
|
-
uv tool install
|
|
90
|
+
uv tool install syncade # recommended
|
|
91
91
|
# or with pip:
|
|
92
|
-
pip install
|
|
92
|
+
pip install syncade
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
To track unreleased `main` instead of the latest release:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
uv tool install git+https://github.com/syncade-ai/syncade-ai.git
|
|
93
99
|
```
|
|
94
100
|
|
|
95
101
|
Then install the harness integration so you can invoke it as `/syncade`:
|
|
@@ -104,6 +110,24 @@ touching the directory and names every file at risk. Ordinary upgrades need no f
|
|
|
104
110
|
recognises its own past output by content, not by filename. Use `--force-install` to overwrite
|
|
105
111
|
deliberately; it still lists what it destroys.
|
|
106
112
|
|
|
113
|
+
### Staying up to date
|
|
114
|
+
|
|
115
|
+
The first time you run syncade in a new terminal or harness window, it checks once whether a
|
|
116
|
+
newer release exists and prints a line if so. It only ever prints — it never blocks a run,
|
|
117
|
+
changes an exit code, or fails one, and every error (offline, bad response) is silent. Nothing
|
|
118
|
+
about your repo, diff, or run is sent. Turn it off with `syncade --config set update.check false`,
|
|
119
|
+
or set `CI` in automation.
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
syncade --update # upgrade, then re-run your command
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
`--update` works out how syncade was installed by looking for a marker inside its own install
|
|
126
|
+
tree, and **refuses rather than guessing** if it cannot tell — printing the manual `pip` command
|
|
127
|
+
instead. It also refuses while a review is still running, and declines to touch a source
|
|
128
|
+
checkout. A running process cannot switch to the version it just installed, so it exits and asks
|
|
129
|
+
you to re-run.
|
|
130
|
+
|
|
107
131
|
**Requirements**
|
|
108
132
|
- **Python 3.11+**, on **macOS or Linux** (on Windows, use WSL).
|
|
109
133
|
- **`codex`** (OpenAI), on your `PATH` and authenticated — required for the default
|
|
@@ -263,7 +287,7 @@ syncade runs AI coding-agent CLIs with **elevated tool access on your repo**:
|
|
|
263
287
|
- **Reviewers** run headless inside throwaway worktree copies under `/tmp/syncade/` (Codex reviewers are additionally sandboxed to theirs).
|
|
264
288
|
- **The producer** runs unsandboxed and **commits to your current branch** (syncade refuses the default branch by default).
|
|
265
289
|
- Full LLM transcripts — including source a reviewer read — are written to `.syncade/runs/` (gitignored, auto-pruned). **A secret in a file a reviewer opens lands there in plaintext.**
|
|
266
|
-
- syncade makes **
|
|
290
|
+
- syncade makes **one network call of its own**: a once-per-session GET to its public update manifest to check for a newer version (suppressible via `[update] check = false`). `syncade --update` additionally invokes your package manager (`uv tool upgrade`, `pipx upgrade`, or `pip install -U`) — operator-requested and only when you run that flag. All other egress is the provider CLIs you already authenticated and the test/check commands you configure.
|
|
267
291
|
|
|
268
292
|
Running syncade is comparable to running the target repo's `Makefile` — it executes code
|
|
269
293
|
with your permissions. See **[SECURITY.md](SECURITY.md)** for the full threat model and how
|
|
@@ -278,12 +302,12 @@ Early access. These are measured, not suspected.
|
|
|
278
302
|
Both findings were real. If a SHIP arrives over code you did not change since a NO-SHIP, treat
|
|
279
303
|
it as the weaker signal — compare the verdict to what the *code* changed, not to the previous
|
|
280
304
|
verdict.
|
|
281
|
-
- **NO-SHIP runs keep their worktrees
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
directory does not remove git's registration of it.
|
|
305
|
+
- **Worktrees accumulate under `<worktree_base>`.** NO-SHIP runs keep their worktrees for
|
|
306
|
+
inspection. `syncade --gc` removes worktrees once a run can no longer plausibly be resumed —
|
|
307
|
+
controlled by `gc.worktree_max_age_days` (default 14 days), which applies even to runs that are
|
|
308
|
+
technically still resume-eligible. It reached 4.4 GB on this machine before a cleanup. Point
|
|
309
|
+
`worktree_base` somewhere you do not mind, and run `git worktree prune` after manual removals:
|
|
310
|
+
removing the directory does not remove git's registration of it.
|
|
287
311
|
- **A hard-killed run keeps almost all of what its reviewers had written.** Reviewer stdout and
|
|
288
312
|
stderr are copied to `<round>/<name>.stdout` / `<round>/<name>.stderr` as the child produces
|
|
289
313
|
them, rather than held in memory until it exits, so a run ended by `SIGKILL` (or a machine
|
|
@@ -57,9 +57,15 @@ terminal or copy-paste between tools.
|
|
|
57
57
|
## Install
|
|
58
58
|
|
|
59
59
|
```bash
|
|
60
|
-
uv tool install
|
|
60
|
+
uv tool install syncade # recommended
|
|
61
61
|
# or with pip:
|
|
62
|
-
pip install
|
|
62
|
+
pip install syncade
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
To track unreleased `main` instead of the latest release:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
uv tool install git+https://github.com/syncade-ai/syncade-ai.git
|
|
63
69
|
```
|
|
64
70
|
|
|
65
71
|
Then install the harness integration so you can invoke it as `/syncade`:
|
|
@@ -74,6 +80,24 @@ touching the directory and names every file at risk. Ordinary upgrades need no f
|
|
|
74
80
|
recognises its own past output by content, not by filename. Use `--force-install` to overwrite
|
|
75
81
|
deliberately; it still lists what it destroys.
|
|
76
82
|
|
|
83
|
+
### Staying up to date
|
|
84
|
+
|
|
85
|
+
The first time you run syncade in a new terminal or harness window, it checks once whether a
|
|
86
|
+
newer release exists and prints a line if so. It only ever prints — it never blocks a run,
|
|
87
|
+
changes an exit code, or fails one, and every error (offline, bad response) is silent. Nothing
|
|
88
|
+
about your repo, diff, or run is sent. Turn it off with `syncade --config set update.check false`,
|
|
89
|
+
or set `CI` in automation.
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
syncade --update # upgrade, then re-run your command
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`--update` works out how syncade was installed by looking for a marker inside its own install
|
|
96
|
+
tree, and **refuses rather than guessing** if it cannot tell — printing the manual `pip` command
|
|
97
|
+
instead. It also refuses while a review is still running, and declines to touch a source
|
|
98
|
+
checkout. A running process cannot switch to the version it just installed, so it exits and asks
|
|
99
|
+
you to re-run.
|
|
100
|
+
|
|
77
101
|
**Requirements**
|
|
78
102
|
- **Python 3.11+**, on **macOS or Linux** (on Windows, use WSL).
|
|
79
103
|
- **`codex`** (OpenAI), on your `PATH` and authenticated — required for the default
|
|
@@ -233,7 +257,7 @@ syncade runs AI coding-agent CLIs with **elevated tool access on your repo**:
|
|
|
233
257
|
- **Reviewers** run headless inside throwaway worktree copies under `/tmp/syncade/` (Codex reviewers are additionally sandboxed to theirs).
|
|
234
258
|
- **The producer** runs unsandboxed and **commits to your current branch** (syncade refuses the default branch by default).
|
|
235
259
|
- Full LLM transcripts — including source a reviewer read — are written to `.syncade/runs/` (gitignored, auto-pruned). **A secret in a file a reviewer opens lands there in plaintext.**
|
|
236
|
-
- syncade makes **
|
|
260
|
+
- syncade makes **one network call of its own**: a once-per-session GET to its public update manifest to check for a newer version (suppressible via `[update] check = false`). `syncade --update` additionally invokes your package manager (`uv tool upgrade`, `pipx upgrade`, or `pip install -U`) — operator-requested and only when you run that flag. All other egress is the provider CLIs you already authenticated and the test/check commands you configure.
|
|
237
261
|
|
|
238
262
|
Running syncade is comparable to running the target repo's `Makefile` — it executes code
|
|
239
263
|
with your permissions. See **[SECURITY.md](SECURITY.md)** for the full threat model and how
|
|
@@ -248,12 +272,12 @@ Early access. These are measured, not suspected.
|
|
|
248
272
|
Both findings were real. If a SHIP arrives over code you did not change since a NO-SHIP, treat
|
|
249
273
|
it as the weaker signal — compare the verdict to what the *code* changed, not to the previous
|
|
250
274
|
verdict.
|
|
251
|
-
- **NO-SHIP runs keep their worktrees
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
directory does not remove git's registration of it.
|
|
275
|
+
- **Worktrees accumulate under `<worktree_base>`.** NO-SHIP runs keep their worktrees for
|
|
276
|
+
inspection. `syncade --gc` removes worktrees once a run can no longer plausibly be resumed —
|
|
277
|
+
controlled by `gc.worktree_max_age_days` (default 14 days), which applies even to runs that are
|
|
278
|
+
technically still resume-eligible. It reached 4.4 GB on this machine before a cleanup. Point
|
|
279
|
+
`worktree_base` somewhere you do not mind, and run `git worktree prune` after manual removals:
|
|
280
|
+
removing the directory does not remove git's registration of it.
|
|
257
281
|
- **A hard-killed run keeps almost all of what its reviewers had written.** Reviewer stdout and
|
|
258
282
|
stderr are copied to `<round>/<name>.stdout` / `<round>/<name>.stderr` as the child produces
|
|
259
283
|
them, rather than held in memory until it exits, so a run ended by `SIGKILL` (or a machine
|
|
@@ -99,6 +99,7 @@ below; `worktree_base` is the one top-level scalar.
|
|
|
99
99
|
| `review` | `[review]` table | — | What reviewers see. See [`[review]`](#review--reviewconfig). |
|
|
100
100
|
| `retry` | `[retry]` table | — | Transient-error retry bound. See [`[retry]`](#retry--retryconfig). |
|
|
101
101
|
| `gc` | `[gc]` table | — | Run-artifact retention. See [`[gc]`](#gc--gcconfig). |
|
|
102
|
+
| `update` | `[update]` table | — | The once-per-session update check. See [`[update]`](#update--updateconfig). |
|
|
102
103
|
| `worktree_base` | path | `/tmp/syncade` | Base dir for per-run git worktrees. Overridable with `--worktree-base`; point it at a fast local disk if `/tmp` is small or slow. |
|
|
103
104
|
| `checks` | `[[checks]]` list | none | Mechanical exit-code gates. See [`[[checks]]`](#checks--checkconfig). |
|
|
104
105
|
| `pricing` | `[pricing]` table | packaged price table | Per-model token pricing for cost estimation. See [`[pricing]`](#pricing--pricingconfig). |
|
|
@@ -221,13 +222,33 @@ any spend.
|
|
|
221
222
|
## `[gc]` — `GcConfig`
|
|
222
223
|
|
|
223
224
|
Governs both the per-loop auto-prune and an explicit `syncade --gc`. Run directories are never
|
|
224
|
-
deleted
|
|
225
|
+
deleted. `keep` / `max_age_days` prune bulky subprocess transcripts; `worktree_max_age_days`
|
|
226
|
+
removes stale worktrees under `worktree_base`, which is a separate retention tier because a
|
|
227
|
+
worktree is reconstructible from a recorded SHA. CLI `--gc-keep` / `--gc-max-age-days` override
|
|
228
|
+
the first two; the worktree bound is config-only (set it with
|
|
229
|
+
`syncade --config set gc.worktree_max_age_days N`).
|
|
225
230
|
|
|
226
231
|
<!-- config-fields: GcConfig -->
|
|
227
232
|
| Field | Type | Default | What it does |
|
|
228
233
|
|---|---|---|---|
|
|
229
234
|
| `keep` | int ≥ 0 | `20` | Newest N runs whose transcripts are always kept. |
|
|
230
235
|
| `max_age_days` | int ≥ 0 | `0` | Additional age floor: a beyond-`keep` run is pruned only if also older than this. `0` disables the age floor. |
|
|
236
|
+
| `worktree_max_age_days` | int ≥ 0 | `14` | Days a run's **worktree** is kept, after which it is removed — including while the run is still resume-eligible. This is the ONLY rule that selects a worktree: one is never removed because its transcripts became prunable, so a young worktree you are still inspecting survives a busy week of runs. Separate from `max_age_days`, which gates transcripts only — a worktree is reconstructible from the SHA the run records, so removing one costs a `git worktree add` and never history. `0` disables the bound and restores the previous never-ending protection. |
|
|
237
|
+
|
|
238
|
+
## `[update]` — `UpdateConfig`
|
|
239
|
+
|
|
240
|
+
Whether syncade checks for a newer release. The check only ever **prints**: it cannot block a run,
|
|
241
|
+
change an exit code, or fail one — every error (offline, timeout, bad JSON) is silent. It fires at
|
|
242
|
+
most once per window, at the first syncade invocation, and is skipped entirely whenever `CI` is
|
|
243
|
+
set. Nothing about your repo, diff, run, or identity is sent.
|
|
244
|
+
|
|
245
|
+
The advisory source is deliberately not configurable — a repo-local file that could repoint or
|
|
246
|
+
silence a security notice would be a hole, not a feature.
|
|
247
|
+
|
|
248
|
+
<!-- config-fields: UpdateConfig -->
|
|
249
|
+
| Field | Type | Default | What it does |
|
|
250
|
+
|---|---|---|---|
|
|
251
|
+
| `check` | bool | `true` | Check once per session whether a newer syncade is published. Set `false` to never make the request. |
|
|
231
252
|
|
|
232
253
|
## `[[checks]]` — `CheckConfig`
|
|
233
254
|
|
|
@@ -450,9 +450,10 @@ below** before you commit anything yourself.
|
|
|
450
450
|
- **Loop mode refuses a tracked-modified dirty tree** unless you pass `--force-dirty`. Single-pass
|
|
451
451
|
review does not refuse: reviewers see committed HEAD, so uncommitted work is invisible to them.
|
|
452
452
|
- **`<worktree_base>` grows.** NO-SHIP runs keep their worktrees so you can inspect what a reviewer
|
|
453
|
-
saw
|
|
454
|
-
|
|
455
|
-
|
|
453
|
+
saw. `syncade --gc` removes worktrees once a run can no longer plausibly be resumed, controlled by
|
|
454
|
+
`gc.worktree_max_age_days` (default 14 days) — this applies even to runs that remain
|
|
455
|
+
resume-eligible. It reached 4.4 GB on this machine before a cleanup. Point `worktree_base`
|
|
456
|
+
somewhere you don't mind and run `git worktree prune` after manual removals — removing the
|
|
456
457
|
directory does not remove git's registration of it.
|
|
457
458
|
- **Run artifacts are never deleted**, only transcripts are pruned. `syncade --metrics` is derived
|
|
458
459
|
from that tree and would lose your history otherwise.
|
|
@@ -9,7 +9,7 @@ build-backend = "setuptools.build_meta"
|
|
|
9
9
|
|
|
10
10
|
[project]
|
|
11
11
|
name = "syncade"
|
|
12
|
-
version = "0.
|
|
12
|
+
version = "0.7.0"
|
|
13
13
|
description = "External blind multi-judge review orchestrator for AI-assisted coding"
|
|
14
14
|
readme = "README.md"
|
|
15
15
|
requires-python = ">=3.11"
|
|
@@ -73,6 +73,7 @@ syncade = ["templates/*.md", "templates/presets/*.toml", "skills/*/*.md"]
|
|
|
73
73
|
testpaths = ["tests"]
|
|
74
74
|
markers = [
|
|
75
75
|
"smoke: end-to-end integration tests that shell out to real provider CLIs (opt-in via `pytest -m smoke`)",
|
|
76
|
+
"real_worktree_base: opt out of the autouse worktree-base isolation, for the drift tests that must assert the REAL default (PR-h-12 item 1b)",
|
|
76
77
|
]
|
|
77
78
|
addopts = "-m 'not smoke'"
|
|
78
79
|
|
|
@@ -492,11 +492,35 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
492
492
|
# re-inject after parsing. Also detect the forbidden prefix form (--repo before --config).
|
|
493
493
|
_config_operands, _argv_parsed, _repo_prefix = _extract_config_operands(_argv)
|
|
494
494
|
parser = build_parser()
|
|
495
|
+
|
|
496
|
+
# NOTE `--version` / `--help` never reach the notice below: argparse handles and exits
|
|
497
|
+
# during parse_args(). That is deliberate, and the brief says so rather than the code
|
|
498
|
+
# working around it. A pre-parse emit was tried and REVERTED — it could not pass `--quiet`
|
|
499
|
+
# through, could not honour a prompt answer (argparse exits first), and matched only the
|
|
500
|
+
# literal spellings, so argparse's own abbreviations (`--ver`, `--he`) still bypassed it.
|
|
501
|
+
# Matching those means reimplementing argparse's prefix matching: an enumeration, for the
|
|
502
|
+
# two flags least worth notifying on. `--version` in particular is what scripts call.
|
|
495
503
|
args = parser.parse_args(_argv_parsed)
|
|
496
504
|
if _config_operands is not None:
|
|
497
505
|
# Merge any trailing non-flag operands that argparse still captured (e.g. `--config list
|
|
498
506
|
# extra`) with the dash-prefixed ones we extracted before parsing.
|
|
499
507
|
args.config = _config_operands + (args.config or [])
|
|
508
|
+
|
|
509
|
+
# FIRST thing after argv is parsed, deliberately — before the `--repo` shape check and
|
|
510
|
+
# before `validate_command_shape`, both of which return early. Placing it after them made
|
|
511
|
+
# every parsed-but-invalid invocation a silent third exception to "every invocation is
|
|
512
|
+
# eligible": `syncade --update --gc` reached syncade code, emitted nothing, marked nothing,
|
|
513
|
+
# and the NEXT valid command in that window printed the stale notice instead. This is the
|
|
514
|
+
# second blocker that one sentence produced, so the position is now structural rather than
|
|
515
|
+
# remembered: nothing can return ahead of it. It can only print — no exit code, no gate.
|
|
516
|
+
# Returns True in a terminal TTY when the operator chose to update now.
|
|
517
|
+
from syncade.cli.update_notice import emit_update_notice
|
|
518
|
+
|
|
519
|
+
_notice_root = Path(args.repo_root).expanduser() if args.repo_root else Path.cwd()
|
|
520
|
+
if emit_update_notice(quiet=args.quiet, repo_root=_notice_root):
|
|
521
|
+
from syncade.cli.update_mode import run_update
|
|
522
|
+
|
|
523
|
+
return run_update(cwd=_notice_root)
|
|
500
524
|
# Reject the prefix form: --repo before --config set violates the suffix-only contract (D1).
|
|
501
525
|
# Only applies to "set" — for other verbs (list/get) or no verb, _reject_config_mode_conflicts
|
|
502
526
|
# will catch the --repo misuse with the appropriate "meaningful only with --config set" message.
|
|
@@ -515,6 +539,13 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
515
539
|
# --install-skill is a standalone local operation — copies bundled skill files into the
|
|
516
540
|
# harness dirs. Dispatched after command-shape validation so --doctor/--quick combinations
|
|
517
541
|
# are rejected before the filesystem mutation.
|
|
542
|
+
if args.update:
|
|
543
|
+
from syncade.cli.update_mode import run_update
|
|
544
|
+
|
|
545
|
+
# Pass the --repo-root hint so run_update() can resolve the actual git root and check
|
|
546
|
+
# for a live run there, rather than always defaulting to Path.cwd().
|
|
547
|
+
_update_cwd = Path(args.repo_root).expanduser() if args.repo_root else Path.cwd()
|
|
548
|
+
return run_update(cwd=_update_cwd)
|
|
518
549
|
if args.install_skill is not None:
|
|
519
550
|
from syncade.cli.install_skill import install_skill
|
|
520
551
|
|
|
@@ -70,7 +70,15 @@ def _run_gc(args) -> int:
|
|
|
70
70
|
# worktree_base: clean the base the runs actually use (config.worktree_base / --worktree-base),
|
|
71
71
|
# not the hardcoded default — else a relocated base leaves worktree leftovers uncollected.
|
|
72
72
|
plan = plan_gc(
|
|
73
|
-
repo_root,
|
|
73
|
+
repo_root,
|
|
74
|
+
keep=keep,
|
|
75
|
+
max_age_days=max_age_days,
|
|
76
|
+
# Tier 3 has no CLI flag on purpose: it is a machine-level retention preference set once,
|
|
77
|
+
# not a per-invocation choice, and `--config set gc.worktree_max_age_days N` already
|
|
78
|
+
# reaches it. Another flag would need parser, validation, docs and two drift-test entries
|
|
79
|
+
# for a knob nobody tunes twice.
|
|
80
|
+
worktree_max_age_days=config.gc.worktree_max_age_days,
|
|
81
|
+
worktree_base=config.worktree_base,
|
|
74
82
|
)
|
|
75
83
|
report = execute_gc(plan, dry_run=args.gc_dry_run, repo_root=repo_root)
|
|
76
84
|
|
|
@@ -162,6 +162,22 @@ def _is_plain_name(key: str) -> bool:
|
|
|
162
162
|
return bool(key) and key not in (".", "..") and "/" not in key and not Path(key).is_absolute()
|
|
163
163
|
|
|
164
164
|
|
|
165
|
+
def resolve_homes(home: Path | None, codex_home: Path | None) -> tuple[Path, Path]:
|
|
166
|
+
"""The ONE place harness home directories are resolved.
|
|
167
|
+
|
|
168
|
+
Extracted in PR-h-field-07 because it was not one place: the installer honoured
|
|
169
|
+
``CODEX_HOME`` while the new runtime skill-status path re-derived ``~/.codex`` by hand. A
|
|
170
|
+
Codex skill installed under a custom home then read as ABSENT, so no stale notice fired and
|
|
171
|
+
``--update`` skipped re-installing it — the exact drift this PR exists to close, hidden by
|
|
172
|
+
the copy. Both reviewers flagged it independently.
|
|
173
|
+
"""
|
|
174
|
+
home = home or Path.home()
|
|
175
|
+
if codex_home is None:
|
|
176
|
+
env_cx = os.environ.get("CODEX_HOME")
|
|
177
|
+
codex_home = Path(env_cx) if env_cx else home / ".codex"
|
|
178
|
+
return home, codex_home
|
|
179
|
+
|
|
180
|
+
|
|
165
181
|
def _dest(harness: str, home: Path, codex_home: Path) -> Path:
|
|
166
182
|
if harness == "claude":
|
|
167
183
|
return home / ".claude" / "skills" / "syncade"
|
|
@@ -336,10 +352,7 @@ def install_skill(
|
|
|
336
352
|
)
|
|
337
353
|
return CLI_USAGE_ERROR
|
|
338
354
|
|
|
339
|
-
home = home
|
|
340
|
-
if codex_home is None:
|
|
341
|
-
env_cx = os.environ.get("CODEX_HOME")
|
|
342
|
-
codex_home = Path(env_cx) if env_cx else home / ".codex"
|
|
355
|
+
home, codex_home = resolve_homes(home, codex_home)
|
|
343
356
|
|
|
344
357
|
harnesses = ["claude", "codex"] if target == "all" else [target]
|
|
345
358
|
plan = [
|
|
@@ -17,6 +17,7 @@ from syncade import billing
|
|
|
17
17
|
from syncade.billing import Billing
|
|
18
18
|
from syncade.exit_codes import SUCCESS, WORKTREE_ERROR
|
|
19
19
|
from syncade.metrics.aggregate import backfill
|
|
20
|
+
from syncade.metrics.findings import blocker_curve, consensus_stats
|
|
20
21
|
from syncade.metrics.schema import open_db
|
|
21
22
|
from syncade.snapshot import SnapshotError, discover_repo_root
|
|
22
23
|
|
|
@@ -238,7 +239,10 @@ def render_report(conn: sqlite3.Connection, last_n: int | None = None) -> str:
|
|
|
238
239
|
"SELECT COALESCE(SUM(blockers),0), COALESCE(SUM(minors),0), COALESCE(SUM(nits),0), "
|
|
239
240
|
"COALESCE(SUM(dismissed),0) FROM runs"
|
|
240
241
|
).fetchone()
|
|
241
|
-
rounds
|
|
242
|
+
# From `rounds`, not `runs.rounds_executed`: the latter comes from loop-manifest.json, so an
|
|
243
|
+
# interrupted run with perfectly good round artifacts reported "rounds: 0 total" while the
|
|
244
|
+
# blocker curve simultaneously knew round 0 had happened.
|
|
245
|
+
rounds = conn.execute("SELECT COUNT(*) FROM rounds").fetchone()[0]
|
|
242
246
|
handoffs, decisions = conn.execute(
|
|
243
247
|
"SELECT COALESCE(SUM(handoff),0), COALESCE(SUM(decision_needed),0) FROM runs"
|
|
244
248
|
).fetchone()
|
|
@@ -294,11 +298,86 @@ def render_report(conn: sqlite3.Connection, last_n: int | None = None) -> str:
|
|
|
294
298
|
if reviewer_rows:
|
|
295
299
|
lines.append(" reviewers by model (findings/wall-clock):")
|
|
296
300
|
for provider, model, findings, run_count, wall, tok in reviewer_rows:
|
|
297
|
-
|
|
301
|
+
# A row with no model is reported as UNKNOWN, never as a bare provider. A bare
|
|
302
|
+
# provider reads like a panel that had no model, which is a claim we cannot make;
|
|
303
|
+
# "unknown" says the thing that is actually true. (No such row exists in this
|
|
304
|
+
# corpus today — every recorded panel carries both — but the renderer must not be
|
|
305
|
+
# the reason a future one silently looks answered.)
|
|
306
|
+
label = f"{provider}/{model}" if model else f"{provider or '?'}/unknown"
|
|
298
307
|
lines.append(
|
|
299
308
|
f" {label:24} findings={findings} runs={run_count} "
|
|
300
309
|
f"wall={int(wall)}s tok={int(tok)}"
|
|
301
310
|
)
|
|
311
|
+
# Rounds whose panel was never recorded are INVISIBLE in the table above, which is worse
|
|
312
|
+
# than being wrong: anyone dividing findings by runs gets a denominator quietly missing
|
|
313
|
+
# them. They come from runs interrupted before the loop terminator wrote a manifest, so
|
|
314
|
+
# nothing ever summarised their reviewers. Never guessed — the configured roster is not
|
|
315
|
+
# evidence of what a killed run actually dispatched.
|
|
316
|
+
#
|
|
317
|
+
# DO NOT write a measured count here. This comment said "all 49" until the release audit
|
|
318
|
+
# found the line below printing 22: excluding known-empty panels changed the number and
|
|
319
|
+
# the prose kept the old one. A count in a comment is a measurement with no test reading
|
|
320
|
+
# it, which is the same defect this release had to fix in its own CHANGELOG.
|
|
321
|
+
|
|
322
|
+
# OUTSIDE the `if reviewer_rows` block on purpose. Nested inside it, a corpus where EVERY
|
|
323
|
+
# panel is unknown printed no warning at all — the one corpus that most needs it, silent.
|
|
324
|
+
# panel_source='unknown' means no manifest had a `reviewers` key for that round. This is
|
|
325
|
+
# independent of blockers_source: a manifest can record a blocker count without a reviewer
|
|
326
|
+
# list, and a no-dispatch round (no_changes_to_review) has panel_source='recorded' and
|
|
327
|
+
# panel_size=0 — explicitly empty, not unknown.
|
|
328
|
+
unknown_rounds, total_rounds = conn.execute(
|
|
329
|
+
"SELECT COALESCE(SUM(CASE WHEN panel_source = 'unknown' THEN 1 ELSE 0 END), 0), "
|
|
330
|
+
"COUNT(*) FROM rounds"
|
|
331
|
+
).fetchone()
|
|
332
|
+
if unknown_rounds:
|
|
333
|
+
lines.append(
|
|
334
|
+
f" {unknown_rounds} of {total_rounds} rounds record no reviewer panel "
|
|
335
|
+
f"(no manifest evidence; excluded from consensus)"
|
|
336
|
+
)
|
|
337
|
+
manifest_only, unknown_only = conn.execute(
|
|
338
|
+
"SELECT "
|
|
339
|
+
"COALESCE(SUM(CASE WHEN blockers_source='manifest' THEN 1 ELSE 0 END), 0), "
|
|
340
|
+
"COALESCE(SUM(CASE WHEN blockers_source='unknown' THEN 1 ELSE 0 END), 0) "
|
|
341
|
+
"FROM rounds "
|
|
342
|
+
"WHERE NOT (panel_source='recorded' AND panel_size=0 AND blockers=0)"
|
|
343
|
+
).fetchone()
|
|
344
|
+
if manifest_only:
|
|
345
|
+
lines.append(
|
|
346
|
+
f" {manifest_only} of {total_rounds} rounds have no synthesizer artifact; their "
|
|
347
|
+
f"blocker counts come from round manifests and carry no provenance"
|
|
348
|
+
)
|
|
349
|
+
if unknown_only:
|
|
350
|
+
lines.append(
|
|
351
|
+
f" {unknown_only} of {total_rounds} rounds have no blocker evidence "
|
|
352
|
+
f"(killed/interrupted before any artifact or manifest); "
|
|
353
|
+
f"excluded from the blocker curve"
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
consensus = consensus_stats(conn)
|
|
357
|
+
if consensus is not None:
|
|
358
|
+
total, single = consensus
|
|
359
|
+
lines.append(
|
|
360
|
+
f" consensus: {single} of {total} active blockers ({single / total:.0%}) were "
|
|
361
|
+
f"raised by exactly ONE reviewer"
|
|
362
|
+
)
|
|
363
|
+
lines.append(" (over runs with a multi-reviewer panel; what a second reviewer earns)")
|
|
364
|
+
|
|
365
|
+
curve = blocker_curve(conn)
|
|
366
|
+
if curve:
|
|
367
|
+
# Density, never the raw total alone. The blocker count falls steeply across rounds and
|
|
368
|
+
# reads as convergence; it is SURVIVORSHIP — fewer runs reach each round, so the
|
|
369
|
+
# denominator falls with it. This repo published the raw sequence as convergence once
|
|
370
|
+
# and had to correct it, so the renderer shows both numbers and the per-round rate.
|
|
371
|
+
lines.append(
|
|
372
|
+
" blockers by round (artifact- or manifest-backed only; "
|
|
373
|
+
"killed runs with no evidence excluded):"
|
|
374
|
+
)
|
|
375
|
+
for round_index, blockers, rounds in curve:
|
|
376
|
+
density = blockers / rounds if rounds else 0.0
|
|
377
|
+
lines.append(
|
|
378
|
+
f" round {round_index}: {blockers} blockers over {rounds} rounds "
|
|
379
|
+
f"({density:.2f}/round)"
|
|
380
|
+
)
|
|
302
381
|
|
|
303
382
|
if last_n:
|
|
304
383
|
ids = [
|
|
@@ -306,6 +306,16 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
306
306
|
"Leaves the instant config / PATH / worktree / branch / plan / cost checks. The "
|
|
307
307
|
"skipped legs are reported as 'skipped', never as passed.",
|
|
308
308
|
)
|
|
309
|
+
parser.add_argument(
|
|
310
|
+
"--update",
|
|
311
|
+
action="store_true",
|
|
312
|
+
help="Upgrade syncade to the latest published release, then exit. Detects how syncade "
|
|
313
|
+
"was installed by finding a marker inside its own install tree (uv tool / pipx) and "
|
|
314
|
+
"REFUSES rather than guessing when it cannot tell, printing the manual pip command "
|
|
315
|
+
"instead. Refuses while a run is still going. Re-installs any skill you already have, "
|
|
316
|
+
"using the upgraded code. A running process cannot switch to the new version, so this "
|
|
317
|
+
"exits and asks you to re-run your command.",
|
|
318
|
+
)
|
|
309
319
|
parser.add_argument(
|
|
310
320
|
"--install-skill",
|
|
311
321
|
nargs="?",
|
|
@@ -371,22 +381,25 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
371
381
|
parser.add_argument(
|
|
372
382
|
"--gc",
|
|
373
383
|
action="store_true",
|
|
374
|
-
help="Run a one-shot maintenance pass. Retention is
|
|
384
|
+
help="Run a one-shot maintenance pass. Retention is FOUR-TIER: run "
|
|
375
385
|
"history is kept FOREVER (loop-manifest.json, findings.md, run-init.json, "
|
|
376
|
-
"round manifests, summaries)
|
|
377
|
-
"
|
|
378
|
-
"
|
|
379
|
-
"
|
|
380
|
-
"
|
|
381
|
-
"
|
|
382
|
-
"
|
|
383
|
-
"
|
|
384
|
-
"
|
|
385
|
-
"
|
|
386
|
-
"
|
|
387
|
-
"
|
|
388
|
-
"
|
|
389
|
-
"
|
|
386
|
+
"round manifests, summaries); transcripts (round-*/*.stdout, *.stderr - "
|
|
387
|
+
"90%% of the corpus) are pruned beyond --gc-keep and any --gc-max-age-days "
|
|
388
|
+
"floor; worktrees are removed once a run can no longer plausibly be resumed "
|
|
389
|
+
"(worktrees are reconstructible from the recorded SHA, so removing one "
|
|
390
|
+
"costs a git worktree add, never history) — resume-eligible runs keep their "
|
|
391
|
+
"worktree while younger than gc.worktree_max_age_days (default 14 days, "
|
|
392
|
+
"set via syncade --config set gc.worktree_max_age_days N); "
|
|
393
|
+
"and metrics.db is a droppable derived view. NO run directory is ever "
|
|
394
|
+
"deleted: .syncade/metrics.db is a derived view over .syncade/runs/, so "
|
|
395
|
+
"deleting a run would destroy its history the next time that view rebuilds. "
|
|
396
|
+
"Also removes matching <worktree_base>/<run-id>/ worktree leftovers whose "
|
|
397
|
+
"directory identity still matches the GC plan, and safely reaps orphaned "
|
|
398
|
+
"reviewer/producer subprocesses whose working dir is INSIDE a worktree "
|
|
399
|
+
"being removed. Runs also auto-prune their transcripts at the start of "
|
|
400
|
+
"every fresh loop, so --gc is for worktree/process cleanup and one-off "
|
|
401
|
+
"maintenance rather than routine disk hygiene. Non-run state "
|
|
402
|
+
"(config.toml, last-reviewed.json, draft-spec-*.md) is never touched. "
|
|
390
403
|
"Mutually exclusive with PR_DOC, --selfcheck, --auth-check, "
|
|
391
404
|
"--spec-audit, --draft-spec, --resume, --openspec.",
|
|
392
405
|
)
|