Files
cleveragents-core/tools/_status_comments.py
T
drew 355af84fb1 refactor(auto-agents): hard-switch supervisor decommission + implementer parity
Combines the 2026-05-09 hard-switch decommissioning of the LLM
implementation/pr-review supervisors with the Phase 2/3/4/5b
implementer parity work (prefetch + preclone + telemetry + operator-
status comments) and the third/fourth-round critique cleanup.

Removed
- .opencode/agents/implementation-supervisor.md (340 LoC)
- .opencode/agents/pr-review-supervisor.md (348 LoC)
- _dispatch_runtime.assert_no_legacy_supervisor +
  detect_legacy_supervisor_sessions and the SUPERVISOR_TAGS /
  SUPERVISOR_OVERRIDE_ENV plumbing in both dispatchers, along with
  the five supervisor-coexistence tests in test_dispatch_runtime.py
- _watchdog_helpers.parse_truthy_env + watchdog_check.py
  --check-env mode + their dedicated unit tests (the legacy
  DISPATCHERS_RUNNING gate had no callers after the watchdog
  rewrite became unconditional)

Added
- tools/_implementer_prefetch.py — pre-dispatch Forgejo fetches
  (PR/issue body, diff, CI status, comments, reviews, linked
  issues, Epic) per work group
- tools/_implementer_prompt.py — pure-function prompt assembly
  with UNTRUSTED CONTENT fences and shared
  PR_COMPLIANCE_CHECKLIST / OUTPUT_CONTRACT
- tools/_phase4_telemetry.py — extractor + JSONL sink for the
  Phase 4 plan metrics
- tools/_status_comments.py — per-fingerprint operator-status
  comment substrate, namespaced for reviewer + implementer
- _dispatch_runtime.SessionContext dataclass + SIGTERM/SIGINT
  cooperative claim release with synchronous handler
- TestSupervisorAgentsDecommissioned and
  TestAutoAgentsMdIsWatchdogOnly anti-regression lints (glob over
  *supervisor*.md in .opencode/agents/, plus body keyword bans
  and bash allow-list lint)
- pyproject.toml `slow` marker registration for the subprocess
  SIGTERM smoke test
- tests/auto_agents/fixtures/{phase4-acceptance.yaml,
  phase4-session-output-sample.txt}

Rewritten
- .opencode/agents/auto-agents.md from supervisor-fleet manager
  (~545 LoC) to dispatcher heartbeat watchdog (~184 LoC); host
  init system / process manager (systemd / runit / docker) is now
  the explicit restart authority instead of "host-level process
  supervisor"
- AGENTS.md production-launch story (Shells A-D) reflects the
  deterministic-Python orchestration boundary; the bot-identity
  fork-mode paragraph reads from FORGEJO_OWNER / FORGEJO_REPO
  env vars instead of the deleted hard-coded supervisor flags
- tools/launch_fork.sh header documents three host-level entry
  points (dispatchers-launcher.sh, opencode-builder.sh,
  merge_drive.py)
- worker self-descriptions (implementation-worker.md,
  pr-review-worker.md) refer to the dispatcher / merge driver
  instead of the deleted supervisors; session-health-quick-util.md
  and async-agent-util.md treat -SUP-suffixed sessions as
  flag-and-escalate signals

Tests: 1006 passed, 3 skipped, 0 failed under tests/auto_agents/.
Lint: zero new ruff errors on touched files; three pre-existing
errors in tools/_pr_diff.py at lines blamed to 2026-05-07.

Operator note: the only in-process rollback knob for prefetch
issues is IMPLEMENTER_DISPATCHER_PREFETCH=0 (and the matching
IMPLEMENTER_DISPATCHER_PRECLONE=0). Anything beyond that is git
revert of this commit. Residual doc surface in the
auto-agents-system and supervised-workers skill READMEs is
documentation-only; the agent files those READMEs reference no
longer exist.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 16:03:43 -04:00

194 lines
7.6 KiB
Python

