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

# Readability audit 분류·lexer·ratchet 우회 최종 보정

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

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

## 배경

세 번째 루프의 76개 unit과 전체 audit은 통과했지만, 독립 경계 probe는 실제 E2E Shell 파일을 production으로 분류하고 supported-language 함수를 잘못 계측하는 것을 확인했다. read-set JSON을 삭제하거나 파손하면 task total ratchet이 생략되는 우회도 남아 있다. 분류·lexer·declaration·identity·required-config 경계를 하나의 재현 가능한 baseline으로 다시 닫는다.

## 사용자 리뷰 요청 흐름

사용자 리뷰 요청은 선택된 Milestone의 구현 잠금 결정만 active review stub의 `사용자 리뷰 요청` 섹션에 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식으로 기록한다. 구현 중 직접 사용자 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.
- 현재 아카이브 작업: `agent-task/m-agent-readable-repository-refactor/12+03,06,10,11_readability_baseline/plan_cloud_G07_2.log`
- 현재 아카이브 리뷰: `agent-task/m-agent-readable-repository-refactor/12+03,06,10,11_readability_baseline/code_review_cloud_G07_2.log`
- 판정: FAIL
- Required 5 / Suggested 0 / Nit 0
  - `scripts/e2e-*.sh`가 production으로 분류되어 Shell E2E test에 잘못된 LOC 정책이 적용된다.
  - missing/malformed `readability_read_sets.json`이 `None`으로 통과되어 task total ratchet 전체를 우회한다.
  - nested block comment와 Shell multiline quote가 조기 종료되어 함수 LOC와 현재 AWK 문자열의 함수 목록을 오염시킨다.
  - Dart callback `Function`, Shell call expression, multiline type declaration이 false positive 또는 scope 손실을 만든다.
  - Dart/Kotlin/Swift overload가 동일 `scope.name`으로 충돌해 strict baseline과 dedupe 신뢰성을 깨뜨린다.
- 영향 파일: `scripts/readability_audit.py`, `scripts/readability_audit_test.py`, `scripts/readability_baseline.json`, `scripts/readability_read_sets.json`.
- 검증 증거: 기존 unit 76개, worktree/tracked audit, 반복 JSON cmp, changed-existing probe, Make target, `git diff --check`는 PASS했다. 독립 probe는 read-set 우회, supported-language LOC 조기 종료, false callable, overload identity 충돌을 재현했다.
- Roadmap carryover: `readability-baseline` Task는 PASS 전까지 미완료로 유지한다.
- 추가 상세가 필요하면 위 `plan_cloud_G07_2.log`와 `code_review_cloud_G07_2.log`만 좁게 재확인한다.

## 분석 결과

### 읽은 파일

- `.gitignore`
- `Makefile`
- `scripts/readability_audit.py`
- `scripts/readability_audit_test.py`
- `scripts/readability_baseline.json`
- `scripts/readability_read_sets.json`
- `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-ops/rules/project/domain/testing/rules.md`
- `agent-test/local/rules.md`
- `agent-test/local/testing-smoke.md`
- `agent-ops/rules/common/rules-agent-spec.md`
- `agent-spec/index.md`
- `agent-task/m-agent-readable-repository-refactor/12+03,06,10,11_readability_baseline/plan_cloud_G07_2.log`
- `agent-task/m-agent-readable-repository-refactor/12+03,06,10,11_readability_baseline/code_review_cloud_G07_2.log`
- `agent-task/archive/2026/07/m-agent-readable-repository-refactor/03+02_openai_provider_tools/complete.log`
- `agent-task/archive/2026/07/m-agent-readable-repository-refactor/06+04,05_core_edge_tests/complete.log`
- `agent-task/archive/2026/07/m-agent-readable-repository-refactor/10+07,08,09_adapter_control_plane_tests/complete.log`
- `agent-task/archive/2026/07/m-agent-readable-repository-refactor/11_client_tests/complete.log`

### SDD 기준

선택 Milestone은 `SDD: 불필요`다. 외부 API/proto/config schema와 runtime 책임 경계를 바꾸지 않는 동작 보존형 저장소 audit 도구라는 기록된 사유를 유지한다.

### 테스트 환경 규칙

`test_env=local`. `agent-test/local/rules.md`와 `agent-test/local/testing-smoke.md`를 읽었다. Python stdlib unittest, 임시 JSON 반복 cmp, tracked/worktree CLI, Make target, `git diff --check`를 적용한다. production/runtime 실행 경로는 바꾸지 않으므로 repo 내 edge-node 진단, 보조 E2E smoke, full-cycle은 대상이 아니며 Docker/외부 서비스 검증도 요구하지 않는다. `make readability-audit`가 공식 entrypoint다.

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

