Skip to content

About

극한의 에이전틱 코딩을 위해 하네스를 만듭니다

Resources

Stars

3 stars

Watchers

0 watching

Forks

Latest commit

 

History

71 Commits

Folders and files

Repository files navigation

Mino Harness

화면을 다 만들고 나면 반복 노동이 남는다. 접근성을 붙이고, 테스트를 쓰고, 시뮬레이터에서 직접 눌러보고, 결과를 읽는 일. 매 피쳐마다 똑같이 반복되는데, 사람이 매번 손으로 한다.

이 저장소는 그 노동을 프롬프트로 옮긴 것이다. Mino(SwiftUI · Clean Architecture · Swift 6 / iOS 17+) iOS 앱에 붙일 QA 자동화 번들이며, 화면 하나를 지목하면 접근성 부여부터 판정 리포트까지 흐른다.

코드를 쓰는 일은 여기 없다. 신규 구현도 Figma 디자인 반영도 계획 승인·stub 계약·사용자 리뷰 같은 사람 게이트를 요구하는 작업이라 대화형 /ios-workflow가 맡고, 이 번들은 그 워크플로우의 동작 테스트 단계가 소환하는 실행부다. 사람이 손으로 앱을 눌러보던 자리를 대신한다.

한 번 정한 방식을 프롬프트로

한 번 내린 판단은 두 번 내리지 않는다. "이 화면은 이렇게 QA한다"를 정했으면, 그 판단을 프롬프트로 박아 AI에게 위임한다. 그래야 다음 피쳐에서 같은 결정을 또 하지 않는다.

그래서 이 번들은 일꾼(에이전트)과 그들이 따르는 판단 기준(스킬)으로 나뉜다. 일꾼은 손을 움직이고, 스킬은 "이렇게 하면 버그"라는 가드레일을 제공한다. 둘을 분리해 둔 이유는, 판단 기준은 외부에서 검증된 것을 그대로 빌려오고(아래 벤더링) 일꾼만 우리 프로젝트에 맞게 조립하기 위해서다.

한 줄 트리거

