Files
company-haness/.claude/hooks/bench_cascade/probe.py
T

87 lines
4.6 KiB
Python

"""Phase 0 headless probe — 실제 claude -p 로 stage 를 헤드리스 실행할 수 있는지, process 를
넘겨도 원장+artifact 만으로 재개되는지 검증한다. slash 직접 실행이 안 되면 adapter prompt 로 전환.
실제 실행은 CLI 의 `probe --execute` 가 담당(예산·claude CLI 필요). 여기 함수는 순수 로직."""
import os
import subprocess
CLAUDE_CMD = os.environ.get("ORGOS_BENCH_CLAUDE", "claude")
def build_stage_invocation(command_name, command_body, brief_path):
"""stage(command)를 headless 로 실행할 사양을 만든다. command_body 가 순수 slash 지시(첫 줄이
`# /<name>`)면 direct-slash 로 `/<name>` 프롬프트를, 아니면 command 본문을 펼친 adapter 프롬프트를 쓴다."""
first = (command_body.strip().splitlines() or [""])[0].strip()
if first.startswith(f"# /{command_name}") or first == f"/{command_name}":
mode = "direct-slash"
prompt = f"/{command_name}\nbrief: {brief_path}"
else:
mode = "adapter"
prompt = (f"다음 커맨드 절차를 이 brief 로 수행하라.\nbrief: {brief_path}\n\n"
f"--- command: {command_name} ---\n{command_body}")
argv = [CLAUDE_CMD, "-p", prompt, "--dangerously-skip-permissions"]
return {"mode": mode, "prompt": prompt, "argv": argv}
def resume_ok(ledger_before, ledger_after):
"""새 process 가 원장만으로 재개 가능한가 — stage 원장이 전진하고 accepted artifact 가 생겼는가."""
before = set((ledger_before or {}).get("stages", []))
after = set((ledger_after or {}).get("stages", []))
return bool(after - before) and bool((ledger_after or {}).get("accepted"))
def run_probe(arm_commit, out_findings_path, execute=False):
"""실제 headless probe: worktree(arm_commit) → /ground 1회 headless → 종료 → 새 process 원장 재로드
→ resume_ok → PROBE-FINDINGS.md 기록(헤드리스 가능성·adapter 여부·stage별 산출 파일 shape).
execute=False 면 미실행(사양만, worktree 도 만들지 않는다) — 무거운 실행은 이 플래그 뒤에 숨긴다.
실행 절차(execute=True):
1. git worktree add → arm 커밋 부스트랩(runner.setup_worktree)
2. worktree 에서 runner.run_stage(ground) 로 headless 1회 실행(원장+산출물 기록)
3. process 종료(암묵적, exec_fn 이 subprocess 로 격리)
4. stage 결과를 원장 anchor 로 재구성(새 process 가 원장만 보고 재개 가능한지 시뮬레이션)
5. resume_ok 호출로 전진 검증
6. PROBE-FINDINGS.md 에 헤드리스 가능/adapter 여부/stage 산출물 shape 기록
"""
plan = {"arm-commit": arm_commit, "executed": execute,
"worktree": None, "workspace": None, "stage-result": None, "resume-ok": None}
if not execute:
_write_findings(out_findings_path, plan)
return plan
from . import paths, runner
rid = paths.run_id(arm_commit)
worktree = runner.setup_worktree(rid, "probe", arm_commit, paths.ROOT)
workspace = paths.workspace_dir(rid, "probe")
os.makedirs(workspace, exist_ok=True)
env = runner.evidence_env(os.path.join(workspace, "evidence-pack"))
def _exec_fn(argv, cwd, exec_env):
r = subprocess.run(argv, cwd=cwd, env=exec_env, capture_output=True, text=True)
return {"exit-code": r.returncode, "artifacts": [], "transcript": [r.stdout, r.stderr]}
ledger_before = {"stages": [], "accepted": []}
result = runner.run_stage(worktree, workspace, runner.STAGES[0], env, _exec_fn)
# process 종료 후 "새 process" 가 보는 원장 상태 — 이 stage 의 반환값만이 그 process 의 유일한
# 산출 신호이므로, 성공한 stage 만 원장에 전진 기록된 것으로 재구성한다(원장 재로드 시뮬레이션).
advanced = result["exit-code"] == 0
ledger_after = {"stages": [result["stage"]] if advanced else [],
"accepted": [result["stage"]] if advanced else []}
ok = resume_ok(ledger_before, ledger_after)
plan.update({"worktree": worktree, "workspace": workspace, "stage-result": result, "resume-ok": ok})
_write_findings(out_findings_path, plan)
return plan
def _write_findings(path, plan):
os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
lines = ["# PROBE-FINDINGS", "",
f"executed: {plan.get('executed')}",
f"arm-commit: {plan.get('arm-commit')}",
f"resume-ok: {plan.get('resume-ok')}",
f"stage-result: {plan.get('stage-result')}"]
with open(path, "w", encoding="utf-8") as f:
f.write("\n".join(lines) + "\n")