- 실제 `scripts/e2e-*.sh` 분류를 assert하는 test가 없다.
- read-set config 파일 부재·malformed JSON의 CLI exit/status test가 없다.
- Kotlin/Swift nested comment와 Shell multiline quote/AWK body regression이 없다.
- Dart callback type, Shell body-less call, next-line type brace scope regression이 없다.
- Dart/Kotlin/Swift overload identity 충돌과 report-wide duplicate identity gate가 없다.

### 심볼 참조

외부 rename/remove 심볼은 없다. 수정 대상 helper의 호출은 `scripts/readability_audit.py` 내 `classify_file`, `_blank_lex_strings_and_comments`, `_extract_functions_brace`, `load_json_file`, `audit_read_sets`, `ratchet_check`, `main`과 동일 unit 파일에 한정된다. 신규 dependency는 추가하지 않는다.

### 분할 판단

분할 정책을 먼저 검토했다. Required 5건은 하나의 Python 계측 pipeline, 동일 unit suite, 동일 generated baseline을 공유하며 분류·parser중 하나만 먼저 archive하면 baseline identity가 의도적으로 어긋난다. 외부 API/ownership/test profile 경계가 없고 하나의 최종 JSON으로 같이 검증해야 하므로 기존 `12+03,06,10,11_readability_baseline` 단일 후속 루프를 유지한다. predecessor `03`, `06`, `10`, `11`은 위에 명시한 각 archive `complete.log`에서 PASS를 확인했다.

### 범위 결정 근거

`scripts/readability_audit.py`, unit test, baseline/read-set JSON만 보정한다. `Makefile`의 worktree target과 `.gitignore` block은 이미 올바르므로 최종 검증만 수행한다. E2E/Client/Agent-Ops 소스는 false-positive fixture/evidence로만 사용하고 수정하지 않는다. production runtime, CI workflow, API/proto/config schema, roadmap, agent-spec/agent-contract는 변경하지 않는다. Spec update not needed: 매칭되는 living spec이 없고 runtime 기능 동작을 바꾸지 않는 내부 repository tooling 보정이다.

### 빌드 등급

`cloud-G07`: Shell/CLI exit-status 계약과 다언어 lexical/parser, generated baseline 증거 신뢰를 함께 회복해야 하고 Required가 5건이므로 cloud-G07 기준을 유지한다.

## 구현 체크리스트

- [ ] [REVIEW_REVIEW_REVIEW_REFACTOR-1] 현재 저장소의 Shell E2E/smoke 진입점을 test policy로 분류하고 baseline regression을 없앤다.
- [ ] [REVIEW_REVIEW_REVIEW_REFACTOR-2] required read-set JSON의 부재·파손·schema 오류가 task total ratchet을 우회하지 못하고 CLI exit 2로 고정한다.
- [ ] [REVIEW_REVIEW_REVIEW_REFACTOR-3] nested block comment와 Shell multiline quote/AWK body를 newline 보존 lexer로 처리하고 함수 LOC 경계를 통과시킨다.
- [ ] [REVIEW_REVIEW_REVIEW_REFACTOR-4] Dart callback·Shell call false positive를 제거하고 multiline type scope를 callable identity에 보존한다.
- [ ] [REVIEW_REVIEW_REVIEW_REFACTOR-5] Dart/Kotlin/Swift overload에 결정적 signature identity를 부여하고 전체 report/baseline의 identity 중복 0을 검증한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REVIEW_REVIEW_REVIEW_REFACTOR-1] 실제 Shell test 진입점 분류

- 문제: `scripts/readability_audit.py:79-85`의 basename 패턴은 `scripts/e2e-*.sh`를 놓쳐 testing domain의 보조 E2E/smoke 스크립트에 production threshold를 적용한다.
- 해결 방법:

~~~text
Before
Shell test = *_test.sh | test_*.sh | test/tests path

After
Shell test = existing conventions + repository scripts/e2e-*.sh testing entrypoints
unrelated production scripts remain production
~~~

- 수정 파일 및 체크리스트:
  - [ ] `scripts/readability_audit.py`: 프로젝트 Shell E2E/smoke 패턴을 test kind로 분류한다.
  - [ ] `scripts/readability_audit_test.py`: 실제 `scripts/e2e-smoke.sh`, `scripts/e2e-long-context-admission-smoke.sh`와 production 대조 경로를 assert한다.
  - [ ] `scripts/readability_baseline.json`: 잘못된 Shell production file violation을 제거한다.
- 테스트 작성: 작성한다. `TestPolicyThresholdsByFileKind.test_classify_project_shell_test_entrypoints`가 실제 경로와 대조 경로의 kind/threshold를 같이 assert한다.
- 중간 검증:

~~~bash
python3 -m unittest scripts.readability_audit_test.TestPolicyThresholdsByFileKind
~~~

기대 결과: 실제 E2E/smoke 경로는 test, 일반 Shell 운영 파일은 production으로 PASS.

