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

# Kotlin type projection token/line context 보정

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

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

## 배경

계획에 있던 단순·중첩 명명 generic과 no-space comparison은 통과했지만, 현재 helper는 Kotlin `type` 문법을 line-local 문자 allowlist로 축약한다. 그 결과 function/annotated type projection, 공백 없는 safe cast, multiline type arguments를 완결 type으로 인식하지 못해 sibling identity를 누락하거나 function span을 closer 전에 조기 종료한다. cast/type-test의 type context를 token과 줄 경계까지 추적하되 일반 comparison continuation은 보존한다.

## 사용자 리뷰 요청 흐름

사용자 리뷰 요청은 선택된 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`: FAIL, Required 4 / Suggested 0 / Nit 0.
  - `plan_local_G09_1.log` / `code_review_local_G09_1.log`: FAIL, Required 5 / Suggested 0 / Nit 0.
  - `plan_cloud_G07_2.log` / `code_review_cloud_G07_2.log`: FAIL, Required 5 / Suggested 0 / Nit 0.
  - `plan_cloud_G07_3.log` / `code_review_cloud_G07_3.log`: FAIL, Required 4 / Suggested 0 / Nit 0.
  - `plan_local_G09_4.log` / `code_review_local_G09_4.log`: FAIL, Required 2 / Suggested 0 / Nit 0.
  - `plan_local_G09_5.log` / `code_review_local_G09_5.log`: FAIL, Required 1 / Suggested 0 / Nit 0.
  - `plan_local_G09_6.log` / `code_review_local_G09_6.log`: FAIL, Required 1 / Suggested 0 / Nit 0.
  - `plan_local_G09_7.log` / `code_review_local_G09_7.log`: FAIL, Required 1 / Suggested 0 / Nit 0.
  - `plan_local_G09_8.log` / `code_review_local_G09_8.log`: FAIL, Required 3 / Suggested 0 / Nit 0.
- 현재 아카이브 작업: `agent-task/m-agent-readable-repository-refactor/12+03,06,10,11_readability_baseline/plan_local_G09_8.log`
- 현재 아카이브 리뷰: `agent-task/m-agent-readable-repository-refactor/12+03,06,10,11_readability_baseline/code_review_local_G09_8.log`
- 판정: FAIL
- Required 3 / Suggested 0 / Nit 0
  - line-local 문자 allowlist가 function/parenthesized/annotated/definitely-non-null type projection의 generic closer를 comparison으로 오판한다.
  - `KOTLIN_TYPE_OPERAND_RE`의 `\s+`가 공백 없는 `as?List<Int>` safe cast를 놓친다.
  - 현재 줄 slice만 보는 helper가 multiline type arguments의 opener/context를 추적하지 못해 sibling 누락 또는 span 조기 종료를 만든다.
- 영향 파일: `scripts/readability_audit.py`, `scripts/readability_audit_test.py`, `scripts/readability_baseline.json`.
- 검증 증거: 제출 및 독립 재실행에서 focused unit/probe, 118 unit, worktree 2회 byte-identical JSON, tracked/worktree RATCHET, baseline exact projection, Make target, `git diff --check`는 PASS했다. 독립 negative probe에서 function/annotated type 및 adjacent safe cast는 다음 sibling을 삼켰고, multiline simple/nested generic은 sibling 누락 또는 closer 전 span 종료를 보였다.
- Roadmap carryover: `readability-baseline` Task는 PASS 전까지 미완료로 유지한다.
- 추가 상세가 필요하면 위 `plan_local_G09_8.log`와 `code_review_local_G09_8.log`만 좁게 재확인한다.

## 분석 결과

### 읽은 파일

- `scripts/readability_audit.py`
- `scripts/readability_audit_test.py`
- `scripts/readability_baseline.json`
- `scripts/readability_read_sets.json`
- `Makefile`
- `agent-ops/rules/project/domain/testing/rules.md`
- `agent-test/local/rules.md`
- `agent-test/local/testing-smoke.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`
- `agent-task/m-agent-readable-repository-refactor/12+03,06,10,11_readability_baseline/plan_local_G09_8.log`
- `agent-task/m-agent-readable-repository-refactor/12+03,06,10,11_readability_baseline/code_review_local_G09_8.log`

### 문법 근거

- [Kotlin 공식 문법](https://kotlinlang.org/grammar/)에서 `typeProjection`은 전체 `type`을 허용하고, `type`은 `functionType`, `parenthesizedType`, `definitelyNonNullableType`, annotation modifier를 포함한다. 따라서 `List<(Int) -> String>`의 `->`는 angle closer가 아니며 끝의 `>`만 type arguments를 닫는다.
- `asExpression`은 `asOperator type`이고 `asOperator`에는 독립 `as?` token이 있으므로 `as?List<Int>`처럼 zero-space type start가 가능하다. 반면 `as`/`is`/`!is`는 identifier tail을 operator로 오인하지 않도록 word boundary를 유지해야 한다.
- `typeArguments`는 `<`와 `>` 사이의 type projection 전후 및 comma 주변에 newline을 허용한다. current-line suffix만으로는 완결 여부를 판정할 수 없다.

### SDD 기준

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

### 테스트 환경 규칙

`test_env=local`. Python stdlib unittest, `/tmp` JSON, worktree/tracked audit, 반복 `cmp`, Make target, whitespace 검증을 적용한다. 사용자 실행/runtime 동작은 바꾸지 않으므로 edge-node 진단, 보조 E2E, full-cycle, 외부 provider/Docker 검증은 대상이 아니다.

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

- 기존 fixture는 `List<Int>`와 `Map<String, List<Int>>` 같은 identifier 기반 single-line type만 검증한다.
- function/parenthesized/annotated/definitely-non-null/escaped-identifier type projection 뒤 sibling의 exact identity/span 검증이 없다.
- `as?List<Int>`와 `as? List<Int>`의 동등한 종료, `asList` 같은 identifier tail의 negative boundary가 없다.
- simple/nested multiline type arguments의 opener-to-closer span과 뒤 sibling 보존 검증이 없다.

### 심볼 참조

- `KOTLIN_TYPE_OPERAND_RE`와 `KOTLIN_TYPE_ARGUMENT_CHARS`는 `_kotlin_trailing_generic_type_closer`에서 generic type context 판정에 사용된다.
- `_kotlin_trailing_generic_type_closer`는 `_kotlin_trailing_continuation`에서만 호출되고, 결과는 `_kotlin_expression_continues` → `_find_expression_end` → `_extract_functions_brace`로 전달된다.
- multiline type state가 필요하면 `_find_expression_end`와 Kotlin helper 사이의 입력 계약까지 함께 갱신하되 Dart/다른 brace language의 종료 계약은 바꾸지 않는다.

### 분할 판단

같은 `12+03,06,10,11_readability_baseline` subtask의 단일 후속 plan을 유지한다. 세 Required는 모두 한 Kotlin type-context classifier와 같은 extraction span 계약을 공유하며, 분리하면 동일 두 Python 파일과 self-referential baseline 충돌만 늘어난다.

### 범위 결정 근거

`scripts/readability_audit.py`의 Kotlin cast/type-test generic context, 대응 unit, 실제 projection 값만 수정한다. Shell lexer, `scripts/readability_read_sets.json`, Makefile, 다른 언어 extractor, runtime 소스, roadmap, spec/contract는 수정하지 않는다. 신규 helper violation은 baseline에 넣지 않는다.

### 빌드 등급

`local-G09`. Required는 3건이지만 모두 Kotlin type token/line context라는 한 bounded parser 경계이고 두 Python 파일과 JSON 고정점에서 결정적으로 검증할 수 있다. 외부 runtime·interactive 동작·증거 신뢰 진단이 필요하지 않아 local 조건을 유지한다.

### Spec 판단

`agent-spec/index.md`에 repository readability tooling과 매칭되는 living spec이 없다. Spec update not needed: runtime/API 동작이나 기존 spec evidence를 바꾸지 않는다.

## 구현 체크리스트

- [ ] [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REFACTOR-1] Kotlin 전체 type projection token을 generic closer 문맥에서 구분하고 immediate sibling identity/span을 보존한다.
- [ ] [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REFACTOR-2] 공백 없는 `as?` safe cast를 인식하면서 word-operator identifier 경계를 보존한다.
- [ ] [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REFACTOR-3] multiline type arguments의 opener-to-closer state를 추적하고 정확한 function span과 sibling identity를 보존한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REFACTOR-1] Kotlin type projection token 판정

- 문제: `scripts/readability_audit.py:782-790`은 모든 `>`를 angle depth로 세고 제한된 문자만 허용한다. `List<(Int) -> String>`, `List<@Ann String>`, `List<T & Any>`, escaped identifier처럼 유효한 type projection은 comparison continuation으로 오판되어 다음 declaration을 삼킨다.
- 해결 방법:

~~~text
Before
generic type argument = identifier/dot/comma/?/*/< > 문자 allowlist
모든 > = generic angle closer

After
generic type argument = Kotlin type token과 balanced delimiter 문맥
function type의 -> = angle depth와 분리
annotation/parenthesized/definitely-non-null/escaped identifier = type projection으로 허용
type을 닫은 뒤 별도 > = 기존 comparison continuation
~~~

- 수정 파일 및 체크리스트:
  - [ ] `scripts/readability_audit.py`: char allowlist 기반 reverse scan을 token/delimiter-aware bounded scan으로 교체하거나 동등한 구조로 보정한다. `->`와 generic `>`를 구분하고 type projection의 표준 token을 처리한다.
  - [ ] `scripts/readability_audit_test.py`: function/parenthesized/annotated/definitely-non-null 또는 escaped identifier의 대표 fixture와 immediate sibling을 추가한다. 전체 name set과 각 start/end/LOC를 assert하고 `value as List<Int> >` comparison 회귀도 유지한다.
  - [ ] `scripts/readability_baseline.json`: 전체 worktree projection의 기존 identity 값이 바뀔 때만 reason을 보존해 고정점 갱신하고 신규 helper violation은 허용하지 않는다.
- 테스트 작성: 작성한다. `test_kotlin_generic_type_projection_closers_preserve_siblings`를 추가해 대표 type projection이 1 LOC, 뒤 sibling이 1 LOC이며 누락이 없음을 검증한다.
- 중간 검증:

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

기대 결과: focused unit PASS, function/annotated 등 representative generic cast와 뒤 sibling의 identity/start/end/LOC가 exact다.

### [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REFACTOR-2] Adjacent safe-cast token 경계

- 문제: `scripts/readability_audit.py:522-524`의 type operand regex가 모든 operator 뒤 공백을 요구한다. `as?`는 `?`가 identifier를 끊는 독립 token이므로 `value as?List<Int>`도 유효하지만 current classifier는 이를 comparison으로 처리한다.
- 해결 방법:

~~~text
Before
as/as?/is/!is 뒤 type = 반드시 whitespace 필요

After
as? 뒤 type = whitespace 0개 이상 허용
as/is/!is = identifier tail 오인을 막는 기존 word boundary 유지
asList/isReady 같은 identifier = operator 아님
~~~

- 수정 파일 및 체크리스트:
  - [ ] `scripts/readability_audit.py`: `as?`와 word operator의 lexical boundary를 분리해 adjacent safe cast만 허용한다.
  - [ ] `scripts/readability_audit_test.py`: `as?List<Int>`와 `as? List<Int>` 뒤 sibling exact span, identifier-tail negative를 추가한다.
- 테스트 작성: 작성한다. `test_kotlin_adjacent_safe_cast_generic_closer_preserves_sibling`을 추가한다.
- 중간 검증:

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

기대 결과: adjacent/spaced safe cast는 각 1 LOC로 끝나고 sibling이 보존되며 identifier tail은 cast context로 오인되지 않는다.

### [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REFACTOR-3] Multiline type-argument state

- 문제: `scripts/readability_audit.py:769-879`의 generic closer helper는 현재 줄만 보므로 이전 줄의 cast/type-test operator와 `<` opener를 알 수 없다. simple multiline generic은 sibling을 삼키고 nested multiline generic은 closer 전에 span을 끝낸다.
- 해결 방법:

~~~text
Before
각 depth-0 줄 suffix만 독립 판정
angle bracket state는 줄 사이에 보존하지 않음

After
cast/type-test RHS type context에서 <...> depth를 줄 사이에 보존
type arguments 내부 newline/comma/nested generic = expression 지속
outer >가 닫히면 해당 줄에서 expression 종료
일반 1< / value> comparison = 기존 RHS continuation 유지
~~~

- 수정 파일 및 체크리스트:
  - [ ] `scripts/readability_audit.py`: `_find_expression_end`에서 expression 시작부터 Kotlin type-context angle state를 전달하거나 동등한 방식으로 multiline opener/closer를 추적한다. Dart/다른 언어는 변경하지 않는다.
  - [ ] `scripts/readability_audit_test.py`: simple/nested multiline generic cast와 immediate sibling의 exact set/start/end/LOC를 추가한다.
- 테스트 작성: 작성한다. `test_kotlin_multiline_generic_type_arguments_preserve_span_and_siblings`를 추가한다.
- 중간 검증:

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

기대 결과: simple/nested multiline target은 opener부터 outer closer까지 정확히 포함하고 모든 뒤 sibling이 1 LOC로 보존된다.

## 수정 파일 요약

| 파일 | 항목 |
|---|---|
| `scripts/readability_audit.py` | REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REFACTOR-1, -2, -3 |
| `scripts/readability_audit_test.py` | REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REFACTOR-1, -2, -3 |
| `scripts/readability_baseline.json` | 실제 worktree projection 고정점 갱신 시 |

## 최종 검증

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

source = '''class C {
  fun functionType(value: Any) = value as List<(Int) -> String>
  fun annotated(value: Any) = value as List<@Ann String>
  fun safe(value: Any) = value as?List<Int>
  fun multiline(value: Any) = value as Map<
    String,
    List<Int>
  >
  fun greater(value: Int) = value>
    0
  fun done() = 3
}
'''
actual = {f['name']: (f['start'], f['end'], f['loc'])
          for f in ra._extract_functions_kotlin(source, 'probe.kt')}
expected = {
    'C.functionType': (2, 2, 1),
    'C.annotated': (3, 3, 1),
    'C.safe': (4, 4, 1),
    'C.multiline': (5, 8, 4),
    'C.greater': (9, 10, 2),
    'C.done': (11, 11, 1),
}
assert actual == expected, (actual, expected)
print('kotlin-type-projection-context=exact')
print('kotlin-adjacent-safe-cast=exact')
print('kotlin-multiline-generics-and-siblings=exact')
PY
rm -f /tmp/iop-readability-followup-s.json /tmp/iop-readability-followup-t.json /tmp/iop-readability-followup-tracked-9.json
python3 scripts/readability_audit.py --check --input-mode worktree --output /tmp/iop-readability-followup-s.json
python3 scripts/readability_audit.py --check --input-mode worktree --output /tmp/iop-readability-followup-t.json
cmp /tmp/iop-readability-followup-s.json /tmp/iop-readability-followup-t.json
python3 scripts/readability_audit.py --check --input-mode tracked --output /tmp/iop-readability-followup-tracked-9.json
python3 - <<'PY'
import collections
import json

with open('/tmp/iop-readability-followup-s.json', encoding='utf-8') as handle:
    report = json.load(handle)
with open('scripts/readability_baseline.json', encoding='utf-8') as handle:
    baseline = json.load(handle)
identities = [(entry['path'], function['name'])
              for entry in report['files']
              for function in entry.get('functions', [])]
assert not [key for key, count in collections.Counter(identities).items() if count > 1]
current_violations = sorted(
    (v['path'], v['metric'], v.get('function', ''), v['level'], v['value'])
    for v in report['violations'])
baseline_violations = sorted(
    (v['path'], v['metric'], v.get('function', ''), v['level'], v['value'])
    for section in ('file_thresholds', 'function_thresholds')
    for v in baseline[section])
assert current_violations == baseline_violations
current_totals = sorted(
    (task['task_id'], task['total_loc'])
    for task in report['task_read_sets']
    if task['total_loc'] > task['budget']['max_total_loc'])
baseline_totals = sorted((entry['task_id'], entry['value'])
                         for entry in baseline['task_read_set_totals'])
assert current_totals == baseline_totals
print('duplicate-function-identities=[]')
print('baseline-projection=exact')
PY
make readability-audit
git diff --check
~~~

기대 결과: 전체 unit PASS, comprehensive Kotlin probe exact, worktree JSON byte-identical, tracked/worktree RATCHET OK, identity 중복 0, report/baseline exact projection, Make target PASS, whitespace 오류 없음. 코드/테스트 LOC로 baseline 값이 증가하면 기존 reason을 identity 기준으로 보존해 고정점으로 수렴시키되 신규 helper violation은 허용하지 않는다.

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