Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions .claude/agents/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# 서브에이전트

**이 폴더의 정의는 초안이다.** 쓸지 말지는 아직 안 정했고, 정하면 이 문장을 지운다.

## 왜 만드나

R9(넓은 조사는 위임)의 담당 지표 M14가 세 버전 연속 0%였다. 규칙이 효과가 없어서가
아니라 **규칙 본문이 "사용자가 요청할 때만"이라고 스스로를 잠가둬서**다.

2026-08-22에 처음으로 허가를 받고 셋을 병렬로 돌렸다. 그 실측이 이 폴더의 근거다.

| 조사 | 읽은 것 | 쓴 토큰 | 걸린 시간 |
|---|---|---|---|
| Features 레이어 | 27파일 5,436줄 | 148,139 | 4분 36초 |
| Shared 레이어 | 32파일 + 타깃 멤버십 | 134,072 | 5분 20초 |
| Common·위젯 | 40파일 | 148,249 | 4분 15초 |
| 워크플로우·하네스 | 문서 20여 개 | 109,595 | 4분 51초 |

**넷이 54만 토큰을 쓰고 요약만 돌려줬다.** 직접 읽었으면 그 54만이 본 컨텍스트에
쌓였을 것이고, 그 뒤 모든 턴이 그것을 다시 읽었을 것이다. 남은 턴이 100개면
5,400만 토큰 차이다.

이게 위임의 유일한 근거다 — **읽은 내용 자체는 남길 필요가 없고, 결론만 필요한 일.**

## 언제 위임하나 / 안 하나

| 위임한다 | 직접 한다 |
|---|---|
| 전수 조사·감사 (파일 10개 이상) | 판단이 필요한 것 |
| "이 심볼 쓰는 데 다 찾아줘" | 이미 열어둔 파일을 고치는 것 |
| 명명 규칙·패턴 훑기 | 왕복 한두 번이면 끝나는 것 |
| 읽어야 결론이 나오지만 읽은 내용은 안 남겨도 되는 조사 | 사용자와 대화가 필요한 것 |

기준 한 줄: **설명 비용이 직접 하는 비용보다 크면 지는 거래다.**

## 정의

### `surveyor` — 넓은 조사

읽기 전용. 파일 여러 개를 훑고 구조화된 요약만 돌려준다.

- **모델**: 하위 (판단이 아니라 수집이다)
- **도구**: 읽기·검색만. 편집 금지
- **출력 규칙**: 파일:줄 근거를 붙이고, 코드를 길게 인용하지 않는다. 제안하지 않고
사실만 적는다 — 제안은 본 세션이 한다

### `test-author` — 뽑아낸 로직에 테스트 붙이기

`Shared/`로 내린 순수 로직에 Swift Testing 테스트를 쓴다.

- **모델**: 하위 (규칙이 명확하고 반복적이다)
- **도구**: 읽기 + `MoscoTests/` 아래 쓰기 + 테스트 실행
- **출력 규칙**: 테스트 이름에 증상을 적는다(`같은_날짜가_월말월초에_두_번_나오지_않는다`).
경계값을 반드시 포함한다. 통과하는 것을 확인하고 결과를 보고한다

## 안 만들기로 한 것

- **`doc-writer`** — 문서 톤은 세션 맥락을 아는 쪽이 잘 쓴다. 요약을 넘기는 비용보다
직접 쓰는 편이 싸다고 판단했다. 다시 볼 만한 후보이긴 하다.
- **`reviewer`** — 코드 리뷰는 판단이다. 위임하면 근거를 다시 설명해야 하고, 그
설명이 리뷰보다 길어진다.
- **`contract-checker`** — 이건 에이전트가 아니라 스크립트가 맞다.
`tools/artifact_check.sh`로 만들었다.

## 검증

에이전트를 쓴 뒤에는 매번 이 세 줄을 남긴다. 그래야 다음 버전 보고서 6.3에서
"위임이 실제로 이득이었나"를 채점할 수 있다.

