<!-- task=m-agent-readable-repository-refactor/12+03,06,10,11_readability_baseline plan=11 tag=REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REFACTOR -->

# Kotlin 단순·복수 qualified annotation type modifier 회귀 보정

## 이 파일을 읽는 구현 에이전트에게

아래 Required 1건을 구현하고 검증 명령을 그대로 실행한 뒤 `CODE_REVIEW-local-G09.md`의 구현 에이전트 소유 섹션을 실제 메모와 stdout/stderr로 채운다. active 파일은 그대로 두고 리뷰 준비 완료만 보고한다. 선택된 Milestone의 `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 review stub의 `사용자 리뷰 요청`에 정확한 연결 근거를 남기고 중단한다. 직접 사용자에게 묻거나 선택지를 제시하거나 `request_user_input`을 호출하거나, `USER_REVIEW.md`/log/`complete.log`를 직접 만들지 않는다. 환경·secret·service, 일반 범위 조정, 후속으로 보완 가능한 증거 공백은 사용자 리뷰 사유가 아니다.

## 배경

constructor annotation 지원을 위해 type operand parser를 분리하면서 annotation user type 뒤의 공백을 두 번 요구하게 됐다. 그 결과 원래 지원하던 `@com.example.Ann List<String>`와 simple/constructor가 섞인 복수 annotation sequence가 generic type context로 시작되지 않아 final closer 뒤로 sibling declaration을 포함한다. 각 annotation의 separator를 한 번만 소비하도록 보정하고 constructor-only sequence 경로를 유지한다.

## 사용자 리뷰 요청 흐름

사용자 리뷰 요청은 선택된 Milestone의 잠금 결정만 active review stub의 `사용자 리뷰 요청`에 기록한다. 구현 중 직접 사용자 prompt는 금지하며, 타당성 검증과 실제 `USER_REVIEW.md` 작성은 code-review가 소유한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/automation-runtime-bridge/milestones/agent-readable-repository-refactor.md`
- Milestone link: [Milestone 문서](agent-roadmap/phase/automation-runtime-bridge/milestones/agent-readable-repository-refactor.md)
- Task ids:
  - `readability-baseline`: 추적 소스의 파일/함수/read-set 기준선과 재현 가능한 ratchet audit 도입
- Completion mode: check-on-pass

## Archive Evidence Snapshot

- 루프 이력: `plan_local_G07_0.log`/`code_review_local_G07_0.log`부터 `plan_local_G09_9.log`/`code_review_local_G09_9.log`까지의 FAIL 이력은 [직전 아카이브 plan](plan_local_G09_10.log)과 [직전 아카이브 review](code_review_local_G09_10.log)의 Snapshot을 따른다.
- 직전 아카이브 작업: `agent-task/m-agent-readable-repository-refactor/12+03,06,10,11_readability_baseline/plan_local_G09_10.log`
- 직전 아카이브 리뷰: `agent-task/m-agent-readable-repository-refactor/12+03,06,10,11_readability_baseline/code_review_local_G09_10.log`
- 판정: FAIL — Required 1 / Suggested 0 / Nit 0.
- 문제: `value as @com.example.Ann List<String>`가 `C.cast`를 2–5행으로 확장해 `as?`, `is`, `done` sibling을 누락시킨다.
- 추가 진단: `@Ann @Other List`, `@Ann("reason") @Other List`, `@Ann @Other("reason") List`도 실패하며, 모든 annotation이 constructor form인 경우만 통과한다.
- 영향 파일: `scripts/readability_audit.py`, `scripts/readability_audit_test.py`, `scripts/readability_baseline.json`.
- 검증 증거: 기존 122 unit과 ratchet은 PASS했지만 independent simple-annotation exact-span probe가 FAIL했다.
- Roadmap carryover: `readability-baseline` Task는 PASS 전까지 미완료로 유지한다.
- 추가 상세가 필요하면 위 두 `*_10.log`만 좁게 재확인한다.

## 분석 결과

### 읽은 파일

- `scripts/readability_audit.py`
- `scripts/readability_audit_test.py`
- `scripts/readability_baseline.json`
- `scripts/readability_read_sets.json`
- `Makefile`
- `.gitignore`
- `agent-ops/rules/project/domain/testing/rules.md`
- `agent-test/local/rules.md`
- `agent-test/local/testing-smoke.md`
- `agent-roadmap/current.md`
- `agent-roadmap/phase/automation-runtime-bridge/PHASE.md`
- `agent-roadmap/phase/automation-runtime-bridge/milestones/agent-readable-repository-refactor.md`
- `agent-spec/index.md`

### SDD 기준

선택 Milestone은 `SDD: 불필요`다. 외부 API/proto/config schema나 runtime 책임 경계를 바꾸지 않는 repository readability tooling의 bounded parser 보정이라는 기존 사유를 유지한다.

