"""Metadata-only diversion classifier. Extracted from :mod:`dispatch_implementer` (C2 harvest 2026-05-15) as the first cohesive unit moved out of the 3.2k-line driver toward the ~500-line per-module budget the rest of ``tools/`` honours. The function is pure: takes a Forgejo item dict + an ``ImplementerPrefetchResult``-shaped carrier, returns a strict ``bool``. No I/O, no LLM, no module-level state — the natural candidate for the first extraction. Re-exported through ``dispatch_implementer._classify_metadata_only`` for backwards-compatibility with the G1 tests, so test imports and call sites land in one place. Caller pattern: see the inline call in ``_prefetch_prompt`` — result is stashed on the per-item context as ``metadata_only_candidate`` for cycle telemetry and any future diversion path to consume. """ from __future__ import annotations from typing import Any def classify_metadata_only(item: dict[str, Any], prefetch: Any) -> bool: """G1 harvest (2026-05-15) — return ``True`` when this work item appears to be **metadata-only** (no code work required), based on deterministic signals already in the prefetch carrier. Returns ``False`` whenever any signal hints at code work or any signal is ambiguous: the harvest plan's explicit tie-breaker is "when in doubt, classify as code work — the cost of an unnecessary code-work attempt is much lower than incorrectly skipping legitimate implementation." Signals consulted (all from the carrier; no new network calls): 1. ``item`` must be PR-shaped — ``new_issue`` work always requires a fresh implementation, so it is never metadata-only. 2. CI overall state must NOT be ``failure`` or ``error``. A failing CI is by definition something the worker has to fix. 3. Per-check ``ci_detail`` rows must NOT include any ``failure`` / ``error`` status, even if the overall combined state happens to be ``success`` (defensive against Forgejo race windows where the combined state lags an individual check). 4. No active ``request_changes_reviews``: a REQUEST_CHANGES review is feedback the worker must respond to. (A label-only request would be a code review smell on the reviewer's part, so we conservatively assume any RC review references source.) When all four hold the item is a candidate for diversion to a grooming-style path. The actual no-clone handling is wired separately and may be deferred — this classifier ships first so an operator can observe how often the diversion would fire on real PRs before any behaviour change. Pure function; safe to call from anywhere in the dispatch path. """ if not isinstance(item.get("head"), dict): return False if prefetch is None: return False ci_status = getattr(prefetch, "ci_status", None) or {} if isinstance(ci_status, dict): state = str(ci_status.get("state") or "").lower() if state in ("failure", "error"): return False ci_detail = getattr(prefetch, "ci_detail", None) or [] if isinstance(ci_detail, list): for check in ci_detail: if not isinstance(check, dict): continue status = str(check.get("status") or "").lower() if status in ("failure", "error"): return False rc_reviews = getattr(prefetch, "request_changes_reviews", None) or [] if isinstance(rc_reviews, list) and len(rc_reviews) > 0: return False return True