처음이라면 기본 과정부터
첫 기록부터 직접 수정과 복원까지 따라가 보세요. 한 가지 설정을 바꾸고, 효과를 확인하고, 다시 끄는 방법을 익힙니다.
CURSOR AGENT 직접 실습
미리 준비할 프로젝트나 문서는 없습니다. 빈 폴더에서 시작해 필요한 파일을 함께 만들고, 훅(Hooks), 규칙(Rules), Skill을 하나씩 설정하며 같은 요청의 결과가 어떻게 달라지는지 확인합니다.
각 단계에서 결과를 먼저 예상하고, 실행한 뒤 코드와 기록으로 이유를 설명해 봅니다. 각 단원 끝의 “왜 이렇게 동작할까요?”를 펼치면 판단 조건과 입력·출력을 볼 수 있습니다. 시간 표시는 실행 기준이며, 원리 읽기와 확인 질문은 자기 속도에 맞춰 진행하세요.
첫 기록부터 직접 수정과 복원까지 따라가 보세요. 한 가지 설정을 바꾸고, 효과를 확인하고, 다시 끄는 방법을 익힙니다.
커밋 제한, 라우팅, 세션 분석, Skill은 필요한 것만 선택해도 됩니다. 기본 과정 뒤에는 자기 프로젝트에 설정 하나를 옮기는 안내도 준비했습니다.
시작하기 · 전체 흐름
학습 목표실행을 기록하는 Hook, 행동을 안내하는 Rule, 필요할 때 불러오는 Skill의 역할을 구분하고, 무엇을 바꿔 무엇을 관찰할지 알아봅니다.
Cursor Agent는 요청을 받고 도구를 골라 실행합니다. 이 실습은 그 앞뒤에 프로젝트 파일을 연결해 동작을 기록하거나 막고, 같은 입력이 어떤 길로 실행됐는지 실제 결과 파일로 확인합니다.
따라가는 순서: 개념과 목표 → 준비 명령 → 바뀐 파일 확인 → Agent 실행 → 결과 확인 → 하단의 원리 설명입니다. 청록색 “입력 · 터미널”은 직접 실행할 명령, 노란색 “입력 · Cursor Agent”는 채팅에 넣을 요청, 초록 점선 “읽기 전용”은 실행하지 않고 읽을 구조·예시입니다.
에이전트가 읽고 따르는 프로젝트 지시입니다. 행동을 유도하지만 실행 자체를 강제로 막지는 않습니다.
도구 실행 전이나 후에 자동으로 불리는 로컬 프로그램입니다. 기록하거나 허용·거부를 반환할 수 있습니다.
사용할 때 불러오는 재사용 지시 묶음입니다. 새 에이전트도, 강한 권한도 아닙니다.
요청이 직접 파일 읽기로 가는지, 정해진 프로그램을 거치는지 보여 주는 순서입니다.
모델은 지시를 읽고 다음 도구를 고릅니다. Cursor는 그 도구를 실행하기 전에 등록된 훅을 호출합니다. Rule과 Skill은 모델이 무엇을 할지에 영향을 주고, 실행 전 훅은 요청된 도구를 허용할지 판단합니다. 이 부품들을 연결한 실행 환경을 여기서는 하네스라고 부릅니다.
사용자 요청 + Rule / 불러온 Skill
→ 모델이 도구 호출을 요청
→ Cursor가 실행 전 훅을 호출
├─ allow → 도구 실행 → 실행 후 기록 → 모델의 다음 행동
└─ deny → 실행 차단 → 거부 응답 → 모델의 다음 행동phase=attempt는 실행 전 시도, deny는 거부, fail은 실패 이벤트입니다. allow는 기록을 남긴 훅과 시점을 함께 봅니다. 기록 훅의 post/after 이벤트인지, R3 정책 훅의 실행 전 허용인지에 따라 뜻이 다릅니다. 어느 쪽도 파일의 최종 내용까지 증명하지는 않습니다.
.cursor-harness-kit/files/deny_write.py는 다시 설치할 기준 원본이고, .cursor/hooks/cursor-harness-lab-deny-write.py는 Cursor가 실제 호출하는 활성 복사본입니다. 뒤에서 활성 복사본만 직접 고칩니다.
모든 작업은 열린 프로젝트 안에서만 이뤄집니다. 전역 설정, 네트워크, 서브에이전트, 자동 커밋은 사용하지 않습니다. 각 고정 비교 전에는 새 Agent 대화를 열어 이전 대화의 영향을 줄입니다.
훅은 모델 안에서 실행되는 코드가 아닙니다. Cursor가 별도 프로그램으로 실행하고 응답을 읽습니다. 거부 메시지를 받은 모델이 다음 행동을 바꿀 수는 있지만, 훅이 문장을 이해해 새 답변을 만드는 것은 아닙니다.
라우팅도 새 모델을 만드는 기능이 아닙니다. 같은 요청에서 모델이 문서를 직접 검토할지, 프로그램이 검사하고 모델이 결과를 설명할지 처리 순서를 정하는 것입니다.
먼저 생각해 보세요. “파일을 수정하지 마세요”라는 Rule이 있는데 수정 요청이 나왔다면, 실행을 실제로 멈추는 부품은 무엇일까요?
수정 요청을 검사하는 실행 전 훅입니다. Rule은 도구를 고르기 전의 지시이고, 훅의 deny는 도구 요청 뒤의 실행 결정입니다.
시작하기 · 작업 공간
자기 프로젝트를 가져오거나 README를 미리 만들 필요가 없습니다. 이 단계에서 연습 공간을 열고, 다음 단계에서 예제 문서와 실습 도구를 함께 설치합니다.
학습 목표열린 프로젝트와 터미널 위치를 맞추고, 이후 명령의 기준이 되는 PROJECT_ROOT를 직접 설정합니다.
Cursor가 없다면 공식 다운로드에서 설치하고 로그인합니다. Finder 또는 파일 탐색기에서 원하는 위치에 cursor-harness-practice라는 새 빈 폴더를 만드세요. 기존 프로젝트 안이 아닌 별도 위치를 선택합니다.
Cursor의 File → Open Folder로 방금 만든 폴더를 엽니다. 신뢰 여부를 묻는 창이 뜨면 직접 만든 폴더를 신뢰합니다. 이제부터 이 폴더를 프로젝트 루트라고 부릅니다.
Cursor의 Terminal → New Terminal을 엽니다. macOS는 기본 zsh, Linux는 Bash를 사용합니다. Windows에서는 Git for Windows를 설치하고 터미널의 프로필 선택에서 Git Bash를 고릅니다. 아래 명령은 PowerShell 문법이 아닙니다.
python3 --version
Python 3.x.x가 나오면 다음으로 갑니다. 명령이 없다면 Python 3 설치 안내를 따라 설치합니다. Windows 설치 화면에서는 Add Python to PATH를 선택하세요. Linux에서는 배포판의 패키지 관리자로 python3를 설치합니다. 설치 뒤 Cursor를 다시 열어 같은 명령을 확인하세요.
터미널이 방금 연 cursor-harness-practice 폴더를 가리키는지 확인하고 실행합니다. PROJECT_ROOT는 이후 명령이 같은 폴더에서 실행되도록 기억해 두는 셸 변수입니다.
export PROJECT_ROOT="$PWD"
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit새 터미널에서는 변수가 이어지지 않습니다. 실습 폴더에서 위 명령을 다시 실행하세요.
export PROJECT_ROOT="$PWD"는 현재 터미널의 작업 경로를 셸 변수에 저장합니다. 이어지는 cd는 그 위치로 이동합니다. 이 변수가 Cursor의 프로젝트를 바꾸거나 훅을 켜지는 않습니다.
훅은 Cursor가 보낸 workspace_roots의 첫 경로를 쓰고, 없으면 실행 중인 작업 폴더를 씁니다. 반면 prepare.py는 설치된 키트의 상위 폴더를 기준으로 삼습니다. 각각 같은 실습 폴더를 가리키게 준비합니다.
먼저 생각해 보세요. 새 터미널에서 변수가 없다는 오류가 나면 키트를 다시 설치해야 할까요?
아니요. 실습 폴더에서 변수를 다시 설정하면 됩니다. 디스크에 설치된 파일과 터미널에만 저장된 변수는 서로 다른 상태입니다.
시작하기 · 키트 설치
다운로드와 직접 만들기 중 하나만 따라 하세요. 두 방법 모두 실습 도구와 예제 README를 준비합니다. 설치가 끝난 뒤 구조도를 확인하고, S1에서 기록 훅을 처음 켜봅니다.
학습 목표설치가 만드는 원본 파일과 다음 단계에서 연결할 활성 훅을 구분합니다. README와 키트를 확인한 뒤 Hook 실습으로 넘어갑니다.
지금은 기본값 Git으로 두어도 됩니다. 이 선택은 나중에 S3~S5 커밋 실습에서 사용할 도구를 정할 뿐, 저장소를 만들거나 검사하지 않습니다. 선택은 lab-results/vcs.txt에 기록됩니다.
파일을 받은 뒤 cursor-harness-kit.tar를 실습할 프로젝트 루트에 놓아 주세요. 아래 명령은 그 폴더에서 바로 압축을 풀고 설치합니다. 브라우저의 다운로드 폴더를 자동으로 찾지 않습니다.
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 -m tarfile -e cursor-harness-kit.tar . || exit
python3 cursor-harness-kit/install.py다운로드한 cursor-harness-kit.tar를 먼저 프로젝트 루트로 옮겨 주세요. 위 명령은 그 자리에 cursor-harness-kit/을 풀고, 실행용 파일을 .cursor-harness-kit/에 설치합니다. 임시 폴더나 기본 다운로드 경로는 사용하지 않습니다.
프로젝트에 이미 cursor-harness-kit/ 폴더가 있다면 기존 내용을 보존할 수 있도록 이름을 바꿔 둔 뒤 새 압축을 풀어 주세요. 설치된 .cursor-harness-kit/와 실습 결과는 지우지 않습니다.
새 압축을 프로젝트 루트에 푼 다음 아래 명령을 실행하세요. 기존 실행용 키트 전체를 lab-results/kit-backups/kit-001/부터 순서대로 보관하고 새 버전을 설치합니다. 직접 수정한 내용과 추가 파일도 백업에 남습니다. 현재 Cursor 설정과 기존 실습 결과는 바꾸지 않습니다.
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 cursor-harness-kit/install.py --replace갱신한 뒤에는 진행하려던 준비 명령을 다시 실행하세요. 예를 들어 첫 단계라면 python3 .cursor-harness-kit/prepare.py s1 git입니다. 커밋 차단 수정본을 적용하려면 Git은 python3 .cursor-harness-kit/prepare.py s4 git, SVN은 마지막 인자를 svn으로 바꿔 실행합니다. 키트 갱신만으로는 활성 훅이 바뀌지 않습니다. 다시 준비하면 훅·공통 분석기·보고서가 함께 갱신되고, 기존 같은 시나리오 기록과 수정한 활성 파일은 lab-results/archive/에 보관됩니다.
페이지에 들어 있는 같은 원본을 한 번의 로컬 명령으로 만듭니다. 실행 전에 전체 보기를 눌러 내용을 확인할 수 있습니다.
이전 키트와 내용이 달라 설치 충돌이 난다면, Cursor 파일 목록에서 기존 .cursor-harness-kit/ 폴더의 이름을 바꿔 보관한 뒤 실행하세요. .cursor/ 설정과 lab-results/ 기록은 건드리지 않습니다. 다운로드 방식의 갱신 명령을 사용하면 이 백업을 자동으로 만들 수도 있습니다.
터미널에 키트 설치 완료와 구조도가 출력되면 Cursor의 파일 목록과 비교해 보세요. 아래는 두 방법이 공통으로 만드는 실행용 파일 구조입니다.
cursor-harness-practice/ ← PROJECT_ROOT
├── README.md ← S1·S2에서 읽고 수정할 예제
└── .cursor-harness-kit/ ← 실습 도구 원본
├── install.py ← 설치와 README 준비
├── prepare.py ← 단계별 설정 켜기
├── manage.py ← 설정 확인·끄기·복원
└── files/ ← 훅·규칙·문서·분석 예제
├── starter-README.md ← README의 처음 내용
├── observe.py ← 실행 기록 훅 원본
├── deny_write.py ← 수정 거부 훅 원본
├── cursor_harness_lab_shell.py ← 셸 명령 공통 분석기
└── ... ← 전체 목록은 아래에서 확인다운로드 방식에는 cursor-harness-kit.tar와 압축을 푼 cursor-harness-kit/도 남습니다. 이후 명령은 점(.)으로 시작하는 .cursor-harness-kit/을 사용합니다.
지금은 README와 도구 원본만 준비했습니다. .cursor/hooks.json, 활성 훅, lab-results/는 아직 없습니다. 다음 S1 준비 명령을 실행하면 생기며, 그때부터 Cursor의 동작을 기록합니다.
프로젝트 루트의 README.md를 열어 보세요. Cursor 하네스 실습이라는 제목과 S1·S2에서 비교할 내용이 들어 있습니다. 별도의 README를 찾거나 작성할 필요가 없습니다. 재설치할 때는 이미 있는 README의 내용을 보존합니다.
두 설치 방법이 함께 사용하는 정식 파일 목록입니다. 각 원본도 아래에서 따로 펼쳐 복사할 수 있습니다.
| 키트 경로 | 하는 일 | 언제 실행 | 효과 | 결과 |
|---|
README.md가 있고 내용을 열어 봤습니다..cursor-harness-kit/prepare.py, manage.py, files/가 있습니다..cursor/와 lab-results/가 없습니다. 다음 단계에서 만듭니다.install.py가 원본을 .cursor-harness-kit/에 두고, README가 없을 때 예제를 만듭니다.prepare.py s1 …가 원본을 활성 경로로 복사하고 .cursor/hooks.json에 이벤트와 실행 명령을 등록합니다.다운로드와 직접 만들기는 이 페이지의 같은 파일 목록에서 만들어집니다. 설치 방법이 달라도 활성화하는 원본은 같습니다. 전체 파일 목록에서 install.py, prepare.py, files/observe.py의 역할을 구분해 보세요.
먼저 생각해 보세요. Python 파일만 .cursor/hooks/에 복사하면 자동으로 호출될까요?
아니요. hooks.json에 어떤 이벤트에서 어떤 명령을 실행할지 등록해야 합니다. 설치 완료와 훅 연결 완료를 따로 확인하세요.
01 · Hook 기본 / S1
먼저 비교 기준을 만듭니다. 준비 명령은 이전 실습 훅만 바꾸고 사용자 훅과 규칙은 남긴 채 기록 훅을 활성화합니다.
Hook(훅)은 Cursor가 도구 실행 전이나 후에 자동으로 호출하는 로컬 프로그램입니다. .cursor/hooks.json이 언제 어떤 프로그램을 부를지 연결합니다.
먼저 기록만 남기고(S1), 수정 요청을 막은 뒤(S2), 조건을 직접 고치고 끄고 복원합니다. Python 파일의 존재와 실제 호출 연결을 구분하는 것이 이 과정의 핵심입니다.
학습 목표hooks.json에 기록 훅을 연결하고, 도구 요청 전후에 남은 로그를 읽습니다. 차단 없이 관찰한 결과가 다음 실험의 비교 기준이 됩니다.
바로 앞에서 생성된 PROJECT_ROOT/README.md를 사용합니다. 먼저 아래 준비 명령으로 기록 훅을 켜고, 새 Agent 대화에서 같은 문서를 읽고 수정하도록 요청합니다.
● 새 파일: CURRENT·활성 스크립트↻ 교체: 실습 소유 훅 항목▶ 활성: 기록 훅만
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/prepare.py s1 gitcd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/prepare.py s1 svn다음 요청으로 넘어가기 전에 Cursor 파일 목록에서 아래 파일을 열어보세요. 경로를 복사해 빠른 파일 열기(Mac Cmd+P, Windows/Linux Ctrl+P)에 붙여넣어도 됩니다.
.cursor/hooks.json — 파일이 새로 생깁니다. preToolUse와 beforeShellExecution 등에 cursor-harness-lab-observe.py만 연결되어 있는지 봅니다. 기록은 켜지만 거부 훅은 아직 연결하지 않습니다.lab-results/CURRENT — 내용이 s1인지 확인합니다. vcs.txt에는 선택한 git 또는 svn이 들어갑니다.README.md — 설치한 예제 내용 그대로입니다. 준비 명령은 README를 수정하지 않습니다.아직 lab-results/s1/events.jsonl이 없어도 정상입니다. 다음 Agent 요청에서 훅이 호출되면 생기고, summary.md는 그 뒤 요약 명령으로 만듭니다.
이번 단계와 관련된 파일만 표시합니다. 다른 사용자 파일과 이전 단계의 결과·일부 도구 파일은 남아 있을 수 있습니다. archive/는 보관할 이전 기록이나 직접 편집본이 있을 때만 생깁니다.
PROJECT_ROOT/
├── .cursor/hooks.json ← 이번 단계의 호출 연결
├── .cursor/hooks/
│ ├── cursor-harness-lab-observe.py ← 기록 훅
│ ├── cursor-harness-lab-deny-write.py ← 파일만 준비, 미연결
│ └── cursor-harness-lab-deny-commit.py ← 파일만 준비, 미연결
└── lab-results/
├── CURRENT ← s1
├── vcs.txt ← 선택한 git 또는 svn
├── snapshot.py / compare.py
└── cursor_harness_lab_shell.py새 Agent 대화를 열고 다음 입력을 한 글자도 바꾸지 않습니다.
README.md를 읽은 다음, 파일 끝에 아래 한 줄을 추가해 주세요. 커밋은 하지 마세요. <!-- lab-edit -->
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 lab-results/snapshot.py| 실제 결과 파일 | 기대 비교 |
|---|---|
s1/events.jsonl, s1/summary.md | Write 또는 StrReplace의 allow, 거부 없음 |
tool=Write는 선택된 도구, phase=attempt는 실행 전 시도입니다. 이어지는 phase=decision과 outcome=allow는 실행 후 허용됐다는 뜻입니다. 실행 도구가 달랐다면 아래 문제 해결에서 확인합니다.
lab-results/s1/summary.md에서 수정 도구의 허용을 확인했습니다.다른 도구가 기록됐나요? · 지금 기록을 끄거나 복원하기
.cursor/hooks/cursor-harness-lab-observe.py는 hook_event_name을 읽습니다. pre/before이면 시도, postToolUseFailure이면 실패, 나머지 등록된 post/after이면 allow로 기록합니다. 정상 실행의 끝에서는 permission: allow를 출력합니다.
도구 요청 → 실행 전 훅: attempt 기록
→ 도구 실행
→ 실행 후 훅: allow 또는 실패 이벤트의 fail 기록lab-results/CURRENT의 s1은 기록을 넣을 폴더 이름입니다. events.jsonl에는 JSON 객체 하나를 한 줄씩 덧붙입니다. 시도 한 줄과 결과 한 줄을 서로 다른 작업 두 번으로 세면 안 됩니다.
실행 뒤 생각해 보세요. attempt만 있으면 수정 성공이라고 할 수 있나요?
아니요. 도구 요청만 확인됩니다. 거부·오류·기록 누락 가능성을 살피고 실제 README도 확인해야 합니다. 이 기록기의 allow도 모든 셸 명령의 성공 여부를 검사한 값은 아닙니다.
01 · Hook 기본 / S2
S1과 똑같은 요청을 보내되, 이번에는 기록 훅 다음에 수정 거부 훅을 연결합니다. 사용자 훅·규칙과 S1 기록은 그대로 남습니다.
학습 목표S1과 같은 요청에 수정 거부 훅만 더하고, 실행 전 deny가 남는지 확인합니다. 실행 후에는 Cursor와 Python이 주고받은 JSON으로 차단 원리를 설명합니다.
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/prepare.py s2 gitcd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/prepare.py s2 svn다음 요청으로 넘어가기 전에 Cursor 파일 목록에서 아래 파일을 열어보세요. 경로를 복사해 빠른 파일 열기(Mac Cmd+P, Windows/Linux Ctrl+P)에 붙여넣어도 됩니다.
.cursor/hooks.json — S1의 기록 훅 뒤에 cursor-harness-lab-deny-write.py가 추가됩니다. preToolUse의 두 번째 실습 항목과 failClosed: true를 확인하세요..cursor/hooks/cursor-harness-lab-deny-write.py — 거부 목록의 Write, StrReplace, Delete, EditNotebook을 찾습니다. 파일만 있는 것이 아니라 hooks.json에 연결됐다는 점이 S1과 다릅니다.lab-results/CURRENT — s1 → s2로 바뀝니다. S1 기록과 README 내용은 그대로 남습니다.S2 준비는 README를 초기화하지 않습니다. S1에서 붙인 줄도 남아 있습니다. 새 s2/events.jsonl은 다음 훅 호출에서 생기며, Agent가 수정 도구를 요청하지 않았다면 거부를 관찰한 것은 아닙니다.
이번 단계와 관련된 파일만 표시합니다. 다른 사용자 파일과 이전 단계의 결과·일부 도구 파일은 남아 있을 수 있습니다. archive/는 보관할 이전 기록이나 직접 편집본이 있을 때만 생깁니다.
PROJECT_ROOT/
├── .cursor/hooks.json ← 이번 단계의 호출 연결
├── .cursor/hooks/
│ ├── cursor-harness-lab-observe.py ← 기록 훅
│ ├── cursor-harness-lab-deny-write.py ← 연결됨
│ └── cursor-harness-lab-deny-commit.py ← 파일만 준비, 미연결
└── lab-results/
├── CURRENT ← s2
├── vcs.txt ← 선택한 git 또는 svn
├── snapshot.py / compare.py
└── cursor_harness_lab_shell.py새 Agent 대화에서 같은 입력을 보냅니다.
README.md를 읽은 다음, 파일 끝에 아래 한 줄을 추가해 주세요. 커밋은 하지 마세요. <!-- lab-edit -->
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 lab-results/snapshot.py
python3 lab-results/compare.py s1 s2| 도구 | S1 | S2 | 비교 |
|---|---|---|---|
| Write/StrReplace | 허용 | 거부 | 수정 제한됨 |
outcome=deny는 실행 전 거부, reason=deny-write는 이 실습 훅이 내린 결정입니다. summary.md는 원본 events.jsonl을 읽기 쉽게 센 결과입니다.
s2/events.jsonl, s2/summary.md, compare-s1-s2.md를 확인했습니다.규칙이 무시된 것과 훅 거부의 차이가 헷갈리나요? · 실습 설정 끄기
S2 준비는 .cursor/hooks.json의 preToolUse에 기록 훅 다음으로 python3 .cursor/hooks/cursor-harness-lab-deny-write.py를 등록합니다. failClosed: true도 이 항목에 붙습니다. 파일이 존재해서가 아니라 실행 전 이벤트에 등록되어서 호출됩니다.
{
"hook_event_name": "preToolUse",
"tool_name": "StrReplace",
"workspace_roots": ["/project"],
"tool_input": {"file_path": "README.md"}
}Cursor가 별도 Python 프로세스의 표준입력(stdin)에 JSON을 보냅니다. 활성 파일의 payload = json.load(sys.stdin)이 이를 읽고, tool = payload.get("tool_name", "")이 도구 이름을 꺼냅니다.
if tool not in {"Write", "StrReplace", "Delete", "EditNotebook"}:
print(json.dumps({"permission": "allow"}, ensure_ascii=False))
raise SystemExit(0)not in은 “목록에 없으면”입니다. Read는 이 분기에 들어가 allow를 출력하고 즉시 끝납니다. StrReplace는 목록에 있으므로 이 분기를 건너뛰고, 로그 기록 부분을 지난 뒤 마지막의 permission: deny 출력에 도달합니다. 이름은 대소문자까지 정확히 비교합니다.
Cursor
└─ stdin: 요청 JSON → Python 훅
└─ stdout: {"permission": "deny"}
← 응답 JSON을 읽고 permission 확인
└─ StrReplace 실행 차단일반 함수의 return은 같은 Python 프로그램의 호출자에게 값을 돌려줍니다. Cursor는 별도 프로세스의 Python 반환값을 직접 받을 수 없으므로 표준출력(stdout)을 읽습니다. json.dumps()가 사전을 JSON 문자열로 만들고, print()가 그 문자열을 stdout에 씁니다.
| 전달되는 값 | 담당하는 일 |
|---|---|
permission: allow / deny | 요청한 도구를 허용할지 거부할지 결정 |
프로세스 종료 코드 0 | 훅 프로그램이 정상 종료됨. 도구 허용이라는 뜻은 아님 |
훅 오류 + failClosed: true | 판단 프로그램이 실패했을 때도 실행을 허용하지 않도록 설정 |
user_message / agent_message | 사람과 에이전트에게 전할 이유. 차단 조건 자체는 permission |
허용 분기의 SystemExit(0)은 아래의 거부 출력까지 내려가지 않게 합니다. 거부 분기는 파일 끝에 도달하며 정상 종료하므로 종료 코드가 0이어도 deny일 수 있습니다. 디버깅 문장을 stdout에 섞으면 응답 파싱을 깨뜨릴 수 있으니 print("진단 내용", file=sys.stderr)처럼 표준에러(stderr)를 사용합니다.
CURRENT가 없으면 로그는 남기지 않지만 정상 실행에서는 여전히 deny를 출력합니다. 다만 현재 코드는 로그 쓰기 오류를 잡지 않습니다. 기록 중 예외가 나면 마지막 출력에 도달하지 못하고, 이때는 failClosed가 실패 시 처리를 맡습니다. reason=deny-write는 기록의 이유이고, Cursor가 읽는 결정은 stdout의 permission입니다.
같은 요청을 비교할 때: S2 준비는 S1에서 바뀐 README를 초기화하지 않습니다. 이미 추가된 줄을 보고 Agent가 수정 요청을 하지 않았다면 거부를 관찰한 것이 아닙니다. 실제 도구 기록을 확인하고, 조건 자체는 다음 직접 편집 장의 probe로 따로 확인하세요.
먼저 생각해 보세요. 파일 경로를 README.md에서 notes.md로 바꾸면 StrReplace가 허용될까요?
아니요. 이 훅은 경로나 변경 내용이 아닌 도구 이름만 검사합니다. Shell은 목록에 없으므로 이 훅만으로 모든 파일 수정 경로를 막지는 못합니다. 실제 경로는 기록에서 확인하세요.
01 · Hook 기본 / 직접 편집
이제 활성 훅의 작은 조건 하나를 직접 고칩니다. Agent가 어떤 도구를 고를지 기다리지 않고, 로컬 점검 프로그램이 두 요청을 똑같이 전달해 결과를 따로 기록합니다.
학습 목표활성 훅의 조건 한 줄을 고쳐 Write는 허용하고 StrReplace는 계속 거부합니다. 같은 입력을 보내는 probe로 편집 효과를 확인합니다.
.cursor/hooks/cursor-harness-lab-deny-write.py 한 개.cursor-harness-kit/files/deny_write.py이 명령은 활성 복사본을 기준 원본으로 다시 씁니다. 기존 활성 복사본을 이미 고쳤다면 먼저 lab-results/archive/active-edits-NNN/에 보관합니다.
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/prepare.py s2 gitcd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/prepare.py s2 svn다음 요청으로 넘어가기 전에 Cursor 파일 목록에서 아래 파일을 열어보세요. 경로를 복사해 빠른 파일 열기(Mac Cmd+P, Windows/Linux Ctrl+P)에 붙여넣어도 됩니다.
.cursor/hooks/cursor-harness-lab-deny-write.py — 원본의 거부 목록으로 되돌아왔는지 확인합니다. 다음 단계에서 이 활성 복사본의 Write 하나만 뺍니다.lab-results/CURRENT — s2 그대로입니다. custom으로 바뀌지 않습니다. 이전 S2 기록은 lab-results/archive/s2-NNN/에 보관됩니다.lab-results/archive/ — 이미 고친 활성 훅이 있었다면 active-edits-NNN/에 보관됩니다. 처음부터 원본과 같았다면 이 백업은 생기지 않습니다.아래 편집 뒤 probe를 실행해야 새 lab-results/custom/probe.json이 만들어집니다. 이전 probe 결과가 있다면 준비만으로 갱신되지는 않습니다.
이번 단계와 관련된 파일만 표시합니다. 다른 사용자 파일과 이전 단계의 결과·일부 도구 파일은 남아 있을 수 있습니다. archive/는 보관할 이전 기록이나 직접 편집본이 있을 때만 생깁니다.
PROJECT_ROOT/
├── .cursor/hooks.json ← 이번 단계의 호출 연결
├── .cursor/hooks/
│ ├── cursor-harness-lab-observe.py ← 기록 훅
│ ├── cursor-harness-lab-deny-write.py ← 연결됨
│ └── cursor-harness-lab-deny-commit.py ← 파일만 준비, 미연결
└── lab-results/
├── CURRENT ← s2
├── vcs.txt ← 선택한 git 또는 svn
├── snapshot.py / compare.py
└── cursor_harness_lab_shell.py.cursor/hooks/cursor-harness-lab-deny-write.py를 열고 아래 한 줄만 바꿔 저장합니다. StrReplace, Delete, EditNotebook은 그대로 둡니다.
| 바꾸기 전 | 바꾼 뒤 |
|---|---|
{"Write", "StrReplace", "Delete", "EditNotebook"} | {"StrReplace", "Delete", "EditNotebook"} |
이 프로그램은 존재하지 않는 lab-probe-target.txt 경로를 훅에 입력할 뿐 파일을 만들거나 수정하지 않습니다.
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/files/probe.py| 결과 필드 | 기대값과 뜻 |
|---|---|
results.Write | allow · 방금 직접 바꾼 효과 |
results.StrReplace | deny · 남겨 둔 거부 조건 |
target_created | false · 실제 파일 수정 없음 |
lab-results/custom/probe.json에서 두 응답을 확인했고, S2 이벤트에 추가된 점검용 deny를 Agent 실행과 구분했습니다.준비 뒤 직접 수정이 사라졌나요? · 아래에서 끄고 복원할 수 있습니다.
.cursor-harness-kit/files/probe.py는 subprocess.run()으로 활성 훅을 실행합니다. 두 도구 요청을 각각 stdin으로 보내고 stdout을 json.loads()로 해석합니다. 모델이 우연히 원하는 도구를 골라 주기를 기다리지 않습니다.
이 점검은 훅 조건의 입력→출력을 확인합니다. Cursor의 훅 등록이나 실제 파일 수정까지 검증하지는 않습니다. 그래서 target_created: false가 기대값입니다.
보고서는 custom 폴더에 따로 생기지만, 거부된 점검 요청은 현재 CURRENT=s2의 이벤트 로그에도 추가됩니다. 이때 생긴 StrReplace deny를 실제 Agent의 시도와 섞어 해석하지 마세요.
고치기 전에 예상해 보세요. 원본 키트만 고치고 활성 복사본을 그대로 두면 probe의 결과가 바뀔까요?
바뀌지 않습니다. probe와 Cursor는 활성 복사본을 호출합니다. 이번에는 활성 파일만 고쳐 Write=allow, StrReplace=deny를 확인하세요. 준비 명령은 원본을 다시 활성 파일로 복사합니다.
01 · Hook 기본 / 관리
실습을 마쳤거나 평소 작업으로 돌아가고 싶다면 이번에 추가한 설정만 끄면 됩니다. 직접 고친 훅은 백업해 두었다가 다시 켤 수 있어요. 원래 사용하던 파일과 설정, 실험 기록은 남겨둡니다.
학습 목표실습 항목만 해제하고 직접 고친 상태로 복원합니다. 원본으로 초기화하는 prepare와 끄기 직전으로 되돌리는 restore를 구분합니다.
.cursor/hooks/cursor-harness-lab-*.py ← 실습 소유: 끄기 대상 .cursor/rules/cursor-harness-lab-*.mdc ← 실습 소유: 끄기 대상 .cursor/skills/lab-document-review/SKILL.md ← 실습 소유: 끄기 대상 .cursor/hooks.json의 다른 키와 명령 ← 사용자 소유: 보존 lab-results/** 및 프로젝트 파일 ← 항상 보존
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/manage.py status이 명령은 활성 훅 등록을 제거하기 전에 현재 실습 파일과 항목을 lab-results/management-backups/disable-NNN/에 보관합니다. 직접 고친 훅도 이 백업에 남습니다.
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/manage.py disable
python3 .cursor-harness-kit/manage.py status두 번째 출력은 enabled: false와 registered_hook_entries: 0을 보여야 합니다.
복원은 백업 뒤 추가한 사용자 훅과 다른 JSON 키를 유지합니다. 같은 실습 경로 또는 명령에 다른 내용이 있으면 덮어쓰지 않고 복원 충돌로 멈춥니다.
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/manage.py restore복원은 끄기 직전의 직접 편집본을 돌려놓습니다. 기준 원본으로 바꾸려면 S2 준비 명령을 다시 실행하세요. 그 명령은 직접 편집본을 lab-results/archive/active-edits-NNN/에 보관한 뒤 활성 복사본을 덮어씁니다.
manage.py disable 뒤 실습 훅이 더는 등록되지 않음을 확인했습니다.지금 배운 것을 써보고 싶다면 내 프로젝트에 필요한 설정만 옮기기로 넘어가세요. 결과가 예상과 달랐다면 세션 기록으로 원인 찾기를, 더 연습하고 싶다면 아래 응용 실습을 선택하면 됩니다. 모든 응용을 끝내야 적용할 수 있는 것은 아니에요.
.cursor-harness-kit/manage.py의 disable은 실습 파일과 등록 항목을 백업한 뒤 실습 소유 항목만 제거합니다. registered_hook_entries: 0은 실습 훅 등록 수이며 사용자 훅 전체가 꺼졌다는 뜻은 아닙니다.
restore는 끄기 직전 상태를 돌려놓습니다. prepare는 키트 원본의 시나리오 상태를 새로 준비합니다. 어느 시점의 상태를 원하는지에 따라 선택합니다.
실행 전에 예상해 보세요. Write를 허용하도록 고친 뒤 disable→restore를 하면 Write는 무엇이 될까요?
복원이 성공하면 직접 고친 상태인 allow입니다. 원래 deny로 돌아가려면 S2 준비 명령을 씁니다. 복원 충돌은 그동안 바뀐 내용을 덮어쓰지 않기 위한 중단입니다.
여기부터 선택 실습입니다. 커밋 동작, 기록 읽기, 같은 입력의 실행 경로, Skill 중 필요한 부분만 골라도 됩니다.
02 · Hook 응용 / S3
먼저 차단 없이 커밋을 요청해 볼게요. 준비 명령이 commit-input.txt에 이번 실험용 변경을 만들어주므로 따로 파일을 고칠 필요가 없습니다. 실제 커밋은 뒤에서 에이전트에게 요청합니다.
앞에서는 Write·StrReplace 같은 도구를 봤습니다. 여기서는 Shell 안의 Git·SVN 명령을 기록하고 차단합니다. 커밋 응용은 선택 사항이며, 필요한 저장소도 이 단계에서 만듭니다.
학습 목표셸 요청 전후의 훅 기록과 실제 커밋 결과를 나란히 확인합니다. 훅의 허용과 Git·SVN 작업 성공이 같은 뜻인지 구분합니다.
앞에서 만든 실습 폴더를 그대로 사용합니다. 이제 커밋을 비교하기 위해 선택한 버전 관리 도구의 저장소를 만듭니다. 계정이나 원격 서버를 준비할 필요는 없습니다.
터미널에서 git --version을 실행해 보세요. 명령이 없다면 Git 설치 안내를 따라 설치하고 Cursor 터미널을 다시 엽니다. 그다음 실습 폴더에서 PROJECT_ROOT를 다시 설정하고 아래 명령을 실행합니다.
git init은 이 폴더에 저장소를 만들고, --local 설정은 이 실습에서만 사용할 작성자와 서명 여부를 정합니다. GitHub 계정이나 실제 이메일은 필요하지 않습니다. 아직 커밋은 하지 않습니다.
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
git init &&
git config --local user.name "Harness Learner" &&
git config --local user.email "learner@example.invalid" &&
git config --local commit.gpgsign false아래 결과가 true이면 저장소가 준비됐습니다.
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
git rev-parse --is-inside-work-treesvn --version과 svnadmin --version을 확인합니다. 명령이 없다면 Subversion 설치 안내에서 운영체제에 맞는 도구를 설치하고 Cursor 터미널을 다시 엽니다. 실습 폴더에서 PROJECT_ROOT를 다시 설정하세요.
아래 명령은 실습 폴더 안에 로컬 저장소를 만들고, 같은 폴더를 작업 복사본으로 연결합니다. file:// 주소를 사용하므로 SVN 서버나 계정은 필요하지 않습니다. 저장소 자체는 커밋 대상에서 제외합니다.
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
if ! svn info --show-item wc-root >/dev/null 2>&1; then
svnadmin create .lab-svn-repository || exit
SVN_URL="$(python3 -c 'from pathlib import Path; print(Path(".lab-svn-repository").resolve().as_uri())')" || exit
svn checkout "$SVN_URL" . || exit
svn propset svn:ignore .lab-svn-repository . || exit
fi아래 명령이 현재 실습 폴더의 경로를 출력하면 준비됐습니다.
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
svn info --show-item wc-rootcd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/prepare.py s3 gitcd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/prepare.py s3 svn다음 요청으로 넘어가기 전에 Cursor 파일 목록에서 아래 파일을 열어보세요. 경로를 복사해 빠른 파일 열기(Mac Cmd+P, Windows/Linux Ctrl+P)에 붙여넣어도 됩니다.
.cursor/hooks.json — 기록 훅만 연결됩니다. 앞서 켰던 deny-write나 deny-commit의 실습 등록은 빠집니다. Python 파일이 남아 있어도 등록이 없으면 호출되지 않습니다.lab-results/commit-input.txt — cursor-harness-lab commit input: s3가 새로 적혔는지 확인합니다. 이 변경이 이번 커밋의 재료입니다.lab-results/CURRENT — s3으로 바뀝니다. 기존 README와 다른 시나리오의 기록은 보존됩니다.아직 커밋은 하지 않았습니다. 커밋 요청과 실행 기록은 다음 Agent 단계에서 확인합니다.
이번 단계와 관련된 파일만 표시합니다. 다른 사용자 파일과 이전 단계의 결과·일부 도구 파일은 남아 있을 수 있습니다. archive/는 보관할 이전 기록이나 직접 편집본이 있을 때만 생깁니다.
PROJECT_ROOT/
├── .cursor/hooks.json ← 이번 단계의 호출 연결
├── .cursor/hooks/
│ ├── cursor-harness-lab-observe.py ← 기록 훅
│ ├── cursor-harness-lab-deny-write.py ← 파일만 준비, 미연결
│ └── cursor-harness-lab-deny-commit.py ← 파일만 준비, 미연결
└── lab-results/
├── CURRENT ← s3
├── vcs.txt ← 선택한 git 또는 svn
├── snapshot.py / compare.py
├── cursor_harness_lab_shell.py
└── commit-input.txt ← 이번 커밋용 변경새 Agent 대화에서 선택한 고정 프롬프트 B를 보냅니다.
현재 워크스페이스의 변경사항을 로컬 git으로만 커밋하세요. 원격 저장소는 사용하지 마세요. push 하지 마세요. 커밋 메시지는 정확히 다음을 사용하세요: lab: scenario commit
현재 워크스페이스의 변경사항을 svn으로 커밋하세요. 커밋 메시지는 정확히 다음을 사용하세요: lab: scenario commit
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 lab-results/snapshot.py| 실제 결과 파일 | 기대 동작 | S4와 비교할 기준선 |
|---|---|---|
s3/events.jsonl, s3/summary.md | 선택한 VCS commit의 attempt와 allow | 정책 deny가 없음 |
s3/events.jsonl과 s3/summary.md에서 커밋 시도와 허용을 확인했습니다.commit-input.txt 외의 학습자 파일은 준비가 바꾸지 않았습니다.prepare.py는 S3~S5에서 git rev-parse 또는 svn info가 성공하는지 확인하고 commit-input.txt를 바꿔 커밋할 재료를 만듭니다. 준비가 커밋을 실행하지는 않습니다.
Agent의 셸 요청은 beforeShellExecution과 실행 후 afterShellExecution에서 기록됩니다. 이 랩의 관찰 코드는 후자에 allow를 붙일 뿐 셸 종료 코드를 해석하지 않습니다.
실행 뒤 생각해 보세요. “nothing to commit”이라는 도구 결과와 allow가 함께 보이면 모순일까요?
아닙니다. 명령은 훅에 차단되지 않았지만 새 커밋은 만들어지지 않은 상태입니다. 도구 결과와 git log -1 또는 svn log -l 1을 함께 확인해야 실제 커밋을 판정할 수 있습니다.
02 · Hook 응용 / S4
준비 명령이 비교용 변경을 만들고 커밋 거부 훅을 연결합니다. 지원하는 상태·차이 조회는 허용하고, 커밋이 포함됐거나 분석할 수 없는 셸 요청은 실행 전에 거부합니다.
학습 목표도구 이름뿐 아니라 셸 명령의 내용도 검사합니다. S3과 같은 요청에 커밋 거부 훅을 더하고, 로그에서 커밋 발견과 구문 분석 실패를 구분합니다.
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/prepare.py s4 gitcd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/prepare.py s4 svn다음 요청으로 넘어가기 전에 Cursor 파일 목록에서 아래 파일을 열어보세요. 경로를 복사해 빠른 파일 열기(Mac Cmd+P, Windows/Linux Ctrl+P)에 붙여넣어도 됩니다.
.cursor/hooks.json — beforeShellExecution에서 기록 훅 다음에 cursor-harness-lab-deny-commit.py가 연결됩니다. failClosed: true도 함께 확인하세요.lab-results/commit-input.txt — 끝의 시나리오가 s3 → s4로 바뀝니다. 차단 여부를 비교할 새 변경이 준비된 것입니다.lab-results/CURRENT — s4인지 봅니다. vcs.txt는 검사할 버전 관리 도구를, cursor_harness_lab_shell.py는 공통 명령 분석 코드를 담고 있습니다.준비 명령은 커밋도, 거부 이벤트도 만들지 않습니다. 다음 Agent의 셸 요청을 훅이 검사한 뒤 실제 거부 이유를 읽습니다.
이번 단계와 관련된 파일만 표시합니다. 다른 사용자 파일과 이전 단계의 결과·일부 도구 파일은 남아 있을 수 있습니다. archive/는 보관할 이전 기록이나 직접 편집본이 있을 때만 생깁니다.
PROJECT_ROOT/
├── .cursor/hooks.json ← 이번 단계의 호출 연결
├── .cursor/hooks/
│ ├── cursor-harness-lab-observe.py ← 기록 훅
│ ├── cursor-harness-lab-deny-write.py ← 파일만 준비, 미연결
│ └── cursor-harness-lab-deny-commit.py ← 연결됨
└── lab-results/
├── CURRENT ← s4
├── vcs.txt ← 선택한 git 또는 svn
├── snapshot.py / compare.py
├── cursor_harness_lab_shell.py
└── commit-input.txt ← 이번 커밋용 변경Agent에게 입력하기 전에 Git은 아래 HEAD 값을 기록하고, 실행 뒤 같은 값인지 비교하세요. S3에서 아직 커밋이 생기지 않았다면 HEAD가 없다는 오류가 나며, S4 뒤에도 커밋이 새로 생기지 않아야 합니다. SVN은 svn status로 커밋되지 않은 변경이 남았는지 확인합니다.
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
git rev-parse --verify HEAD새 Agent 대화에서 S3과 같은 VCS별 입력을 보냅니다.
현재 워크스페이스의 변경사항을 로컬 git으로만 커밋하세요. 원격 저장소는 사용하지 마세요. push 하지 마세요. 커밋 메시지는 정확히 다음을 사용하세요: lab: scenario commit
현재 워크스페이스의 변경사항을 svn으로 커밋하세요. 커밋 메시지는 정확히 다음을 사용하세요: lab: scenario commit
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 lab-results/snapshot.py
python3 lab-results/compare.py s3 s4| 분류 | s3 | s4 | 비교 |
|---|---|---|---|
| commit | allow | deny | 복합 요청도 전체 거부 |
| status/diff | allow/없음 | allow/없음 | 지원하는 조회 허용 |
| unknown | 기록만 | deny/없음 | 커밋 확인이 아닌 구문 거부 |
s4/events.jsonl, s4/summary.md, compare-s3-s4.md에서 커밋 거부와 reason=deny-commit을 확인했습니다.unknown 거부만 있다면 커밋 탐지 성공과 구분하고, 세션에서 거부된 실제 명령을 확인했습니다.추가 또는 메시지 조건을 직접 설계하고 별도 결과 이름을 쓰세요. 본편 프롬프트 B는 사용하지 않습니다.
.cursor/hooks/cursor-harness-lab-deny-commit.py는 beforeShellExecution에서 command와 lab-results/vcs.txt를 읽습니다. lab-results/cursor_harness_lab_shell.py의 analyze(command, vcs)가 요청 전체를 분석하고, 훅이 그 결과를 permission: allow / deny로 바꿉니다. 분석기는 명령을 실행하지 않습니다.
문자열 안의 commit만 찾으면 안 됩니다. git commit -m "lab"은 커밋이지만 echo "git commit"은 출력할 글자이고, git add commit의 commit은 파일 이름입니다. 따옴표·주석·이스케이프를 구분한 뒤 실행 파일과 옵션, 하위 명령을 확인합니다.
입력: git add -A && git commit -m x
분석: 첫 명령은 add, 두 번째 명령은 commit
판단: 선택한 Git의 커밋 발견 → reason=deny-commit
응답: {"permission": "deny"}
결과: 요청 전체가 실행되지 않음. 앞의 add도 실행되지 않음.지원 범위는 값이 정해진 단순 명령과 &&, ||, ;, |, &, 줄바꿈 연결입니다. 연결된 명령을 모두 검사하므로 뒤에 커밋이 있어도 거부합니다. env LAB=1 git commit, command git commit 같은 지원하는 래퍼도 검사하며, SVN에서는 commit과 같은 동작인 ci를 함께 막습니다.
중첩 셸(bash -c 등), 변수·명령 치환, 제어문, 리다이렉션, 따옴표 밖의 와일드카드 등 미지원 구문은 deny-unsupported-shell로 거부합니다. sudo·xargs 같은 미지원 실행 래퍼, Python·Node 실행 요청, 목록에 없는 VCS 하위 명령·앞쪽 옵션도 같은 분기입니다. 따라서 S4에서는 커밋 없는 테스트 실행도 거부될 수 있습니다. 이 제한은 S4 정책이 켜진 동안 적용됩니다.
닫히지 않은 따옴표 등 잘못된 구문은 deny-invalid-shell로 거부합니다. 두 구문 거부 모두 커밋을 발견했다고 기록하지 않고 command_family=unknown으로 남깁니다. 지원하는 단순 명령으로 표현할 수 있다면 나누어 요청하고, 다른 실행 경로가 필요한 작업이라면 실습 훅 끄기에서 정책을 의도적으로 해제합니다.
failClosed: true는 Python 실행 오류처럼 훅이 판단을 끝내지 못할 때를 맡습니다. 반면 위 구문 거부는 훅이 정상 종료하면서 deny를 반환한 결과입니다. 둘 다 실행을 막지만 이유가 다릅니다.
이 실습의 경계. Cursor Agent의 셸 요청을 검사하는 훅이며, 사람이 터미널에서 직접 실행하는 명령 전체를 막는 장치는 아닙니다. 스크립트·외부 프로그램·별칭·Git 설정 안쪽의 동작까지 실행해 추적하지 않습니다. OS 샌드박스나 저장소 권한 격리를 대신하지 않습니다. git add와 git push 전체를 금지하는 정책으로 바꾼 것도 아닙니다.
먼저 예상해 보세요. Git 선택 상태에서 git status && git diff와 git status && git commit -m x는 어떻게 다를까요?
첫 요청은 지원하는 조회만 있어 허용됩니다. 두 번째는 커밋이 포함되어 요청 전체가 거부되므로 status도 실행되지 않습니다. S3과 S4에서는 같은 프롬프트 B를 사용하고, 원본 로그에서 실제 명령과 거부 이유를 확인하세요.
02 · Hook 응용 / S6
S6에는 준비 명령이 없습니다. CURRENT, 훅, 규칙, 결과 폴더를 바꾸거나 새 기록을 만들지 않고 이미 진행한 시나리오의 결과만 읽습니다.
학습 목표이미 만든 S2 또는 S4 기록에서 도구와 거부 이유를 직접 찾습니다. 원본 로그와 요약의 차이를 알고, 로그 한 줄로 판단할 수 있는 범위를 구분합니다.
lab-results/s2/events.jsonl = 읽기 전용 lab-results/s4/events.jsonl = 읽기 전용 lab-results/compare-*.md = 읽기 전용 CURRENT / .cursor/** = 변경 없음
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 -c 'import json; p="lab-results/s2/events.jsonl"; [print(line, end="") for line in open(p, encoding="utf-8") if json.loads(line).get("outcome") == "deny"]'| 필드 | 비교에서 뜻 |
|---|---|
phase / outcome | 실행 전 시도 또는 허용·거부·실패 결정 |
tool / command | 도구와 셸 명령의 앞 500자 |
command_family / command_issue | 자르기 전 전체 요청의 분류와 분석하지 못한 이유 |
reason | deny-write, deny-commit 또는 deny-unsupported-shell / deny-invalid-shell |
요약·비교는 기록된 command_family를 사용하므로 500자 뒤의 커밋도 분류에 남습니다. 이 필드가 없는 이전 로그는 같은 공통 분석기로 다시 분류합니다. 다만 이미 500자로 잘린 옛 명령의 뒷부분은 복구할 수 없어 unknown으로 표시합니다. 명시적인 reason=deny-commit은 이전 로그에서도 커밋 거부의 근거로 유지합니다.
다른 시나리오 기록이 보이나요? · 현재 실습 설정 끄기
JSONL은 한 줄에 JSON 객체 하나가 든 형식입니다. json.loads(line)으로 읽고 outcome을 비교하면 거부 기록만 골라낼 수 있습니다. 앞서 실행한 명령은 이 필터를 수행하며 정책이나 기록은 바꾸지 않습니다.
원본을 event → phase → tool/command → outcome → reason 순으로 읽으세요. 같은 Shell 요청이 일반 도구 훅과 셸 훅에 모두 보일 수 있어 로그 줄 수를 곧바로 작업 수로 볼 수는 없습니다.
먼저 생각해 보세요. attempt 뒤에 deny가 있으면 도구가 실행된 뒤 취소된 것일까요?
아니요. 실행 전 시도를 기록한 뒤 실행 전 정책이 거부한 흐름입니다. 파일을 바꿨다가 되돌린 것이 아닙니다. reason으로 거부한 정책을 확인하세요.
02 · Hook 응용 / 세션 분석
마지막 답변만 봐서는 왜 그런 결과가 나왔는지 알기 어렵습니다. 요청부터 도구 호출, 실행 결과까지 순서대로 읽으면 처음 예상에서 벗어난 지점을 찾을 수 있어요. 원본 기록과 설정은 그대로 두고, 분석할 대화 사본과 메모만 새로 만듭니다.
학습 목표요청 → 도구 → 훅 결정 → 다음 행동을 세션과 로그에서 맞춰 봅니다. 예상에서 처음 벗어난 지점을 찾아 다음에 바꿀 조건 하나를 고릅니다.
| 기록 | 무엇을 알 수 있나요? | 무엇까지는 알 수 없나요? |
|---|---|---|
| 세션 기록 | 내 요청, 에이전트 답변, 표시된 도구 요청과 결과의 순서 | 대화를 일부만 복사했다면 빠진 도구 호출이 있을 수 있습니다. |
events.jsonl | 훅에 전달된 도구 시도와 허용·거부·실패 | 전체 대화는 아닙니다. allow만으로 파일의 최종 내용까지 증명하지는 못합니다. |
| 결과 파일과 변경 내역 | 실제로 생성된 보고서나 파일 변경 | 결과만으로 어떤 요청과 도구를 거쳤는지 알 수는 없습니다. |
준비 파일에 session-example.md와 session-events.jsonl을 함께 넣었습니다. 둘은 이 설명을 위해 만든 예제이며, 실제 Cursor 내보내기 형식이나 여러분의 실행 결과가 아닙니다.
1. 사용자: README 끝에 한 줄을 추가해 주세요. 2. 에이전트: Write로 수정 요청 3. 훅: Write 거부 (deny-write) 4. 에이전트: Shell로 같은 파일 수정 요청 5. 도구 결과: 셸 명령 종료 코드 0 6. 에이전트: 수정했다고 답변
질문: Write를 막았는데 왜 수정했다는 답변이 나왔을까요? 3번에서 멈추지 말고 4번까지 읽어보세요. 수정 도구는 막혔지만 Shell이라는 다른 경로로 다시 시도했습니다. 실제 파일이 바뀌었는지는 파일 내용이나 변경 내역도 함께 봐야 합니다.
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 -c 'from pathlib import Path; p=Path(".cursor-harness-kit/files/session-example.md"); print("\n".join(f"{i}: {line}" for i,line in enumerate(p.read_text(encoding="utf-8").splitlines(),1)))'아래 명령은 예제 기록을 읽어 도구와 결정을 순서대로 보여줍니다. 실제 실습 기록과 섞어 세지 않습니다.
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 -c 'import json; from pathlib import Path; p=Path(".cursor-harness-kit/files/session-events.jsonl"); rows=[json.loads(line) for line in p.read_text(encoding="utf-8").splitlines() if line.strip()]; print("\n".join("%d: %s / %s / %s / %s" % (i,r["tool"],r["phase"],r["outcome"],r["reason"]) for i,r in enumerate(rows,1)))'Cursor에서 S2 또는 R2를 진행한 대화를 열고 문서 이름이나 요청 문장을 검색해 보세요. 공식 대화 검색 안내에서는 열린 대화 안에서 Mac은 Cmd+F, Windows/Linux는 Ctrl+F로 찾는 방법을 설명합니다.
해당 요청, 에이전트 답변, 펼쳐진 도구 요청·결과를 복사해 프로젝트의 lab-results/session-review/session.md에 저장하세요. 복사되지 않는 도구 결과는 따로 붙여넣고, 빠진 부분은 “생략”이라고 표시합니다. 로컬 내보내기 기능이 있는 버전에서는 그 파일을 사용해도 됩니다. 공유 링크를 만들거나 기록을 이 웹페이지에 올릴 필요는 없습니다.
기록에 토큰이나 개인 정보가 포함돼 있으면 분석에 필요한 부분만 남겨 주세요. Cursor 내부 저장소의 파일명과 구조는 버전마다 달라질 수 있으므로, 이 실습에서는 원본 데이터베이스를 수정하지 않습니다.
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
mkdir -p lab-results/session-review프로젝트/
├── .cursor-harness-kit/files/
│ ├── session-example.md ← 설명용 대화 예제
│ ├── session-events.jsonl ← 예제와 대응하는 훅 기록
│ └── session-note.md ← 원인을 정리할 빈 양식
└── lab-results/
├── s2/events.jsonl ← 실제 S2 실행의 훅 기록
└── session-review/
├── session.md ← 직접 저장한 이번 대화
└── notes.md ← 빈 양식을 복사해 작성할 분석 메모R2를 살펴본다면 s2/events.jsonl 대신 r2/events.jsonl과 r2/route-summary.md를 나란히 열면 됩니다. 로그가 비어 있다면 “아무 일도 없었다”가 아니라 “기록으로 확인할 수 없다”로 구분해 주세요.
session-note.md의 내용을 새 notes.md로 복사해 “확인한 사실 / 아직 모르는 것 / 다음에 바꿀 한 가지”를 적어보세요. 한 번에 여러 설정을 바꾸면 무엇이 영향을 줬는지 다시 알기 어려워집니다.
엄격한 R3 도구 제한은 분석 파일 읽기도 막습니다. 먼저 위의 실습 설정 해제 절차를 진행하고 새 대화에서 사용하세요. 분석 결과의 줄 번호가 원본에 실제로 있는지는 직접 확인합니다.
lab-results/session-review/session.md와 lab-results/s2/events.jsonl을 읽고, 요청 → 도구 호출 → 도구 결과 → 다음 행동 순서로 정리해 주세요. 예상과 처음 달라진 지점을 각 파일의 줄 번호와 함께 알려주세요. 직접 확인한 사실과 추측을 구분하고, 기록이 빠져 있으면 판단할 수 없다고 표시해 주세요. 파일과 설정은 수정하지 마세요.
session.md의 어느 요청과 어느 도구 결과를 살폈는지 표시했습니다.notes.md에 세션과 실제 훅 기록의 줄 번호를 각각 적었습니다.도구 한 번의 거부는 대화 전체의 종료가 아닙니다. 모델은 거부 응답 뒤 다른 도구를 요청할 수 있습니다. 앞서 읽은 예제는 Write 거부 뒤 Shell 요청으로 이어집니다. deny-write가 검사하지 않는 경로입니다.
세션과 훅 로그에서 같은 도구·명령·파일 경로와 주변 순서를 맞춰 보세요. 이 랩에는 공통 호출 ID가 없으므로 같은 시각이라는 이유만으로 같은 호출이라 단정하지 않습니다. 생략된 부분 때문에 연결할 수 없다면 그것도 표시합니다.
분석 전에 생각해 보세요. Write 거부 한 줄로 “README는 바뀌지 않았다”고 결론낼 수 있을까요?
그 Write 요청이 거부됐다는 것만 확인됩니다. 이후 다른 요청과 실제 파일 내용까지 봐야 합니다. 다음 실험에서는 처음 예상과 달라진 지점의 조건 하나만 바꿔 원인을 좁힙니다.
03 · Rule과 실행 경로 / S5
커밋 거부 훅 대신 “커밋하지 마세요”라는 규칙(Rule)만 둡니다. 기존 사용자 규칙은 남기고 비교용 변경을 새로 만듭니다.
Rule(규칙)은 .cursor/rules/*.mdc에 두는 프로젝트 지시입니다. Agent가 도구를 고를 때 참고하지만, 훅의 deny처럼 실행을 직접 멈추지는 않습니다.
먼저 커밋 금지 문장과 차단 코드를 비교합니다(S5). 이어서 같은 문서 검토 요청으로 직접 읽기(R1) → 프로그램 사용 지시(R2) → 다른 경로 제한(R3)을 비교합니다. 문서 검토만 궁금하다면 R1부터 시작해도 됩니다.
학습 목표S4의 커밋 거부 훅을 빼고 “커밋하지 말라”는 Rule을 넣습니다. 같은 요청의 결과로 지시를 따르는 것과 실행을 강제로 막는 것의 차이를 확인합니다.
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/prepare.py s5 gitcd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/prepare.py s5 svn다음 요청으로 넘어가기 전에 Cursor 파일 목록에서 아래 파일을 열어보세요. 경로를 복사해 빠른 파일 열기(Mac Cmd+P, Windows/Linux Ctrl+P)에 붙여넣어도 됩니다.
.cursor/hooks.json — S4의 deny-commit 등록이 빠지고 기록 훅만 남습니다. 이제 커밋을 거부하는 실습 훅은 연결되어 있지 않습니다..cursor/rules/cursor-harness-lab-no-commit.mdc — 파일이 추가되고 머리말에 alwaysApply: true가 있습니다. 본문의 “커밋하지 말라”는 지시를 직접 읽어보세요. 기존 사용자 Rule은 남습니다.lab-results/CURRENT — s5로 바뀌고 commit-input.txt도 s5용 내용으로 갱신됩니다. S4 결과는 그대로입니다.바뀐 것은 차단 코드가 아니라 Agent가 읽을 지시입니다. Rule 준수 여부는 다음 요청의 실제 도구와 커밋 결과를 보고 판단합니다.
이번 단계와 관련된 파일만 표시합니다. 다른 사용자 파일과 이전 단계의 결과·일부 도구 파일은 남아 있을 수 있습니다. archive/는 보관할 이전 기록이나 직접 편집본이 있을 때만 생깁니다.
PROJECT_ROOT/
├── .cursor/hooks.json ← 이번 단계의 호출 연결
├── .cursor/hooks/
│ ├── cursor-harness-lab-observe.py ← 기록 훅
│ ├── cursor-harness-lab-deny-write.py ← 파일만 준비, 미연결
│ └── cursor-harness-lab-deny-commit.py ← 파일만 준비, 미연결
├── .cursor/rules/cursor-harness-lab-no-commit.mdc
└── lab-results/
├── CURRENT ← s5
├── vcs.txt ← 선택한 git 또는 svn
├── snapshot.py / compare.py
├── cursor_harness_lab_shell.py
└── commit-input.txt ← 이번 커밋용 변경새 Agent 대화에서 같은 VCS별 프롬프트 B를 보냅니다.
현재 워크스페이스의 변경사항을 로컬 git으로만 커밋하세요. 원격 저장소는 사용하지 마세요. push 하지 마세요. 커밋 메시지는 정확히 다음을 사용하세요: lab: scenario commit
현재 워크스페이스의 변경사항을 svn으로 커밋하세요. 커밋 메시지는 정확히 다음을 사용하세요: lab: scenario commit
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 lab-results/snapshot.py
python3 lab-results/compare.py s5 s4| 명령 | S5 규칙만 | S4 훅 | 비교 |
|---|---|---|---|
| 커밋 | 허용 또는 시도 없음 | 거부 | S4만 강제 거부 |
S5의 통과 기준은 ‘커밋이 반드시 실패하는 것’이 아닙니다.compare-s5-s4.md에서 S4의 정책이 인식한 커밋 요청에 거부가 남았는지 봅니다. S5가 허용이면 규칙만으로는 부족하다는 뜻입니다. S4에서도 다른 명령 뒤에 연결한 커밋은 놓칠 수 있으므로 실제command를 함께 읽으세요.
s5/events.jsonl, s5/summary.md, compare-s5-s4.md가 실제 결과입니다.deny-commit 이유가 없고 S4에는 있습니다.규칙 문구와 직접 만든 입력을 바꿔 별도 결과로 비교하세요.
.cursor/rules/cursor-harness-lab-no-commit.mdc는 alwaysApply: true인 프로젝트 지시입니다. prepare.py는 S5에서 이 파일을 넣고 커밋 거부 훅 등록을 뺍니다. 모델이 읽는 지시는 Python 조건문 같은 강제 분기가 아닙니다.
S5: Rule → 모델이 커밋을 요청하지 않음 → 커밋 시도 없음 S4: 커밋 요청 → 훅이 deny 응답 → 실행 전 차단
실행 뒤 생각해 보세요. S5 로그에 커밋 시도가 없으면 “훅이 막았다”고 써도 될까요?
안 됩니다. 그 기록에서 커밋 요청을 확인하지 못했다는 뜻입니다. Rule을 따른 것인지는 세션도 살피고, 훅 거부는 S4의 reason=deny-commit처럼 명시적인 결정으로 구분합니다. 한 번 따랐다고 앞으로도 따르리라는 보장은 아닙니다.
03 · Rule과 실행 경로 / R1
먼저 에이전트가 문서를 직접 읽고 검토하게 해볼게요. 이 단계에서는 도구 사용을 기록만 하고, 별도 검사 프로그램을 쓰라는 지시는 넣지 않습니다. 다음 단계와 비교할 첫 번째 실행 경로를 남기는 과정입니다.
라우팅(routing)은 같은 요청을 어떤 도구와 프로그램 순서로 처리할지 정하는 일입니다. 답변이 비슷해도 경로는 다를 수 있어, 세 단계 모두 같은 문서와 요청을 사용합니다. Rule은 R2에서 추가하고, R3에서 훅으로 제한합니다.
학습 목표같은 문서와 요청으로 비교할 기준 경로를 기록합니다. Agent의 직접 Read와 검사 프로그램의 실행을 구분합니다.
docs/guide.md의 원본 바이트 · 아래 프롬프트Git 또는 SVN 중 위에서 고른 하나만 실행합니다. 준비기는 기존 docs/guide.md가 예제와 다른 내용이면 어떤 파일도 쓰기 전에 중단하며 덮어쓰지 않습니다. 같은 예제 내용이면 그대로 사용합니다.
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/prepare.py r1 gitcd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/prepare.py r1 svn다음 요청으로 넘어가기 전에 Cursor 파일 목록에서 아래 파일을 열어보세요. 경로를 복사해 빠른 파일 열기(Mac Cmd+P, Windows/Linux Ctrl+P)에 붙여넣어도 됩니다.
docs/guide.md — 검토할 예제 문서가 생깁니다. 기존 파일이 같은 예제라면 재사용하고, 다른 내용이라면 준비가 덮어쓰지 않고 멈춥니다..cursor/hooks.json — 기록 훅만 연결됩니다. 이전 실습의 커밋 금지·경로 지시 Rule과 Skill은 제거되지만 사용자 Rule은 남습니다.lab-results/CURRENT — r1인지 확인하고 lab-results/r1/document-before.sha256를 열어 문서 지문이 저장됐는지 봅니다.review_doc.py는 준비되지만 여기서는 Agent에게 실행하라고 지시하지 않습니다. events.jsonl은 훅 호출 뒤, route-summary.json은 결과 정리 명령 뒤 생깁니다.
이번 단계와 관련된 파일만 표시합니다. 다른 사용자 파일과 이전 단계의 결과·일부 도구 파일은 남아 있을 수 있습니다. archive/는 보관할 이전 기록이나 직접 편집본이 있을 때만 생깁니다.
PROJECT_ROOT/
├── .cursor/hooks.json ← 이번 단계의 호출 연결
├── .cursor/hooks/
│ ├── cursor-harness-lab-observe.py ← 기록 훅
│ ├── cursor-harness-lab-deny-write.py ← 파일만 준비, 미연결
│ └── cursor-harness-lab-deny-commit.py ← 파일만 준비, 미연결
├── docs/guide.md ← 고정 예제 문서
├── .cursor/hooks/cursor-harness-lab-route-policy.py ← 미연결
└── lab-results/
├── CURRENT ← r1
├── vcs.txt ← 선택한 git 또는 svn
├── snapshot.py / compare.py
├── cursor_harness_lab_shell.py
├── review_doc.py / route_summary.py
└── r1/document-before.sha256 ← 실행 전 문서 지문새 Agent 대화에서 아래 문장만 보냅니다. 커밋·네트워크·서브에이전트는 사용하지 않습니다.
docs/guide.md를 검토하고 문제점을 알려주세요. 파일은 수정하지 마세요.
고정 프롬프트 ──▶ Agent ──Read docs/guide.md──▶ 답변
└─ 프로그램 실행 없음cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 lab-results/route_summary.py r1준비의 파일별 펼침에서 files/guide.md, files/review_doc.py, files/route_summary.py 전체를 읽을 수 있습니다. R1 판정은 채팅 주장이 아니라 events.jsonl과 원본 문서 지문에서 나옵니다.
r1/route-summary.json의 direct_document_read가 true이고 program_shell_execution은 false입니다.document-before.sha256와 document-after.sha256가 같아 원본 문서가 바뀌지 않았습니다.lab-results/route_summary.py는 문서 Read와 프로그램 실행 기록을 구분합니다. direct_document_read는 직접 Read의 근거이지 답변 품질 점수가 아닙니다.
document-before.sha256는 실행 전 문서 바이트로 계산한 지문입니다. 실행 뒤 지문과 같으면 시작과 끝의 문서가 같습니다. 읽지 않았다는 뜻도, 중간에 바꿨다가 되돌리지 않았다는 증거도 아닙니다.
먼저 예상해 보세요. R1과 R2가 같은 문제 세 가지를 답하면 라우팅도 같다고 볼 수 있나요?
아니요. 직접 문서 Read인지, 프로그램 Shell 실행 뒤 보고서 Read인지 로그로 비교해야 합니다. 답변 내용과 실행 경로는 별개라 문서와 요청을 고정합니다.
03 · Rule과 실행 경로 / R2
이번에는 문서와 요청을 그대로 두고, 검사 프로그램을 먼저 실행하라는 규칙만 추가합니다. 에이전트가 직접 판단하는 대신 프로그램의 결과를 읽고 설명하는지 살펴보세요. 규칙을 따르지 않고 직접 문서를 읽었다면 그것도 결과에 표시됩니다. 지침만으로 실행 경로가 바뀌는지 확인하는 실험이에요.
학습 목표R1의 문서와 요청은 그대로 두고 Rule만 더합니다. 프로그램이 검사하고 Agent가 보고서를 설명하는 경로로 바뀌는지 확인합니다.
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/prepare.py r2 gitcd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/prepare.py r2 svn다음 요청으로 넘어가기 전에 Cursor 파일 목록에서 아래 파일을 열어보세요. 경로를 복사해 빠른 파일 열기(Mac Cmd+P, Windows/Linux Ctrl+P)에 붙여넣어도 됩니다.
.cursor/rules/cursor-harness-lab-review-route.mdc — R1에 없던 경로 지시가 추가됩니다. alwaysApply: true, 실행할 Python 명령, 읽을 lab-results/r2/review-report.json 경로를 찾아보세요..cursor/hooks.json — R1처럼 기록 훅만 연결되어 있습니다. 다른 경로를 막는 route-policy는 아직 등록되지 않습니다.lab-results/CURRENT — r2로 바뀝니다. docs/guide.md는 R1과 같은 내용이고 새 r2/document-before.sha256에 지문을 저장합니다.재사용할 .cursor/skills/lab-document-review/SKILL.md도 함께 설치되지만 여기서는 호출하지 않습니다. review-report.json은 다음 Agent가 프로그램을 실행해야 생깁니다.
이번 단계와 관련된 파일만 표시합니다. 다른 사용자 파일과 이전 단계의 결과·일부 도구 파일은 남아 있을 수 있습니다. archive/는 보관할 이전 기록이나 직접 편집본이 있을 때만 생깁니다.
PROJECT_ROOT/
├── .cursor/hooks.json ← 이번 단계의 호출 연결
├── .cursor/hooks/
│ ├── cursor-harness-lab-observe.py ← 기록 훅
│ ├── cursor-harness-lab-deny-write.py ← 파일만 준비, 미연결
│ └── cursor-harness-lab-deny-commit.py ← 파일만 준비, 미연결
├── docs/guide.md ← 고정 예제 문서
├── .cursor/rules/cursor-harness-lab-review-route.mdc
├── .cursor/skills/lab-document-review/SKILL.md
├── .cursor/hooks/cursor-harness-lab-route-policy.py ← 미연결
└── lab-results/
├── CURRENT ← r2
├── vcs.txt ← 선택한 git 또는 svn
├── snapshot.py / compare.py
├── cursor_harness_lab_shell.py
├── review_doc.py / route_summary.py
└── r2/document-before.sha256 ← 실행 전 문서 지문새 Agent 대화에서 R1과 완전히 같은 입력을 보냅니다.
docs/guide.md를 검토하고 문제점을 알려주세요. 파일은 수정하지 마세요.
고정 프롬프트 ──▶ Rule ──▶ Shell: python3 lab-results/review_doc.py docs/guide.md
└─▶ Read lab-results/r2/review-report.json ──▶ 답변
직접 docs/guide.md 읽기 ──▶ 허용되지만 instruction-noncompliance로 표시cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 lab-results/route_summary.py r2| 관찰 결과 | 요약의 판정 |
|---|---|
| 정확한 Shell 실행 + 프로그램 보고서 + 보고서 Read, 직접 문서 Read 없음 | route-matched |
| 직접 문서 Read, 명령 누락, 프로그램 보고서 누락 중 하나 | instruction-noncompliance |
review-report.json의 findings와 route_receipt는 공개된 files/review_doc.py가 씁니다. 준비에서 표준 라이브러리만 쓰는 프로그램 전체를 읽을 수 있습니다. 채팅이나 별도 파일의 “실행했다”는 주장은 근거로 받지 않습니다.
r2/route-summary.json에서 셸 실행, 프로그램 보고서, 보고서 읽기가 각각 구분됩니다.result=instruction-noncompliance이며 이것도 실제 관찰 결과입니다..cursor/rules/cursor-harness-lab-review-route.mdc가 프로그램 명령과 보고서 경로를 알려 줍니다.lab-results/review_doc.py가 문서를 줄 단위로 읽어 Markdown 제목과 아래 내용을 나눕니다.REQUIRED_HEADINGS로 누락을 찾고, Counter(headings)로 중복을 세며, 제목 아래 내용이 비었는지 검사합니다.findings와 실행 정보인 route_receipt를 JSON 파일로 씁니다. 모델은 원문 대신 이 결과를 읽습니다.프로그램은 AI를 호출하지 않습니다. 정해진 형식 검사는 반복하기 좋지만 문장의 설득력이나 사실관계까지 평가하지는 않습니다. 실행 영수증도 위조 불가능한 서명이 아니라 프로그램이 적은 정보입니다. 요약은 셸 실행·보고서·보고서 Read를 함께 확인합니다.
route-matched는 요약기가 확인하는 조건을 만족했다는 뜻입니다. 호출 순서나 추가 Grep·Glob까지 모두 검사하지는 않습니다. 정확한 순서가 궁금하면 원본 로그와 세션을 함께 읽으세요.
실행 뒤 생각해 보세요. 보고서는 있지만 Agent가 읽은 기록이 없다면 기대 경로를 마친 것일까요?
아직 아닙니다. 프로그램 실행과 모델의 결과 읽기는 별도 단계입니다. route-summary.json에서 각각 확인하세요. R2 지시를 따르지 않은 instruction-noncompliance도 실험에서 배울 수 있는 결과입니다.
03 · Rule과 실행 경로 / R3
이번에는 같은 규칙에 도구 제한을 더합니다. 검사 프로그램 실행과 그 결과 파일 읽기만 허용하고, 문서를 직접 읽거나 검색·수정하는 시도는 막아요. “이 경로로 해주세요”라고 요청하는 것과 다른 경로를 실제로 차단하는 것이 어떻게 다른지 비교해 보세요.
학습 목표R2의 지시에 실행 전 훅을 더해 승인된 명령과 보고서 Read만 허용합니다. 경로 안내와 실제 제한의 차이를 로그로 확인합니다.
preToolUse + beforeShellExecutioncd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/prepare.py r3 gitcd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/prepare.py r3 svn다음 요청으로 넘어가기 전에 Cursor 파일 목록에서 아래 파일을 열어보세요. 경로를 복사해 빠른 파일 열기(Mac Cmd+P, Windows/Linux Ctrl+P)에 붙여넣어도 됩니다.
.cursor/hooks.json — preToolUse와 beforeShellExecution 각각에 기록 훅 다음으로 cursor-harness-lab-route-policy.py가 추가됩니다. 정책 항목의 failClosed: true를 확인하세요..cursor/rules/cursor-harness-lab-review-route.mdc — 프로그램을 먼저 실행하라는 지시는 R2와 같고, 보고서 경로만 lab-results/r3/review-report.json으로 바뀝니다.lab-results/CURRENT — r3인지 봅니다. 문서는 그대로이고, 정책이 허용하는 Read 대상은 원문이 아니라 R3 보고서입니다.아직 R3 보고서는 없습니다. 먼저 승인된 프로그램이 보고서를 만들고, 그다음 Agent가 읽는 순서입니다. 준비만으로 두 단계가 실행되지는 않습니다.
이번 단계와 관련된 파일만 표시합니다. 다른 사용자 파일과 이전 단계의 결과·일부 도구 파일은 남아 있을 수 있습니다. archive/는 보관할 이전 기록이나 직접 편집본이 있을 때만 생깁니다.
PROJECT_ROOT/
├── .cursor/hooks.json ← 이번 단계의 호출 연결
├── .cursor/hooks/
│ ├── cursor-harness-lab-observe.py ← 기록 훅
│ ├── cursor-harness-lab-deny-write.py ← 파일만 준비, 미연결
│ └── cursor-harness-lab-deny-commit.py ← 파일만 준비, 미연결
├── docs/guide.md ← 고정 예제 문서
├── .cursor/rules/cursor-harness-lab-review-route.mdc
├── .cursor/skills/lab-document-review/SKILL.md
├── .cursor/hooks/cursor-harness-lab-route-policy.py ← 연결됨
└── lab-results/
├── CURRENT ← r3
├── vcs.txt ← 선택한 git 또는 svn
├── snapshot.py / compare.py
├── cursor_harness_lab_shell.py
├── review_doc.py / route_summary.py
└── r3/document-before.sha256 ← 실행 전 문서 지문새 Agent 대화에서 다시 같은 입력만 보냅니다.
docs/guide.md를 검토하고 문제점을 알려주세요. 파일은 수정하지 마세요.
┌─ Read/search/glob docs ── deny + 정책 로그
고정 프롬프트 ──▶ Agent ├─ 다른 Shell·체인·리다이렉션 ── deny + 정책 로그
└─ 정확한 review_doc 명령 ── allow
└─ 정확한 r3 보고서 Read ── allow ──▶ 답변Agent 실행 뒤 아래 자체 점검은 금지된 직접 Read 요청을 정책 스크립트에 한 번 전달합니다. 정책이 직접 남긴 deny라서 에이전트가 만든 주장을 대신 받지 않습니다. 이어서 경로 요약을 만듭니다.
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 -c 'import json,subprocess; payload={"workspace_roots":[],"hook_event_name":"preToolUse","tool_name":"Read","tool_input":{"file_path":"docs/guide.md"}}; subprocess.run(["python3",".cursor/hooks/cursor-harness-lab-route-policy.py"],input=json.dumps(payload),text=True,check=True)'
python3 lab-results/route_summary.py r3이것은 정해진 랩에서 Cursor 도구 경로를 제한하는 연습이며 OS 샌드박스나 모든 가능한 도구에 대한 보장이 아닙니다. 기존 사용자 훅은 제거하지 않으므로 정확한 프로그램 명령도 사용자 훅의 영향을 받을 수 있습니다. 정책 오류는 failClosed: true로 닫힙니다.
준비에서 files/route_policy.py 전문을 확인하세요. file_path와 path를 프로젝트 절대 경로로 맞춘 뒤, 정확한 review-report.json 읽기와 정확한 프로그램 명령 외에는 거부합니다. 명령 연결, 출력 전환, 인터프리터 조각, cat/grep류와 쓰기·삭제·편집 도구는 허용하지 않습니다.
r3/route-summary.json에서 정확한 프로그램 셸 실행과 프로그램 보고서가 구분되어 있습니다.events.jsonl에 자체 점검의 outcome=deny, reason=route-policy가 있고 정확한 명령의 허용과 따로 집계됩니다..cursor/hooks/cursor-harness-lab-route-policy.py는 allowed = False에서 시작합니다. Shell이면 command == APPROVED_COMMAND, Read이면 경로가 정확히 lab-results/r3/review-report.json인지 확인합니다. 조건에 맞을 때만 allow로 바뀝니다.
훅이 보는 것은 Cursor의 도구 요청입니다. 승인된 Python이 내부에서 문서를 여는 것은 별도의 Cursor Read 요청이 아닙니다. 프로그램은 원문을 읽고 모델에는 보고서만 읽게 역할을 나눕니다. 그래서 승인 프로그램 자체도 확인해야 합니다.
명령은 문자열을 정확히 비교합니다. 앞에 cd를 붙이거나 뒤에 다른 명령을 연결하면 거부됩니다. 파일 경로는 resolve()로 프로젝트 기준 절대 경로를 맞춰 비교합니다.
실행 뒤 생각해 보세요. 앞서 실행한 자체 점검에서 deny가 나오면 Agent도 금지된 Read를 시도했다고 볼 수 있나요?
아니요. 사람이 터미널에서 보낸 점검 요청입니다. 이것도 같은 로그에 남으므로 실제 Agent 시도와 혼동하지 마세요. 다른 경로를 막아도 Agent가 승인 경로를 끝까지 실행한다는 보장은 없어, 프로그램 실행과 보고서 읽기를 따로 확인합니다.
04 · Skill / 절차 재사용
프로젝트 Skill은 재사용 지시입니다. 새 에이전트나 강제 권한이 아니며, disable-model-invocation: true라서 사용자가 슬래시 명령을 직접 입력해야 시작됩니다.
Skill은 필요할 때 불러오는 재사용 지시 묶음입니다. 이 실습에서는 사용자가 슬래시 명령으로 호출합니다. 항상 적용되는 R2 Rule과 달리 지시를 불러오는 시점을 직접 고릅니다. 실행 권한을 새로 주는 기능은 아닙니다.
학습 목표SKILL.md에 담긴 절차를 읽고 /lab-document-review로 직접 호출합니다. 지시를 불러오는 것과 도구가 실제 실행되는 것을 구분합니다.
docs/guide.md 존재.cursor/skills/lab-document-review/SKILL.mdcd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/prepare.py r2 gitcd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/prepare.py r2 svn다음 요청으로 넘어가기 전에 Cursor 파일 목록에서 아래 파일을 열어보세요. 경로를 복사해 빠른 파일 열기(Mac Cmd+P, Windows/Linux Ctrl+P)에 붙여넣어도 됩니다.
.cursor/skills/lab-document-review/SKILL.md — name: lab-document-review와 disable-model-invocation: true를 찾습니다. 본문의 프로그램 실행 → 보고서 읽기 → 설명 절차도 읽어보세요.lab-results/CURRENT — r2입니다. R3에 있었다면 경로 제한 훅을 해제하고 R2로 돌아왔습니다. 이전 R2 결과는 archive/r2-NNN/에 보관됩니다..cursor/rules/cursor-harness-lab-review-route.mdc — R2 Rule도 함께 남아 있습니다. 이번 학습은 Skill을 직접 불러오는 방법이며, Rule과 Skill의 효과를 분리 비교하는 실험은 아닙니다.설치만으로 Skill이 실행되지는 않습니다. 아래 슬래시 명령을 새 Agent 대화에서 직접 호출한 뒤 새 보고서가 생기는지 확인합니다.
이번 단계와 관련된 파일만 표시합니다. 다른 사용자 파일과 이전 단계의 결과·일부 도구 파일은 남아 있을 수 있습니다. archive/는 보관할 이전 기록이나 직접 편집본이 있을 때만 생깁니다.
PROJECT_ROOT/
├── .cursor/hooks.json ← 이번 단계의 호출 연결
├── .cursor/hooks/
│ ├── cursor-harness-lab-observe.py ← 기록 훅
│ ├── cursor-harness-lab-deny-write.py ← 파일만 준비, 미연결
│ └── cursor-harness-lab-deny-commit.py ← 파일만 준비, 미연결
├── docs/guide.md ← 고정 예제 문서
├── .cursor/rules/cursor-harness-lab-review-route.mdc
├── .cursor/skills/lab-document-review/SKILL.md
├── .cursor/hooks/cursor-harness-lab-route-policy.py ← 미연결
└── lab-results/
├── CURRENT ← r2
├── vcs.txt ← 선택한 git 또는 svn
├── snapshot.py / compare.py
├── cursor_harness_lab_shell.py
├── review_doc.py / route_summary.py
└── r2/document-before.sha256 ← 실행 전 문서 지문/lab-document-review
└─ python3 lab-results/review_doc.py docs/guide.md
└─ lab-results/<r2|r3>/review-report.jsonR2 또는 R3를 준비하면 정식 목록의 Skill 원본이 활성 위치에 바이트 그대로 복사됩니다. 소스는 준비의 전체 파일 목록에서 언제든 확인할 수 있습니다.
/lab-document-review
| 실제 근거 | 확인할 값 |
|---|---|
review-report.json의 route_receipt.producer | cursor-harness-review-doc |
findings[].code | missing-required-heading, duplicate-heading, empty-section |
프로젝트 Skill 위치와 슬래시 호출은 Cursor Skills 문서를 따릅니다. 전역 Skill 설치나 네트워크 호출은 하지 않습니다.
Skill이 자동으로 실행되지 않나요? · Skill까지 포함해 실습 설정 끄기
SKILL.md 머리말의 name과 description은 이름과 용도를 알려 줍니다. disable-model-invocation: true로 정해 사용자가 /lab-document-review를 직접 불러옵니다. 본문은 프로그램 실행→보고서 읽기→설명을 지시합니다.
슬래시 호출은 지시를 불러오는 시작점입니다. 실제 Python 실행은 그 지시를 따른 Agent의 Shell 요청이며 활성 훅이 있으면 똑같이 검사받습니다. Skill이 R3의 제한을 건너뛰지는 않습니다.
여기서는 R2의 Rule도 함께 둡니다. 결과만으로 Skill 때문에 경로가 바뀌었다고 판정하지 않습니다. 이 장에서는 재사용 절차를 직접 불러오고 실제 실행 근거를 확인하는 방법을 익힙니다.
이 장의 준비 명령처럼 R2에서 시작하세요. R3를 유지하면 Skill의 준비 상태 확인을 위해 CURRENT를 직접 Read하는 요청도 거부됩니다. R3에서는 정확한 승인 프로그램이 내부에서 CURRENT를 확인한다는 점을 구분해야 합니다.
먼저 생각해 보세요. Skill에 “모든 도구를 허용한다”고 적으면 훅의 deny가 풀릴까요?
아니요. Skill은 모델의 지시이고 실행 권한은 훅 조건이 결정합니다. 절차 변경은 SKILL.md, 차단 조건 변경은 활성 정책 코드에서 다룹니다.
프로젝트 적용 · 필요한 연결만
예시는 파일 수정 거부 하나입니다. 엄격한 R3 전체 설정은 일상 프로젝트에 그대로 복사하지 않습니다. 먼저 실습에서 S2 준비를 다시 실행해 기준 원본 상태를 만든 뒤 활성 스크립트 하나만 가져갑니다.
학습 목표수정 거부 스크립트 하나와 호출 항목만 새 프로젝트에 옮깁니다. 기존 훅을 보존하며 적용·점검·해제하는 방법을 익힙니다.
아직 새 프로젝트로 이동하지 마세요. 아래 명령은 지금까지 사용한 실습 폴더에서 실행합니다.
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/prepare.py s2 gitcd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/prepare.py s2 svnlab-results/CURRENT가 s2이고 활성 cursor-harness-lab-deny-write.py의 거부 목록에 Write가 다시 포함됐는지 확인합니다. 앞서 직접 고친 파일은 lab-results/archive/active-edits-NNN/에 보관됩니다.
R2·R3를 사용했다면 실습 경로 Rule과 Skill은 제거됩니다. 아직 새 프로젝트는 바뀌지 않았습니다. 다음 단계부터 새 프로젝트의 파일을 편집합니다.
실습 프로젝트의 .cursor/hooks/cursor-harness-lab-deny-write.py 내용을 새 프로젝트의 같은 경로에 저장합니다. .cursor-harness-kit/, lab-results/, R3 경로 정책은 복사하지 않습니다.
파일 전체를 바꾸지 말고 기존 키와 훅을 남긴 채 preToolUse 배열에 아래 실습 항목만 추가합니다.
| 추가 전 예시 | 추가 뒤 예시 |
|---|---|
{
"version": 1,
"owner": "my-project",
"hooks": {
"preToolUse": [
{"command": "python3 user-hook.py",
"failClosed": false}
]
}
} | {
"version": 1,
"owner": "my-project",
"hooks": {
"preToolUse": [
{"command": "python3 user-hook.py",
"failClosed": false},
{"command": "python3 .cursor/hooks/cursor-harness-lab-deny-write.py",
"failClosed": true}
]
}
} |
아래 로컬 점검은 실제 파일을 만들지 않고 활성 훅에 Write와 Read를 한 번씩 전달합니다. Write deny, Read allow가 나와야 합니다.
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 -c 'import json,subprocess; p=".cursor/hooks/cursor-harness-lab-deny-write.py"; base={"workspace_roots":[],"hook_event_name":"preToolUse"}; [(lambda r: print(t, json.loads(r.stdout)["permission"]))(subprocess.run(["python3",p],input=json.dumps({**base,"tool_name":t,"tool_input":{"file_path":"hook-check.txt"}}),text=True,capture_output=True,check=True)) for t in ("Write","Read")]'끄려면 방금 추가한 정확한 명령의 항목 하나만 hooks.json에서 제거합니다. 다른 훅과 스크립트는 건드리지 않습니다.
커밋 실습을 골랐다면 Git은 로컬 저장소만, SVN은 기존 작업 복사본만 사용합니다. 원격 사용은 필요하지 않습니다.
deny-write는 CURRENT가 없으면 기록을 건너뛰고 정상적으로 deny를 출력합니다. 이 정책 하나에는 결과 폴더가 필수가 아닙니다. 복사한 훅의 메시지에 lab-results를 보라는 문구가 있어도 이 프로젝트에는 로그가 없을 수 있습니다. 반면 R3는 CURRENT=r3와 정확한 프로그램·보고서 경로를 전제로 하므로 같은 방식으로 옮기면 안 됩니다.
S4의 커밋 거부를 옮길 때는 조건이 다릅니다. 커밋 훅과 함께 lab-results/cursor_harness_lab_shell.py도 같은 상대 경로에 필요합니다. SVN을 선택하려면 lab-results/vcs.txt도 옮깁니다. 기록용 CURRENT가 없어도 거부는 동작하지만, 공통 모듈이 없으면 정상 판단이 아니라 훅 실행 오류가 됩니다.
앞서 실행한 로컬 점검은 응답만 확인합니다. hooks.json 연결은 새 Cursor 대화에서 작은 읽기·수정 요청을 보내 도구 결과까지 확인해야 합니다. 새 프로젝트 터미널에서는 그 폴더에서 PROJECT_ROOT도 다시 설정하세요.
옮기기 전에 설명해 보세요. 언제 실행하는가, 무엇을 검사하는가, 오류 나면 어떻게 하는가는 각각 어디에 있나요?
이벤트 preToolUse는 hooks.json, 도구 이름 조건은 Python, 오류 처리인 failClosed는 등록 항목에 있습니다. 셋을 구분하면 필요한 부분만 바꿀 수 있습니다.
프로젝트 적용 · 문제 해결
학습 목표등록, 실행 위치, 실제 도구 입력, 훅 응답 순서로 확인합니다. 조건을 바꾸기 전에 어느 연결에서 문제가 생겼는지 찾아봅니다.
python3 .cursor-harness-kit/manage.py status에서 enabled를 보고, .cursor/hooks.json에 실습 명령이 등록됐는지 확인합니다. 기대 파일은 lab-results/<CURRENT>/events.jsonl입니다.PROJECT_ROOT를 열린 프로젝트 루트로 다시 설정하고 lab-results/CURRENT가 실행할 시나리오인지 확인한 뒤 해당 준비 명령을 다시 실행합니다.lab-results/archive/active-edits-NNN/에 남고 활성 파일은 기준 원본으로 복구됩니다. cursor_harness_lab_shell을 찾을 수 없다는 오류라면 키트 갱신 뒤 준비 명령까지 실행했는지 확인합니다.reason=deny-unsupported-shell 또는 deny-invalid-shell인지 확인합니다. command_issue가 있는 unknown은 커밋 발견이 아니라 구문 거부입니다. S4의 지원 범위를 보고 단순 명령으로 나누어 요청하세요.events.jsonl의 tool을 실제 결과로 인정합니다. 직접 편집 확인은 모델 선택에 기대지 말고 files/probe.py를 실행합니다.outcome=deny와 reason을 남깁니다. S5와 S4 결과를 대조합니다.prepare.py s2 <git|svn>는 활성 파일을 다시 씁니다. 보관본은 lab-results/archive/active-edits-NNN/.cursor/hooks/에서 찾습니다.로그가 없다는 것만으로는 판단할 수 없습니다. 등록이 없거나 다른 프로젝트·시나리오에 기록됐거나 기록 전에 오류가 났을 수 있습니다. failClosed가 true이면 정상 deny 로그 없이도 훅 오류로 차단될 수 있습니다.
manage.py status와 hooks.json으로 호출 연결을 봅니다.먼저 생각해 보세요. probe는 맞는데 Cursor에서 다르게 보이면 어디부터 볼까요?
probe는 등록을 거치지 않으므로 hooks.json의 이벤트·명령·활성 파일부터 봅니다. 그다음 실제 도구 입력이 같은지 확인합니다. 처음부터 Python 조건을 다시 바꿀 필요는 없습니다.
프로젝트 적용 · 마무리
학습 목표원하는 변화에 맞는 Hook·Rule·Skill을 고르고 이유를 설명합니다. 실습 설정은 끄거나 필요한 것만 의도적으로 남깁니다.
이제 결과를 자기 말로 설명해 보세요. 어떤 입력이 들어와서, 어느 파일의 조건을 지나고, 어떤 응답 때문에 결과가 달라졌는가를 연결하면 다른 프로젝트에도 적용할 수 있습니다.
| 하려는 일 | 담당 파일 | 강제로 막는가 |
|---|---|---|
| 실행 전후 기록 | cursor-harness-lab-observe.py | 아니요 |
| 파일 수정 거부 | cursor-harness-lab-deny-write.py | 예 |
| 커밋 거부 | cursor-harness-lab-deny-commit.py | 예 |
| 커밋하지 말라는 지시 | cursor-harness-lab-no-commit.mdc | 아니요 |
| 문서 검토 절차 재사용 | lab-document-review/SKILL.md | 아니요 |
| 정해진 검토 경로 강제 | cursor-harness-lab-route-policy.py | 예 · 실습 범위만 |
규칙을 따르지 않은 결과는 관찰 대상이고, 실제 거부는 훅이 담당합니다.
probe.json 또는 해당 결과 파일로 확인했습니다.manage.py disable 뒤 실습 훅 등록이 0인지 확인했거나, 필요한 설정만 의도적으로 남겼습니다.lab-results/management-backups/에 있고 기록과 사용자 파일은 그대로입니다.R3는 정의된 Cursor 도구 경로를 제한하는 학습용 설정이지 운영체제 샌드박스가 아닙니다. 프로그램 보고서도 임의의 파일 변경 전체를 막는 보안 장치는 아닙니다.
막히는 질문은 훅의 입력과 응답, Rule과 거부의 차이, 검사 프로그램, 허용 경로로 돌아가 실제 파일에서 근거를 찾아보세요.