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

# Kotlin spacing-independent comparison 및 literal infix 연속식 보정

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

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

## 배경

118개 unit과 공식 ratchet은 통과하지만 Kotlin 연속식 판정이 lexical spacing과 string blanking 결과에 의존한다. Kotlin grammar가 허용하는 공백 없는 comparison과 string literal 좌항 custom infix가 선언 줄에서 종료되므로, grammar token 판정과 operand evidence를 분리해 고정한다.

## 사용자 리뷰 요청 흐름

사용자 리뷰 요청은 선택된 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.
- 현재 아카이브 작업: `agent-task/m-agent-readable-repository-refactor/12+03,06,10,11_readability_baseline/plan_local_G09_6.log`
- 현재 아카이브 리뷰: `agent-task/m-agent-readable-repository-refactor/12+03,06,10,11_readability_baseline/code_review_local_G09_6.log`
- 판정: FAIL
- Required 1 / Suggested 0 / Nit 0
  - `KOTLIN_BARE_COMPARISON_RE`가 `<`/`>` 앞 공백을 요구하고, custom infix operand 판정이 literal이 지워진 cleaned text만 사용해 `1<`, `2>`, `"x" then` 연속식을 누락한다.
- 영향 파일: `scripts/readability_audit.py`, `scripts/readability_audit_test.py`, `scripts/readability_baseline.json`.
- 검증 증거: 제출 및 독립 재실행에서 118 unit, worktree 2회 byte-identical JSON, tracked/worktree RATCHET, baseline exact projection, Make target, `git diff --check`는 PASS했다. 독립 grammar probe의 `C.lessNoSpace`, `C.greaterNoSpace`, `C.stringInfix`는 모두 기대 2 LOC 대신 1 LOC였다.
- Roadmap carryover: `readability-baseline` Task는 PASS 전까지 미완료로 유지한다.
- 추가 상세가 필요하면 위 `plan_local_G09_6.log`와 `code_review_local_G09_6.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_6.log`
- `agent-task/m-agent-readable-repository-refactor/12+03,06,10,11_readability_baseline/code_review_local_G09_6.log`

### SDD 기준

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

### 테스트 환경 규칙