```
누가 · 무엇을 · 쓴 토큰
돌아온 요약이 실제로 쓸모 있었나 (그대로 썼나 / 다시 조사했나)
직접 했으면 어땠을까 (추정)
```
27 changes: 27 additions & 0 deletions .claude/agents/surveyor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
name: surveyor
description: 넓은 전수 조사·감사 전용. 파일 여러 개를 훑고 구조화된 요약만 돌려준다. 읽기 전용이며 제안하지 않는다. 파일 10개 이상을 읽어야 결론이 나오지만 읽은 내용 자체는 본 세션에 남길 필요가 없을 때 쓴다.
tools: Read, Grep, Glob, Bash
model: haiku
---

너는 이 저장소를 조사해 **사실만** 돌려주는 조사원이다.

## 지켜야 할 것

1. **제안하지 않는다.** "이렇게 고치면 좋겠다"를 쓰지 않는다. 무엇이 어디에 어떻게
있는지만 적는다. 판단은 본 세션이 한다.
2. **파일:줄 근거를 붙인다.** 근거 없는 서술은 쓰지 않는다.
3. **코드를 길게 인용하지 않는다.** 한 줄 이상 붙여야 할 때는 왜 그 줄이 중요한지
한 문장으로 대신한다.
4. **표로 정리한다.** 문단으로 늘어놓으면 본 세션이 다시 읽어야 한다.
5. **한국어로 쓴다.**
6. **못 찾은 것은 못 찾았다고 적는다.** 추측으로 메우지 않는다.

## 출력 형태

받은 질문의 번호를 그대로 절 번호로 쓴다. 질문에 없던 것을 발견하면 마지막에
"질문에 없었지만 눈에 띈 것"으로 따로 모은다.

분량은 **읽은 양의 1/20 이하**를 목표로 한다. 요약이 원본만큼 길면 위임한 의미가
없다.
45 changes: 45 additions & 0 deletions .claude/agents/test-author.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
name: test-author
description: Shared/로 옮긴 순수 로직에 Swift Testing 테스트를 붙인다. 대상 타입과 검증할 규칙을 받아 MoscoTests/에 테스트를 쓰고, 실제로 돌려 통과를 확인한 뒤 결과를 보고한다.
tools: Read, Grep, Glob, Bash, Write, Edit
model: haiku
---

너는 이 저장소의 순수 로직에 테스트를 붙이는 일만 한다.

## 이 프로젝트의 테스트 규칙

- 프레임워크는 **Swift Testing** (`@Test`, `#expect`). XCTest가 아니다.
- 테스트는 `Mosco/MoscoTests/`에 둔다. **폴더가 통째로 동기화되므로 Xcode 프로젝트에
등록할 필요가 없다.**
- 테스트 타깃은 **호스트 앱 없이 `Shared/`만 컴파일한다.** 그러므로 `App/` 아래의
타입은 테스트할 수 없다. 대상이 거기 있으면 **테스트를 쓰지 말고 그 사실을
보고한다.**
- `@testable import`를 쓰지 않는다. 소스 멤버십으로 직접 컴파일된다.

## 이름 규칙

테스트 이름에 **증상**을 적는다. 무엇을 검증하는지가 아니라, 깨졌을 때 무엇이
잘못되는지를 적는다.

```swift
@Test("같은_날짜가_월말월초에_두_번_나오지_않는다")
@Test("4시~7시의_종료_시각이_19시로_읽힌다")
```

## 반드시 포함할 것

- **경계값**. 0, 최대, 하루 경계, 월말·월초, 윤년, 자정, 정오.
- **틀렸던 적이 있는 케이스**. 대상 코드의 주석에 "예전엔 ~였다"가 있으면 그것을
테스트로 만든다.
- 실패 메시지. `#expect(x == y, "왜 이래야 하는지")`

## 끝내기 전에

```bash
xcodebuild -project Mosco/Mosco.xcodeproj -scheme App -sdk iphonesimulator \
-destination 'platform=iOS Simulator,name=iPhone 17 Pro' test
```

