87 lines
4.6 KiB
Python
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")
|