`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 검증을 적용한다. `command -v kotlinc`는 현재 runner에서 출력이 없어 compiler fixture는 사용하지 않고 Kotlin 공식 grammar와 deterministic extractor fixture를 기준으로 삼는다. 사용자 실행/runtime 동작은 바꾸지 않으므로 edge-node 진단, 보조 E2E, full-cycle, 외부 provider/Docker 검증은 대상이 아니다.

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

- 기존 operator grammar fixture는 공백이 있는 `1 <`와 숫자 좌항 `1 join`만 검증한다.
- 공백 없는 `1<`/`2>` comparison과 string literal 좌항 `"x" then` 연속식이 없다.
- literal-aware operand 보정 뒤 완결 simple identifier와 마지막 sibling을 삼키지 않는 negative assertion이 없다.

### 심볼 참조

- `KOTLIN_BARE_COMPARISON_RE`는 `_kotlin_trailing_continuation` 한 곳에서만 참조된다.
- `_kotlin_trailing_infix_identifier`는 `_kotlin_trailing_continuation`, `_kotlin_trailing_continuation`은 `_kotlin_expression_continues`, `_kotlin_expression_continues`는 `_find_expression_end`에서만 호출된다.
- `_find_expression_end`의 production call site는 `_extract_functions_brace` 한 곳이다. 변경 시 aligned raw/cleaned line 전달을 이 체인 전체에서 일관되게 갱신한다.

### 분할 판단

split 정책을 재평가했으며 같은 `12+03,06,10,11_readability_baseline` subtask의 단일 후속 plan을 유지한다. 한 Kotlin continuation classifier, 한 deterministic fixture, 같은 self-referential baseline projection을 함께 바꾸는 단일 ownership/risk 경계이며, 분리하면 동일 두 Python 파일 충돌과 baseline 재생성만 중복된다. 기존 predecessor `03`, `06`, `10`, `11` 충족 상태는 이 후속에서 바뀌지 않는다.

### 범위 결정 근거

`scripts/readability_audit.py`의 Kotlin depth-0 comparison/infix 판정, 대응 unit, 실제 projection 값만 수정한다. 이미 통과한 Shell lexer, `scripts/readability_read_sets.json`, Makefile, 다른 언어 extractor, runtime 소스, roadmap, spec/contract는 수정하지 않는다. 신규 helper violation은 baseline에 넣지 않는다.

### 빌드 등급

`local-G09`. 공식 grammar와 실패 probe가 명확하고 두 Python 파일 및 JSON 고정점에서 결정적으로 검증 가능하며 외부 runtime 판단이 필요 없다.

### 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_REFACTOR-1] Kotlin comparison token을 spacing-independent하게 판정하고 literal 좌항 custom infix의 raw operand evidence를 보존하면서 sibling 경계를 유지한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REFACTOR-1] Kotlin comparison 및 literal infix continuation

- 문제: `scripts/readability_audit.py:510-512`는 bare comparison에 앞 공백을 요구해 `1<\n2`와 `2>\n1`을 1 LOC로 줄인다. `scripts/readability_audit.py:728-742`는 cleaned text에서만 custom infix 좌항을 찾으므로 string literal이 blank된 `"x" then\n1`도 1 LOC로 종료한다.
- 해결 방법:

~~~text
Before
bare < 또는 > = 앞 공백이 있을 때만 continuation
custom infix 좌항 = string/comment가 지워진 cleaned prefix에서만 확인

After
comparison token = lexical spacing과 무관하게 < 또는 > 자체를 판정
operator/identifier token = cleaned line에서 판정
custom infix 좌항 = 길이가 정렬된 raw/cleaned line slice를 함께 사용해 literal operand를 확인
완결 simple identifier, 다음 declaration/annotation/type close = continuation 아님
~~~

- 수정 파일 및 체크리스트:
  - [ ] `scripts/readability_audit.py`: `KOTLIN_BARE_COMPARISON_RE`의 whitespace heuristic을 제거하고 `_find_expression_end`부터 Kotlin continuation helper까지 aligned raw/cleaned line evidence를 전달한다. token 자체는 cleaned text로 판정하고 literal 좌항 여부만 raw evidence로 보강하며 Dart/다른 brace language 계약은 유지한다.
  - [ ] `scripts/readability_audit_test.py`: 기존 operator grammar fixture에 no-space `<`/`>`, string literal 좌항 custom infix, 완결 simple identifier와 마지막 sibling의 start/end/LOC를 추가한다.
  - [ ] `scripts/readability_baseline.json`: 전체 worktree projection의 기존 identity 값이 바뀔 때만 reason을 보존해 고정점 갱신하고 신규 helper violation은 허용하지 않는다.
- 테스트 작성: 작성한다. `test_kotlin_depth_zero_operator_grammar_continuations_span_full_expression`이 `lessNoSpace`, `greaterNoSpace`, `stringInfix`를 각 2 LOC로, 완결 identifier와 마지막 sibling을 각 1 LOC로 assert한다. 기존 comparison/word/range/custom/leading operator assertion도 유지한다.
- 중간 검증:

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

source = '''class C {
  infix fun String.then(other: Int) = other
  fun lessNoSpace() = 1<
    2
  fun greaterNoSpace() = 2>
    1
  fun stringInfix() = "x" then
    1
  fun plain(value: Int) = value
  fun done() = 3
}
'''
actual = {f['name']: (f['start'], f['end'], f['loc'])
          for f in ra._extract_functions_kotlin(source, 'probe.kt')}
for name in ('C.lessNoSpace', 'C.greaterNoSpace', 'C.stringInfix'):
    assert actual[name][2] == 2, (name, actual[name])
for name in ('C.plain', 'C.done'):
    assert actual[name][2] == 1, (name, actual[name])
print('kotlin-spacing-independent-comparisons=exact')
print('kotlin-literal-infix-and-sibling-boundary=exact')
PY
~~~

기대 결과: 3 tests PASS이고 독립 probe의 no-space comparison/string infix는 2 LOC, 완결 identifier와 sibling은 1 LOC다.

## 수정 파일 요약

| 파일 | 항목 |
|---|---|
| `scripts/readability_audit.py` | REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REFACTOR-1 |
| `scripts/readability_audit_test.py` | REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REFACTOR-1 |
| `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 {
  infix fun String.then(other: Int) = other
  fun lessNoSpace() = 1<
    2
  fun greaterNoSpace() = 2>
    1
  fun stringInfix() = "x" then
    1
  fun plain(value: Int) = value
  fun done() = 3
}
'''
actual = {f['name']: (f['start'], f['end'], f['loc'])
          for f in ra._extract_functions_kotlin(source, 'probe.kt')}
for name in ('C.lessNoSpace', 'C.greaterNoSpace', 'C.stringInfix'):
    assert actual[name][2] == 2, (name, actual[name])
for name in ('C.plain', 'C.done'):
    assert actual[name][2] == 1, (name, actual[name])
print('kotlin-spacing-independent-comparisons=exact')
print('kotlin-literal-infix-and-sibling-boundary=exact')
PY
rm -f /tmp/iop-readability-followup-o.json /tmp/iop-readability-followup-p.json /tmp/iop-readability-followup-tracked-7.json
python3 scripts/readability_audit.py --check --input-mode worktree --output /tmp/iop-readability-followup-o.json
python3 scripts/readability_audit.py --check --input-mode worktree --output /tmp/iop-readability-followup-p.json
cmp /tmp/iop-readability-followup-o.json /tmp/iop-readability-followup-p.json
python3 scripts/readability_audit.py --check --input-mode tracked --output /tmp/iop-readability-followup-tracked-7.json
python3 - <<'PY'
import collections
import json

with open('/tmp/iop-readability-followup-o.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, 독립 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`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