**통과를 확인하고 개수를 보고한다.** 실패하면 고치거나, 못 고치면 무엇이 왜
실패하는지 적는다. 깨진 채로 두고 "됐습니다"라고 쓰지 않는다.
2 changes: 1 addition & 1 deletion .claude/skills/handoff/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ description: 작업을 마치고 사람에게 확인을 넘길 때 쓰는 검증

```
빌드 App ✅ / MoscoWidget ✅
테스트 MonthLayoutTests 6개 통과 — 월말/월초 중복, 5주·6주 격자
테스트 MonthLayoutTests 7개 통과 — 월말/월초 중복, 5주·6주 격자
카드 아래 2건
못 봄 위젯 렌더링 (실기기)
```
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/report/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ M13과 모델별 턴 수를 놓고 **다음 버전에 무엇을 어디에 쓸지

| 모아야 할 것 | 어디서 나오나 |
|---|---|
| 위임했으면 쌌을 지점 | R9에 따라 답 끝에 남긴 "여기는 위임했으면 쌌다" 줄 |
| 위임했으면 쌌을 지점 | R9에 따라 **PR 본문**에 남긴 "위임했으면 쌌을 곳" 칸 |
| 그 지점의 성격 | 조사인가 / 판단인가 / 반복 작업인가 |
| 한 번에 오간 컨텍스트 크기 | 그 구간의 `Read` 수와 검색 폭 |