"""Shared per-fingerprint operator-status comment substrate.
Houses the dedup substrate every dispatcher uses to post operator-
visibility comments on PR / issue timelines without re-implementing
the fingerprinting logic in two places. The reviewer dispatcher
(:mod:`_review_post`) and the implementer dispatcher
(``tools/dispatch_implementer.py`` via :mod:`_review_post`) both
key off this module; their public posters (``post_operator_status_comment``,
``post_implementer_status_comment``) are thin per-pipeline wrappers
that supply the marker family + body template.
Public surface:
- ``_sanitize_status_detail`` — normalise + cap free-form reason
strings before they enter the fingerprint or the body.
- ``_compute_status_fingerprint`` — sha256-derived 12-hex-char
identifier for ``(action, sanitised(reason))``.
- ``_has_existing_operator_status`` — generic "marker prefix +
fingerprint already on this PR" predicate (parameterised on the
pipeline's marker prefix).
- ``OPERATOR_STATUS_MARKER_PREFIX`` /
``IMPLEMENTER_STATUS_MARKER_PREFIX`` (and their suffix
counterparts) — the on-wire marker namespaces. Two-key namespacing
protects each pipeline's idempotency from cross-talk if a future
audit enumerates "every status comment one dispatcher kind posted
on a given PR".
- ``operator_status_marker`` / ``implementer_status_marker`` —
public marker constructors so external callers don't have to
string-concat the prefix + fingerprint themselves.
The module is import-safe and self-contained; no cycle risk against
:mod:`_review_post` or any dispatcher driver. Extracting it here
prevents :mod:`_review_post` from drifting into a junk drawer of
"reviewer-specific posters AND a shared dedup substrate AND the
implementer's marker family" — the previous shape paid the cohesion
price for backwards compatibility.
"""
from __future__ import annotations
import hashlib
import re
from typing import Any
# ─── Reviewer-side marker family ────────────────────────────────────────────
#
# Format: ``<!-- pr-review-dispatcher:operator-status:fp=ABCDEF123456 -->``
#
# Stable wire string: lands in PR comment bodies; mutating it would
# break fingerprint compatibility with previously-posted comments.
_OPERATOR_STATUS_MARKER_PREFIX = "<!-- pr-review-dispatcher:operator-status:fp="
_OPERATOR_STATUS_MARKER_SUFFIX = " -->"
OPERATOR_STATUS_MARKER_PREFIX = _OPERATOR_STATUS_MARKER_PREFIX
OPERATOR_STATUS_MARKER_SUFFIX = _OPERATOR_STATUS_MARKER_SUFFIX
def _operator_status_marker(fingerprint: str) -> str:
"""Build the full HTML-comment marker for ``fingerprint``."""
return (
f"{_OPERATOR_STATUS_MARKER_PREFIX}"
f"{fingerprint}"
f"{_OPERATOR_STATUS_MARKER_SUFFIX}"
)
def operator_status_marker(fingerprint: str) -> str:
"""Public form of :func:`_operator_status_marker`. Tests and any
external caller that needs to construct or recognise a marker
should import this rather than the underscore-prefixed helper.
"""
return _operator_status_marker(fingerprint)
# ─── Implementer-side marker family (Phase 5b) ──────────────────────────────
#
# Distinct prefix from the reviewer namespace so the dedup logic
# keyed off either prefix cannot mis-match a comment from the other
# pipeline.
_IMPLEMENTER_STATUS_MARKER_PREFIX = (
"<!-- pr-implementer-dispatcher:operator-status:fp="
)
_IMPLEMENTER_STATUS_MARKER_SUFFIX = " -->"
IMPLEMENTER_STATUS_MARKER_PREFIX = _IMPLEMENTER_STATUS_MARKER_PREFIX
IMPLEMENTER_STATUS_MARKER_SUFFIX = _IMPLEMENTER_STATUS_MARKER_SUFFIX
def implementer_status_marker(fingerprint: str) -> str:
"""Public form of the implementer-side fingerprint marker. Mirrors
:func:`operator_status_marker` on the reviewer side.
"""
return (
f"{_IMPLEMENTER_STATUS_MARKER_PREFIX}"
f"{fingerprint}"
f"{_IMPLEMENTER_STATUS_MARKER_SUFFIX}"
)
# ─── Detail sanitisation + fingerprinting ───────────────────────────────────
# Cap on the action_reason / detail string we surface in the
# operator-status body. Large reasons (a verbose Forgejo error blob,
# a 5 KB Python traceback excerpt) hurt readability and inflate the
# fingerprint surface area.
_STATUS_DETAIL_MAX_CHARS = 600
def _sanitize_status_detail(detail: str) -> str:
"""Normalise + cap the action_reason string for operator display.
- Collapse runs of whitespace (newlines, tabs) into single spaces
so a multi-line traceback doesn't span the operator comment.
- Strip control characters (other than tab/newline) for log
hygiene.
- Cap at :data:`_STATUS_DETAIL_MAX_CHARS` characters with an
ellipsis. Without the cap a runaway worker error message
could blow past the comment-body limit.
Used both for fingerprint computation (so stylistic whitespace
differences don't fork a new fingerprint) and for the rendered
body. Returns the empty string when ``detail`` is empty.
"""
if not detail:
return ""
cleaned = "".join(
ch
for ch in detail
if ch == "\n" or ch == "\t" or (0x20 <= ord(ch) < 0x7F) or ord(ch) >= 0xA0
)
cleaned = re.sub(r"\s+", " ", cleaned).strip()
if len(cleaned) > _STATUS_DETAIL_MAX_CHARS:
cleaned = cleaned[: _STATUS_DETAIL_MAX_CHARS - 1] + "\u2026"
return cleaned
def _compute_status_fingerprint(action: str, detail: str) -> str:
"""Compute the 12-hex-char idempotency fingerprint for a status
comment.
Built from ``sha256(f"{action}|{sanitised_detail}")[:12]`` so two
cycles producing the same outcome surface the same fingerprint
and skip the duplicate post; distinct outcomes get distinct
fingerprints and distinct posts. The ``action`` argument is the
pipeline-specific verb (``failed`` / ``stale`` / ``rebase-failed``
/ ``timeout`` / etc.); the ``detail`` argument is whatever
free-form reason string the dispatcher captured.
"""
canonical = f"{action}|{_sanitize_status_detail(detail)}"
return hashlib.sha256(canonical.encode("utf-8", errors="replace")).hexdigest()[:12]
def _has_existing_operator_status(
pr_comments: list[dict[str, Any]] | None,
fingerprint: str,
*,
marker_prefix: str,
) -> bool:
"""Return True iff any prefetched PR comment carries the marker
for ``fingerprint``. Used to short-circuit the operator-status
POST so identical-outcome cycles don't double-post.
``marker_prefix`` is required (no default) so that a third
pipeline cannot accidentally key against the reviewer's namespace.
Reviewer callers pass :data:`_OPERATOR_STATUS_MARKER_PREFIX`;
implementer callers pass :data:`_IMPLEMENTER_STATUS_MARKER_PREFIX`;
each pipeline's idempotency stays scoped to its own namespace.
Tolerates malformed entries: any comment whose ``body`` is not a
string is skipped silently.
"""
if not pr_comments:
return False
needle = f"{marker_prefix}{fingerprint}"
for comment in pr_comments:
if not isinstance(comment, dict):
continue
body = comment.get("body")
if isinstance(body, str) and needle in body:
return True
return False
__all__ = (
"IMPLEMENTER_STATUS_MARKER_PREFIX",
"IMPLEMENTER_STATUS_MARKER_SUFFIX",
"OPERATOR_STATUS_MARKER_PREFIX",
"OPERATOR_STATUS_MARKER_SUFFIX",
"_compute_status_fingerprint",
"_has_existing_operator_status",
"_operator_status_marker",
"_sanitize_status_detail",
"implementer_status_marker",
"operator_status_marker",
)