init: document-haness 설계
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
"""Optional live runner for the installed technical-doc-flow Claude plugin."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from helpers import ROOT
|
||||
|
||||
|
||||
CLAUDE_BIN = shutil.which("claude")
|
||||
|
||||
|
||||
class LiveSkillError(RuntimeError):
|
||||
"""The external skill run could not produce one verified run."""
|
||||
|
||||
|
||||
def run_live_case(case_dir: Path, *, timeout: int = 300) -> tuple[Path, dict[str, Any]]:
|
||||
if CLAUDE_BIN is None:
|
||||
raise LiveSkillError("claude CLI를 찾을 수 없습니다.")
|
||||
case_dir.mkdir(parents=True, exist_ok=True)
|
||||
brief = case_dir / "brief.md"
|
||||
draft = case_dir / "draft.md"
|
||||
workspace = case_dir / "_workspace"
|
||||
brief.write_text(
|
||||
"이 초안을 HTTP 요청과 응답만 아는 주니어 백엔드 개발자가 따라갈 수 있게 수정하세요. "
|
||||
"논리의 원인→요구→선택→검증→한계 흐름을 만들고, 전문용어는 쉬운 설명 뒤에 소개하세요. "
|
||||
"light revise 경로를 사용하세요.\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
draft.write_text(
|
||||
"# 캐시 복구\n\n"
|
||||
"42ms TTL 만료 뒤 cache stampede가 생기므로 `cacheKey`에 distributed mutex, "
|
||||
"request coalescing, jitter를 적용한다. 자세한 배경은 https://example.test/cache 이다.\n\n"
|
||||
"## 검증\n\nload test가 correctness와 resilience를 증명한다.\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
prompt = (
|
||||
"이 세션에 로드된 technical-doc-flow 스킬을 반드시 활성화해 전체 절차와 결정적 스크립트를 실행하세요. "
|
||||
f"brief={brief}, draft={draft}, workspace={workspace}. "
|
||||
"문서 종류는 explanation이고, 사용자가 light revise를 명시했습니다. "
|
||||
"09_final_report.json verdict가 pass가 아니면 성공이라고 말하지 마세요."
|
||||
)
|
||||
command = [
|
||||
CLAUDE_BIN,
|
||||
"--print",
|
||||
"--no-session-persistence",
|
||||
"--plugin-dir",
|
||||
str(ROOT),
|
||||
"--permission-mode",
|
||||
"acceptEdits",
|
||||
"--allowedTools",
|
||||
"Read,Write,Edit,Bash,Task",
|
||||
prompt,
|
||||
]
|
||||
environment = os.environ.copy()
|
||||
try:
|
||||
completed = subprocess.run(
|
||||
command,
|
||||
cwd=case_dir,
|
||||
stdin=subprocess.DEVNULL,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=timeout,
|
||||
env=environment,
|
||||
check=False,
|
||||
)
|
||||
except subprocess.TimeoutExpired as exc:
|
||||
raise LiveSkillError(f"live 스킬 실행이 {timeout}초를 넘었습니다.") from exc
|
||||
if completed.returncode != 0:
|
||||
diagnostic = (completed.stderr or completed.stdout)[-1000:]
|
||||
raise LiveSkillError(f"claude CLI exit={completed.returncode}: {diagnostic}")
|
||||
|
||||
reports = sorted(workspace.glob("*/09_final_report.json"))
|
||||
if len(reports) != 1:
|
||||
raise LiveSkillError(
|
||||
f"09_final_report.json이 정확히 하나여야 합니다: {[str(path) for path in reports]}"
|
||||
)
|
||||
try:
|
||||
report = json.loads(reports[0].read_text(encoding="utf-8"))
|
||||
except (OSError, UnicodeError, json.JSONDecodeError) as exc:
|
||||
raise LiveSkillError(f"final report를 읽을 수 없습니다: {exc}") from exc
|
||||
if not isinstance(report, dict):
|
||||
raise LiveSkillError("final report 최상위 값이 객체가 아닙니다.")
|
||||
return reports[0].parent, report
|
||||
Reference in New Issue
Block a user