Files
cleveragents-core/tools/controller/mcp/implementer_builder.py
T
drew eb1d81828f feat(controller): implementer_retrigger_ci — agent re-triggers CI on ci-infra-failure
A CI job hard-killed by OOM / pod eviction produces no verdict — nothing
in the diff to fix. The implementer correctly emits
outcome=ci-infra-failure, but that alone only shuffles workflow state;
CI never re-runs, so every retry reads the same dead run and the
workflow loops until the _MAX_CI_INFRA_FAILURE backstop STUCKs it
(observed live on PR #39 in run-3).

New ``implementer_retrigger_ci(owner, repo, pr_branch)`` MCP tool on the
implementer response-builder lets the agent kick a fresh CI run. Forgejo
15.x has no Actions rerun API, so it reuses the controller's existing
mechanism — ``ci_rerun.trigger_ci_rerun_via_empty_commit`` — an empty
commit that advances the PR head SHA, which is the unambiguous
fresh-state signal for CI (and a stale review).

- ci_rerun.py is loaded standalone (importlib by file path, registered
  in sys.modules before exec so its @dataclass resolves) — the
  response-builder MCP must not pull the heavy tools.controller.master
  package.
- _retrigger_ci resolves the Forgejo base URL + token from env
  (FORGEJO_URL / FORGEJO_API_BASE, FORGEJO_TOKEN / GITEA_TOKEN) and
  never raises.
- Once per session: a second implementer_retrigger_ci call is refused
  benignly so a looping agent cannot pile junk commits on the PR.
- The implementer prompt's ci-infra-failure block now instructs the
  agent to call the tool before emitting the outcome.

10 new tests (test_mcp_builders.py, test_worker_prompts.py); full
controller suite (1192) green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 00:22:46 -04:00

670 lines
27 KiB
Python

#!/usr/bin/env python3
"""Implementer response-builder MCP server.
Spawned per-attempt by the worker controller. Validates each builder
call against ImplementerOutputV1 and enforces outcome-specific
invariants at finalize:
- outcome='resolved' → requires ≥1 commit AND ≥1 file modified
- outcome='rebase-failed' → no extra requirements; controller routes
to CONFLICT_RESOLVING
- outcome='blocked' → requires ≥1 entry in blockers
- outcome='noop' → forbids commits + files + blockers
- outcome='competence-failure' → no extra requirements
- outcome='dispute-reviewer' → T5-4 + T5-9 (dispute-at-any-tier).
Requires all four dispute fields
(set via ``implementer_set_dispute``).
Forbids commits + files + blockers —
the implementer isn't fixing anything,
it's contesting the reviewer's claim.
Per-tier cap (1 dispute per tier) is
enforced at the controller (outcomes
mapper), not here — subsequent disputes
at the same tier downgrade to
competence-failure to force escalation.
- outcome='verified-clean' → T5-11 fast-success path. Use when the
prior attempt was a ``conflict_resolver``
whose commits already cover everything
and no further code changes are needed.
Forbids commits + files + blockers (same
as noop). Routes the workflow to
AWAITING_CI for CI re-verification
instead of escalating. Picking this
outcome when CI is actually red (or
when there's genuine code work missing)
will surface as a competence-failure on
the next cycle.
- outcome='ci-not-ready' → CI for the PR head has no verdict yet
(still pending) and there is nothing to
fix. Forbids commits + files + blockers
(same as noop). Routes the workflow to
AWAITING_CI to wait for the verdict
instead of STUCK. Exists for the
RUN_CI_LOCAL on-demand-CI path.
- outcome='ci-infra-failure' → the CI failure is NOT a code failure:
the failing job's log carries no
verdict (no ##[error], test summary,
Traceback or exit code — it stops
mid-run), the signature of a hard
process kill (OOM / pod eviction).
Nothing to fix. Forbids commits +
files + blockers (same as noop).
Routes IMPLEMENTING → DISCOVERED so
the CI-freshness gate reruns CI under
its bounded budget instead of
dead-ending at blocked → STUCK.
"""
from __future__ import annotations
import os
import sys
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
from mcp.server.fastmcp import FastMCP
_REPO_ROOT = Path(__file__).resolve().parents[3]
if str(_REPO_ROOT) not in sys.path:
sys.path.insert(0, str(_REPO_ROOT))
from tools._mcp_common import make_main # noqa: E402
from tools.controller.contracts.v1 import ImplementerOutputV1 # noqa: E402
from tools.controller.mcp._builder_base import ( # noqa: E402
BuilderError,
BuilderState,
call_with_invariants,
finalize_and_emit,
ok,
)
server = FastMCP("implementer-builder")
_STATE = BuilderState()
_OUTCOME_VALUES = {
"resolved",
"rebase-failed",
"noop",
"blocked",
"competence-failure",
"dispute-reviewer",
"verified-clean",
"ci-not-ready",
"ci-infra-failure",
}
_CONFIDENCE_VALUES = {"high", "medium", "low"}
_TIER_VALUES = {0, 1, 2}
# T5-9 (2026-05-19): dispute available at any tier. The MCP no longer
# enforces a tier floor — trial-5 data showed even haiku produced
# credible structured disputes when given the right evidence-required
# contract. Per-tier cap is enforced upstream in the outcomes mapper
# (one dispute per tier; a second is downgraded to competence-failure
# so the workflow escalates rather than ping-pongs).
_DISPUTE_EVIDENCE_MIN_CHARS = 200
import re as _re_mod # noqa: E402 (kept local; consistent with record_commit)
_SHA_RE = _re_mod.compile(r"[0-9a-f]{7,40}")
# ─── CI re-trigger (ci-infra-failure path) ───────────────────────────
#
# A CI job hard-killed by OOM / pod eviction produces no verdict —
# nothing in the diff to fix. The implementer emits
# outcome='ci-infra-failure', but that alone only shuffles workflow
# state; CI never re-runs unless a new commit is pushed.
# ``implementer_retrigger_ci`` lets the agent kick a fresh CI run.
# Forgejo 15.x has no Actions rerun API, so the re-trigger is an
# empty-commit push — the exact mechanism the controller's CI-freshness
# gate already uses (tools/controller/master/ci_rerun.py).
def _load_ci_rerun() -> Any:
"""Load ``ci_rerun.py`` as a standalone module.
``ci_rerun`` imports only the stdlib, but importing it through the
``tools.controller.master`` package would execute that package's
heavy ``__init__`` (the whole master orchestration layer) inside
this lightweight response-builder MCP. Loading the file directly
sidesteps that. Returns the module, or None if it cannot be loaded
— the MCP must still start; the tool then reports the failure.
"""
import importlib.util
path = _REPO_ROOT / "tools" / "controller" / "master" / "ci_rerun.py"
mod_name = "_controller_ci_rerun_standalone"
try:
spec = importlib.util.spec_from_file_location(mod_name, path)
if spec is None or spec.loader is None:
return None
mod = importlib.util.module_from_spec(spec)
# Register in sys.modules BEFORE exec: ci_rerun.py defines a
# @dataclass, and (Python 3.12+) the dataclass machinery looks
# the module up via sys.modules[cls.__module__] — an unregistered
# module makes that an AttributeError.
sys.modules[mod_name] = mod
spec.loader.exec_module(mod)
return mod
except Exception: # noqa: BLE001 — MCP must start even if this fails
sys.modules.pop(mod_name, None)
return None
_ci_rerun = _load_ci_rerun()
def _retrigger_ci(owner: str, repo: str, pr_branch: str) -> dict[str, Any]:
"""Re-trigger CI for ``owner/repo``'s ``pr_branch`` by pushing an
empty commit. Resolves the Forgejo base URL + token from the env
the worker passed down. Returns
``{"ok": bool, "new_head_sha": str|None, "error": str|None}`` and
never raises — a failed re-trigger is non-fatal for the agent.
"""
token = (
os.environ.get("FORGEJO_TOKEN") or os.environ.get("GITEA_TOKEN") or ""
).strip()
base = os.environ.get("FORGEJO_URL", "").strip().rstrip("/")
if not base:
# Fall back to FORGEJO_API_BASE with the ``/api/v1`` suffix
# stripped (explicit endswith, not rstrip char-set — see PD4 in
# worker/__main__.py).
api = os.environ.get("FORGEJO_API_BASE", "").strip().rstrip("/")
if api.endswith("/api/v1"):
api = api[: -len("/api/v1")]
base = api
if not token:
return {
"ok": False,
"new_head_sha": None,
"error": "no Forgejo token in env (FORGEJO_TOKEN / GITEA_TOKEN)",
}
if not base or "://" not in base:
return {
"ok": False,
"new_head_sha": None,
"error": f"no usable Forgejo base URL in env (got {base!r})",
}
if _ci_rerun is None:
return {
"ok": False,
"new_head_sha": None,
"error": "ci_rerun helper module could not be loaded",
}
try:
result = _ci_rerun.trigger_ci_rerun_via_empty_commit(
owner=owner,
repo=repo,
pr_branch=pr_branch,
https_remote=f"{base}/{owner}/{repo}.git",
token=token,
)
except Exception as exc: # noqa: BLE001 — _retrigger_ci must never raise
# trigger_ci_rerun_via_empty_commit is contracted never to
# raise; this is belt-and-suspenders so a contract regression
# there can't crash the MCP request.
return {
"ok": False,
"new_head_sha": None,
"error": f"CI re-trigger raised unexpectedly: {exc}",
}
return {
"ok": result.ok,
"new_head_sha": result.new_head_sha,
"error": result.error,
}
@server.tool()
def implementer_start(
workflow_id: int, attempt_id: int, pr_number: int, tier: int
) -> dict[str, Any]:
"""Initialize the implementer session. MUST be the first tool called."""
args = {
"workflow_id": workflow_id,
"attempt_id": attempt_id,
"pr_number": pr_number,
"tier": tier,
}
def body() -> dict[str, Any]:
# OpenCode reuses local MCP subprocesses across sessions. Any
# state in _STATE is from a prior session and must be cleared.
# We can't distinguish "agent retry in same session" from "new
# session reusing this MCP" reliably — both have the same
# observable signature — so we just reset whenever _start is
# called with state present. ``reset_for_new_attempt`` logs a
# WARN if there's an in-flight (started-but-not-finalized)
# carryover so abandoned attempts are still visible to ops.
#
# (Earlier code keyed on identity.attempt_id but the agent
# passes whatever the prompt told it to — if the prompt has
# ``attempt_id=...`` as a literal placeholder the agent guesses
# 1 every time, the comparison always matches, and reset never
# fires → stale state persists permanently.)
if _STATE.started:
_STATE.reset_for_new_attempt()
if tier not in _TIER_VALUES:
raise BuilderError(
f"invalid tier {tier!r}; expected one of {sorted(_TIER_VALUES)}"
)
_STATE.started = True
_STATE.started_at = datetime.now(timezone.utc)
_STATE.identity = {
"workflow_id": workflow_id,
"attempt_id": attempt_id,
"pr_number": pr_number,
"tier": tier,
}
_STATE.fields["used_tier"] = tier
return ok(tier=tier)
return call_with_invariants(_STATE, "implementer_start", body, args)
@server.tool()
def implementer_record_file_modified(
path: str, lines_added: int = 0, lines_deleted: int = 0
) -> dict[str, Any]:
"""Record one file the implementer modified."""
args = {"path": path, "lines_added": lines_added, "lines_deleted": lines_deleted}
def body() -> dict[str, Any]:
_STATE.require_started()
_STATE.require_not_finalized()
if not path.strip():
raise BuilderError("path must be non-empty")
if lines_added < 0 or lines_deleted < 0:
raise BuilderError("lines_added/lines_deleted must be ≥ 0")
files = _STATE.fields.setdefault("files_touched", [])
if path not in files:
files.append(path)
return ok(files_touched_count=len(files))
return call_with_invariants(_STATE, "implementer_record_file_modified", body, args)
@server.tool()
def implementer_record_commit(sha: str, message: str) -> dict[str, Any]:
"""Record a git commit the implementer pushed."""
args = {"sha": sha, "message_len": len(message)}
def body() -> dict[str, Any]:
_STATE.require_started()
_STATE.require_not_finalized()
# A-1 fix (2026-05-19): tighter SHA validation. Pre-fix only
# ``len(sha)>=7`` was checked — agent could pass arbitrary
# 7+ char strings and the controller's ``head_sha_advanced``
# check would treat the hallucination as a real push, routing
# to AWAITING_CI; ci_status_poll then 404s on the fake SHA;
# workflow wastes 2h until ci_poll_exhaustion fires.
# Real git SHAs are exactly 7-40 lowercase hex chars.
import re as _re
if not sha.strip() or not _re.fullmatch(r"[0-9a-f]{7,40}", sha.strip()):
raise BuilderError(
f"sha must be 7-40 lowercase hex chars (a git SHA); got {sha!r}"
)
if not message.strip():
raise BuilderError("commit message must be non-empty")
commits = _STATE.fields.setdefault("commit_shas", [])
if sha not in commits:
commits.append(sha)
return ok(commits_count=len(commits))
return call_with_invariants(_STATE, "implementer_record_commit", body, args)
@server.tool()
def implementer_add_blocker(description: str) -> dict[str, Any]:
"""Add a blocker explanation. Only valid when outcome will be 'blocked'."""
args = {"description": description}
def body() -> dict[str, Any]:
_STATE.require_started()
_STATE.require_not_finalized()
if not description.strip():
raise BuilderError("blocker description must be non-empty")
current_outcome = _STATE.fields.get("outcome")
if current_outcome and current_outcome != "blocked":
raise BuilderError(
f"cannot add blocker with outcome={current_outcome!r}; "
"set outcome='blocked' first"
)
blockers = _STATE.fields.setdefault("blockers", [])
blockers.append(description)
return ok(blockers_count=len(blockers))
return call_with_invariants(_STATE, "implementer_add_blocker", body, args)
@server.tool()
def implementer_set_outcome(outcome: str) -> dict[str, Any]:
args = {"outcome": outcome}
def body() -> dict[str, Any]:
_STATE.require_started()
_STATE.require_not_finalized()
if outcome not in _OUTCOME_VALUES:
raise BuilderError(
f"invalid outcome {outcome!r}; expected one of {sorted(_OUTCOME_VALUES)}"
)
# T5-9: dispute-reviewer accepted at any tier. The per-tier cap
# is enforced at the controller side (outcomes mapper), so the
# MCP just validates the structural requirements (set_dispute
# must be called before finalize).
_STATE.fields["outcome"] = outcome
return ok(outcome=outcome)
return call_with_invariants(_STATE, "implementer_set_outcome", body, args)
@server.tool()
def implementer_set_dispute(
disputed_review_id: int,
disputed_blocker_index: int,
dispute_evidence: str,
verified_at_sha: str,
) -> dict[str, Any]:
"""Record an evidence-bearing dispute against a prior reviewer's
blocking_issue. Required when ``outcome='dispute-reviewer'``.
Available at any tier (T5-9). ``dispute_evidence`` must be at
least ``_DISPUTE_EVIDENCE_MIN_CHARS`` characters — bound by the
controller to force concrete file:line citations + quoted bytes
rather than hand-waving rebuttals. ``verified_at_sha`` is the
commit the implementer read while preparing the rebuttal
(lowercase hex, 7-40 chars).
Per-tier cap (1 dispute per tier) is enforced by the controller's
outcomes mapper; the MCP only validates structural requirements.
"""
args = {
"disputed_review_id": disputed_review_id,
"disputed_blocker_index": disputed_blocker_index,
"dispute_evidence_len": len(dispute_evidence or ""),
"verified_at_sha": verified_at_sha,
}
def body() -> dict[str, Any]:
_STATE.require_started()
_STATE.require_not_finalized()
if not isinstance(disputed_review_id, int) or disputed_review_id < 0:
raise BuilderError(
f"disputed_review_id must be a non-negative int; "
f"got {disputed_review_id!r}"
)
if not isinstance(disputed_blocker_index, int) or disputed_blocker_index < 0:
raise BuilderError(
f"disputed_blocker_index must be a non-negative int (0-based); "
f"got {disputed_blocker_index!r}"
)
evidence = (dispute_evidence or "").strip()
if len(evidence) < _DISPUTE_EVIDENCE_MIN_CHARS:
raise BuilderError(
f"dispute_evidence must be ≥ {_DISPUTE_EVIDENCE_MIN_CHARS} "
f"chars after stripping (got {len(evidence)}); "
"include concrete file:line citations + quoted bytes"
)
sha = (verified_at_sha or "").strip()
if not _SHA_RE.fullmatch(sha):
raise BuilderError(
f"verified_at_sha must be 7-40 lowercase hex chars; "
f"got {verified_at_sha!r}"
)
_STATE.fields["disputed_review_id"] = disputed_review_id
_STATE.fields["disputed_blocker_index"] = disputed_blocker_index
_STATE.fields["dispute_evidence"] = evidence
_STATE.fields["verified_at_sha"] = sha
return ok(
disputed_review_id=disputed_review_id,
disputed_blocker_index=disputed_blocker_index,
dispute_evidence_chars=len(evidence),
)
return call_with_invariants(_STATE, "implementer_set_dispute", body, args)
@server.tool()
def implementer_set_confidence(confidence: str) -> dict[str, Any]:
args = {"confidence": confidence}
def body() -> dict[str, Any]:
_STATE.require_started()
_STATE.require_not_finalized()
if confidence not in _CONFIDENCE_VALUES:
raise BuilderError(
f"invalid confidence {confidence!r}; expected one of {sorted(_CONFIDENCE_VALUES)}"
)
_STATE.fields["confidence"] = confidence
return ok(confidence=confidence)
return call_with_invariants(_STATE, "implementer_set_confidence", body, args)
@server.tool()
def implementer_retrigger_ci(
owner: str, repo: str, pr_branch: str
) -> dict[str, Any]:
"""Re-trigger CI for this PR by pushing an empty commit to its head
branch.
Call this exactly once, immediately before emitting
``outcome='ci-infra-failure'``: a CI job hard-killed by OOM / pod
eviction produced no verdict, and a fresh run is the only way to
get one. Forgejo has no Actions rerun API — advancing HEAD with an
empty commit fires a fresh ``pull_request`` run.
Pass ``owner`` / ``repo`` / ``pr_branch`` from the PR context in
this prompt. Returns ``retriggered=True`` + the new head SHA on
success. A failed re-trigger is NON-fatal — emit ``ci-infra-failure``
anyway; the controller's CI-rerun budget is the backstop. The empty
re-trigger commit is CI machinery, not a fix: do NOT record it with
``implementer_record_commit``.
"""
args = {"owner": owner, "repo": repo, "pr_branch": pr_branch}
def body() -> dict[str, Any]:
_STATE.require_started()
_STATE.require_not_finalized()
for name, val in (
("owner", owner),
("repo", repo),
("pr_branch", pr_branch),
):
if not isinstance(val, str) or not val.strip():
raise BuilderError(f"{name} must be a non-empty string")
# Once per session. call_with_invariants records THIS call into
# _STATE.audit before body() runs, so a count > 1 means a prior
# call already pushed an empty commit + kicked a fresh CI run.
# Re-triggering again would only pile junk commits on the PR —
# refuse benignly so the agent still proceeds to ci-infra-failure.
# The audit is wiped by reset_for_new_attempt, so the next
# attempt (a fresh session) gets its own re-trigger.
if (
sum(
1
for e in _STATE.audit
if e.get("tool") == "implementer_retrigger_ci"
)
> 1
):
return ok(
retriggered=False,
new_head_sha=None,
error=(
"CI already re-triggered once this session; a fresh "
"run is in flight — proceed to ci-infra-failure"
),
)
result = _retrigger_ci(owner.strip(), repo.strip(), pr_branch.strip())
return ok(
retriggered=result["ok"],
new_head_sha=result.get("new_head_sha"),
error=result.get("error"),
)
return call_with_invariants(_STATE, "implementer_retrigger_ci", body, args)
def _check_outcome_invariants(state: BuilderState) -> None:
"""Outcome-specific finalize check."""
outcome = state.fields.get("outcome")
files = state.fields.get("files_touched") or []
commits = state.fields.get("commit_shas") or []
blockers = state.fields.get("blockers") or []
tier = state.fields.get("used_tier")
if outcome == "resolved":
if not commits:
raise BuilderError(
"outcome='resolved' requires ≥1 commit; "
"call implementer_record_commit at least once"
)
if not files:
raise BuilderError(
"outcome='resolved' requires ≥1 file modified; "
"call implementer_record_file_modified at least once"
)
elif outcome == "blocked":
if not blockers:
raise BuilderError(
"outcome='blocked' requires ≥1 blocker; "
"call implementer_add_blocker at least once"
)
elif outcome == "noop":
if commits or files or blockers:
raise BuilderError(
"outcome='noop' forbids commits/files/blockers; "
f"got {len(commits)} commits, {len(files)} files, "
f"{len(blockers)} blockers"
)
elif outcome == "verified-clean":
# T5-11: post-conflict-resolution fast-success path. Same
# invariants as noop (no new work) but routes to AWAITING_CI
# rather than competence-failure → escalation. The
# ``verified-clean`` semantic is "I checked the resolver's
# commits and they cover everything; CI should re-run to
# confirm." If the agent has anything to record (commits /
# files / blockers), it should be using a different outcome.
if commits or files or blockers:
raise BuilderError(
"outcome='verified-clean' forbids commits/files/blockers — "
"use 'resolved' if you pushed, 'blocked' if you found "
"issues; "
f"got {len(commits)} commits, {len(files)} files, "
f"{len(blockers)} blockers"
)
elif outcome == "ci-not-ready":
# RUN_CI_LOCAL: CI for the PR head has no verdict yet and there
# is nothing to fix. Same no-work invariant as noop; routes to
# AWAITING_CI to wait for the verdict rather than STUCK.
if commits or files or blockers:
raise BuilderError(
"outcome='ci-not-ready' forbids commits/files/blockers — "
"use 'resolved' if you pushed a fix, 'blocked' if CI is "
"red and you genuinely cannot fix it; "
f"got {len(commits)} commits, {len(files)} files, "
f"{len(blockers)} blockers"
)
elif outcome == "ci-infra-failure":
# The CI failure is a hard-kill (OOM / pod eviction) with no
# verdict in the log — nothing to fix. Same no-work invariant
# as noop; routes IMPLEMENTING → DISCOVERED so the CI-freshness
# gate reruns CI under its bounded budget rather than STUCK.
if commits or files or blockers:
raise BuilderError(
"outcome='ci-infra-failure' forbids commits/files/blockers "
"— use 'resolved' if you pushed a fix for a real CI "
"failure, 'blocked' if CI is red with a real verdict you "
"genuinely cannot fix; "
f"got {len(commits)} commits, {len(files)} files, "
f"{len(blockers)} blockers"
)
elif outcome == "dispute-reviewer":
# T5-9: any tier may dispute. No tier-floor check.
if commits or files or blockers:
raise BuilderError(
"outcome='dispute-reviewer' forbids commits/files/blockers — "
"the implementer is contesting the reviewer's claim, not "
"fixing code; "
f"got {len(commits)} commits, {len(files)} files, "
f"{len(blockers)} blockers"
)
missing = [
f
for f in (
"disputed_review_id",
"disputed_blocker_index",
"dispute_evidence",
"verified_at_sha",
)
if f not in state.fields
]
if missing:
raise BuilderError(
f"outcome='dispute-reviewer' requires dispute fields "
f"{missing}; call implementer_set_dispute(...) before "
"finalize"
)
@server.tool()
def implementer_finalize(output_path: str | None = None) -> dict[str, Any]:
"""Validate state + emit ImplementerOutputV1 JSON to ``output_path``
(per-attempt path from the controller's prompt). Falls back to
env var / stdout when ``output_path`` is None."""
def body() -> dict[str, Any]:
_STATE.require_started()
_STATE.require_not_finalized()
missing = [
f for f in ("outcome", "confidence", "used_tier") if f not in _STATE.fields
]
if missing:
raise BuilderError(
f"missing required fields: {missing}. Call the matching "
"setters before finalize."
)
started = _STATE.started_at
wallclock = (
(datetime.now(timezone.utc) - started).total_seconds() if started else 0.0
)
_STATE.fields["output_version"] = "V1"
_STATE.fields["wallclock_seconds"] = wallclock
_STATE.fields.setdefault("files_touched", [])
_STATE.fields.setdefault("commit_shas", [])
_STATE.fields.setdefault("blockers", [])
return finalize_and_emit(
_STATE,
ImplementerOutputV1,
output_path=output_path,
extra_required_check=_check_outcome_invariants,
)
_STATE.record("implementer_finalize", {"output_path": output_path})
try:
return body()
except BuilderError as e:
return {"error": str(e), "tool": "implementer_finalize"}
@server.tool()
def implementer_state() -> dict[str, Any]:
return {
"started": _STATE.started,
"finalized": _STATE.finalized,
"identity": dict(_STATE.identity),
"fields_set": sorted(_STATE.fields.keys()),
"files_count": len(_STATE.fields.get("files_touched") or []),
"commits_count": len(_STATE.fields.get("commit_shas") or []),
"blockers_count": len(_STATE.fields.get("blockers") or []),
"audit_entries": len(_STATE.audit),
}
main = make_main(server, "implementer-builder")
if __name__ == "__main__":
raise SystemExit(main())