처음이라면 기본 과정부터
첫 기록부터 직접 수정과 복원까지 따라가 보세요. 한 가지 설정을 바꾸고, 효과를 확인하고, 다시 끄는 방법을 익힙니다.
CURSOR AGENT 직접 실습
훅(Hooks), 규칙(Rules), Skill을 처음 보는 사람도 프로젝트 안의 훅 파일 하나를 직접 고쳐 효과를 확인하고, 실습 설정을 끄거나 복원한 뒤, 필요한 파일만 자기 프로젝트로 옮길 수 있습니다.
첫 기록부터 직접 수정과 복원까지 따라가 보세요. 한 가지 설정을 바꾸고, 효과를 확인하고, 다시 끄는 방법을 익힙니다.
커밋 제한, 라우팅, 세션 분석, Skill은 필요한 것만 선택해도 됩니다. 기본 과정 뒤에는 자기 프로젝트에 설정 하나를 옮기는 안내도 준비했습니다.
용어와 전체 흐름
Cursor Agent는 요청을 받고 도구를 골라 실행합니다. 이 실습은 그 앞뒤에 프로젝트 파일을 연결해 동작을 기록하거나 막고, 같은 입력이 어떤 길로 실행됐는지 실제 결과 파일로 확인합니다.
에이전트가 읽고 따르는 프로젝트 지시입니다. 행동을 유도하지만 실행 자체를 강제로 막지는 않습니다.
도구 실행 전이나 후에 자동으로 불리는 로컬 프로그램입니다. 기록하거나 허용·거부를 반환할 수 있습니다.
사용할 때 불러오는 재사용 지시 묶음입니다. 새 에이전트도, 강한 권한도 아닙니다.
요청이 직접 파일 읽기로 가는지, 정해진 프로그램을 거치는지 보여 주는 순서입니다.
phase=attempt는 실행 전 시도, outcome=allow는 실행 후 허용, deny는 거부, fail은 실행 실패입니다. reason은 거부 이유입니다.
.cursor-harness-kit/files/deny_write.py는 다시 설치할 기준 원본이고, .cursor/hooks/cursor-harness-lab-deny-write.py는 Cursor가 실제 호출하는 활성 복사본입니다. 뒤에서 활성 복사본만 직접 고칩니다.
모든 작업은 열린 프로젝트 안에서만 이뤄집니다. 전역 설정, 네트워크, 서브에이전트, 자동 커밋은 사용하지 않습니다. 각 고정 비교 전에는 새 Agent 대화를 열어 이전 대화의 영향을 줄입니다.
시작 전
README.md는 학습자가 준비했습니다. 키트가 생성하거나 내용을 강제하지 않습니다.python3가 동작합니다. 전역 Cursor 설정은 바꾸지 않습니다.현재 터미널이 프로젝트 루트일 때 한 번 실행합니다. 새 터미널을 열면 셸 변수는 이어지지 않으므로 이 초기화를 다시 실행해야 합니다.
export PROJECT_ROOT="$PWD"
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit실습 키트 준비
다운로드와 직접 만들기 중 어느 쪽을 골라도 같은 파일이 만들어집니다. 같은 이름의 파일에 다른 내용이 있으면 덮어쓰지 않고 멈춥니다. 여기서는 파일만 준비하고, Cursor 설정은 첫 실습에서 켜볼 거예요.
Git은 로컬 저장소만, SVN은 기존 작업 복사본만 사용합니다. 선택은 lab-results/vcs.txt에 기록됩니다.
전체 목록은 아직 외우지 않아도 됩니다. 준비 명령이 원본에서 활성 복사본을 만들고, Cursor가 훅 목록을 읽고, 결과는 별도 폴더에 쌓입니다.
PROJECT_ROOT/ ├── .cursor-harness-kit/prepare.py ← 준비 명령 ├── .cursor/hooks.json ← Cursor가 읽는 훅 목록 └── lab-results/ ← 보존되는 기록과 비교 결과
이 페이지 안의 원본을 tar 파일로 저장한 뒤 임시 폴더에서 검사하고 프로젝트에 복사합니다.
cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
export LAB_KIT_UNPACK="${TMPDIR:-/tmp}/cursor-harness-kit-$$"
mkdir "$LAB_KIT_UNPACK" || exit
tar -xf "${HOME}/Downloads/cursor-harness-kit.tar" -C "$LAB_KIT_UNPACK" || exit
python3 "$LAB_KIT_UNPACK/cursor-harness-kit/install.py"브라우저 저장 위치가 다르면 tar 경로만 실제 위치로 바꿉니다.
페이지에 들어 있는 같은 원본을 한 번의 로컬 명령으로 만듭니다. 실행 전에 전체 보기를 눌러 내용을 확인할 수 있습니다.
두 설치 방법이 함께 사용하는 정식 파일 목록입니다. 각 원본도 아래에서 따로 펼쳐 복사할 수 있습니다.
| 키트 경로 | 하는 일 | 언제 실행 | 효과 | 결과 |
|---|
.cursor-harness-kit/prepare.py, manage.py, files/가 있습니다.lab-results/는 바뀌지 않았습니다..cursor/hooks.json과 홈 전역 설정은 바뀌지 않았습니다.첫 동작 기록 · S1
먼저 비교 기준을 만듭니다. 준비 명령은 이전 실습 훅만 바꾸고 사용자 훅과 규칙은 남긴 채 기록 훅을 활성화합니다.
● 새 파일: CURRENT·활성 스크립트↻ 교체: 실습 소유 훅 항목▶ 활성: 기록 훅만
프로젝트/
├── README.md ← 기존 파일 유지
├── .cursor/
│ ├── hooks.json ← 이번 설정으로 변경 (사용자 훅 유지)
│ ├── hooks/
│ │ ├── cursor-harness-lab-observe.py ← 기록 켜짐
│ │ ├── cursor-harness-lab-deny-write.py ← 사용하지 않음
│ │ └── cursor-harness-lab-deny-commit.py ← 사용하지 않음
│ └── rules/ ← 사용자 규칙 유지
└── lab-results/
├── CURRENT ← s1
├── vcs.txt ← 선택한 Git 또는 SVN
├── snapshot.py / compare.py ← 결과 정리 도구
├── s1/
│ ├── events.jsonl ← 실행 기록
│ └── summary.md ← 실행 후 요약 명령으로 생성
└── archive/ ← 재실행 전 기록 보관cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/prepare.py s1 gitcd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/prepare.py s1 svn새 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에서 수정 도구의 허용을 확인했습니다.같은 요청 거부 · S2
S1과 똑같은 요청을 보내되, 이번에는 기록 훅 다음에 수정 거부 훅을 연결합니다. 사용자 훅·규칙과 S1 기록은 그대로 남습니다.
프로젝트/
├── README.md ← 기존 파일 유지
├── .cursor/
│ ├── hooks.json ← 이번 설정으로 변경 (사용자 훅 유지)
│ ├── hooks/
│ │ ├── cursor-harness-lab-observe.py ← 기록 켜짐
│ │ ├── cursor-harness-lab-deny-write.py ← 수정 차단 켜짐
│ │ └── cursor-harness-lab-deny-commit.py ← 사용하지 않음
│ └── rules/ ← 사용자 규칙 유지
└── lab-results/
├── CURRENT ← s2
├── vcs.txt ← 선택한 Git 또는 SVN
├── snapshot.py / compare.py ← 결과 정리 도구
├── s2/
│ ├── events.jsonl ← 실행 기록
│ └── summary.md ← 실행 후 요약 명령으로 생성
└── archive/ ← 재실행 전 기록 보관cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/prepare.py s2 gitcd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/prepare.py s2 svn새 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를 확인했습니다.직접 편집 실습
이제 활성 훅의 작은 조건 하나를 직접 고칩니다. Agent가 어떤 도구를 고를지 기다리지 않고, 로컬 점검 프로그램이 두 요청을 똑같이 전달해 결과를 따로 기록합니다.
.cursor/hooks/cursor-harness-lab-deny-write.py 한 개.cursor-harness-kit/files/deny_write.pyPROJECT_ROOT/ ├── .cursor-harness-kit/files/deny_write.py ← 기준 원본, 그대로 둠 ├── .cursor/hooks/cursor-harness-lab-deny-write.py ← 직접 고칠 활성 복사본 └── lab-results/custom/probe.json ← 점검 뒤 새 결과
이 명령은 활성 복사본을 기준 원본으로 다시 씁니다. 기존 활성 복사본을 이미 고쳤다면 먼저 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/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이 S1·S2 기록과 분리돼 있습니다.준비 뒤 직접 수정이 사라졌나요? · 아래에서 끄고 복원할 수 있습니다.
적용 · 끄기 · 복원
실습을 마쳤거나 평소 작업으로 돌아가고 싶다면 이번에 추가한 설정만 끄면 됩니다. 직접 고친 훅은 백업해 두었다가 다시 켤 수 있어요. 원래 사용하던 파일과 설정, 실험 기록은 남겨둡니다.
.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 뒤 실습 훅이 더는 등록되지 않음을 확인했습니다.지금 배운 것을 써보고 싶다면 내 프로젝트에 필요한 설정만 옮기기로 넘어가세요. 결과가 예상과 달랐다면 세션 기록으로 원인 찾기를, 더 연습하고 싶다면 아래 응용 실습을 선택하면 됩니다. 모든 응용을 끝내야 적용할 수 있는 것은 아니에요.
여기부터 선택 실습입니다. 커밋 동작, 기록 읽기, 같은 입력의 실행 경로, Skill 중 필요한 부분만 골라도 됩니다.
커밋 기록 · S3
먼저 차단 없이 커밋을 요청해 볼게요. 준비 명령이 commit-input.txt에 이번 실험용 변경을 만들어주므로 따로 파일을 고칠 필요가 없습니다. 실제 커밋은 뒤에서 에이전트에게 요청합니다.
프로젝트/
├── README.md ← 기존 파일 유지
├── .cursor/
│ ├── hooks.json ← 이번 설정으로 변경 (사용자 훅 유지)
│ ├── hooks/
│ │ ├── cursor-harness-lab-observe.py ← 기록 켜짐
│ │ ├── cursor-harness-lab-deny-write.py ← 사용하지 않음
│ │ └── cursor-harness-lab-deny-commit.py ← 사용하지 않음
│ └── rules/ ← 사용자 규칙 유지
└── lab-results/
├── CURRENT ← s3
├── vcs.txt ← 선택한 Git 또는 SVN
├── snapshot.py / compare.py ← 결과 정리 도구
├── commit-input.txt ← 이번 커밋 실험용 변경
├── s3/
│ ├── events.jsonl ← 실행 기록
│ └── summary.md ← 실행 후 요약 명령으로 생성
└── archive/ ← 재실행 전 기록 보관cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/prepare.py s3 gitcd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/prepare.py s3 svn새 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 외의 학습자 파일은 준비가 바꾸지 않았습니다.커밋 거부 · S4
준비 명령이 비교용 변경을 만들고 커밋 거부 훅을 연결합니다. 상태와 차이 조회는 허용하고 커밋만 거부되는지 확인합니다.
프로젝트/
├── README.md ← 기존 파일 유지
├── .cursor/
│ ├── hooks.json ← 이번 설정으로 변경 (사용자 훅 유지)
│ ├── hooks/
│ │ ├── cursor-harness-lab-observe.py ← 기록 켜짐
│ │ ├── cursor-harness-lab-deny-write.py ← 사용하지 않음
│ │ └── cursor-harness-lab-deny-commit.py ← 커밋 차단 켜짐
│ └── rules/ ← 사용자 규칙 유지
└── lab-results/
├── CURRENT ← s4
├── vcs.txt ← 선택한 Git 또는 SVN
├── snapshot.py / compare.py ← 결과 정리 도구
├── commit-input.txt ← 이번 커밋 실험용 변경
├── s4/
│ ├── events.jsonl ← 실행 기록
│ └── summary.md ← 실행 후 요약 명령으로 생성
└── archive/ ← 재실행 전 기록 보관cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/prepare.py s4 gitcd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/prepare.py s4 svn새 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/없음 | 조회 허용 |
s4/events.jsonl, s4/summary.md, compare-s3-s4.md에서 커밋 거부와 reason=deny-commit을 확인했습니다.추가 또는 메시지 조건을 직접 설계하고 별도 결과 이름을 쓰세요. 본편 프롬프트 B는 사용하지 않습니다.
규칙과 훅 대조 · S5
커밋 거부 훅 대신 “커밋하지 마세요”라는 규칙(Rule)만 둡니다. 기존 사용자 규칙은 남기고 비교용 변경을 새로 만듭니다.
프로젝트/
├── README.md ← 기존 파일 유지
├── .cursor/
│ ├── hooks.json ← 이번 설정으로 변경 (사용자 훅 유지)
│ ├── hooks/
│ │ ├── cursor-harness-lab-observe.py ← 기록 켜짐
│ │ ├── cursor-harness-lab-deny-write.py ← 사용하지 않음
│ │ └── cursor-harness-lab-deny-commit.py ← 사용하지 않음
│ └── rules/ ← 사용자 규칙 유지 + 실습 커밋 금지 규칙 추가
└── lab-results/
├── CURRENT ← s5
├── vcs.txt ← 선택한 Git 또는 SVN
├── snapshot.py / compare.py ← 결과 정리 도구
├── commit-input.txt ← 이번 커밋 실험용 변경
├── s5/
│ ├── events.jsonl ← 실행 기록
│ └── summary.md ← 실행 후 요약 명령으로 생성
└── archive/ ← 재실행 전 기록 보관cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/prepare.py s5 gitcd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/prepare.py s5 svn새 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가 허용이면 규칙만으로는 부족하다는 뜻입니다.
s5/events.jsonl, s5/summary.md, compare-s5-s4.md가 실제 결과입니다.deny-commit 이유가 없고 S4에는 있습니다.규칙 문구와 직접 만든 입력을 바꿔 별도 결과로 비교하세요.
기록 깊게 읽기 · S6
S6에는 준비 명령이 없습니다. CURRENT, 훅, 규칙, 결과 폴더를 바꾸거나 새 기록을 만들지 않고 S1~S5 결과만 읽습니다.
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 | 도구와 셸 명령 |
reason | deny-write 또는 deny-commit |
선택 학습 · 기록으로 원인 찾기
마지막 답변만 봐서는 왜 그런 결과가 나왔는지 알기 어렵습니다. 요청부터 도구 호출, 실행 결과까지 순서대로 읽으면 처음 예상에서 벗어난 지점을 찾을 수 있어요. 여기서는 기록을 읽기만 하고 설정이나 파일 내용은 바꾸지 않습니다.
| 기록 | 무엇을 알 수 있나요? | 무엇까지는 알 수 없나요? |
|---|---|---|
| 세션 기록 | 내 요청, 에이전트 답변, 표시된 도구 요청과 결과의 순서 | 대화를 일부만 복사했다면 빠진 도구 호출이 있을 수 있습니다. |
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에 세션과 실제 훅 기록의 줄 번호를 각각 적었습니다.직접 검토 · R1
먼저 에이전트가 문서를 직접 읽고 검토하게 해볼게요. 이 단계에서는 도구 사용을 기록만 하고, 별도 검사 프로그램을 쓰라는 지시는 넣지 않습니다. 다음 단계와 비교할 첫 번째 실행 경로를 남기는 과정입니다.
docs/guide.md의 원본 바이트 · 아래 프롬프트프로젝트/
├── docs/guide.md ← 결함 3종이 있는 고정 입력, 준비 전 충돌 검사
├── .cursor/hooks.json ← 기록 훅만 활성
├── .cursor/rules/ ← 경로 지시 없음, 사용자 규칙 보존
└── lab-results/
├── review_doc.py ← R1에서는 실행하지 않는 비교용 검토 프로그램
├── route_summary.py ← 관찰 결과와 기대 경로 비교
└── r1/
├── document-before.sha256 ← 실행 전 원본 문서 지문
├── events.jsonl ← 직접 Read 근거
└── route-summary.{json,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새 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가 같아 원본 문서가 바뀌지 않았습니다.프로그램 경로 지시 · R2
이번에는 문서와 요청을 그대로 두고, 검사 프로그램을 먼저 실행하라는 규칙만 추가합니다. 에이전트가 직접 판단하는 대신 프로그램의 결과를 읽고 설명하는지 살펴보세요. 규칙을 따르지 않고 직접 문서를 읽었다면 그것도 결과에 표시됩니다. 지침만으로 실행 경로가 바뀌는지 확인하는 실험이에요.
프로젝트/
├── docs/guide.md ← R1과 바이트가 같은 고정 입력
├── .cursor/
│ ├── hooks.json ← 기록 훅만 활성
│ └── rules/cursor-harness-lab-review-route.mdc ← 정확한 명령 실행 후 JSON 읽기 지시
└── lab-results/r2/
├── document-before.sha256
├── events.jsonl ← Read와 Shell 경로를 따로 기록
├── review-report.json ← review_doc.py가 만든 결과와 실행 영수증
└── route-summary.{json,md} ← 기대 경로와 실제 경로 비교cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/prepare.py r2 gitcd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/prepare.py r2 svn새 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이며 이것도 실제 관찰 결과입니다.프로그램 경로 강제 · R3
이번에는 같은 규칙에 도구 제한을 더합니다. 검사 프로그램 실행과 그 결과 파일 읽기만 허용하고, 문서를 직접 읽거나 검색·수정하는 시도는 막아요. “이 경로로 해주세요”라고 요청하는 것과 다른 경로를 실제로 차단하는 것이 어떻게 다른지 비교해 보세요.
preToolUse + beforeShellExecution프로젝트/
├── docs/guide.md ← 직접 Read/search/glob 차단
├── .cursor/
│ ├── hooks.json ← 기록 다음 route-policy, failClosed=true
│ ├── hooks/cursor-harness-lab-route-policy.py ← 정확한 명령·보고서만 허용
│ └── rules/cursor-harness-lab-review-route.mdc ← R2와 같은 실행 지시
└── lab-results/r3/
├── events.jsonl ← allow/deny와 세부 이유
├── review-report.json ← 허용된 프로그램만 만드는 결과
├── document-{before,after}.sha256
└── route-summary.{json,md}cd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/prepare.py r3 gitcd "${PROJECT_ROOT:?먼저 프로젝트 경로를 설정해 주세요}" || exit
python3 .cursor-harness-kit/prepare.py r3 svn새 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가 있고 정확한 명령의 허용과 따로 집계됩니다.선택 · 문서 검토 Skill
프로젝트 Skill은 재사용 지시입니다. 새 에이전트나 강제 권한이 아니며, disable-model-invocation: true라서 사용자가 슬래시 명령을 직접 입력해야 시작됩니다.
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/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 설치나 네트워크 호출은 하지 않습니다.
내 프로젝트에 적용
예시는 파일 수정 거부 하나입니다. 엄격한 R3 전체 설정은 일상 프로젝트에 그대로 복사하지 않습니다. 먼저 실습에서 S2 준비를 다시 실행해 기준 원본 상태를 만든 뒤 활성 스크립트 하나만 가져갑니다.
실습 프로젝트의 .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은 기존 작업 복사본만 사용합니다. 원격 사용은 필요하지 않습니다.
문제 해결
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/에 남고 활성 파일은 기준 원본으로 복구됩니다.events.jsonl의 tool을 실제 결과로 인정합니다. 직접 편집 확인은 모델 선택에 기대지 말고 files/probe.py를 실행합니다.outcome=deny와 reason을 남깁니다. S5와 S4 결과를 대조합니다.prepare.py s2 <git|svn>는 활성 파일을 다시 씁니다. 보관본은 lab-results/archive/active-edits-NNN/.cursor/hooks/에서 찾습니다.마무리
| 하려는 일 | 담당 파일 | 강제로 막는가 |
|---|---|---|
| 실행 전후 기록 | 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 도구 경로를 제한하는 학습용 설정이지 운영체제 샌드박스가 아닙니다. 프로그램 보고서도 임의의 파일 변경 전체를 막는 보안 장치는 아닙니다.