Ctrl K
ComunidadGuías…

butler/tools/delegate_prepare.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"""Delegate phase 1 — task enrichment and depth guard (ENG-2)."""

from __future__ import annotations

import json

from butler.tools.delegate_run_state import DelegateRunState
from butler.tools.delegate_prepare_ops import infer_delegate_category_safe
from butler.delegate.category_resolver import apply_category_to_delegate
from butler.core.handoff import default_visual_acceptance, render_handoff_block
from butler.core.handoff import merge_handoff_into_context
from butler.tools.delegate_prepare_ops import inject_verify_checklist_safe
from butler.delegate.policy import MAX_DELEGATE_DEPTH
from butler.tools.delegate_prepare_ops import inject_production_playbook_bridge_safe
from butler.tools.delegate_role_guard import apply_user_role_override, block_lead_readonly_delegate, user_explicit_delegate_role, _explicit_role_from_state, _turn_user_text


def infer_delegate_category(task: str) -> str:
    """Best-effort category inference from task intent (1a)."""

    return str(infer_delegate_category_safe(task))


def apply_category_resolver(state: DelegateRunState) -> None:
    """Apply category resolver — rewrites role/task/context (1b)."""
    if not str(state.category or "").strip():
        return

    new_role, new_task, new_context, category_meta = apply_category_to_delegate(
        category=str(state.category).strip(),
        role=state.role,
        task=state.task,
        context=state.context,
    )
    state.role = new_role
    state.task = new_task
    state.context = new_context
    state.category_meta = category_meta


def build_handoff_block_text(cat_name: str, role: str, task: str) -> str:
    """Render the handoff markdown for a category (1c, pure)."""

    if cat_name == "ui-build":
        acceptance = default_visual_acceptance()
        evidence_required = ["read_file DESIGN.md", "read_file 改动文件"]
    else:
        acceptance = [
            "任务描述中的目标已达成",
            "关键改动有 read_file 或测试证据",
        ]
        evidence_required = ["read_file 或 pytest"]
    return str(
        render_handoff_block(
            from_role="butler",
            to_role=str(role or "dev"),
            task=task,
            acceptance=acceptance,
            evidence_required=evidence_required,
        )
    )


def inject_handoff_block(state: DelegateRunState) -> None:
    """Inject handoff markdown for nexus / ui-build / first-time (1c)."""

    cat_name = str(
        state.category or state.category_meta.get("category") or ""
    ).strip().lower()
    needs_handoff = (
        cat_name.startswith("nexus")
        or cat_name == "ui-build"
        or "## Handoff" not in str(state.context or "")
    )
    if not needs_handoff:
        return
    handoff = build_handoff_block_text(cat_name, state.role, state.task)
    state.context = merge_handoff_into_context(state.context, handoff)


def inject_verify_checklist(state: DelegateRunState) -> None:
    """Append the delegate verify checklist to context (1d, best-effort)."""

    inject_verify_checklist_safe(state)


def check_delegate_depth(state: DelegateRunState) -> None:
    """Set ``early_return`` when ``depth >= MAX_DELEGATE_DEPTH`` (1f)."""

    if state.depth >= MAX_DELEGATE_DEPTH:
        state.early_return = json.dumps(
            {"error": f"Maximum delegation depth ({MAX_DELEGATE_DEPTH}) exceeded"}
        )


def inject_production_playbook_bridge(state: DelegateRunState) -> None:
    """Prepend B9 seed playbooks for production-shaped dev delegates (P0 bridge)."""

    inject_production_playbook_bridge_safe(state)


def prepare_delegate_task(state: DelegateRunState) -> None:
    """Phase 1: enrich (role, task, context, category) and check depth."""

    blocked = block_lead_readonly_delegate(depth=state.depth)
    if blocked:
        state.early_return = blocked
        return

    pinned = user_explicit_delegate_role(_turn_user_text()) or _explicit_role_from_state(state)
    if pinned:
        state.role = pinned
    elif not str(state.category or "").strip():
        state.category = infer_delegate_category(state.task)
    if not pinned:
        apply_category_resolver(state)
    apply_user_role_override(state)
    inject_handoff_block(state)
    inject_verify_checklist(state)
    inject_production_playbook_bridge(state)
    if state.bridge is not None:
        state.bridge.notify_delegate_start(state.role, preview=state.task[:80])
    check_delegate_depth(state)


__all__ = [
    "apply_category_resolver",
    "build_handoff_block_text",
    "check_delegate_depth",
    "infer_delegate_category",
    "inject_handoff_block",
    "inject_production_playbook_bridge",
    "inject_verify_checklist",
    "prepare_delegate_task",
]