Expand Down
78 changes: 71 additions & 7 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,23 @@ docs/TRAPS.md 플랫폼 함정. 해당 영역 건드리기 전에
docs/CATEGORIZATION.md 카테고리 자동 분류가 어떻게 돌고 왜 Core ML을 버렸나
docs/BACKLOG.md 밀린 일. 목록을 던지면 여기로 들어간다
docs/harness/ 이 규칙들이 효과 있었는지 채점하는 체계
docs/workflow.md 프롬프트 하나가 어떤 순서로 도는지. 도식 + 근거
docs/verification.md 무엇을 어떻게 검증하나. 테스트만으로 안 되는 이유
docs/review-criteria.md 사람이 봐야 하는 코드와 안 봐도 되는 코드
docs/architecture/ 지금 구조(CURRENT.md)와 후보 패턴 비교(PATTERNS.md)
.claude/agents/ 서브에이전트 정의와 그 근거
tools/harness_report.py 버전별 지표 추출
tools/quality_baseline.py 코드 품질 기준선. 리팩터링 전후를 비교하려고 센다
tools/artifact_check.sh entitlements·plist·버전 일치 검사. 계약 검증
tools/deadcode_audit.py 안 쓰이는 코드 훑기. periphery는 이 프로젝트에서 못 쓴다
```

배포 타깃 iOS 17.0. 번들 ID `com.Mosco.App`. Swift 106파일 ≈ 13,900줄.
**테스트 타깃 `MoscoTests`가 있다** (Swift Testing, 112건). **CI도 있다** —
PR마다 App·MoscoWidget 빌드와 테스트, 그리고 Mac Catalyst 빌드가 자동으로 돈다
(`.github/workflows/ci.yml`). Catalyst를 따로 빌드하는 것은 **iOS가 통과해도 맥은
깨질 수 있어서**다 — 라이브 액티비티가 조건부 컴파일로 가려져 있다. 그래도 먼저 돌리는 것은 AI다 — CI는 마지막 그물이지
깨질 수 있어서**다 — 라이브 액티비티가 조건부 컴파일로 가려져 있다. 그래도 먼저
돌리는 것은 AI다 — CI는 마지막 그물이지
편집 후 확인을 대신하지 않는다 (R1).

> **시뮬레이터는 AI가 직접 쓰지 않는다.** 열지도, 탭하지도, 캡처하지도 않는다.
Expand Down Expand Up @@ -63,13 +71,13 @@ UDID를 박아 쓰지 않는다 — 시뮬레이터는 지워지고 다시 생

## 규칙

열 개다. **각 규칙에는 담당 지표가 있고, 버전마다 채점받는다** —
열한 개다. **각 규칙에는 담당 지표가 있고, 버전마다 채점받는다** —
`docs/harness/rules.md`에 도입 시점과 채점 이력이 있다. 두 버전 연속 효과가 없으면
그 규칙은 폐기한다. 지켜지지 않는 문장을 문서에 남겨두지 않는 것이 이 체계의 요점이다.

### 이 규칙들이 무엇을 향하는가 — M12 프롬프트당 토큰

**최종 목표는 같은 결론에 토큰을 덜 쓰고 도달하는 것이다.** 규칙 열 개가 전부
**최종 목표는 같은 결론에 토큰을 덜 쓰고 도달하는 것이다.** 규칙 열한 개가 전부
여기로 모이고, 다른 지표가 다 좋아져도 M12가 안 내려가면 그 버전의 하네스는
실패로 적는다.

Expand Down Expand Up @@ -106,6 +114,16 @@ v1.1.0이 1.8배가 된 이유가 이 체계의 핵심 사례다. 스크린샷
거짓 완료 보고는 빌드 실패로 안 잡히고 **재지시로 돌아온다.** 그래서 이 규칙의
담당 지표에 M1이 같이 붙어 있다.

**고친 뒤 증상이 똑같으면, 다음 가설을 세우기 전에 그 코드가 실행되는지부터
확인한다** (2026-08-22 추가). v1.3.0에서 스와이프 삭제를 세 번 연속 헛짚었다. 세 번
모두 증상이 한 글자도 안 바뀌었는데 매번 다음 가설로 넘어갔다. 원인은 코드가 아니라
**그때 화면에 그 코드가 없었다는 것**이었다.

증상이 안 바뀌는 것은 "아직 못 찾았다"가 아니라 **"그 코드가 안 돈다"는 신호다.**
로그 한 줄, 조건 출력 하나, 탐침 하나 — 무엇이든 좋으니 **세 번째 시도 전에는
반드시** 경로가 도는지 찍는다. 시뮬레이터를 못 여는 자리에서는 탐침을 심어 사람에게
실행을 부탁한다(R2를 어기지 않는다). 자세한 것은 `docs/TRAPS.md`.

### R2 · 시뮬레이터를 직접 쓰지 않는다. 검증은 테스트 코드로 남긴다
담당 지표 M11 시뮬레이터 호출 수, M10 회귀 테스트 수

Expand All @@ -126,6 +144,14 @@ v1.1.0이 1.8배가 된 이유가 이 체계의 핵심 사례다. 스크린샷
그리고 이 요청은 처음이 아니다 — 2026-07-31, 2026-08-05, 2026-08-18 세 번 나왔다.
앞의 두 번은 문서로 안 남겨서 다음 세션에 잊혔다. 세 번째에 규칙이 됐다.

**예외를 썼으면 무엇을 얻었는지 PR 본문에 적는다** (2026-08-22 추가). v1.3.0에서
PR 스크린샷을 찍다가 KVS entitlement가 시뮬레이터 실행을 막고 있는 것을 발견했다 —
실기기도 CI도 초록이던 버그다. 예외가 값을 한 사례이므로 목표 0을 기계적으로 밀지
않는다. 대신 **얻은 것이 "그려졌다" 하나뿐이면 그건 낭비로 센다.**

`xcrun simctl`을 Bash로 부르는 것도 시뮬레이터 사용이다. 지표(M11)가 아직 그 경로를
안 세지만 규칙은 센다.

**대신 무엇을 하나.** 확인하고 싶은 것이 로직이면 테스트를 쓴다 (R7). 화면이면
사용자에게 넘긴다. **"확인했다"고 말할 수 있는 근거는 빌드 결과와 테스트 결과뿐이다.**
그 둘로 덮이지 않는 것은 확인 안 한 것이고, 그렇게 적는다.
Expand Down Expand Up @@ -233,7 +259,7 @@ R2로 시뮬레이터를 닫았으므로 **테스트가 AI가 쓸 수 있는 유
| 자연어 시각 파싱 | `4시~7시`의 종료 시각 · 오전/오후 해석 |
| 카테고리 분류 | 임계값 `0.35`에 근거 데이터가 없다 |

**지금까지 덮은 것** (2026-08-19, 108건):
**지금까지 덮은 것** (2026-08-22, 112건):

| 스위트 | 무엇을 잡나 |
|---|---|
Expand Down Expand Up @@ -292,9 +318,25 @@ AI는 자기 모델을 못 바꾼다. 바꾸는 것은 사용자다. AI가 할
한두 번이면 끝나는 것. 서브에이전트는 맥락 없이 시작하므로 **설명하는 비용이
직접 하는 비용보다 크면 지는 거래**다.

**서브에이전트는 사용자가 요청할 때만 띄운다.** 그래서 이 규칙의 지금 몫은 절반이다 —
띄우지 않은 경우에도 **"여기는 위임했으면 쌌다"를 남긴다.** 그 기록이 쌓여야 다음
버전에 상시 위임 구성을 정할 수 있다. 보고서 6.3이 그 자리다.
**조사·감사·전수 훑기는 묻지 않고 위임한다** (2026-08-22 개정). 예전엔 "사용자가
요청할 때만"이었는데, 그 문장이 규칙의 실행 자체를 막아 M14가 세 버전 연속 0%였다.
효과가 없었던 게 아니라 **한 번도 시험대에 못 올랐다.**

기준은 이렇다.

| 묻지 않고 위임 | 물어보거나 직접 |
|---|---|
| 파일 10개 이상을 읽어야 하는 조사 | 판단이 필요한 것 |
| "이 심볼 쓰는 데 다 찾아줘" | 이미 열어둔 파일을 고치는 것 |
| 명명 규칙·패턴 훑기, 감사 | 왕복 한두 번이면 끝나는 것 |
| 읽은 내용 자체는 안 남겨도 되는 것 | 편집이 따르는 것 |

2026-08-22에 넷을 병렬로 돌려 54만 토큰을 쓰고 요약만 받았다. 직접 읽었으면 그 54만이
본 컨텍스트에 쌓여 이후 모든 턴이 다시 읽었을 것이다. 정의와 근거는
`.claude/agents/README.md`.

위임하지 **않은** 경우에도 **"여기는 위임했으면 쌌다"를 남긴다.** 그 기록이 쌓여야
다음 버전에 구성을 조정할 수 있다. 보고서 6.3이 그 자리다.

**기록하는 자리는 PR 본문이다** (2026-08-19 변경). 예전엔 "답 끝에 한 줄"이었는데
매 턴 기억해야 하는 것이라 v1.2.0에서 **한 번도 안 남겼다.** PR 본문은 어차피 쓰는
Expand All @@ -312,6 +354,24 @@ AI는 자기 모델을 못 바꾼다. 바꾸는 것은 사용자다. AI가 할
- 이미 읽은 파일을 다시 읽지 않는다. 편집 도구는 실패하면 에러를 낸다 — 확인하려고
다시 읽는 것이 가장 흔한 낭비다.

**Bash로 읽을 때도 같다.** `sed -n '100,140p'`·`grep -n`·`head`는 범위를 준 것이고
`cat`은 아니다. 지표(M15)가 `Read` 도구만 세는 것은 지표의 한계지 규칙의 한계가
아니다 (2026-08-22).

### R11 · 진단하려고 넣은 것은 표식을 달고, 커밋 전에 걷는다
담당 지표 M16 임시 코드 잔존

원인을 좁히려고 로그·탐침·임시 분기를 넣을 때가 있다. **그것들은 진단이 끝나면
쓰레기다.** v1.3.0에서 탐침 파일 하나와 추적 로그 두 줄이 남을 뻔했고, 사용자가
"불필요한 코드는 제거하고"라고 말한 뒤에야 걷었다.

- 넣을 때 **`// TEMP:` 표식**을 같이 단다. 왜 넣었는지도 한 줄 적는다.
- 커밋 전에 `grep -rn "TEMP:" Mosco/`로 찾아 전부 걷는다.
- **남길 가치가 있는 것은 표식을 떼고 이유를 주석으로 바꾼다.** 실패했을 때의 로그가
대개 그렇다 — v1.3.0의 날씨 실패 로그는 남겼고, 매번 찍히는 추적 로그는 걷었다.

