출처와 신뢰 등급
이 문서의 모든 주장은 아래 단일 커밋에 고정된 저장소 상태와, 캡처 시점의 GitHub·PyPI API 응답만을 근거로 한다. 코드는 한 줄도 실행하지 않았다 (설치·빌드·테스트·import 전부 없음).
47f37a6dd5baae9f10f16e21e50a6898ee42cd22 (main, 캡처 전후 드리프트 없음)b32cedef5441a721026bd16f5100a6f627a9596f6df62909-cd29-444a-aa5d-f83b85c72c91B인 이유: 불변 식별자 고정 + 완전한 인벤토리 + 선정 파일 전수 정독까지 갖췄지만, 정적 분석의 천장에 걸린다 — 실기기 동작은 미검증(runtime unknown)이고, 의존성·코드 스캔 상태는 API가 거부(403)해 알 수 없다.
읽기 전에 — 10분 가이드
핵심 질문 하나
"에이전트에게 내 실제 폰의 손과 눈을 쥐여주는 가장 얇은 방법은 무엇이고, 그 대가로 나는 어떤 통제를 잃는가?"
멘탈 모델
폰을 하나의 화면 좌표 공간으로 환원한다: 눈 = 스크린샷 + OCR(또는 접근성 트리), 손 = 좌표 탭/키 입력. iOS와 Android의 물리적 차이(비디오 스트림 vs adb)는 전부 send(op) 한 개의 어휘 뒤에 숨는다. DOM이 없으므로 검증도 스크린샷이 곧 진실이다.
권장 읽기 순서
- 채택 판단 — 나에게 필요한 물건인지 3분 안에 결정
- 아키텍처 — transport 한 개 이음매의 설계를 이해
- 보안 · 프라이버시 — 프롬프트-규칙과 코드-강제의 경계 (이 문서의 가장 중요한 발견)
- 실제 구현 — 코드가 실제로 하는 일, 전 주장 file:line 인용 (README는 번역하지 않는다 — 소스가 SSOT)
선행 개념 (모르면 막히는 것만)
- iPhone 미러링 — macOS Sequoia+가 아이폰 화면을 맥 창으로 띄우고 마우스·키보드를 터치로 전달하는 기능. 이 창은 비디오 스트림이라 접근성 트리가 없다.
- adb — Android Debug Bridge. USB/Wi-Fi로 폰에 직접 명령(
screencap,input tap)을 보내는 공식 도구. - 접근성 트리 — OS가 화면 요소를 텍스트+좌표로 보고하는 구조. Android엔 있고(
uiautomator), 미러링 창엔 없다 — 그래서 iOS 쪽은 OCR이 대신한다.
점검 질문 3
- iOS 백엔드는 왜 접근성 트리 대신 Vision OCR을 쓰는가? 그 결과
screen.text의source필드는 왜 필요한가? - "PIN을 절대 입력하지 않는다"는 규칙은 iOS와 Android에서 각각 어떤 방식으로 지켜지는가 — 코드인가 프롬프트인가?
- 붙여넣은 텍스트가 맥 클립보드에 남는 것은 버그인가 설계인가? 저자가 복원을 기본값으로 하지 않은 이유는?
예상 소요: 요약+판단만 10분 · 완독 35–45분
채택 판단
무엇: 에이전트(Claude Code/Codex)가 사용자의 실제 폰을 구동하게 하는 ~3.7천 줄짜리 파이썬 하네스. iPhone은 맥의 미러링 창을 OCR로 읽고 CGEvent로 두드리며, Android는 adb로 직접 만진다. 폰엔 아무것도 설치하지 않는다.
왜 주목: 공개 2주 남짓에 스타 1,997개. "제일 얇은 층으로 실기기를 잡는다"는 설계 취향이 일관되고, 코드 주석이 실패 경험(안 되는 것들)을 이례적으로 정직하게 기록한다.
경계할 것: 안전 규칙 대부분이 프롬프트 규칙이지 코드 강제가 아니다. 테스트 0개, 메인테이너 1인, 비공개 API(SkyLight) 의존. 본질적으로 "임의 코드로 내 폰을 조작하는 표면"을 여는 도구다.
본인 폰으로 개인 자동화·에이전트 실험을 하고, 도구가 하는 일을 코드 수준에서 직접 읽고 감수할 수 있는 개발자. "폰 자동화 = Appium/WebDriverAgent 무게"라는 통념에 지친 사람에게 특히 신선하다.
팀/프로덕션 QA 파이프라인용 기기 자동화가 필요한 경우(테스트·CI·멀티 디바이스·계약 안정성 전무), 또는 타인의 기기·민감 계정이 물린 폰을 다루는 경우(동의 게이트가 코드로 강제되지 않음).
대안 대비 위치
| 도구 | 접근 | 대가 |
|---|---|---|
| phone-harness | 미러링 창 OCR + adb, 폰 무설치 | iOS는 픽셀 수준 인식뿐, 비공개 API, 1인 프로젝트 |
| Appium/XCUITest | 공식 테스트 스택, 요소 트리 | WebDriverAgent·프로비저닝의 무게, 에이전트 루프엔 과체중 |
| 클라우드 디바이스 팜 | 관리형 실기기 | 비용, 내 폰·내 계정이 아님 |
실제 구현 — 20파일 전수 정독 결과
README의 문장이 아니라 코드가 하는 일이다. 소스가 SSOT이고 README는 주장일 뿐이므로, 이 섹션의 모든 항목은 고정 커밋 47f37a6의 파일을 직접 읽고 file:line으로 인용한다 (경로 접두 src/phone_harness/ 생략).
엔트리포인트 → op 디스패치: 체인 전체
"에이전트가 폰을 만진다"의 실제 코드 경로는 다섯 홉이다.
- 진입 —
run.py:49-57: 서브커맨드가 아니면 stdin 전체를 읽어exec(code, g)—g는helpers모듈의 밑줄 없는 이름 전부(run.py:55). 사용법 자체가 "임의 파이썬 실행"이다. - 헬퍼 네임스페이스 —
helpers.py:514-528: import 시점에agent-workspace/agent_helpers.py를 자동 로드해 같은 네임스페이스에 합친다. 에이전트가 써넣은 코드가 다음 실행부터 표준 어휘가 된다. - op 어휘 —
transport.py:87-97:OP_NAMES25종 — screen 5 · input 6 · nav 3 · apps 3 · session 4 · focus 2 · tree/raw 2. - 디스패치 —
transport.py:113-117:Backend.send(op)가op를_screen_capture식 메서드명으로 바꿔 호출하고, 메서드가 없으면Unsupported를 던진다(transport.py:79-84). 능력 목록이 따로 없는 이유다. - 백엔드 선택 —
transport.py:128-143:connect(platform)이 인자 →PHONE_HARNESS_PLATFORM→ config → 기본 ios 순으로 결정해IPhone/Android객체를 반환한다(전역 설치 아님 — 한 스크립트가 기기 두 대를 동시에 잡을 수 있다,transport.py:133-135).
iOS 경로: 비디오 스트림을 더듬는 기술
- 창 찾기 —
mirror.py:27-74: 미러링 창을 지역화된kCGWindowOwnerName이 아니라 소유자 프로세스 PID(kCGWindowOwnerPID,mirror.py:51)로 찾는다 — 비영어 맥에서 창 이름이 번역돼도 동작(mirror.py:36-42). 크기 필터도 없앴다: Stage Manager가 창을 38×130으로 "주차"해두는 버그(#8)를 겪은 흔적(mirror.py:62-68). - 눈 —
mirror.py:301-324가screencapture -l <창id>로 창만 캡처하고,ocr.py:19-56이 VisionVNRecognizeTextRequest(accurate,ocr.py:28-29)로 모든 문자열의 박스를 얻어 좌하단 원점 정규화 좌표를 화면 좌표계로 뒤집고(ocr.py:44-47) 그대로tap()에 넣을 수 있는 중심점을 반환한다(ocr.py:51-52).ios.py:75-79가 이 결과에source="pixels"를 달아screen.text로 내보낸다. - 손 — HID 수준 CGEvent: 탭은
mirror.py:333-349(CGEventCreateMouseEvent→CGEventPost), 키는mirror.py:415-421. 수정자 키는 플래그 마스크가 아니라 실제로 눌러 유지한다(mirror.py:425의_holding; 미러링이 플래그를 떨어뜨림). shift 래치가 남아1,200이!<@))로 찍히던 버그를 고친 경위가 주석에 있다. - 붙여넣기 —
input.text기본값은 클립보드 붙여넣기(mirror.py:549-561). 이전 클립보드 복원은 의도적으로 기본 꺼짐:mirror.py:496-523docstring이 복원 레이스로 직전 클립보드(URL·토큰·암호)가 폰에 새는 사고를 명시하고, 잔류를 택한 이유와 opt-in 복원(ios.restore_clipboard,mirror.py:521-523)을 기록한다. - 백그라운드 모드 —
background.py:52-64가 SkyLight 비공개 프레임워크를ctypes로 직접 열고,background.py:165-177이 yabai가 역공학한 0xf8 이벤트 레코드를SLPSPostEventRecordTo로 프로세스에 직송한다 — 창 포커스 없이 입력. 기본 활성(PHONE_HARNESS_BACKGROUND=1,ios.py:45-46), import 실패 시 포그라운드mirror로 폴백(ios.py:47-52). - 세션 상태 —
ios.py:130-153: "연결됨"을 구조로 판별한다 — 비디오 스트림은 접근성 UI를 노출하지 않고 중간 화면(연결 버튼 등)은 노출한다(ios.py:149-150). 그리고_session_require(ios.py:164-190)는 Connect 버튼을 대신 눌러주지 않고 예외 메시지로 사용자를 부른다("I will not tap Connect for you",ios.py:190).
Android 경로: 창이 필요 없는 쪽
- adb 직결이라 포커스·창 개념이 없다. 눈 =
adb exec-out screencap -p(android.py:265-273), 정확한 텍스트·박스 =uiautomator접근성 트리(android.py:275-283,source="tree"confidence 1.0), 손 =input tap/swipe/text/keyevent(android.py:291-337). - 잠금 게이트가 코드로 존재 —
android.py:223-240: 모든 input op가_gate()를 먼저 호출하고(android.py:292,296,301,309,314,327), 게이트는dumpsys로 잠금을 읽어(android.py:215-221) 잠겨 있으면RuntimeError("I won't enter a PIN",android.py:234-239)로 거부한다. 깨우기(KEYCODE_WAKEUP)만 코드가 하고 잠금 해제는 사용자 몫. PIN 입력은 구현 자체가 없다. (iOS 쪽엔 상응하는 코드 게이트가 없다 — §9.) - USB 우선 → 페어링된 Wi-Fi 폰 순으로 자동 탐색(
android.py:160-211), 세션 동안 설정 변경 없이 깨어 있게 유지.input text는 ASCII 한 토큰 제약이라 개행·백스페이스를 keyevent로 쪼갠다(android.py:326-337).
실행 표면 (알아야 할 사실)
run.py:51-57은 stdin으로 받은 임의 파이썬을exec()한다 — 하네스의 사용법 자체가 "에이전트가 코드를 써서 폰을 만진다"이다.agent-workspace/agent_helpers.py는 에이전트가 편집하라고 만든 파일이며 모든 스크립트 네임스페이스에 자동 로드된다(helpers.py:514-528). 예:tap_icon(label)은 홈 화면에서 라벨 탭이 무효라는 것을 배운 뒤 라벨보다 ~35pt 위(아이콘)를 탭하도록 에이전트가 적어둔 헬퍼다(agent_helpers.py:9-15).- 설정은 XDG 규약(
~/.config/phone-harness의도 /~/.local/state학습 상태)으로 분리(config.py), 원자적 쓰기, 손상 파일은 경고 후 기본값 — 작지만 성숙한 습관.
아키텍처 — 이음매는 하나다
설계의 전부는 transport.py의 한 문장이다: "op는 플랫폼이 어떻게 하는지가 아니라 무엇을 의미하는지로 이름 짓는다." nav.home은 아이폰에선 비디오 스트림으로 가는 Cmd+1이고 픽셀에선 adb KEYCODE_HOME이지만, 헬퍼 층은 그 차이를 모른다.
설계 결정 세 가지가 물건의 성격을 정한다
- 능력 목록이 없다. 백엔드는 할 수 있는 op의 메서드만 정의하고, 없는 op는 자동으로
Unsupported예외가 된다 — "iPhone엔 Back이 없다"를 흉내 내지 않고 부재로 보고한다. 가짜 Back(엣지 스와이프 추측)은 호출자가 진짜와 구별할 수 없기 때문이라고 명시. - fidelity를 필드로 노출한다.
screen.text의 각 박스는source: "pixels" | "tree"를 달고 온다 — Vision은 깨끗한 텍스트에 confidence 1.0을 주므로 신뢰도 수치로는 OCR 오독과 OS 보고를 구별할 수 없다는 이유까지 docstring에 있다. - 데몬이 없다. 호출마다 창 좌표·캡처를 다시 묻는 무상태 설계. 지속해야 할 것(기본 플랫폼, 페어링된 폰)만 파일로 산다. 스크립트 하나가 기기 두 대를 동시에 잡을 수 있도록
connect()는 전역이 아니라 객체를 반환한다.
온보딩 — 이 분석에서는 실행하지 않았다
아래는 문서·코드가 기술하는 절차의 정적 요약이며, 우리는 설치·실행하지 않았다. 절차의 실제 동작(권한 프롬프트 타이밍, --doctor 판정)은 미검증이다.
- 저장소를
~/.phone-harness에 클론하고pip install -e .— 의존성은 PyObjC 계열(맥 전용).phone-harness가 PATH의 명령이 된다. SKILL.md를 에이전트 스킬(~/.claude/skills,$CODEX_HOME/skills)로 등록 — 에이전트가 알아서 집어 들게 하는 배포 방식.- 사용자 손이 필요한 것만 사용자에게: iPhone 미러링 최초 페어링, 터미널에 손쉬운 사용 + 화면 기록 권한(후자는 터미널 재시작 후 발효). Android는 개발자 옵션 + USB 허용 또는 무선 디버깅 6자리 페어링.
phone-harness --doctor가 체인을 검증하고,config set platform ios|android로 기본을 고른다.
onboarding.md의 규칙: "절대 PIN을 입력하지 않는다 · 묻지 않고 설정을 바꾸지 않는다 · 실패를 루프로 재시도하지 않는다." — 그리고 §5 데모는 사용자가 예라고 하면 폰에서 phone-harness.com을 열고 저장소에 스타를 누르는 흐름까지 안내한다 (opt-in 셀프 프로모션; §9).
정독 범위 — substantive 20파일 · 3,721줄 전수
인벤토리 26엔트리 중 blob 21개, 그중 빈 패키지 마커(__init__.py)를 제외한 20개를 전부 읽었다. 부분 정독 없음.
| 파일 | 줄 | 왜 읽었나 |
|---|---|---|
README.md | 139 | 프로젝트 주장 원천 — §11 원장·§4 대조 입력 전용, 완역하지 않음 (§12) |
SKILL.md | — | 에이전트-대면 사용 계약 · 동의 규칙의 원문 |
install.md | 100 | 설치 경로 · 권한 · "PIN 금지" 규칙 |
onboarding.md | 60 | 최초 실행 흐름 · 부작용 경계 · 스타 데모 |
LICENSE · pyproject.toml · .gitignore | — | 라이선스 게이트 · 의존성/엔트리포인트 · 위생 |
.github/workflows/publish.yml | 38 | 유일한 CI — PyPI trusted publishing |
phone-harness (런처) | 4 | 레포 루트 실행 shim |
agent-workspace/agent_helpers.py | 23 | 에이전트 편집 층 — 자동 로드 표면 |
src/phone_harness/run.py · admin.py | — | exec 표면 · --doctor 권한 진단 |
android.py (최대 모듈) | — | adb 전체 · 잠금 게이트 · 페어링 · awake |
ios.py · mirror.py · background.py · ocr.py | 207+563+339+57 | 아이폰 경로 전체: 세션 판별 · CGEvent · SkyLight · Vision |
transport.py · helpers.py · config.py | 144+—+280 | 이음매 · 프리미티브 · 설정/상태 분리 |
파일별 object id·sha256·줄수 원장은 공개 증거 인덱스의 selected-blobs 원장에 있다.
유지보수 신호 — 2026-08-24 관측
- 속도의 프로젝트다. 2026-08-07 생성 → 08-17/08-18 릴리즈 2회(0.1.0, 0.2.0) → 08-21 마지막 push. 전 커밋 66개가 전부 ShawnPana — 100% 단독 저작.
- 태그 4 ≠ 릴리즈 2.
0.1.1과0.1은 태그만 있고 GitHub 릴리즈가 없다. 그런데 0.1.1은 PyPI에는 올라갔다(08-17) — 릴리즈 노트 규율보다 배포 속도가 앞서는 단계. - 유일한 CI는 배포다. 워크플로 런 2회 모두
publish(성공). 테스트·린트 CI가 아예 없으므로 "CI green"은 품질 신호가 아니다. - 이슈: 열림 7 · 닫힘 3 (샘플). 첫 응답 실측: #17 생성(08-10 17:41Z) → 메인테이너 답변(08-11 04:58Z) = 11.3시간. 다만 #22와 #3은 메인테이너가 아닌 제3자(kri-ruj, Petrusreno)가 답했다 — 커뮤니티가 메인테이너보다 빨리 움직이는 구간이 있다.
- PR이 가장 큰 경고다. 샘플 30건 중 머지 14건은 전부 메인테이너 본인 PR이고, 외부 기여자 PR 13건(7인)은 모두 미머지 상태로 열려 있다. 외부 기여 수용 이력이 아직 0이다.
- 커뮤니티 문서 42%: license+readme만 있고 CONTRIBUTING·행동강령·이슈 템플릿 없음.
- 외부 확인: PyPI에
phone-harness 0.2.0라이브, project_urls가 저장소·phone-harness.com을 가리킴 — README의 배포 주장과 일치.
보안 · 프라이버시 — 프롬프트 규칙과 코드 강제의 경계
이 도구의 위험 평가에서 가장 중요한 축은 "안전 규칙이 어디에 사는가"다. 정독 결과, 코드로 강제되는 게이트는 Android 잠금/PIN 거부 하나이고 나머지는 전부 에이전트가 지키기를 기대하는 프롬프트 문장이다.
| 안전 규칙 | 사는 곳 | 강제 수준 |
|---|---|---|
| 잠긴 Android 폰은 구동 거부, PIN 입력 코드 부재 | android.py:223-240 (전 input op가 _gate() 경유) | 코드 강제 |
| "PIN·암호는 절대 입력하지 않는다" (iOS 포함) | SKILL.md:109 · install.md:96 · onboarding.md:56 | 프롬프트 — iOS엔 감지·차단 코드 없음 (C003) |
| "바깥으로 나가는 행동(발신 등) 전에 멈추고 묻는다" | SKILL.md:120-124 (Consent 절) | 프롬프트 — input.text/tap 코드엔 발신 검사 없음 (C004) |
| "묻지 않고 설정을 바꾸지 않는다 · 재시도 루프 금지" | onboarding.md:56 | 프롬프트 |
| Connect 버튼은 대신 누르지 않는다 (폰 연결 = 사용자) | ios.py:164-190 (_session_require) | 코드 강제 (예외 발생) |
구조적 위험
- 실행 표면:
run.py는 stdin의 임의 파이썬을exec()하고,agent_helpers.py는 에이전트가 편집하는 코드가 자동 import된다. 이건 결함이 아니라 제품 정의지만 — 하네스를 설치한 순간 "에이전트가 쓰는 코드 = 내 맥과 폰에서 도는 코드"가 된다. - 클립보드 잔류: 폰에 타이핑한 텍스트가 맥 클립보드에 남는다(기본). 복원-레이스로 이전 클립보드가 폰에 새는 사고보다 잔류가 낫다는 문서화된 트레이드오프. 민감 텍스트를 하네스로 입력한다면 알아야 한다.
- 비공개 API 의존: 백그라운드 모드는 SkyLight 역공학 호출이다. macOS 업데이트가 언제든 깨뜨릴 수 있고, Apple 관점에서 지원되지 않는 표면이다.
- 권한의 무게: 터미널에 손쉬운 사용+화면 기록을 준다 = 하네스가 아니라 그 터미널에서 도는 모든 것이 화면을 읽고 입력을 합성할 수 있게 된다.
- 셀프 프로모션 데모: 온보딩 §5가 (동의 시) 사용자의 폰으로 저장소에 스타를 누르게 한다. opt-in이 명시돼 있지만, "에이전트가 내 계정으로 하는 외부 행동"의 첫 사례가 프로젝트 자신의 스타라는 점은 알고 시작하는 게 좋다. 스타 1,997개 중 이 경로의 비중은 알 수 없다.
- 알 수 없음(unknown): Dependabot 알림·코드 스캔 상태는 API 403으로 확인 불가 — 0건이라는 뜻이 아니다. 취약점 공고는 0건(공개 API 기준).
이 분석은 다음을 하지 않았다: checkout · worktree · 의존성 설치 · build · test · run/import · 컨테이너 · git hooks · submodule · LFS · filter · textconv. 따라서 런타임 신뢰도는 unknown이 정직한 값이다.
라이선스
LICENSE 원문 + GitHub API spdx_id 이중 확인)주장 검증 원장 — 20건
README·SKILL의 주장과 정독 발견을 대조했다. 정적 분석이므로 실행 관측(execution-observed) 증거는 0건 — "supported"도 "코드가 그렇게 짜여 있음"이지 "실기기에서 그렇게 동작함"이 아니다. 전체 원장(JSON)은 증거 인덱스에 있다.
| # | 주장 | 판정 | 근거 요지 |
|---|---|---|---|
| C001 | 에이전트가 실제 폰을 직접 구동 (iOS 미러링 / adb) | 지지 | 구현 경로 전체 존재; 실행 미검증 |
| C002 | iOS·Android가 하나의 op 어휘로 통일 | 지지 | transport.py op 25종 + 디스패치 |
| C003 | 하네스는 절대 PIN을 입력하지 않는다 | 부분 지지 | Android는 코드 게이트, iOS는 프롬프트 규칙뿐 |
| C004 | 외부로 나가는 행동 전에 멈추고 묻는다 | 부분 지지 | 프롬프트에만 존재 — 코드 enforcement 없음 |
| C005 | run.py는 stdin 임의 코드를 exec한다 | 지지 | 코드 확인 (문서엔 절반만 명시) |
| C006 | agent_helpers.py는 에이전트 편집 + 자동 실행 | 지지 | 파일 헤더 + import 경로 확인 |
| C007 | 클립보드 잔류는 의도된 트레이드오프 | 지지 | mirror.py 주석 + opt-in 설정 |
| C008 | 백그라운드 모드: 포커스 없이 SkyLight로 구동 | 부분 지지 | 구현 존재; 효과 수치는 저자 자기보고 |
| C009 | Vision OCR 좌표를 그대로 탭에 쓸 수 있다 | 지지 | ocr.py 좌표 변환 확인 |
| C010 | 미러링 창을 PID로 찾는다 (비영어 맥 대응) | 지지 | mirror.py 확인 |
| C011 | PyPI에 0.2.0 배포됨 | 지지 | PyPI API 외부 확인 |
| C012 | OIDC trusted publishing (토큰 없음) | 지지 | publish.yml 확인 |
| C013 | 자동화 테스트 0개 | 지지 | 트리 전수 대조 |
| C014 | 단일 메인테이너 (66/66 커밋) | 지지 | contributors + 전체 히스토리 |
| C015 | 외부 PR 13건 미머지, 머지는 전부 본인 PR | 지지 | pulls API 샘플 30건 |
| C016 | 의존성·코드 스캔 보안 상태 | 알 수 없음 | API 403 — 0건 단정 불가 |
| C017 | 온보딩 데모가 opt-in으로 스타를 누르게 한다 | 지지 | onboarding.md §5 확인 |
| C018 | MIT 라이선스 | 지지 | LICENSE + API 이중 확인 |
| C019 | 릴리즈 2 ≠ 태그 4 (0.1.1은 PyPI만) | 지지 | releases·tags·PyPI 삼중 대조 |
| C020 | 공개 2주에 스타 ~2천 | 지지 | repo 메타데이터 |
원문 링크 원장 — README는 완역하지 않는다
이 도시에는 저장소 루트 README의 한국어 완역을 싣지 않는다 (2026-08-24 산출 계약 개정). 소스 코드가 SSOT이고 팩트이며, README는 저자의 주장이다. README의 문장들은 §11 주장 검증 원장의 입력과 §4 코드 실증의 대조 대상으로만 썼고, 완역이 차지하던 지면은 file:line 코드 인용에 재배정했다. 원문이 필요하면 고정 커밋의 README.md(blob a9d337c, 139줄) 자체를 읽어라 — 사본은 공개 증거 번들에 있다.
README가 저작한 링크 5개는 원문 문맥과 함께 여기 보존한다 (occurrence 원장과 1:1; 저장소 내부 링크는 커밋 고정 주소).
- README 1행 표제 — 프로젝트 공식 사이트 phone-harness (이 분석에서는 방문하지 않았다 — §13).
- "설정 프롬프트" 문맥(README 35행) — 에이전트가 사용자를 안내하는 최초 실행 절차 onboarding.md — PIN 금지·설정 변경 금지 규칙과 opt-in 스타 데모가 사는 곳 (§6·§9).
- 권한 트러블슈팅 문맥(README 47행) — 설치·권한 레퍼런스 install.md — "하네스는 절대 PIN을 입력하지 않는다"의 원문(
install.md:96). - "사용법" 문맥 — 에이전트-대면 사용 계약 SKILL.md — Consent 절(
SKILL.md:120-124)이 C004의 유일한 실체다. - 같은 문단의 스킬 등록 문맥 — install.md (occurrence 3과 동일 대상) —
phone-harness skill이 스킬 본문을 출력해 에이전트가 스스로 집어 들게 하는 배포 방식.
README의 실질 내용(무엇을·왜·한계)은 이 문서의 §4·§5·§6이 코드 대조와 함께 담고 있다 — 번역본이 아니라 검증본이다.
수집 한계 — 이 문서가 모르는 것
- 정적 분석 천장: 코드를 실행하지 않았으므로 "실기기에서 실제로 동작한다"는 어떤 주장도 이 문서는 증명하지 못한다. README의 동작 수치(플릭 효과 등)는 저자 자기보고다.
- API 샘플 경계: 이슈·PR·커밋은 per_page=30 단일 페이지 샘플이다 (커밋 전체 히스토리 66건은 bare git으로 완전). 이슈 코멘트는 대표 3건(#17·#22·#3)만 열었다.
- 거부된 엔드포인트: Dependabot 알림·코드 스캔 API가 403 — 해당 상태는 unknown이며 0으로 간주하지 않았다.
- SKILL.md의 세부: 스킬 본문은 정독했으나 이 문서에는 규칙·계약 수준만 반영했다 (문서 완역은 산출물이 아니다 — 소스가 SSOT, §12).
- phone-harness.com 미방문: 공식 사이트는 열지 않았다. 링크 툴팁에 미확인으로 표기.
- 시점 고정: 모든 수치는 2026-08-24 03:22–03:28 UTC 관측이다. 이 프로젝트는 빠르게 움직이고 있어(마지막 push 08-21) 숫자는 곧 낡는다.