Files
cleveragents-core/tools/implementer_workspace.py
T
drew 0b657cd0d9 fix(auto-agents): three-case contract, work_type dispatch, hardening for filesystem handoff
Post-commit review of d386ff4e surfaced two real bugs and several
rough edges. None changed the architecture — all changes harden the
existing dispatcher↔worker filesystem-handshake contract.

P0 bug fixes
- Three-case read contract for implementer_pr_context.py. The old
  ``or None`` projection conflated "field missing" with "field
  present but empty," forcing the worker to re-curl Forgejo every
  time the dispatcher had already confirmed a section was empty.
  New contract: empty stdout = "didn't try; fall through to legacy
  GET"; ``null\n`` = "tried and authoritatively empty; SKIP GET";
  any other content = use it.
- ``comments`` field dispatches on ``work_type`` instead of using
  the ``pr_comments or issue_comments`` chain. The old code would
  silently leak ``issue_comments`` from a stale issue context into
  a ``pr_fix`` worker's ``--field comments`` read.
- Every section's projection now honours its ``*_completed`` flag.
  A failed upstream fetch (transient API error) maps to empty
  stdout instead of authoritative empty data.

P1 hardening
- Dropped ``_resolve_branch_for_sha``. The pre-clone path was
  shelling out to ``git for-each-ref --points-at <sha>`` for data
  the dispatcher already had from ``pr_details.head.ref``. Now
  ``prepare_pr_worktree`` takes ``head_ref`` as a kwarg.
- Both writers (PR-context and workspace sentinels) clean up
  their ``.tmp`` orphan files on partial-write / serialisation
  failure.
- Removed the dead ``cleanup`` subcommand from
  tools/implementer_workspace.py — worktree cleanup is the
  dispatcher's job (WorktreeHandle.cleanup); the worker has no
  legitimate reason to rm -rf a worktree mid-session.
- Tightened bash allow-rules in task-implementor.md from
  ``<script> *`` to ``<script> <subcommand> *`` so future
  subcommands require explicit operator review.
- Retired the prompt-vs-sentinel "use either" softener in
  task-implementor.md and the implementer-pr-context SKILL.md.
  The scripts are now documented as the SINGLE SOURCE OF TRUTH.

Test additions
- 5 new dispatcher↔sentinel integration tests in
  test_dispatch_implementer.py: writer call site, new_issue
  work_type mapping, cleanup integration with and without a
  context dict, partial-fetch completion-flag propagation.
- 5 new contract tests in test_implementer_pr_context_cli.py:
  the three-case epic contract, work_type dispatch in both
  directions, failed-fetch fall-through.
- 2 new sentinel writer tests in test_pr_context_sentinel.py:
  ``.tmp`` orphan cleanup paths, real ImplementerPrefetchResult
  round-trip (defends against silent-attribute-miss when fields
  are added to the dataclass).
- ``test_workspace_handoff.py`` integration test now asserts NO
  ``git for-each-ref`` invocation (regression guard for the
  dropped helper).

Full auto_agents suite: 1,128 passed, 3 skipped (was 1,123 before).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 23:18:46 -04:00

223 lines
8.1 KiB
Python
Executable File