`tools/quality_baseline.py`가 이 표식을 센다. 0이 아니면 커밋하지 않는다.

## 일하는 방식

- 사용자는 `auto`/`acceptEdits`로 거의 항상 열어둔다. 편집을 일일이 승인하지 않으므로
Expand All @@ -326,3 +386,7 @@ AI는 자기 모델을 못 바꾼다. 바꾸는 것은 사용자다. AI가 할
이건 취향이 아니라 M12로 채점되는 항목이다 (R8·R9·R10).
- **모든 작업은 브랜치와 PR을 거친다.** `main`에 직접 커밋하지 않는다.
규칙은 `CONTRIBUTING.md`.
- **문서는 사람이 읽을 글로 쓴다** (2026-08-22 요청). 보고서·규범·계획 전부
해당한다. 표와 목록으로만 채우지 말고, 왜 그런지를 문장으로 적는다. 딱딱한
번역체("~를 수행한다", "~에 대한 검증")를 쓰지 않는다. **읽는 사람이 새벽에
피곤한 상태라고 가정한다** — 한 번 읽고 이해되지 않으면 그 문서는 실패한 것이다.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ Firebase Analytics를 쓰지만 `GoogleService-Info.plist`는 저장소에 들
(`FirebaseAnalyticsSink.configure()`가 nil을 낸다).
시뮬레이터는 이름으로 지정한다 — UDID는 지워졌다 다시 생기면서 바뀐다.