/mino-qa <화면 또는 뷰 파일 경로>
화면 지목
  │
  ├─ 접근성      accessibility-auditor → 식별자 부여 + qa/manifests/*.json (게이트: 식별자 0개면 중단)
  ├─ 테스트      test-author → 단위테스트 + AXe 시나리오 (컴파일 게이트)
  ├─ 매니페스트  verify_manifest.py → 식별자·시나리오가 소스에 실재하는지 (기계 게이트, 빌드 전)
  ├─ 빌드        build-runner → 빌드·설치·실행 (실패 게이트, 시뮬레이터 미가용은 소프트)
  ├─ 실행        simulator-qa → AXe 로 시나리오 구동 + 단계별 스크린샷
  └─ 판정        qa-reviewer → PASS/FAIL/PARTIAL/HOLD + 미검증 경로는 보류로 분리

의존: axe CLI, python3, 부팅된 시뮬레이터. 하네스는 커밋을 만들지 않는다 — 식별자 부여·테스트는 워킹트리에, 판정은 리포트 파일에 남고, 사람이 diff 로 검토한 뒤 커밋한다.

대화형이라 게이트에 걸리면 멈추고 사람에게 보고한다. 옆에 판단할 사람이 있다는 것이 이 번들의 전제다.

모델은 단계 성격에 맞춘다

LLM 작업의 비용은 대부분 모델 등급에서 갈린다. 모든 단계를 가장 똑똑한 모델로 돌리면 정확하지만 비싸고, 전부 가벼운 모델로 돌리면 싸지만 엉성하다. 답은 판단이 필요한 곳에만 비싼 모델을 쓰는 것이다.

에이전트는 전부 Sonnet 기본이다. 기계적인 단계를 Haiku 로 떨어뜨려 봤으나 틀린 지적이 많아 되돌렸다(실측) — 테스트 작성·스크린샷 해석처럼 분석이 섞인 단계는 Haiku 의 출력 노이즈가 절감분을 잡아먹는다.

접근성이 자동화의 전제다

화면을 밖에서 조작하려면 요소를 가리킬 이름이 있어야 한다. 그 이름이 accessibilityIdentifier다. 이 식별자는 두 가지 일을 동시에 한다 — VoiceOver 사용자를 위한 접근성이자, AXe가 요소를 찾는 선택자다.

그래서 QA 흐름의 첫 일꾼이 접근성이다. accessibility-auditor가 인터랙션 요소에 "<Screen>.<element>" 꼴의 식별자를 붙이고 매니페스트를 qa/manifests/<Screen>.json으로 남긴다. 이름은 표시 텍스트가 아니라 역할에서 온다 — "로그인"이 아니라 Login.submitButton — 지역화나 문구 변경에 안 깨지게. 식별자가 하나도 안 붙은 화면은 뒤 단계가 요소를 못 찾으므로, 이 단계는 게이트다. 식별자 0개면 그 화면은 파이프라인에서 드롭된다. 드롭은 침묵하지 않는다 — 최종 리포트의 dropped[]에 화면·단계·사유가 남는다.

테스트 코드 없이도 화면을 돌려본다

SwiftUI 화면의 동작을 확인하려면 보통 UI 테스트 타깃을 만들고 XCUITest를 쓴다. 이 번들은 대신 AXe CLI로 시뮬레이터를 밖에서 조작한다. 앱에 테스트 번들을 심는 대신 tap·type·describe-ui로 화면을 움직이고 스크린샷으로 결과를 본다.

그래서 "테스트 코드 없이"는 절반만 사실이다. 순수 로직(Domain·UseCase)은 여전히 Swift Testing 단위 테스트로 박지만, 화면 흐름은 코드 대신 자연어에 가까운 시나리오 파일로 적고 AXe가 구동한다. 대신 한계가 분명하다 — AXe는 한글 입력을 못 하고(US 키보드 한정), HID 명령은 "전달"만 보장할 뿐 앱이 처리했는지는 모른다. 그래서 결과는 항상 스크린샷·describe-ui로 따로 검증한다.

만들고 끝이 아니라, 적대적으로 단단해진다

AI가 쓴 프롬프트와 에이전트 정의에도 빈틈이 있다. 혼자 검토하면 자기 논리의 구멍을 못 본다. 그래서 여러 비평가가 차원을 나눠 동시에 공격하게 한다.

공격을 두 갈래로 나눴다. grep으로 끝나는 결정론적 정합(호명 오류·죽은 링크·axe 명령 존재·agentType 실재·금지 문자열)은 check_consistency.py가 CI에서 기계로 잡는다. 판단이 필요한 것만 adversarial-harden에 남긴다 — 문서가 약속한 게이트·산출물·흐름이 실제 코드와 맞는가(harness-truth)·레이어 적합성·접근성 커버리지·테스트 품질을 각각 다른 눈으로 공격한다. 발견된 결함은 독립 검증자가 반증을 기본값으로 두고 다시 검사해, 살아남은 것만 채택한다. 실제로 이 README와 하네스도 그 검증을 한 번 거쳤다 — 그때 "게이트라고 써놓고 코드엔 게이트가 없다"는 지적이 나와 위의 접근성 게이트를 진짜로 구현했다.

수정까지 자동화하지는 않는다. 검증을 통과한 결함도 오판일 수 있어서, 워크플로우는 "확정 결함 목록"까지만 내고 반영은 사람이 diff로 판단할 수 있는 지점에 둔다.

구성요소

입구

무엇 한 줄
mino-qa QA 단계를 순서대로 엮는 파이프라인 (게이트 포함). 대화형 /ios-workflow 의 동작 테스트 단계가 소환한다

에이전트 (각자 독립 컨텍스트, 전문 스킬 소환)

에이전트 한 줄
accessibility-auditor SwiftUI 뷰에 accessibilityIdentifier 부여 + 매니페스트 산출 (qa/manifests/)
test-author Swift Testing 단위테스트 + AXe UI 시나리오 작성
build-runner 빌드 → 시뮬레이터 설치·실행 (앱 타깃 없으면 대상 없음 보고)
simulator-qa AXe로 시뮬레이터 실행 + 단계별 스크린샷
qa-reviewer 스크린샷·테스트 결과 판정 → PR용 리포트

전문 스킬 (벤더링 — 코드 작성 시 판단 기준, 출처 NOTICE)

스킬 한 줄
swiftui-expert-skill SwiftUI 상태·성능·접근성·애니메이션 규칙 (디렉토리명은 swiftui-expert)
swift-concurrency async/await·actor·Sendable·Swift 6 마이그레이션
swift-testing-expert Swift Testing 작성·XCTest 이관
axe iOS 시뮬레이터 CLI 자동화 (tap/type/describe-ui…)

자기 개선

무엇 한 줄
adversarial-harden.js 차원별 비평가가 산출물을 적대적으로 공격(의미론적 판단만) → 렌즈 다른 검증자 3명이 반증 → 살아남은 결함만 보고
check_consistency.py 결정론적 정합(호명·죽은 링크·axe 명령·agentType 실재·금지 문자열)을 CI에서 기계로 검사
verify_manifest.py 접근성 매니페스트·시나리오가 실제 소스에 실재하는지 런타임 게이트로 검사 (빌드 전)

문서

설치

하네스가 실제로 도는 유일한 배치는 Mino 본체 레포에 sync 후, 본체를 cwd로 여는 것이다. 이 레포에는 앱 코드가 없어(그래서 build-runner가 "대상 없음"을 낸다) 여기서만 열면 QA 파이프라인이 성립하지 않는다. 루트 Makefile이 번들을 본체로 복사한다.

make sync TARGET=/path/to/Team-MINO-iOS   # .claude/agents·skills, workflows, scripts 를 본체로 복사(추가만, 삭제 없음)
brew install cameroncooke/axe/axe         # 시뮬레이터 자동화 실행용
axe list-simulators                       # UDID 확인

brew 가 Command Line Tools 버전을 이유로 거부하면(Xcode 자체는 정상인데 CLT 만 구버전인 경우가 있다) 릴리스의 prebuilt 유니버설 바이너리를 받아 사용자 경로에 풀면 된다 — 같은 upstream·같은 버전이다.

그다음 본체 레포를 cwd로 Claude Code를 열고 /mino-qa <화면>. 번들을 고칠 때는 이 레포 원본을 고친 뒤 다시 make sync 한다(본체의 .claude/를 직접 고치지 않는다). 번들 자체를 편집하거나 adversarial-harden을 돌릴 때만 이 레포를 cwd로 연다.

sync 는 삭제를 하지 않으므로, 자산을 폐기한 버전으로 올릴 때는 make unsync → make sync 순으로 돌린다. sync 가 배치 목록을 본체의 .claude/.mino-harness-manifest 에 남기고 unsync 가 그 기록을 지우므로, 원본에서 사라진 자산도 정확히 제거된다(본체가 원래 갖고 있던 .claude/skills/ 항목은 건드리지 않는다). 이 매니페스트는 본체 레포의 .gitignore 에 넣는다 — 배치 상태를 기록한 로컬 파일이다.

벤더 스킬은 원본이 Claude Code 플러그인이기도 하다. 최신 추적이 필요하면 플러그인으로 설치할 수 있다.

/plugin marketplace add AvdLee/Swift-Concurrency-Agent-Skill
/plugin install swift-concurrency

라이선스

MIT(LICENSE). .claude/skills/ 아래 네 스킬은 외부에서 벤더링했으며 출처·라이선스는 NOTICE.

About

극한의 에이전틱 코딩을 위해 하네스를 만듭니다

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages