Files
cleveragents-core/tools/_implementer_metadata_classifier.py
T
drew 363bcfad61 refactor(auto-agents): extract _classify_metadata_only as the first C2 step
dispatch_implementer.py is 3225 lines — well over the ~500-line per-
module budget the rest of tools/ honours. C2 is the long-overdue
decomposition; this commit starts the work with the smallest /
cleanest extraction available: the pure G1 metadata-only classifier
(no module state, single public surface) moves to a new sibling
_implementer_metadata_classifier.py, loaded through the existing
_loader.load_sibling machinery just like every other helper in this
file.

The leading-underscore alias dispatch_implementer._classify_metadata_only
re-exports the function so the G1 tests (and any other call sites)
keep working unchanged. The extraction is a pure refactor — the
full 1754-test auto-agents suite passes unchanged.

Subsequent C2 steps (extract _post_session_action_with_escalation —
the ~350-line nested loop the harvest plan singled out by name —
plus prompt-assembly + short-circuit blocks) are larger pieces that
warrant fresh-context attention. This commit establishes the
pattern (new module, _load_sibling line, leading-underscore alias)
for those follow-ups.

Refs: docs/development/final-working-harvest-plan.md (C2).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 20:29:59 -04:00

81 lines
3.5 KiB
Python

"""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