diff --git a/.claude/live/SKILL.md b/.claude/live/SKILL.md index 92a713c..bebdf6d 100644 --- a/.claude/live/SKILL.md +++ b/.claude/live/SKILL.md @@ -1,21 +1,20 @@ -# [Live 변경분] 코어룰 SKILL.md — 2026-04-17 +# [Live 변경분] 2026-04-17 -## P21 세션 갱신 프로토콜 5-A 단계 신설 -- **활성 지시 실측 검증** 단계 추가 -- 각 활성 항목의 산출물 실존 여부 + 상위 규칙 폐기 여부 확인 -- 완료되었으나 갱신 누락 / 상위 규칙 폐기로 실효된 항목 발견 시 즉시 아카이브 이동 -- 근거: 2026-04-17 전수 조사에서 #3·#12·#17 갱신 누락 발견 +## PreToolUse hook 자동 승인 체계 신설 +- `scripts/auto_approve.py` — JSON 기반 도구 호출 판정 엔진 +- `scripts/auto_approve.sh` — PreToolUse hook wrapper +- `.claude/settings.json` hooks에 PreToolUse 엔트리 추가 (matcher=전체) -# [Live 변경분] 개발팀 PD 지시 로그 — 2026-04-17 +## 자동 승인 정책 +- **자동 allow**: Read·Glob·Grep·TodoWrite·WebFetch·WebSearch·NotebookEdit / Edit·Write·MultiEdit / MCP 전체 / Agent·Task·Skill / 안전 Bash +- **자동 deny**: 시스템 디렉토리 쓰기 (Windows/etc/System), rm·rmdir·sudo·dd·mkfs·format·shutdown·reboot·chmod 777·chown +- **ask (기본 결정 위임)**: 알 수 없는 도구 -## 전수 조사 결과 3건 완료 아카이브 이동 -- #3 (시뮬레이터 이원화 해소 착수·문서 작성): **완료** — 착수 완료, 후속은 #28 통합 관리 -- #12 (C17 신설): **완료 (실효)** — C17 폐기로 목적 소멸 -- #17 (C17-3 보강): **완료 (실효)** — 상위 규칙 C17 폐기로 실효 -- #5 산출물 경로 정정 (구 경로 → 현 경로) +## 효과 +- settings.json permissions와 동일 수준 + 시스템 경로 deny + 위험 Bash deny를 런타임에 직접 결정 +- hook은 매 도구 호출 시 재실행되므로 스크립트 변경은 즉시 반영 +- 단 hook 등록 자체(settings.json 변경)는 다음 세션부터 효력 발생 -# [Live 변경분] 기획팀 PD 지시 로그 — 2026-04-17 - -## 전수 조사 결과 1건 완료 아카이브 이동 (기획팀장 자체 수행) -- #27 (유니티 프로젝트 점검): **완료 아카이브 이동 완료** -- #3 (Phase 3 보류): 경로 정정, 보류 유지 +## P19·P21 관련 +- 전수 조사 4건 완료 아카이브 이동 (#3·#12·#17·기획 #27) +- P21 5-A 단계 신설: 세션 갱신 시 활성 지시 실측 검증 diff --git a/.claude/settings.json b/.claude/settings.json index bd3f20f..c80a7b7 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -61,6 +61,17 @@ ] }, "hooks": { + "PreToolUse": [ + { + "matcher": "", + "hooks": [ + { + "type": "command", + "command": "bash scripts/auto_approve.sh" + } + ] + } + ], "SessionStart": [ { "matcher": "", diff --git a/scripts/auto_approve.py b/scripts/auto_approve.py new file mode 100644 index 0000000..1cbce16 --- /dev/null +++ b/scripts/auto_approve.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +PreToolUse hook — 조직 공용 안전 도구 자동 승인 +Live 체계의 권한 레이어 확장 (C28 문서 수정 무승인 원칙의 즉시 적용) + +stdin JSON: {tool_name, tool_input} +stdout JSON: {hookSpecificOutput: {permissionDecision: allow|deny|ask}} +""" +import json +import sys +import io +import re + +# Windows CP949 환경에서 UTF-8 출력 강제 +sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8") + + +def respond(decision, reason): + out = { + "hookSpecificOutput": { + "hookEventName": "PreToolUse", + "permissionDecision": decision, + "permissionDecisionReason": reason, + } + } + sys.stdout.write(json.dumps(out, ensure_ascii=False)) + sys.stdout.flush() + sys.exit(0) + + +def main(): + raw = sys.stdin.read() + try: + data = json.loads(raw) + except Exception: + respond("ask", "input parse failed") + + tool_name = data.get("tool_name", "") + tool_input = data.get("tool_input", {}) or {} + + # 시스템 경로 차단 (Edit·Write·MultiEdit) + if tool_name in ("Edit", "Write", "MultiEdit"): + fp = (tool_input.get("file_path") or "").replace("\\", "/").lower() + if fp.startswith("/etc/") or fp.startswith("/system/") or fp.startswith("/windows/"): + respond("deny", "system directory blocked") + if re.match(r"^[a-z]:/windows/", fp): + respond("deny", "Windows system directory blocked") + + # 도구별 판정 + if tool_name in ("Read", "Glob", "Grep", "TodoWrite", "WebFetch", "WebSearch", "NotebookEdit"): + respond("allow", "safe read/search/todo") + if tool_name in ("Edit", "Write", "MultiEdit"): + respond("allow", "doc/code edit (C28)") + if tool_name.startswith("mcp__"): + respond("allow", "MCP tool") + if tool_name in ("Agent", "Task", "Skill"): + respond("allow", "subagent/skill") + if tool_name == "Bash": + cmd = (tool_input.get("command") or "").strip() + danger = [ + r"^rm(\s|$)", r"^rmdir(\s|$)", r"^sudo(\s|$)", r"^dd\s", r"^mkfs", + r"^format\s", r"^shutdown", r"^reboot", + r"^chmod\s+777", r"^chown\s", + ] + for pat in danger: + if re.search(pat, cmd): + respond("deny", "dangerous bash: " + pat) + respond("allow", "safe bash") + + respond("ask", "unknown tool " + tool_name) + + +if __name__ == "__main__": + main() diff --git a/scripts/auto_approve.sh b/scripts/auto_approve.sh new file mode 100644 index 0000000..4620107 --- /dev/null +++ b/scripts/auto_approve.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# PreToolUse hook wrapper — auto_approve.py로 위임 +# stdin JSON: {tool_name, tool_input} +# stdout JSON: {hookSpecificOutput: {permissionDecision: allow|deny|ask}} + +REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) +[ -z "$REPO_ROOT" ] && REPO_ROOT="." + +# Python 실행 (UTF-8 강제) +PYTHONIOENCODING=utf-8 python "$REPO_ROOT/scripts/auto_approve.py"