chore(harness): Claude Code 하네스 구성 — 안전 가드·규칙 검사·배포 스킬
- .claude/settings.json (공유) 신설, hooks 를 settings.local.json 에서 이동 - bash_guard (PreToolUse): 로컬 스택 기동·운영 DB alembic·_test 아닌 pytest·main 강제 push·위험 rm 차단, main push/deploy.sh 는 장중 경고와 함께 확인 요청, backend 커밋 전 유닛 스위트 게이트 - rules_check (PostToolUse): py_compile + CLAUDE.md 규칙(json.dumps, except 후 logger.debug, except-pass, 실시간 계층 DB I/O) — diff 추가 라인만 - session_context (SessionStart): 브랜치·미커밋·장 상태·alembic head 주입 - stop_check (Stop): frontend 변경 시 tsc - auto-orchestrate: 서브에이전트 보고·시스템 알림·질문형 프롬프트 오발동 제거 - /deploy 스킬 + deploy.sh / prod_smoke.sh: 테스트→dev push→main 병합→재기동 감시→공개 API 스모크 - 팀 에이전트·orchestrate 스킬 추적 시작, settings.local.json 추적 해제(.gitignore) - CLAUDE.md §9 하네스 - 훅 테스트 69 passed Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
57
.claude/hooks/stop_check.py
Executable file
57
.claude/hooks/stop_check.py
Executable file
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Stop — frontend/src 미커밋 변경이 직전 tsc 통과 이후 바뀌었으면 npx tsc --noEmit (90초)."""
|
||||
import hashlib
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from _lib import git, project_dir, read_stdin_json # noqa: E402
|
||||
|
||||
|
||||
def main() -> int:
|
||||
data = read_stdin_json()
|
||||
if data.get("stop_hook_active"):
|
||||
return 0
|
||||
root = project_dir(data)
|
||||
_, st = git(["status", "--porcelain", "--", "frontend/src"], root)
|
||||
files = [l[3:].strip() for l in st.splitlines() if l.strip()]
|
||||
if not files:
|
||||
return 0
|
||||
h = hashlib.sha1()
|
||||
for f in sorted(files):
|
||||
p = os.path.join(root, f)
|
||||
try:
|
||||
h.update(f.encode() + str(os.stat(p).st_mtime_ns).encode())
|
||||
except OSError:
|
||||
h.update(f.encode() + b"deleted")
|
||||
digest = h.hexdigest()
|
||||
stamp = os.path.join(os.environ.get("HARNESS_STATE_DIR", os.path.join(root, ".claude")), ".tsc_stamp")
|
||||
try:
|
||||
if open(stamp).read().strip() == digest:
|
||||
return 0
|
||||
except OSError:
|
||||
pass
|
||||
fe = os.path.join(root, "frontend")
|
||||
cmd = os.environ.get("HARNESS_TSC_CMD")
|
||||
argv = ["bash", "-c", cmd] if cmd else ["npx", "--no-install", "tsc", "--noEmit"]
|
||||
try:
|
||||
p = subprocess.run(argv, cwd=fe, capture_output=True, text=True, timeout=90)
|
||||
except subprocess.TimeoutExpired:
|
||||
sys.stderr.write("[harness] tsc 90초 초과 — 건너뜀\n")
|
||||
return 0
|
||||
except Exception:
|
||||
return 0
|
||||
if p.returncode != 0:
|
||||
tail = "\n".join((p.stdout + p.stderr).strip().splitlines()[:30])
|
||||
sys.stderr.write(f"[harness] frontend 타입 체크 실패(tsc --noEmit). 수정 후 마치라:\n{tail}\n")
|
||||
return 2
|
||||
try:
|
||||
open(stamp, "w").write(digest)
|
||||
except OSError:
|
||||
pass
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user