### 테스트 환경 규칙

`test_env=local`이며 `agent-test/local/rules.md`와 `agent-test/local/testing-smoke.md`를 읽었다. Python stdlib unittest, `/tmp` JSON, worktree/tracked audit, 반복 `cmp`, Make target, whitespace 검증을 적용한다. 사용자 실행/runtime 동작은 바꾸지 않으므로 full-cycle/E2E/external provider 검증은 대상이 아니다.

### 테스트 커버리지 공백

- 현재 신규 fixture는 `@com.example.Ann("reason")`만 검증해 no-argument qualified annotation과 mixed annotation sequence의 separator 회귀를 포착하지 못했다.
- `as`/`as?`/`is` 각각에서 `@com.example.Ann List<String>`가 one-line span과 immediate sibling identity를 보존하는 assertion이 없고, simple→simple, constructor→simple, simple→constructor, constructor→constructor의 복수 annotation transition fixture도 없다.

### 심볼 참조

`_kotlin_type_operand_before_generic`은 `_KotlinTypeArgumentState.consume`에서만 사용된다. 이름 변경이나 외부 call site 변경은 없다.

### 분할 판단

단일 plan을 선택한다. parser separator 처리와 같은 Python fixture를 동시에 바꾸는 하나의 bounded lexer 경계이며, API/call-site·도메인·검증 전략 분리가 없고 분할하면 동일 state contract를 두 번 수정하게 된다.

### 범위 결정 근거

Kotlin cast/type-test RHS의 단순 qualified annotation separator, 해당 unit fixture, self-referential baseline projection만 수정한다. 다른 언어 extractor, `scripts/readability_read_sets.json`, Makefile, runtime 코드, roadmap/spec/contract는 변경하지 않는다.

### 빌드 등급

`local-G09`. 하나의 parser helper와 regression fixture·baseline 고정점에서 결정적으로 검증할 수 있으며 외부 runtime 또는 대화형 동작은 없다.

## 구현 체크리스트

- [x] [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REFACTOR-1] 단순·복수 qualified annotation의 separator를 한 번만 소비해 `as`/`as?`/`is` generic closer와 immediate sibling span을 보존한다.
- [x] [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REFACTOR-2] 줄 끝 type annotation 뒤 다음 행의 type head까지 Kotlin expression span을 이어 간다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REFACTOR-1] 단순·복수 annotation separator scanner

- 문제: `scripts/readability_audit.py:587-603`은 annotation user type 뒤 whitespace를 먼저 소비한 뒤 다시 required separator로 검사한다. 따라서 `@com.example.Ann List`와 simple/constructor가 섞인 annotation sequence가 실패하고 모든 annotation이 constructor form인 sequence만 통과한다.
- 해결 방법:

~~~text
Before
annotation user type 뒤 whitespace를 consume
constructor가 없으면 같은 whitespace를 다시 요구 → invalid

After
각 annotation user type 뒤 separator를 한 번 검증
constructor가 있으면 constructor close 뒤 separator를 다음 annotation 또는 type head에 전달
~~~

- 수정 파일 및 체크리스트:
  - [x] `scripts/readability_audit.py`: no-argument, constructor, mixed multiple annotation의 separator 소비를 분리한다.
  - [x] `scripts/readability_audit_test.py`: `as`/`as?`/`is @com.example.Ann List<String>`와 simple→simple, constructor→simple, simple→constructor, constructor→constructor multiple annotation의 exact name/start/end/LOC 및 `done` sibling fixture를 추가한다.
  - [x] `scripts/readability_baseline.json`: worktree projection 또는 read-set total이 바뀌면 기존 reason을 보존해 정확한 고정점으로 갱신한다. 신규 helper violation은 추가하지 않는다.
- 테스트 작성: 작성한다. 기존 `test_kotlin_qualified_annotation_type_projection_closer_preserves_siblings`를 확장해 no-argument, constructor와 네 가지 multiple annotation transition을 함께 assert한다.
- 추가 점검 범위: Kotlin 문법이 허용하는 `as\n @Ann List<String>`, `as? @Ann\n List<String>`, `is @Ann\n @Other List<String>`, `!is @Ann("reason")\n List<String>`, simple→constructor mixed multiline sequence를 같은 fixture에 넣어 annotation 뒤 줄바꿈에서도 exact span과 `done` sibling을 고정한다.
- 중간 검증:

~~~bash
python3 -m unittest scripts.readability_audit_test.TestMultiLanguageFunctionIdentity.test_kotlin_qualified_annotation_type_projection_closer_preserves_siblings
~~~

기대 결과: focused unit PASS, one-line/multiline `as`/`as?`/`is`/`!is`와 mixed multiple annotation이 source layout에 맞는 span으로 끝나고 `C.done` sibling이 보존된다.

