syncade 0.6.2__py3-none-any.whl
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/__init__.py +3 -0
- syncade/__main__.py +6 -0
- syncade/adapters/__init__.py +0 -0
- syncade/adapters/anthropic.py +457 -0
- syncade/adapters/base.py +221 -0
- syncade/adapters/fake.py +73 -0
- syncade/adapters/fake_common.py +29 -0
- syncade/adapters/fake_producer_audit_draft.py +460 -0
- syncade/adapters/fake_reviewer_synth.py +310 -0
- syncade/adapters/openai.py +484 -0
- syncade/adapters/openai_parsing.py +119 -0
- syncade/adapters/producer.py +221 -0
- syncade/adapters/producer_anthropic.py +300 -0
- syncade/adapters/producer_openai.py +226 -0
- syncade/adapters/registry.py +81 -0
- syncade/auth_check.py +554 -0
- syncade/auth_preflight.py +342 -0
- syncade/base_resolution.py +214 -0
- syncade/billing.py +141 -0
- syncade/checks_config.py +113 -0
- syncade/cli/__init__.py +546 -0
- syncade/cli/auth_gate.py +59 -0
- syncade/cli/config_keys.py +135 -0
- syncade/cli/config_list.py +82 -0
- syncade/cli/config_menu_rows.py +166 -0
- syncade/cli/config_mode.py +609 -0
- syncade/cli/config_overrides.py +122 -0
- syncade/cli/config_tui.py +476 -0
- syncade/cli/doctor_mode.py +72 -0
- syncade/cli/gc_mode.py +109 -0
- syncade/cli/install_skill.py +514 -0
- syncade/cli/metrics_mode.py +363 -0
- syncade/cli/modes.py +573 -0
- syncade/cli/parser.py +450 -0
- syncade/cli/parser_types.py +137 -0
- syncade/cli/paths.py +38 -0
- syncade/cli/preflight_paths.py +90 -0
- syncade/cli/resolve.py +116 -0
- syncade/cli/resume_mode.py +324 -0
- syncade/cli/toml_writer.py +410 -0
- syncade/cli/validate.py +421 -0
- syncade/config.py +478 -0
- syncade/config_auth.py +310 -0
- syncade/config_cold.py +209 -0
- syncade/config_gc.py +55 -0
- syncade/config_loader.py +182 -0
- syncade/config_loop.py +282 -0
- syncade/config_producer.py +222 -0
- syncade/config_retry.py +49 -0
- syncade/config_types.py +59 -0
- syncade/diff_filter.py +437 -0
- syncade/dispatcher.py +571 -0
- syncade/doctor.py +425 -0
- syncade/doctor_env.py +218 -0
- syncade/doctor_preview.py +524 -0
- syncade/doctor_types.py +28 -0
- syncade/exit_codes.py +82 -0
- syncade/findings.py +242 -0
- syncade/findings_json.py +456 -0
- syncade/gc.py +211 -0
- syncade/gc_execute.py +372 -0
- syncade/gc_protection.py +129 -0
- syncade/gc_types.py +50 -0
- syncade/gc_worktrees.py +200 -0
- syncade/git_object_id.py +12 -0
- syncade/git_preconditions.py +389 -0
- syncade/logging.py +289 -0
- syncade/metrics/__init__.py +32 -0
- syncade/metrics/aggregate.py +550 -0
- syncade/metrics/schema.py +221 -0
- syncade/orchestrator/__init__.py +61 -0
- syncade/orchestrator/_runs_dir.py +24 -0
- syncade/orchestrator/branch_advance.py +165 -0
- syncade/orchestrator/branch_guard.py +98 -0
- syncade/orchestrator/budget.py +107 -0
- syncade/orchestrator/escalation_coverage.py +81 -0
- syncade/orchestrator/loop.py +611 -0
- syncade/orchestrator/loop_dispatch_check.py +112 -0
- syncade/orchestrator/loop_finalize.py +404 -0
- syncade/orchestrator/loop_preflight.py +131 -0
- syncade/orchestrator/loop_resume.py +91 -0
- syncade/orchestrator/loop_rmtree.py +70 -0
- syncade/orchestrator/loop_round_step.py +599 -0
- syncade/orchestrator/prior_round.py +336 -0
- syncade/orchestrator/producer_phase.py +169 -0
- syncade/orchestrator/results.py +306 -0
- syncade/orchestrator/resume.py +96 -0
- syncade/orchestrator/resume_load.py +483 -0
- syncade/orchestrator/resume_plan.py +554 -0
- syncade/orchestrator/resume_target.py +215 -0
- syncade/orchestrator/resume_types.py +182 -0
- syncade/orchestrator/reviewer_template_failure.py +99 -0
- syncade/orchestrator/round.py +573 -0
- syncade/orchestrator/round_checks.py +91 -0
- syncade/orchestrator/round_no_changes.py +369 -0
- syncade/orchestrator/round_predispatch.py +212 -0
- syncade/orchestrator/verdict.py +279 -0
- syncade/persistence/__init__.py +189 -0
- syncade/persistence/_atomic.py +33 -0
- syncade/persistence/_clusters.py +70 -0
- syncade/persistence/_findings_verdict.py +201 -0
- syncade/persistence/_markdown.py +286 -0
- syncade/persistence/_validation.py +37 -0
- syncade/persistence/checks.py +249 -0
- syncade/persistence/decision_needed.py +289 -0
- syncade/persistence/findings_md.py +389 -0
- syncade/persistence/handoff.py +389 -0
- syncade/persistence/handoff_classify.py +196 -0
- syncade/persistence/last_reviewed.py +67 -0
- syncade/persistence/loop_manifest.py +165 -0
- syncade/persistence/loop_summary.py +352 -0
- syncade/persistence/loop_summary_text.py +428 -0
- syncade/persistence/producer.py +250 -0
- syncade/persistence/reviewer.py +198 -0
- syncade/persistence/round_manifest.py +238 -0
- syncade/persistence/run_init.py +153 -0
- syncade/persistence/run_summary.py +585 -0
- syncade/persistence/run_summary_next_steps.py +443 -0
- syncade/persistence/synth.py +242 -0
- syncade/persistence/test_run.py +152 -0
- syncade/presets.py +36 -0
- syncade/pricing_config.py +72 -0
- syncade/process.py +600 -0
- syncade/producer.py +189 -0
- syncade/producer_attempt.py +463 -0
- syncade/producer_escalation.py +146 -0
- syncade/producer_git.py +199 -0
- syncade/producer_result.py +205 -0
- syncade/prompts.py +448 -0
- syncade/prompts_loader.py +238 -0
- syncade/retry.py +159 -0
- syncade/run_inputs.py +40 -0
- syncade/run_status.py +198 -0
- syncade/selfcheck.py +471 -0
- syncade/skills/claude/README.md +221 -0
- syncade/skills/claude/SKILL.md +625 -0
- syncade/skills/codex/README.md +116 -0
- syncade/skills/codex/SKILL.md +574 -0
- syncade/snapshot.py +598 -0
- syncade/spec_audit.py +437 -0
- syncade/spec_audit_schema.py +190 -0
- syncade/spec_draft.py +423 -0
- syncade/spec_source.py +135 -0
- syncade/synthesis.py +428 -0
- syncade/synthesis_clusters.py +203 -0
- syncade/synthesis_repair.py +230 -0
- syncade/synthesis_schema.py +65 -0
- syncade/synthesizer/__init__.py +38 -0
- syncade/synthesizer/constants.py +33 -0
- syncade/synthesizer/driver.py +531 -0
- syncade/synthesizer/rendering.py +63 -0
- syncade/synthesizer/result.py +73 -0
- syncade/synthesizer/validation.py +421 -0
- syncade/synthesizer/workspace.py +208 -0
- syncade/templates/presets/balanced.toml +13 -0
- syncade/templates/presets/cheap.toml +12 -0
- syncade/templates/presets/thorough.toml +9 -0
- syncade/templates/producer.md +231 -0
- syncade/templates/reviewer.md +279 -0
- syncade/templates/reviewer_adversarial.md +164 -0
- syncade/templates/reviewer_codex.md +165 -0
- syncade/templates/spec_audit.md +168 -0
- syncade/templates/spec_draft.md +62 -0
- syncade/templates/synthesizer.md +204 -0
- syncade/test_runner.py +476 -0
- syncade/test_runner_classify.py +98 -0
- syncade/transcript.py +150 -0
- syncade/usage.py +407 -0
- syncade/worktree.py +497 -0
- syncade/worktree_env.py +133 -0
- syncade/worktree_paths.py +139 -0
- syncade-0.6.2.dist-info/METADATA +314 -0
- syncade-0.6.2.dist-info/RECORD +177 -0
- syncade-0.6.2.dist-info/WHEEL +5 -0
- syncade-0.6.2.dist-info/entry_points.txt +2 -0
- syncade-0.6.2.dist-info/licenses/LICENSE +202 -0
- syncade-0.6.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Escalation coverage guard.
|
|
2
|
+
|
|
3
|
+
The loop terminator honors a producer escalation (checkpoint → exit 10 +
|
|
4
|
+
``decision_needed`` + ``decision-needed.md``) **only when the escalation
|
|
5
|
+
covers every active blocker in the round**. Otherwise the escalation left
|
|
6
|
+
work undone and is treated as an ordinary stall (exit 30).
|
|
7
|
+
|
|
8
|
+
**The bug this closes (codex reproduced it in the validation):** the
|
|
9
|
+
terminator honored *any* escalation unconditionally. Because ``escalated``
|
|
10
|
+
is mutually exclusive with committing (``escalated`` ⟺ no-commit), a
|
|
11
|
+
producer could escalate finding A, commit nothing for active blocker B, and
|
|
12
|
+
still short-circuit the loop to exit 10 — leaving B silently unresolved.
|
|
13
|
+
|
|
14
|
+
**The fix is mechanical set-containment, not a count and not prompt-trust.**
|
|
15
|
+
Escalation references findings by index into
|
|
16
|
+
``SynthesizerOutput.consolidated_findings``. The orchestrator computes the
|
|
17
|
+
active-blocker index set from the
|
|
18
|
+
synth's OWN output and honors the escalation iff:
|
|
19
|
+
|
|
20
|
+
1. every referenced ``finding_indices`` entry is a real active (non-dismissed)
|
|
21
|
+
blocker — catches out-of-range, dismissed, and minor/nit references; and
|
|
22
|
+
2. every active blocker is referenced.
|
|
23
|
+
|
|
24
|
+
Together those two are set equality, but they're checked separately so the
|
|
25
|
+
mapping to the brief's two requirements (and the cluster cross-check shape)
|
|
26
|
+
stays legible. A count-guard ("honor only when the escalated finding is the
|
|
27
|
+
sole blocker") was rejected: one operator decision can legitimately unblock
|
|
28
|
+
several blockers (A, B, C all downstream of one design question), and a
|
|
29
|
+
sole-blocker guard would false-downgrade that to a stall.
|
|
30
|
+
|
|
31
|
+
Pure module — no I/O, no loop logic. The terminator wiring lives in
|
|
32
|
+
:func:`syncade.orchestrator.loop.run_review`.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
37
|
+
from syncade.producer_escalation import ProducerEscalation
|
|
38
|
+
from syncade.synthesis import SynthesizerOutput
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _active_blocker_indices(synth_output: SynthesizerOutput) -> set[int]:
|
|
42
|
+
"""The indices into ``consolidated_findings`` that are active
|
|
43
|
+
(non-dismissed) blockers — the same condition
|
|
44
|
+
:func:`syncade.synthesis.has_active_blocker` reduces over."""
|
|
45
|
+
return {
|
|
46
|
+
i
|
|
47
|
+
for i, f in enumerate(synth_output.consolidated_findings)
|
|
48
|
+
if f.severity == "blocker" and not f.dismissed
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def escalation_covers_active_blockers(
|
|
53
|
+
escalation: ProducerEscalation,
|
|
54
|
+
synth_output: SynthesizerOutput | None,
|
|
55
|
+
) -> bool:
|
|
56
|
+
"""Return ``True`` iff the loop should honor this escalation as an
|
|
57
|
+
operator-decision checkpoint.
|
|
58
|
+
|
|
59
|
+
The escalation is honored only when its ``finding_indices`` exactly cover
|
|
60
|
+
the round's active-blocker set: every referenced index is a real active
|
|
61
|
+
blocker (in range + non-dismissed + ``severity == "blocker"``) and every
|
|
62
|
+
active blocker is referenced. Any mismatch — a left-uncovered blocker, an
|
|
63
|
+
out-of-range index, or a reference to a dismissed / minor / nit finding —
|
|
64
|
+
returns ``False`` (the terminator then treats the round as a stall, exit
|
|
65
|
+
30). Never raises: a bad reference is a non-honored escalation, not a crash.
|
|
66
|
+
|
|
67
|
+
``synth_output is None`` (no consolidated findings in hand) → ``False``.
|
|
68
|
+
No active blockers at all (e.g. a test-failure-driven NO-SHIP round) →
|
|
69
|
+
``False``: there is no blocker an operator decision could resolve.
|
|
70
|
+
"""
|
|
71
|
+
if synth_output is None:
|
|
72
|
+
return False
|
|
73
|
+
active = _active_blocker_indices(synth_output)
|
|
74
|
+
if not active:
|
|
75
|
+
return False
|
|
76
|
+
covered = set(escalation.finding_indices)
|
|
77
|
+
# (1) every referenced index must be a real active blocker.
|
|
78
|
+
if not covered <= active:
|
|
79
|
+
return False
|
|
80
|
+
# (2) every active blocker must be covered.
|
|
81
|
+
return active <= covered
|