#!/usr/bin/env python3
"""Worker-side reader for the dispatcher's workspace handoff sentinel.
What this is
------------
The dispatcher pre-clones a PR's head_sha into
``/tmp/cleveragents-implementer-worktrees/pr-{N}-implementer-{tag}/``
and drops a sentinel JSON file alongside it at
``/tmp/cleveragents-implementer-worktrees/.handoff-pr-{N}.json``
describing the worktree. This script is what the implementer worker
calls (via the ``implementer-workspace`` skill) to discover that
worktree without re-cloning.
Why a script + skill instead of carrying the path in the prompt
---------------------------------------------------------------
The dispatcher embeds a ``## Pre-cloned working copy`` section in
the worker's prompt with the worktree path, but the implementer's
``task`` tool chain re-summarises the prompt at every level:
``implementation-worker`` (depth 0) → ``tier-dispatcher`` (depth 1) →
``tier-qwen-med`` (depth 2) → ``task-implementor`` (depth 3). By the
time the chain reaches ``task-implementor``, only ``BEGIN_PR_DIFF``
reliably survives — every other section gets dropped by intermediate
agents' summarisation. A filesystem-mediated handshake is robust to
that summarisation; the script is loaded by ``task-implementor`` via
its skill allowlist and runs deterministically regardless of what
the prompt looks like.
Contract
--------
Subcommand ``discover``:
``python3 tools/implementer_workspace.py discover --pr <N>``
Reads the sentinel at ``/tmp/cleveragents-implementer-worktrees/.handoff-pr-{N}.json``,
validates it (schema version, file exists on disk, worktree dir
still exists), and prints two lines on stdout:
repo_dir=<absolute-path-to-worktree>
branch=<branch-name-or-empty>
If the sentinel is missing, stale, malformed, or points at a
vanished worktree, the script prints
repo_dir=
branch=
and exits with code 0. The worker calling this script checks the
``repo_dir=`` line; if empty, it falls through to its legacy
workflow (``git-isolator-util`` for the implementer).
This script NEVER exits non-zero on a "no handoff available" path
— that's a normal condition, not a failure. Non-zero exit codes
are reserved for usage errors (bad CLI args, schema-version
mismatch with a sentinel we cannot read at all).
Exit codes
----------
- 0: success — output is on stdout. An empty ``repo_dir=`` line
also means "no handoff" and is still exit 0.
- 2: usage error (bad / missing CLI arg, missing subcommand)
- 3: schema-version mismatch (the sentinel exists but its
``schema_version`` differs from this script's expectation; the
worker should NOT proceed with the worktree because the field
semantics may have changed). The worker still falls through to
the legacy workflow.
Allow-rule
----------
This script is whitelisted as
``python3 tools/implementer_workspace.py discover *`` in the agent's
permission allowlist (see :file:`.opencode/agents/task-implementor.md`).
The cleanup of worktrees is the dispatcher's responsibility
(:meth:`tools._pr_clone.WorktreeHandle.cleanup`), not the worker's,
so this script is read-only by design — there is no ``cleanup``
subcommand.
"""
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_clone.WORKSPACE_HANDOFF_SCHEMA_VERSION`.
# Bump in both places when the on-disk shape changes.
EXPECTED_SCHEMA_VERSION = 1
_DEFAULT_WORKTREE_BASE = Path("/tmp/cleveragents-implementer-worktrees")
def _worktree_base() -> Path:
"""Resolve the worktree base. Honours
``IMPLEMENTER_DISPATCHER_WORKTREE_BASE`` so tests don't touch
real ``/tmp`` and so an operator can sandbox a long-running
dispatcher.
"""
return Path(
os.environ.get("IMPLEMENTER_DISPATCHER_WORKTREE_BASE")
or str(_DEFAULT_WORKTREE_BASE)
)
def _handoff_path(pr_number: int) -> Path:
"""Resolve the sentinel path for ``pr_number``. Mirrors
:func:`tools._pr_clone._workspace_handoff_path`."""
return _worktree_base() / f".handoff-pr-{int(pr_number)}.json"
def _load_handoff(pr_number: int) -> tuple[dict[str, Any] | None, str | None]:
"""Read + parse + validate the sentinel.
Returns a tuple of ``(payload, error)``:
- ``(payload, None)`` — sentinel exists, parses, schema matches,
and the referenced ``repo_dir`` is on disk.
- ``(None, None)`` — sentinel does not exist (the dispatcher
didn't pre-clone, or already cleaned up). Normal "no handoff"
condition; the worker falls through to its legacy path.
- ``(None, error)`` — sentinel exists but is unusable (malformed,
schema mismatch, repo_dir vanished). ``error`` is a short
diagnostic emitted on stderr so the worker can log the reason.
"""
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}"
)
repo_dir = payload.get("repo_dir")
if not isinstance(repo_dir, str) or not repo_dir:
return None, f"handoff at {target} has no usable repo_dir"
if not Path(repo_dir).is_dir():
return None, (
f"handoff at {target} points at vanished worktree {repo_dir}"
)
return payload, None
def _cmd_discover(args: argparse.Namespace) -> int:
"""Implement ``discover``. Always prints both lines and exits
0 on the happy "no handoff available" path; exits 3 only if the
sentinel exists but has a schema mismatch (a config issue an
operator must notice). All other "unusable sentinel" cases
treat the absence as "no handoff" and exit 0."""
payload, error = _load_handoff(int(args.pr))
if error:
print(f"warning: {error}", file=sys.stderr)
# Schema mismatch is the one situation the worker should
# NOT silently swallow — different field semantics between
# script and dispatcher could mis-route the worker. Surface
# via exit code while still printing the empty contract
# output so even a worker that ignores exit codes falls back
# safely.
if "schema_version" in error:
print("repo_dir=")
print("branch=")
return 3
print("repo_dir=")
print("branch=")
return 0
if payload is None:
# No handoff exists — the dispatcher didn't pre-clone (e.g.
# preclone gated off, fetch failed, no head_sha yet). Normal
# path; emit the empty contract and exit 0.
print("repo_dir=")
print("branch=")
return 0
print(f"repo_dir={payload['repo_dir']}")
print(f"branch={payload.get('branch') or ''}")
return 0
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(
prog="implementer_workspace",
description=(
"Worker-side reader for the dispatcher's workspace handoff "
"sentinel. See module docstring for the full contract."
),
)
sub = parser.add_subparsers(dest="cmd", required=True)
p_discover = sub.add_parser(
"discover",
help=(
"Read the workspace handoff sentinel for PR N. Prints "
"repo_dir=<path> and branch=<name> on stdout (both empty "
"if no usable handoff). Always exits 0 except on schema "
"mismatch (exit 3)."
),
)
p_discover.add_argument(
"--pr", required=True, type=int, help="PR number"
)
args = parser.parse_args(argv)
if args.cmd == "discover":
return _cmd_discover(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())