Files
cleveragents-core/tools/implementer_pr_context.py
T
drew 0bc734c020 style: ruff format the controller-state-machine branch (288 files)
Applies `ruff format` to the accumulated formatting debt on this branch.
Formatting-only — no behavioral changes. Required for CI/lint's format
gate (`nox -s format -- --check`), which the branch was failing on 288
tracked files that drifted from ruff's canonical style.

In-progress WIP files are intentionally excluded so this commit stays a
clean formatting-only diff.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 00:09:17 -04:00

514 lines
21 KiB
Python
Executable File

#!/usr/bin/env python3
"""Worker-side reader for the dispatcher's PR-context handoff sentinel.
What this is
------------
The dispatcher pre-fetches everything the implementer worker needs to
make a code change — PR description, full diff, CI status + per-check
detail, PR comments, active REQUEST_CHANGES reviews, linked issues,
parent Epic body — and writes the lot to a sentinel at
``/tmp/cleveragents-implementer-handoff/pr-{N}.json``. The dispatcher
ALSO embeds those sections in the wrapper session's user prompt, but
each ``task`` tool call inside OpenCode re-summarises the prompt for
the child agent — by the time the chain reaches ``task-implementor``
at depth 3, only the diff section reliably survives.
This script is what the implementer worker calls (via the
``implementer-pr-context`` skill) to re-read any prefetched section
on demand, regardless of how the prompt got mangled upstream. It is
the read half of :mod:`tools._pr_context_sentinel`.
Contract
--------
Subcommand ``read``:
``python3 tools/implementer_pr_context.py read --pr <N> --field <name>``
Reads the sentinel at ``/tmp/cleveragents-implementer-handoff/pr-{N}.json``,
pulls the requested field, prints it on stdout, and exits 0. The
``--field`` value selects which slice of the prefetched data:
- ``description`` — the PR body text (plain string)
- ``title`` — the PR title (plain string)
- ``metadata`` — JSON with work_type / work_group / head_sha /
head_ref / base_ref / pr_number / data_complete / error_kinds
- ``ci`` — JSON with the latest CI ``state`` plus ``checks[]``
(per-status detail, including ``target_url`` and any prefetched
failure-log excerpts)
- ``comments`` — JSON list of PR / issue comments (chronological).
For PRs this is the BOUNDED view: the most-recent N comments plus
every human/reviewer comment — the older bot ``**Implementation
Attempt**`` comments are rolled up into ``comments_digest`` instead.
- ``comments_digest`` — JSON dict: the deterministic attempt-history
digest (counts by tier / outcome, gates failing most, last
success) computed from the FULL PR comment list. ``{}`` when no
attempt comments were parsed.
- ``reviews`` — JSON list of active REQUEST_CHANGES reviews + their
inline comments (for ``request_changes_pr`` work only)
- ``issues`` — JSON list of linked issues (the ``Closes #N``
targets, with their bodies inlined)
- ``epic`` — JSON dict with the parent Epic body, or empty if no
Epic reference was found
- ``diff`` — the unified diff as plain text (between
``BEGIN_PR_DIFF`` / ``END_PR_DIFF`` markers in the original
prompt)
- ``compliance_gaps`` — JSON dict of the dispatcher's deterministic
PR Compliance Checklist scan (worktree_clean, changelog,
contributors, issues-closed footer). Empty stdout when the
dispatcher did not run the scan this cycle.
- ``gate_preflight`` — JSON dict of the heavy flaky-test gate
pre-flight (two ``local_ci_gate.sh --fast`` runs, classification
of persistent vs. flaky failures). Empty stdout when the
dispatcher did not run the pre-flight this cycle.
- ``all`` — the entire sentinel payload as JSON (useful for
debugging; ``task-implementor`` should pull narrowly)
The script emits one of three signals on stdout, all with exit
code 0:
- **Empty stdout** — the sentinel is missing, malformed, or the
dispatcher's fetch for this field did not complete (per the
matching ``*_completed`` flag). The worker should fall through
to its legacy curl-based GET against the live Forgejo API.
- ``null\\n`` — the dispatcher fetched successfully and
authoritatively confirmed absence (e.g. the PR has no Epic
reference). The worker should SKIP its legacy GET; there is
nothing to fetch.
- Any other content — the field's value (plain text for
description/title/diff/issue_body, pretty-printed JSON for
everything else). The worker should use it verbatim.
This three-case contract is how the worker distinguishes "the
dispatcher didn't try" (fall through) from "the dispatcher tried
and confirmed there is nothing" (don't re-curl). Conflating the
two — as the original ``or None`` projection did — caused the
worker to redundantly hit Forgejo for sections the dispatcher
had already verified empty.
Exit codes
----------
- 0: success (or empty stdout for "no data available" — see above).
- 2: usage error (bad / missing CLI arg, unknown ``--field``).
- 3: schema-version mismatch (a config issue the operator must
notice; the worker should treat this as "no data available" but
the non-zero code makes the situation visible in logs).
Allow-rule
----------
This script's path is whitelisted under
``python3 tools/implementer_pr_context.py *`` in the agent's
permission allowlist (see :file:`.opencode/agents/task-implementor.md`).
"""
from __future__ import annotations
import argparse
import json
import os
import sys
from pathlib import Path
from typing import Any
# Must match :data:`tools._pr_context_sentinel.SCHEMA_VERSION`.
EXPECTED_SCHEMA_VERSION = 1
_DEFAULT_HANDOFF_DIR = Path("/tmp/cleveragents-implementer-handoff")
def _handoff_dir() -> Path:
"""Resolve the on-disk directory. Mirrors
:func:`tools._pr_context_sentinel.handoff_dir`."""
return Path(
os.environ.get("IMPLEMENTER_DISPATCHER_PR_CONTEXT_DIR")
or str(_DEFAULT_HANDOFF_DIR)
)
def _handoff_path(pr_number: int) -> Path:
return _handoff_dir() / f"pr-{int(pr_number)}.json"
def _load(pr_number: int) -> tuple[dict[str, Any] | None, str | None]:
"""Read + parse + validate the sentinel.
Returns ``(payload, error)``:
- ``(payload, None)`` — sentinel exists, parses, schema matches.
- ``(None, None)`` — sentinel does not exist (normal: dispatcher
didn't prefetch, or already cleaned up).
- ``(None, error)`` — sentinel exists but is unusable (malformed,
schema mismatch). The caller emits the error to stderr.
"""
target = _handoff_path(pr_number)
if not target.exists():
return None, None
try:
raw = target.read_text(encoding="utf-8")
payload = json.loads(raw)
except (OSError, ValueError) as e:
return None, f"handoff read/parse failed at {target}: {e}"
if not isinstance(payload, dict):
return None, f"handoff at {target} is not a JSON object"
schema = payload.get("schema_version")
if schema != EXPECTED_SCHEMA_VERSION:
return None, (
f"handoff at {target} has schema_version={schema!r}, "
f"expected {EXPECTED_SCHEMA_VERSION}"
)
return payload, None
# Sentinel for "field is not present in this handoff" / "fetch failed
# upstream". Distinct from ``None`` so the worker contract can
# distinguish three cases:
#
# - ``_MISSING`` → emit NOTHING (empty stdout); worker falls through
# to its legacy GET. Used when the dispatcher didn't attempt this
# section (legacy prompt path) OR when the dispatcher attempted
# and failed (transient error reflected in the matching
# ``*_completed=False`` flag).
# - ``None`` → emit the JSON literal ``null`` (stdout = ``null\n``).
# Used when the dispatcher fetched successfully and confirmed
# absence (e.g. the PR has no Epic reference). The worker sees
# non-empty stdout and skips its legacy GET, treating "null" as
# the authoritative "no data" answer.
# - any other value → emit it; worker uses it verbatim.
#
# Without this distinction the worker re-curls the live Forgejo API
# every time the dispatcher confirmed an absence, defeating the
# whole point of the handoff.
_MISSING: Any = object()
def _gated_list(payload: dict[str, Any], value_key: str, completed_key: str) -> Any:
"""Three-case helper for list-valued fields. ``_MISSING`` when
``completed_key`` is False; the value (or ``[]`` if absent /
falsy) otherwise. Empty list serialises as ``[]\\n``, which
the worker reads as authoritative-empty (skip legacy GET)."""
if not payload.get(completed_key, True):
return _MISSING
return payload.get(value_key) or []
def _gated_text(payload: dict[str, Any], value_key: str, completed_key: str) -> Any:
"""Three-case helper for plain-text fields.
- completion flag False → ``_MISSING`` (worker falls through to
legacy GET).
- completion flag True + empty body → ``None`` (rendered as
``null\\n``; worker reads as authoritative-empty and skips
the legacy GET).
- completion flag True + non-empty body → the body verbatim.
The completion-flag-aware path is what lets a legitimately-empty
PR description (``description_completed=True``, ``description=""``)
cleanly signal "no description, don't re-fetch" rather than
forcing a redundant GET. Before the flag existed, every empty
description / title / diff / issue_body cost the worker a
redundant Forgejo round-trip.
"""
if not payload.get(completed_key, True):
return _MISSING
body = payload.get(value_key)
if not body:
return None
return body
def _project_field(payload: dict[str, Any], field: str) -> Any:
"""Return the slice of ``payload`` selected by ``--field``.
Implements the **three-case contract** the worker relies on:
- :data:`_MISSING` — the field is genuinely absent OR the
dispatcher's fetch for it did not complete successfully (per
the field's ``*_completed`` flag). :func:`_emit` renders this
as empty stdout; the worker falls through to its legacy GET.
- ``None`` — the dispatcher's fetch succeeded AND confirmed
the field is empty in a way the JSON schema can encode (e.g.
``epic=None`` means "PR has no parent Epic"). :func:`_emit`
renders this as ``null\\n``; the worker skips the legacy GET.
- any other value — the value itself, rendered verbatim (plain
text for ``description``/``title``/``diff``/``issue_body``,
pretty-printed JSON for the rest). Empty lists / empty dicts
ALSO fall through this case and render as ``[]\\n`` / ``{}\\n``
— they carry the "fetched, confirmed empty" semantics for
list/dict-valued fields.
The :data:`_MISSING` vs ``None`` vs value distinction is what
stops the worker from re-curling Forgejo for sections the
dispatcher has already confirmed are empty. Mapping all three
cases to ``None`` (the pre-2026-05-10 behavior) collapsed the
"didn't try" and "tried, confirmed empty" cases together and
every confirmed-empty section cost the worker a redundant GET.
"""
if field == "all":
return payload
if field == "description":
return _gated_text(payload, "description", "description_completed")
if field == "title":
return _gated_text(payload, "title", "title_completed")
if field == "diff":
return _gated_text(payload, "diff", "diff_completed")
if field == "issue_body":
return _gated_text(payload, "issue_body", "issue_body_completed")
if field == "metadata":
# The ``completion`` sub-object surfaces the per-section
# ``*_completed`` flags so an operator inspecting via
# ``--field metadata`` can see ALL fetcher state in one
# query without having to iterate every ``--field``. The
# worker's per-field reads still rely on the individual
# field projections — this is for human/operator triage.
#
# Enumerates payload keys (rather than hardcoding the
# name list) so a new ``*_completed`` flag added to the
# writer's ``COMPLETION_FLAG_NAMES`` tuple appears here
# automatically — keeps this reader script standalone
# (no dispatcher / prefetch imports) while still tracking
# writer-side additions without code changes. Stable
# sort order so two consecutive ``--field metadata``
# reads produce byte-identical output (worker / operator
# diff tooling depends on this).
completion = {
k.removesuffix("_completed"): payload.get(k, True)
for k in sorted(payload)
if k.endswith("_completed")
}
return {
"schema_version": payload.get("schema_version"),
"pr_number": payload.get("pr_number"),
"work_type": payload.get("work_type"),
"work_group": payload.get("work_group"),
"head_sha": payload.get("head_sha"),
"head_ref": payload.get("head_ref"),
"base_ref": payload.get("base_ref"),
"listing_title": payload.get("listing_title"),
"data_complete": payload.get("data_complete"),
"completion": completion,
"error_kinds": payload.get("error_kinds") or [],
"created_at": payload.get("created_at"),
"dispatcher_pid": payload.get("dispatcher_pid"),
}
if field == "ci":
# ─── Contract divergence (intentional) ────────────────
# Every OTHER field gated on a completion flag uses
# _gated_list / _gated_text and returns ``_MISSING``
# when the flag is False (empty stdout → worker falls
# through to legacy GET). ``ci`` is the exception: it
# ALWAYS emits the bundle when the sentinel exists at
# all, and the worker is expected to inspect the inner
# ``status_completed`` / ``detail_completed`` booleans
# to decide what to do.
#
# Why diverge:
# 1. CI carries TWO independent completion signals
# (status + detail). Collapsing them into one
# ``_MISSING`` would lose the case where the status
# summary fetch succeeded but the per-check detail
# fetch failed — the worker still has useful
# aggregate state ("PR is red"), it just lacks
# per-check evidence.
# 2. A green PR legitimately has ``ci_detail=[]`` (no
# failing checks to enumerate) with
# ``ci_detail_completed=True``. Emitting that as a
# bundle is the right answer.
#
# Workers reading ``--field ci`` MUST check both
# completion booleans before treating the bundle as
# authoritative. See SKILL.md "ci field exception" for
# the worker-side procedure.
return {
"status": payload.get("ci_status"),
"checks": payload.get("ci_detail") or [],
"status_completed": payload.get("ci_status_completed", True),
"detail_completed": payload.get("ci_detail_completed", True),
}
if field == "comments":
# Dispatch on ``work_type``: pr_fix / request_changes_pr
# cycles populate ``pr_comments``; new_issue cycles populate
# ``issue_comments``. Mixing them was a latent bug — if a
# future change ever populated both (e.g. an issue_impl
# cycle that also happened to have a stale PR), the previous
# ``or`` chain would return the wrong one.
if payload.get("work_type") == "issue_impl":
return _gated_list(payload, "issue_comments", "issue_comments_completed")
return _gated_list(payload, "pr_comments", "pr_comments_completed")
if field == "comments_digest":
# The deterministic attempt-history digest the dispatcher
# computed from the FULL comment list at prefetch time (see
# _attempt_history.summarize_attempt_history). The verbatim
# ``comments`` field only carries the bounded view; this is the
# rollup of everything dropped from that window. Always emit the
# dict when the sentinel exists — an empty ``{}`` means
# "computed, zero attempts parsed", still authoritative. There
# is no legacy GET to fall through to.
return payload.get("pr_comments_digest") or {}
if field == "reviews":
return _gated_list(
payload,
"request_changes_reviews",
"request_changes_reviews_completed",
)
if field == "comment_reviews":
# R3.4 (2026-05-17): non-RC reviews (COMMENT/APPROVE) — the
# reviewer's advisory feedback. Separate field from RC so
# callers can keep the blocking-vs-advisory distinction.
return _gated_list(
payload,
"comment_reviews",
"comment_reviews_completed",
)
if field == "issues":
return _gated_list(payload, "linked_issues", "linked_issues_completed")
if field == "epic":
# ``epic_completed=False`` means the fetch failed; fall
# through. ``epic_completed=True`` with ``epic=None`` means
# the dispatcher confirmed the PR has no Epic reference —
# emit ``null`` so the worker skips its legacy GET.
if not payload.get("epic_completed", True):
return _MISSING
return payload.get("epic")
if field in (
"compliance_gaps",
"gate_preflight",
"recent_implementer_push",
):
# Deterministic / cross-cycle sections — BINARY contract
# (not the generic three-case prefetch contract). The
# dispatcher either computed this cycle (``*_completed=True``
# AND a populated dict, emitted as JSON) or it did not
# (both keys absent, emitted as empty stdout / worker falls
# through). The detectors always produce a structured dict
# when they run, so there is no "tried-but-empty" middle
# state to surface.
completed_key = f"{field}_completed"
if not payload.get(completed_key):
return _MISSING
return payload.get(field)
raise ValueError(f"unknown --field: {field}")
_ALLOWED_FIELDS = (
"all",
"description",
"title",
"diff",
"metadata",
"ci",
"comments",
"comments_digest",
"reviews",
"issues",
"epic",
"issue_body",
"compliance_gaps",
"gate_preflight",
"recent_implementer_push",
)
def _emit(value: Any, field: str) -> None:
"""Render ``value`` to stdout in the format the worker expects.
Three cases (see :data:`_MISSING` docstring for full rationale):
- ``value is _MISSING`` → emit nothing. The worker reads empty
stdout and falls through to its legacy GET.
- ``value is None`` → emit ``null\\n``. The worker reads
non-empty stdout and skips its legacy GET (the dispatcher
authoritatively confirmed absence).
- any other value → emit verbatim. Strings print as plain text
with a trailing newline; everything else prints as
pretty-printed JSON.
"""
if value is _MISSING:
return
if value is None:
# Authoritative "no data" answer (e.g. dispatcher confirmed
# no Epic). The worker contract reads this as "skip the
# legacy GET; there's nothing to fetch." JSON ``null`` is
# parseable in any language the worker might use.
sys.stdout.write("null\n")
return
if isinstance(value, str):
# Plain-text contract for these fields so the worker can
# pipe them straight into a file or display them inline
# without an extra json parse.
sys.stdout.write(value)
# Ensure a trailing newline so terminal usage looks right.
if not value.endswith("\n"):
sys.stdout.write("\n")
return
# Structured fields: pretty-print JSON so the worker (or an
# operator inspecting from the shell) gets a readable blob.
json.dump(value, sys.stdout, indent=2, default=str)
sys.stdout.write("\n")
def _cmd_read(args: argparse.Namespace) -> int:
"""Implement ``read``. Always exits 0 on the "no data" path —
that's a normal condition (e.g. epic-field on a PR with no
Epic reference). Exit 3 only on schema mismatch, which is a
config issue the operator must notice."""
payload, error = _load(int(args.pr))
if error:
print(f"warning: {error}", file=sys.stderr)
if "schema_version" in error:
return 3
return 0
if payload is None:
# No sentinel — the dispatcher didn't prefetch (or cleaned
# up already). Empty stdout is the "no data" signal.
return 0
try:
value = _project_field(payload, args.field)
except ValueError as e:
print(f"error: {e}", file=sys.stderr)
return 2
_emit(value, args.field)
return 0
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(
prog="implementer_pr_context",
description=(
"Worker-side reader for the dispatcher's PR-context "
"handoff sentinel. See module docstring for the full "
"contract."
),
)
sub = parser.add_subparsers(dest="cmd", required=True)
p_read = sub.add_parser(
"read",
help=(
"Read one field from the PR's context handoff. Prints "
"the field on stdout (plain text for description/title/"
"diff/issue_body, JSON for everything else). Empty "
"stdout means 'no data available — fall back to curl'."
),
)
p_read.add_argument("--pr", required=True, type=int, help="PR number")
p_read.add_argument(
"--field",
required=True,
choices=_ALLOWED_FIELDS,
help="Which slice of the prefetched data to print",
)
args = parser.parse_args(argv)
if args.cmd == "read":
return _cmd_read(args)
parser.error(f"unknown subcommand {args.cmd!r}")
return 2 # pragma: no cover — argparse exits before reaching here
if __name__ == "__main__":
sys.exit(main())