### [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REFACTOR-2] multiline type annotation continuation

- 문제: line-local continuation은 `value as @Ann` 또는 `value is @Ann` 뒤의 다음 행 type head를 새 sibling으로 오인해 expression span을 annotation 행에서 끝냈다.
- 해결 방법: annotation sequence scanner가 type head 부재를 반환하고, Kotlin type-argument state가 이 상태인 동안 다음 행까지 expression을 연다. 다음 행의 generic opener는 축적된 prefix로 기존 type-context scanner가 판별한다.
- 검증: no-argument/constructor annotation, simple→simple·simple→constructor multiple annotation, `as`/`as?`/`is`/`!is`의 multiline fixture와 direct probe를 exact name/start/end/LOC로 고정한다.

## 수정 파일 요약

| 파일 | 항목 |
|---|---|
| `scripts/readability_audit.py` | REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REFACTOR-1, -2 |
| `scripts/readability_audit_test.py` | REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REFACTOR-1, -2 |
| `scripts/readability_baseline.json` | 실제 projection/read-set 값 변경 시 |

## 최종 검증

~~~bash
python3 -m unittest scripts.readability_audit_test
python3 - <<'PY'
from scripts import readability_audit as ra

source = '''class C {
  fun cast(value: Any) = value as @com.example.Ann List<String>
  fun safeCast(value: Any) = value as?@com.example.Ann List<String>
  fun typed(value: Any) = value is @com.example.Ann List<String>
  fun simpleMultiple(value: Any) = value as @Ann @com.example.Other List<String>
  fun constructorThenSimple(value: Any) = value as @Ann("reason") @com.example.Other List<String>
  fun simpleThenConstructor(value: Any) = value as @Ann @com.example.Other("reason") List<String>
  fun constructorMultiple(value: Any) = value as @Ann("one") @com.example.Other("two") List<String>
  fun done() = 3
}
'''
actual = {f['name']: (f['start'], f['end'], f['loc'])
          for f in ra._extract_functions_kotlin(source, 'probe.kt')}
expected = {
    'C.cast': (2, 2, 1),
    'C.safeCast': (3, 3, 1),
    'C.typed': (4, 4, 1),
    'C.simpleMultiple': (5, 5, 1),
    'C.constructorThenSimple': (6, 6, 1),
    'C.simpleThenConstructor': (7, 7, 1),
    'C.constructorMultiple': (8, 8, 1),
    'C.done': (9, 9, 1),
}
assert actual == expected, (actual, expected)
print('kotlin-simple-qualified-annotation-type-projection=exact')
PY
python3 - <<'PY'
from scripts import readability_audit as ra

source = '''class C {
  fun afterOperator(value: Any) = value as
    @com.example.Ann List<String>
  fun afterAnnotation(value: Any) = value as? @com.example.Ann
    List<String>
  fun betweenAnnotations(value: Any) = value is @Ann
    @com.example.Other List<String>
  fun afterConstructor(value: Any) = value !is @com.example.Ann("reason")
    List<String>
  fun mixedMultiline(value: Any) = value as @Ann
    @com.example.Other("reason")
    List<String>
  fun done() = 3
}
'''
actual = {f['name']: (f['start'], f['end'], f['loc'])
          for f in ra._extract_functions_kotlin(source, 'probe.kt')}
expected = {
    'C.afterOperator': (2, 3, 2),
    'C.afterAnnotation': (4, 5, 2),
    'C.betweenAnnotations': (6, 7, 2),
    'C.afterConstructor': (8, 9, 2),
    'C.mixedMultiline': (10, 12, 3),
    'C.done': (13, 13, 1),
}
assert actual == expected, (actual, expected)
print('kotlin-multiline-qualified-annotation-type-projection=exact')
PY
rm -f /tmp/iop-readability-simple-annotation-a.json /tmp/iop-readability-simple-annotation-b.json /tmp/iop-readability-simple-annotation-tracked.json
python3 scripts/readability_audit.py --check --input-mode worktree --output /tmp/iop-readability-simple-annotation-a.json
python3 scripts/readability_audit.py --check --input-mode worktree --output /tmp/iop-readability-simple-annotation-b.json
cmp /tmp/iop-readability-simple-annotation-a.json /tmp/iop-readability-simple-annotation-b.json
python3 scripts/readability_audit.py --check --input-mode tracked --output /tmp/iop-readability-simple-annotation-tracked.json
make readability-audit
git diff --check
~~~

기대 결과: 전체 unit PASS, simple/multiline qualified annotation probe exact, worktree JSON byte-identical, tracked/worktree RATCHET OK, Make target PASS, whitespace 오류 없음.

모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