검증 사다리는 빌드 → 테스트 → 사람 → 심사다. **`MoscoTests`(Swift Testing, 108건)와
CI가 앞의 두 칸을 맡는다** — PR마다 App·MoscoWidget 빌드와 테스트가 자동으로 돈다
([.github/workflows/ci.yml](.github/workflows/ci.yml)).
검증 사다리는 빌드 → 테스트 → 사람 → 심사다. **`MoscoTests`(Swift Testing, 112건)와
CI가 앞의 두 칸을 맡는다** — PR마다 App·MoscoWidget 빌드와 테스트, 그리고 Mac
Catalyst 빌드가 자동으로 돈다 ([.github/workflows/ci.yml](.github/workflows/ci.yml)).

```bash
xcodebuild -project Mosco/Mosco.xcodeproj -scheme App -sdk iphonesimulator \
Expand Down Expand Up @@ -68,7 +68,7 @@ Mosco/MoscoTests/ 유닛 테스트 (Swift Testing)

| 문서 | 무엇이 있나 |
|---|---|
| [CLAUDE.md](CLAUDE.md) | AI 작업 규칙 열 개 (R1~R10). 규칙마다 담당 지표가 있다 |
| [CLAUDE.md](CLAUDE.md) | AI 작업 규칙 열한 개 (R1~R11). 규칙마다 담당 지표가 있다 |
| [CONTRIBUTING.md](CONTRIBUTING.md) | 커밋·브랜치·PR·문서 갱신 규칙 |
| [RELEASING.md](RELEASING.md) | 버전 규칙, 태그, 릴리스 노트 쓰는 법 |
| [docs/TRAPS.md](docs/TRAPS.md) | 한 번씩 크게 시간을 쓴 플랫폼 함정 |
Expand Down
2 changes: 1 addition & 1 deletion RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ App Store의 "새로운 기능" 칸에 들어가는 글입니다. 규칙은 넷
| | 절 구성 | 분량 |
|---|---|---|
| **MAJOR · MINOR** | 1~6절 전부 | 제한 없음 |
| **PATCH** | 2절(지표) · 3절(원인) · 6절(다음 하네스) | 한 쪽 |
| **PATCH** | 2절(지난 결과) · 3절(원인) · 4절(다음 하네스) | 한 쪽 |

PATCH 보고서에서 **하네스를 안 바꿨으면 "안 바꿨다"고 적습니다.** 매 버전 규칙을
늘리는 것이 목적이 아니고, 안 바꾼 것도 판단입니다.
Expand Down
Loading
Loading