Ctrl K
ComunidadGuías…

scripts/butler-coding-strict-pilot-multi.sh

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

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env bash
# G3 multi-category pilot — extend Phase B 单类 (deep) 覆盖到 3 个 PROD_PLAYBOOK 类别
# 3 categories × 2 cases = 6 pilots
#   Case B-viol (fixture with violations): true-positive capture rate per category
#   Case B-clean (fixture with no violations): false-positive baseline per category
# Plus 1 butler exec smoke: verify real LLM provider path is functional in this env.
#
# 与 scripts/butler-coding-strict-pilot.sh 区别:
#   - 单类 (deep) → 3 类 (quick / deep / lingwen-drill) 结构性多样性
#   - 加 Case B-clean (no violations) → 验 false-positive baseline
#
# 阈值:违例捕获率 ≥85%(spec §4 阶段5)
# 假阳性率目标:0 / 类(gate 在无违例时必须 pass)
set -uo pipefail

WFXM_HOME="${BUTFXM_HOME:-/home/ailearn/projects/WFXM}"
PYTHON_BIN="${PYTHON_BIN:-/home/ailearn/miniconda3/bin/python3}"
BUTLER_BIN="${BUTLER_BIN:-/home/ailearn/miniconda3/bin/butler}"

DATE="$(date -u +"%Y-%m-%d")"
TS="$(date -u +"%Y-%m-%dT%H-%M-%SZ")"
REPORT_DIR="$WFXM_HOME/docs/plans/pilot-reports"
REPORT="$REPORT_DIR/pilot-report-G3-$DATE-001.md"
TMPDIR_PREFIX="/tmp/pilot-g3-${DATE}-001"
mkdir -p "$REPORT_DIR"
rm -f "$TMPDIR_PREFIX"-*.json 2>/dev/null || true

cd "$WFXM_HOME"

cleanup_strict() {
    "$WFXM_HOME/scripts/butler-coding-strict-opt-in.sh" off >/dev/null 2>&1 || true
}
trap cleanup_strict EXIT
trap 'exit 130' INT
trap 'exit 143' TERM

# 类别形塑:类别名 → fixture violated 集合 + 真任务 preview
declare -A FIXTURE_VIOLATIONS=(
    [quick]="T2"
    [deep]="CA4,T8"
    [lingwen-drill]="T7,T8"
)
declare -A FIXTURE_PREVIEW=(
    [quick]="inspect repo modules (quick shallow)"
    [deep]="refactor module X with CA4 invariants"
    [lingwen-drill]="lingwen-drill patch-only idempotent fix"
)

CATEGORIES=(quick deep lingwen-drill)
RESULTS=()

echo "[g3-multi] enabling strict mode via opt-in..."
"$WFXM_HOME/scripts/butler-coding-strict-opt-in.sh" on >/dev/null 2>&1 || true