### [REVIEW_REVIEW_REVIEW_REFACTOR-2] required read-set config 우회 차단

- 문제: `scripts/readability_audit.py:725-732`, `1113-1114`, `1199-1203`이 missing/malformed read-set JSON을 정상적인 optional 상태로 취급하고 `scripts/readability_audit.py:1032-1034`의 total ratchet을 생략한다.
- 해결 방법:

~~~text
Before
missing or malformed JSON -> None -> no read-set issues -> ratchet skipped

After
required config load distinguishes missing, malformed JSON, and valid object
missing/malformed/root-schema errors render deterministic diagnostics and exit 2
valid config always participates in task total ratchet
~~~

- 수정 파일 및 체크리스트:
  - [ ] `scripts/readability_audit.py`: required JSON load/error model과 read-set root/task schema validation을 구현한다.
  - [ ] `scripts/readability_audit_test.py`: missing file, malformed JSON, invalid root, valid config CLI exit/stdout-stderr fixture를 추가한다.
  - [ ] `scripts/readability_read_sets.json`: 최종 strict schema에 맞게 유지한다.
- 테스트 작성: 작성한다. `TestCliExitCodes` 신규 case가 read-set 설정 파일을 삭제/파손한 subprocess의 exit 2와 명시적 stderr를 assert한다.
- 중간 검증:

~~~bash
python3 -m unittest scripts.readability_audit_test.TestTaskReadSetTotalsAndBudget scripts.readability_audit_test.TestCliExitCodes
~~~

기대 결과: config 오류는 exit 2, ratchet 위반은 exit 4, 정상 config는 exit 0으로 분리된다.

### [REVIEW_REVIEW_REVIEW_REFACTOR-3] nested comment와 Shell multiline quote lexer

- 문제: `scripts/readability_audit.py:185-190`은 nested block comment depth를 잃고 `scripts/readability_audit.py:240-252`은 Shell quote를 newline에서 끝내 문자열/AWK body 내 brace와 callable text를 실제 코드로 계측한다.
- 해결 방법:

~~~text
Before
block comment closes at first */
shell quote closes at newline

After
supported nested-comment languages track comment depth
shell single/double quote state spans newlines and preserves every newline
heredoc and quote state do not leak callable/braces into cleaned source
~~~

- 수정 파일 및 체크리스트:
  - [ ] `scripts/readability_audit.py`: nested comment depth와 Shell multiline quote 상태를 보정한다.
  - [ ] `scripts/readability_audit_test.py`: Kotlin/Swift nested comment, Shell multiline quoted AWK/function-like text fixture의 LOC/name을 assert한다.
  - [ ] `scripts/readability_baseline.json`: 수정된 실제 함수 계측으로 재생성한다.
- 테스트 작성: 작성한다. 각 fixture에 조기 `}`와 function-like call을 넣고 원본 함수 end line과 정확한 name set을 assert한다.
- 중간 검증:

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

기대 결과: nested comment/multiline quote 내 brace와 callable text가 함수 LOC/name에 영향을 주지 않는다.

### [REVIEW_REVIEW_REVIEW_REFACTOR-4] callable 선언 필터와 multiline type scope

- 문제: `scripts/readability_audit.py:299-315`의 regex가 Dart `void Function` parameter와 body-less Shell call을 함수로 계측하고, `scripts/readability_audit.py:417-420`은 type brace가 다음 줄에 있으면 scope를 만들지 않는다.
- 해결 방법:

~~~text
Before
regex match alone creates a callable, even with no body
type scope opens only when the declaration line itself contains {

After
callable requires a valid declaration header and body/form for that language
parameter/callback/call expressions are excluded
type scope follows a bounded multiline declaration to its real body opener
~~~

- 수정 파일 및 체크리스트:
  - [ ] `scripts/readability_audit.py`: language-aware declaration/body gate와 multiline type scope 탐지를 추가한다.
  - [ ] `scripts/readability_audit_test.py`: Dart callback/setter parameter, Shell call, next-line Dart/Kotlin/Swift type brace fixture를 추가한다.
  - [ ] `scripts/readability_baseline.json`: false callable을 제거하고 scope-qualified identity로 재생성한다.
- 테스트 작성: 작성한다. `Function`·body-less call이 name set에 없고 next-line brace type의 method가 `Type.method`로 보고되는지 assert한다.
- 중간 검증:

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

기대 결과: false callable 0, multiline type scope-qualified identity PASS.

### [REVIEW_REVIEW_REVIEW_REFACTOR-5] overload-safe identity와 baseline 재생성

- 문제: `scripts/readability_audit.py:338-350`이 Dart/Kotlin/Swift overload의 parameter 차이를 버려 동일 `scope.name`을 만들고 `scripts/readability_audit.py:1036-1042`의 dedupe와 baseline duplicate validator가 서로 다른 위반을 하나로 충돌시킨다.
- 해결 방법:

~~~text
Before
C.run(Int), C.run(String) -> C.run, C.run

After
unique callable keeps the stable scope.name identity
only colliding overload groups receive deterministic normalized signature/arity suffixes
report and baseline identities are unique per path/metric/function
~~~

- 수정 파일 및 체크리스트:
  - [ ] `scripts/readability_audit.py`: declaration signature capture/normalization과 collision disambiguation을 구현한다.
  - [ ] `scripts/readability_audit_test.py`: Dart/Kotlin/Swift overload, 반복 결정성, report duplicate identity 0 regression을 추가한다.
  - [ ] `scripts/readability_baseline.json`: 최종 worktree report의 file/function violation과 task total에 정확히 맞게 재생성한다.
  - [ ] `scripts/readability_read_sets.json`: 추가 test로 변경된 self read-set total와 strict schema를 맞춘다.
- 테스트 작성: 작성한다. 언어별 overload 두 건의 identity가 서로 다르고 반복 실행에서 동일하며 baseline schema/ratchet이 모두 보존됨을 assert한다.
- 중간 검증:

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

기대 결과: overload identity 충돌 0, baseline schema/ratchet PASS.

## 의존 관계 및 구현 순서

1. REVIEW_REVIEW_REVIEW_REFACTOR-1과 2의 분류/config gate를 고정한다.
2. REVIEW_REVIEW_REVIEW_REFACTOR-3과 4의 cleaned source/declaration set을 고정한다.
3. REVIEW_REVIEW_REVIEW_REFACTOR-5의 overload identity를 고정한 후 하나의 worktree projection으로 baseline/read-set total을 재생성한다.

## 수정 파일 요약

| 파일 | 항목 |
|---|---|
| `scripts/readability_audit.py` | REVIEW_REVIEW_REVIEW_REFACTOR-1, 2, 3, 4, 5 |
| `scripts/readability_audit_test.py` | REVIEW_REVIEW_REVIEW_REFACTOR-1, 2, 3, 4, 5 |
| `scripts/readability_baseline.json` | REVIEW_REVIEW_REVIEW_REFACTOR-1, 3, 4, 5 |
| `scripts/readability_read_sets.json` | REVIEW_REVIEW_REVIEW_REFACTOR-2, 5 |

## 최종 검증

~~~bash
python3 -m unittest scripts.readability_audit_test
rm -f /tmp/iop-readability-followup-e.json /tmp/iop-readability-followup-f.json /tmp/iop-readability-followup-tracked-3.json
python3 scripts/readability_audit.py --check --input-mode worktree --output /tmp/iop-readability-followup-e.json
python3 scripts/readability_audit.py --check --input-mode worktree --output /tmp/iop-readability-followup-f.json
cmp /tmp/iop-readability-followup-e.json /tmp/iop-readability-followup-f.json
python3 scripts/readability_audit.py --check --input-mode tracked --output /tmp/iop-readability-followup-tracked-3.json
python3 - <<'PY'
import collections
import json
import os
import subprocess

with open('/tmp/iop-readability-followup-e.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)

files = {entry['path']: entry for entry in report['files']}
for path in ('scripts/e2e-smoke.sh', 'scripts/e2e-long-context-admission-smoke.sh'):
    assert files[path]['kind'] == 'test', (path, files[path]['kind'])

identities = [
    (entry['path'], function['name'])
    for entry in report['files']
    for function in entry.get('functions', [])
]
duplicates = sorted(key for key, count in collections.Counter(identities).items() if count > 1)
false_functions = sorted(key for key in identities if key[1].endswith('.Function'))
print(f'duplicate-function-identities={duplicates}')
print(f'false-dart-functions={false_functions}')
assert not duplicates and not false_functions

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

reported = set(files)
changed_existing = {
    path
    for path in subprocess.run(
        ['git', 'diff', '--name-only', '-z', 'HEAD'],
        check=True,
        capture_output=True,
    ).stdout.decode().rstrip('\0').split('\0')
    if path and os.path.isfile(path) and os.path.splitext(path)[1] in {'.go', '.dart', '.sh', '.py', '.kt', '.swift'}
}
missing = sorted(changed_existing - reported)
print(f'changed-existing={len(changed_existing)} missing={missing}')
assert not missing
PY
make readability-audit
git diff --check
~~~

기대 결과: 전체 unit PASS, worktree JSON byte-identical, tracked/worktree RATCHET OK, E2E Shell kind=test, report 함수 identity 중복/false `Function` 0, report-baseline violation/task total 정확 일치, 수정 tracked 누락 0, Make target PASS, whitespace 오류 없음. production/runtime 미변경으로 edge-node 진단·보조 E2E·full-cycle은 생략한다.

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