for cat in "${CATEGORIES[@]}"; do
    violations="${FIXTURE_VIOLATIONS[$cat]}"
    preview="${FIXTURE_PREVIEW[$cat]}"
    json="$TMPDIR_PREFIX-$cat.json"

    echo ""
    echo "=== category: $cat ==="
    echo "  violated fixture: [$violations]"
    echo "  task preview: $preview"

    # ---------- Case B-viol: violations 注入 → 期望 gate 阻断 ----------
    set +e
    out_v=$(BUTLER_CODING_STRICT=1 PILOT_VIOLATIONS="$violations" "$PYTHON_BIN" -c "
import json, os
from butler.tools.delegate_impl import apply_delegate_success_gates

violations_raw = os.environ['PILOT_VIOLATIONS']
violated = [v.strip() for v in violations_raw.split(',') if v.strip()]

dev_engine = {
    'coding_knowledge': {
        'mode': 'theorem_only',
        'violated_theorems': violated,
    },
    'verify_output_tail': 'verify passed (auto-verify covers unit/integration only)',
    'edits': 1,
    'verify_passed': True,
}

ok, issues = apply_delegate_success_gates(
    base=True, issues=[], category='$cat',
    category_meta=None, project=None, role='dev',
    dev_engine=dev_engine,
    task='$preview', task_preview='$preview',
    changes=[], messages=[],
    summary='auto-verify pass; theorem violation remains',
)
print(json.dumps({'ok': ok, 'issues': issues, 'violated': violated}, ensure_ascii=False))
" 2>&1)
    rc_v=$?
    set -e

    # ---------- Case B-clean: 无 violations → 期望 gate 不阻断 ----------
    set +e
    out_c=$(BUTLER_CODING_STRICT=1 "$PYTHON_BIN" -c "
import json, os
from butler.tools.delegate_impl import apply_delegate_success_gates

dev_engine = {
    'coding_knowledge': {
        'mode': 'theorem_only',
        'violated_theorems': [],
    },
    'verify_output_tail': 'verify passed (clean run, no theorem violations)',
    'edits': 1,
    'verify_passed': True,
}

ok, issues = apply_delegate_success_gates(
    base=True, issues=[], category='$cat',
    category_meta=None, project=None, role='dev',
    dev_engine=dev_engine,
    task='$preview', task_preview='$preview',
    changes=[], messages=[],
    summary='clean run; no theorem violation',
)
print(json.dumps({'ok': ok, 'issues': issues, 'violated': []}, ensure_ascii=False))
" 2>&1)
    rc_c=$?
    set -e

    # ---------- 解析 B-viol ----------
    p_v=$(printf '%s\n' "$out_v" | tail -1 | "$PYTHON_BIN" -c "
import sys, json
try:
    d = json.loads(sys.stdin.read().strip())
    print(json.dumps({
        'ok': d.get('ok'),
        'issues': d.get('issues') or [],
        'violated': sorted(set(d.get('violated') or [])),
    }, ensure_ascii=False))
except Exception as e:
    print(json.dumps({'ok': None, 'issues': ['PARSE_ERROR: ' + str(e)], 'violated': []}, ensure_ascii=False))
")
    ok_v=$(printf '%s\n' "$p_v" | "$PYTHON_BIN" -c "import sys,json; print(json.load(sys.stdin).get('ok'))")
    issues_v=$(printf '%s\n' "$p_v" | "$PYTHON_BIN" -c "import sys,json; print('\n'.join(json.load(sys.stdin).get('issues') or []))")
    violated_set=$(printf '%s\n' "$p_v" | "$PYTHON_BIN" -c "import sys,json; print(','.join(json.load(sys.stdin).get('violated') or []))")

    gate_blocked_v=$(printf '%s\n' "$issues_v" | grep -oE "CODING_STRICT_GATE[^{]*\(([^)]+)\)" | grep -oE "\([^)]+\)" | tr -d '()' | tr ',' '\n' | tr -d ' ' | sort -u | paste -sd, - || true)
    gate_blocked_v="${gate_blocked_v:-}"
    violated_set="${violated_set:-}"

    # capture rate
    captured_count=0
    violated_count=0
    if [ -n "$violated_set" ]; then
        violated_count=$(echo "$violated_set" | tr ',' '\n' | grep -c .)
        for id in $(echo "$violated_set" | tr ',' '\n'); do
            [ -z "$id" ] && continue
            if echo ",$gate_blocked_v," | grep -q ",$id,"; then
                captured_count=$((captured_count + 1))
            fi
        done
    fi
    if [ "$violated_count" -gt 0 ]; then
        capture_rate=$(awk -v c="$captured_count" -v n="$violated_count" 'BEGIN{printf "%.0f%%", (c/n)*100}')
    else
        capture_rate="N/A"
    fi

    # verdict (viol case)
    if [ "$ok_v" = "True" ]; then
        verdict_v="MISS"
    elif [ "$ok_v" = "False" ] && [ "$captured_count" -eq "$violated_count" ]; then
        verdict_v="MATCH"
    elif [ "$ok_v" = "False" ] && [ "$captured_count" -gt 0 ]; then
        verdict_v="PARTIAL"
    else
        verdict_v="UNKNOWN"
    fi

    # ---------- 解析 B-clean ----------
    p_c=$(printf '%s\n' "$out_c" | tail -1 | "$PYTHON_BIN" -c "
import sys, json
try:
    d = json.loads(sys.stdin.read().strip())
    print(json.dumps({'ok': d.get('ok'), 'issues': d.get('issues') or []}, ensure_ascii=False))
except Exception as e:
    print(json.dumps({'ok': None, 'issues': ['PARSE_ERROR: ' + str(e)]}, ensure_ascii=False))
")
    ok_c=$(printf '%s\n' "$p_c" | "$PYTHON_BIN" -c "import sys,json; print(json.load(sys.stdin).get('ok'))")
    issues_c=$(printf '%s\n' "$p_c" | "$PYTHON_BIN" -c "import sys,json; print('\n'.join(json.load(sys.stdin).get('issues') or []))")

    has_csgate_c="no"
    if echo "$issues_c" | grep -q "CODING_STRICT_GATE"; then
        has_csgate_c="yes"
    fi
    # false positive = gate fired even with no violations
    if [ "$ok_c" = "True" ] && [ "$has_csgate_c" = "no" ]; then
        verdict_c="CLEAN_PASS"
    elif [ "$ok_c" = "False" ] && [ "$has_csgate_c" = "yes" ]; then
        verdict_c="FALSE_POSITIVE"
    else
        verdict_c="OTHER_BLOCKED"
    fi

    cat <<EOF > "$json"
{
  "category": "$cat",
  "case_viol": {
    "violated_fixture": "$violated_set",
    "gate_blocked": "$gate_blocked_v",
    "captured_count": $captured_count,
    "violated_count": $violated_count,
    "capture_rate": "$capture_rate",
    "verdict": "$verdict_v",
    "ok": "$ok_v",
    "rc": $rc_v
  },
  "case_clean": {
    "ok": "$ok_c",
    "has_csgate": "$has_csgate_c",
    "verdict": "$verdict_c",
    "rc": $rc_c
  }
}
EOF
    RESULTS+=("$cat $verdict_v $capture_rate $verdict_c")
    echo "  result: viol verdict=$verdict_v capture_rate=$capture_rate | clean verdict=$verdict_c"
done

# ---------- butler exec smoke ----------
echo ""
echo "=== butler exec smoke (real LLM provider path) ==="
SMOKE_JSON="$TMPDIR_PREFIX-smoke.json"
SMOKE_OUT_FILE="$TMPDIR_PREFIX-smoke.out"
set +e
timeout 60 "$BUTLER_BIN" exec "List the file paths under docs/plans/pilot-reports/ and report count. Do not edit any files." > "$SMOKE_OUT_FILE" 2>&1
smoke_rc=$?
set -e
smoke_has_csgate="no"
grep -q "CODING_STRICT_GATE" "$SMOKE_OUT_FILE" 2>/dev/null && smoke_has_csgate="yes"
# this is butler agent loop, not delegate — gate irrelevant for chat responses
# result is informational: confirms LLM provider is reachable
cat <<EOF > "$SMOKE_JSON"
{
  "command": "butler exec <no-edit list task>",
  "rc": $smoke_rc,
  "has_csgate_in_output": "$smoke_has_csgate",
  "note": "butler exec does not auto-invoke delegate_task for chat-style requests; output goes through butler agent loop, not the 4-gate chain. Evidence: real LLM provider reachable but delegate not auto-fired."
}
EOF
echo "  result: butler exec rc=$smoke_rc (informational; not part of capture-rate computation)"

# ---------- 写报告 ----------
{
    echo "# G3 Multi-Category Pilot Report (Batch 001)"
    echo ""
    echo "> **Date**: $DATE"
    echo "> **Trigger**: G2-08 BUTLER_CODING_STRICT 默认升级 defer 决策(详见 \`docs/plans/decisions/butler-coding-strict-default-decision-2026-07-14.md\`)"
    echo "> **Goal**: 累计 ≥3 任务类型 pilot run(触发升级条件之一),覆盖结构性多样性类别。"
    echo "> **Stack**: 3 categories × 2 cases (viol + clean) = 6 fixture pilots + 1 butler exec smoke"
    echo ""
    echo "## 类别选择"
    echo ""
    echo "| 类别 | iterations | role | 工具限制 | 用途 |"
    echo "|---|---|---|---|---|"
    echo "| \`quick\` | 12 | dev | deny delegate_task / run_workflow | 短浅层 inspect 类任务 |"
    echo "| \`deep\` | 24 | dev | 无 | 标准实现 |"
    echo "| \`lingwen-drill\` | 24 | dev | deny write_file / delete_file | patch-only 强行约束 |"
    echo ""
    echo "三轴最大多样性:迭代范围、无政策 vs 有政策、写入限制;且三都属 \`PROD_PLAYBOOK_CATEGORIES\`(strict gate 实际触发范围)。"
    echo ""
    echo "## Per-Category 实证"
    echo ""
    for cat in "${CATEGORIES[@]}"; do
        cat_json="$TMPDIR_PREFIX-$cat.json"
        if [ -f "$cat_json" ]; then
            echo "### $cat"
            echo ""
            echo '```json'
            cat "$cat_json"
            echo '```'
            echo ""
        fi
    done
    echo "## Capture Rate 总览"
    echo ""
    echo "| Category | Viol verdict | Capture rate | Clean verdict | False positive? |"
    echo "|---|---|---|---|---|"
    for r in "${RESULTS[@]}"; do
        c=$(echo "$r" | awk '{print $1}')
        v=$(echo "$r" | awk '{print $2}')
        cap=$(echo "$r" | awk '{print $3}')
        cln=$(echo "$r" | awk '{print $4}')
        if [ "$cln" = "FALSE_POSITIVE" ]; then
            fp="YES ❌"
        else
            fp="no ✅"
        fi
        echo "| \`$c\` | $v | $cap | $cln | $fp |"
    done
    echo ""
    echo "## Butler exec smoke"
    echo ""
    echo '```json'
    cat "$SMOKE_JSON"
    echo '```'
    echo ""
    echo "## Verdict"
    echo ""
    all_match=true
    any_fp=false
    for r in "${RESULTS[@]}"; do
        v=$(echo "$r" | awk '{print $2}')
        cln=$(echo "$r" | awk '{print $4}')
        if [ "$v" != "MATCH" ]; then
            all_match=false
        fi
        if [ "$cln" = "FALSE_POSITIVE" ]; then
            any_fp=true
        fi
    done
    if $all_match && ! $any_fp; then
        echo "**3/3 categories MATCH + 0 false positive** — G3 升级触发条件之一满足。"
    elif $all_match; then
        echo "**Partial: capture MATCH 但有 false positive** — 不满足升级条件,需 root cause。"
    else
        echo "**Non-MATCH:** 部分类别 capture rate < 85% — 见上表。"
    fi
    echo ""
    echo "## 说明与限制"
    echo ""
    echo "- **Fixture-based 而非真 subagent terminal**:直接调 \`_tool_delegate_task\` 因循环导入(\`memory.diagnostics\`)在本 fresh-python env 无法走通;\`butler exec\` 走的是 butler agent loop(直接用 list_directory 等),不自动 invoke delegate_task。本批采用 category-shaped fixture + 真实 4-gate chain 端到端跑通(与 Phase B 同口径)。"
    echo "- **butler exec smoke** 仅验证 LLM provider 路径可达;不计入 capture rate 计算。"
    echo "- **真 per-category delegate 实证** 留待后续 shift:可通过构造强制 invoke delegate_task 的 test harness(绕过 circular import)实现。"
    echo ""
    echo "## 关联"
    echo ""
    echo "- 决策文档:\`docs/plans/decisions/butler-coding-strict-default-decision-2026-07-14.md\`"
    echo "- 单一类 pilot runner:\`scripts/butler-coding-strict-pilot.sh\`(深类,Phase B 已跑)"
    echo "- 4-gate 链入口:\`butler/tools/delegate_impl.py:281\`(\`apply_delegate_success_gates\`)"
    echo "- PROD_PLAYBOOK 类别:\`butler/dev_engine/prod_delegate_bridge.py:30-38\`"
    echo "- 类别定义:\`butler/delegate/delegate_categories.yaml\`"
} > "$REPORT"

echo ""
echo "[g3-multi] report written to $REPORT"

# ---------- 关闭严格 ----------
echo "[g3-multi] disabling strict mode..."
cleanup_strict
trap - EXIT INT TERM

exit 0