<!-- task=ai_first_improvements plan=0 tag=AI_FIRST -->
# AI-first 개선 계획

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

- 각 항목의 체크리스트를 순서대로 완료한다.
- 항목 완료마다 **중간 검증** 커맨드를 실행하고 통과를 확인한다.
- 모든 항목 완료 후 **최종 검증** 커맨드를 실행한다.
- 작업이 끝나면 `CODE_REVIEW.md`의 각 섹션을 실제 구현 내용으로 채운다:
  - 구현 항목별 완료 여부 체크
  - 계획 대비 변경 사항 (없으면 "계획서 그대로 구현")
  - 주요 설계 결정
  - 리뷰어를 위한 체크포인트
  - 검증 결과 (실제 실행한 명령과 출력 붙여넣기)

## 배경

현재 프로젝트는 Go와 Dart 양방향 crosstest가 통과하는 좋은 기반을 갖고 있다. 다음 단계는 AI 에이전트가 새 언어, 새 메시지 타입, 새 릴리스 규칙을 안전하게 따라갈 수 있도록 반복 작업을 명문화하고 자동화하는 것이다. CI 실행 자체는 README에 명시한 대로 Jenkins가 담당하므로, 이 계획은 저장소 내부의 구현 정리와 검증 도구, 버전 정책, 언어 추가 scaffolding에 집중한다.

---

### [AI_FIRST-1] Dart 전송 계층을 Go 기준으로 정렬

**문제**

Go는 `Transport` 인터페이스를 통해 `Communicator`가 TCP/WS 세부 구현을 모르도록 분리되어 있다. 반면 Dart는 `Communicator`가 `transmitPacket` 추상 메서드를 직접 요구하고, TCP/WS 클라이언트가 각각 heartbeat와 전송 세부를 갖는다.

```dart
// Before — dart/lib/src/communicator.dart:44
/// Transport-specific framing and write. Implemented by each client subclass.
Future<void> transmitPacket(PacketBase base);

/// Serializes writes so stream transports do not interleave packets.
Future<void> queuePacket(PacketBase base) {
  final write = _outboundWrite.then((_) => transmitPacket(base));
  _outboundWrite = write.catchError((_) {});
  return write;
}
```

`PORTING_GUIDE.md:14`는 새 언어 구현에서 `Transport` 분리를 반드시 유지해야 한다고 말한다. Dart가 현재 레거시 패턴으로 남아 있으면 이후 AI 포팅 작업이 Go와 Dart 사이에서 서로 다른 기준을 학습하게 된다.

**해결 방법**

`dart/lib/src/transport.dart`를 새로 만들고 Dart에도 Go와 같은 최소 전송 인터페이스를 추가한다. `Communicator`는 `Transport? _transport`를 보유하고, `initialize`에서 parser map과 transport를 함께 받는 새 경로를 제공한다. 기존 `ProtobufClient`/`WsProtobufClient` public 생성자는 유지하되 내부에서 각각 TCP/WS transport adapter를 만들어 `Communicator`에 주입한다.

```dart
// After
abstract interface class Transport {
  Future<void> writePacket(PacketBase base);
  Future<void> close();
}

Future<void> queuePacket(PacketBase base) {
  final transport = _transport;
  if (transport == null) {
    return Future.error(StateError('transport is not initialized'));
  }
  final write = _outboundWrite.then((_) => transport.writePacket(base));
  _outboundWrite = write.catchError((_) {});
  return write;
}
```

TCP 파서는 Go와 같은 최대 패킷 제한도 갖는다. Go는 `go/tcp_client.go:17`의 `MaxPacketSize = 64 << 20`과 `go/tcp_client.go:73`의 초과 close 처리가 있다. Dart도 같은 상수를 두고 음수/초과 길이는 즉시 `close()`로 처리한다.

```dart
// Before — dart/lib/src/protobuf_client.dart:61
final length = Uint8List.fromList(_arrivedData.sublist(0, _headerSize))
    .buffer
    .asByteData()
    .getInt32(0);
if (length == 0) {
  _length = null;
  _arrivedData.clear();
  return;
}
_length = length;

// After
final length = Uint8List.fromList(_arrivedData.sublist(0, _headerSize))
    .buffer
    .asByteData()
    .getInt32(0);
if (length == 0) {
  _length = null;
  _arrivedData.clear();
  return;
}
if (length < 0 || length > maxPacketSize) {
  unawaited(close());
  return;
}
_length = length;
```

**수정 파일 및 체크리스트**

- [ ] `dart/lib/src/transport.dart` — Dart 전송 인터페이스 추가
  - [ ] `Transport.writePacket(PacketBase base)` 정의
  - [ ] `Transport.close()` 정의
- [ ] `dart/lib/src/communicator.dart` — transport 주입 경로 추가
  - [ ] 기존 `initialize(Map<...>)` 호출부와 호환되는 migration 경로 설계
  - [ ] `queuePacket`이 `transport.writePacket`을 사용하도록 변경
  - [ ] 기존 `transmitPacket` 추상 메서드 제거 또는 deprecated 호환 경로 유지 여부를 `CODE_REVIEW.md`에 기록
- [ ] `dart/lib/src/protobuf_client.dart` — TCP transport adapter 적용
  - [ ] `maxPacketSize = 64 << 20` 추가
  - [ ] 음수 길이와 최대 크기 초과 시 `close()` 호출
  - [ ] `_socket.add`/`flush` 로직을 TCP transport adapter로 이동
- [ ] `dart/lib/src/ws_protobuf_client.dart` — WS transport adapter 적용
  - [ ] `_ws.add(base.writeToBuffer())` 로직을 WS transport adapter로 이동
  - [ ] close 로직을 adapter와 client close 순서가 충돌하지 않게 정리
- [ ] `dart/lib/toki_socket.dart` — 필요한 경우 `transport.dart` export

**테스트 작성**

| 파일 | 테스트 이름 | 검증 목표 |
|------|-------------|-----------|
| `dart/test/communicator_test.dart` | `queuePacket uses injected transport` | fake transport가 받은 `PacketBase`와 에러 전파 확인 |
| `dart/test/socket_test.dart` | `TCP closes on oversized packet length` | 수동 socket으로 64 MiB 초과 header 전송 후 disconnect 확인 |

내부 리팩터링이지만 전송 계층과 TCP 파서 동작이 바뀌므로 회귀 테스트를 추가한다.

**중간 검증**

```bash
# 정적 분석
cd dart && dart analyze
# 테스트
cd dart && dart test
```

Expected: 기존 40개 + 신규 2개 = 총 42개 통과

---

### [AI_FIRST-2] Proto sync와 codegen 검증 도구 추가

**문제**

`PROTOCOL.md:182`는 Dart proto를 canonical source로 지정하고, `PROTOCOL.md:185`는 Go copy를 수동으로 sync해야 한다고 설명한다. README도 같은 수동 절차를 반복한다.

```markdown
// Before — README.md:81
## Adding Message Types

Edit `dart/lib/src/packets/message_common.proto` and regenerate:
```

이 상태에서는 AI 에이전트나 사람이 proto를 바꾼 뒤 Go copy, generated Dart, generated Go 중 하나를 빠뜨리기 쉽다.

**해결 방법**

`tools/` 아래에 proto 검증과 재생성 스크립트를 추가한다. Go proto에는 `option go_package`만 허용되는 차이로 남기고, 나머지 message body가 Dart canonical proto와 일치하는지 검사한다. 재생성 스크립트는 Dart와 Go 생성 명령을 한 곳에 모은다.

```bash
# After
tools/check_proto_sync.sh
tools/generate_proto.sh
```

`README.md`의 Adding Message Types 섹션은 수동 명령 나열에서 도구 기반 절차로 바꾼다.

````markdown
// After
## Adding Message Types

Edit `dart/lib/src/packets/message_common.proto`, then run:

```bash
tools/generate_proto.sh
tools/check_proto_sync.sh
```
````

**수정 파일 및 체크리스트**

- [ ] `tools/check_proto_sync.sh` — Dart/Go proto sync 검사
  - [ ] Dart canonical proto와 Go proto를 읽는다
  - [ ] Go 전용 `option go_package` 라인을 제외한 schema body 비교
  - [ ] mismatch 시 diff와 함께 non-zero exit
- [ ] `tools/generate_proto.sh` — Dart/Go protobuf 재생성
  - [ ] Dart `protoc --dart_out=lib/src/packets ...` 실행
  - [ ] Go `protoc --go_out=. --go_opt=paths=source_relative ...` 실행
  - [ ] 필요한 binary가 없을 때 명확한 에러 메시지 출력
- [ ] `README.md` — Adding Message Types와 Running Tests 갱신
  - [ ] proto 변경 절차를 `tools/generate_proto.sh` 중심으로 정리
  - [ ] Jenkins가 외부 도구에서 전체 검증을 실행한다는 문구 유지
- [ ] `.gitignore` — 필요 시 generated temp/output 제외

**테스트 작성**

스크립트 추가이므로 별도 Dart/Go 단위 테스트는 불필요하다. 대신 `tools/check_proto_sync.sh`를 직접 실행하고, 최종 검증 명령에 포함한다.

**중간 검증**

```bash
# proto sync
tools/check_proto_sync.sh
# 기존 테스트
cd dart && dart test
cd go && go test ./...
```

Expected: proto sync 성공, Dart 40개 이상 통과, Go 전체 테스트 통과

---

### [AI_FIRST-3] 프로토콜/패키지 버전 정책 문서화

**문제**

Dart 패키지는 `version: 0.1.0`을 갖지만 `publish_to: none`이라 배포 정책이 닫혀 있고, 프로토콜 자체의 호환성 버전은 별도로 없다.

```yaml
// Before — dart/pubspec.yaml:1
name: toki_socket
description: Multi-language standard socket protocol library. Dart implementation.
version: 0.1.0
publish_to: none
```

프로토콜 필드 추가, `typeName` 규칙 변경, heartbeat 의미 변경 같은 변화가 언어별 패키지 버전과 어떻게 연결되는지 기준이 없다. AI-first 포팅에서는 이 기준이 없으면 새 언어 구현체가 어느 프로토콜 버전을 만족해야 하는지 판단하기 어렵다.

**해결 방법**

`VERSIONING.md`를 추가하고 protocol compatibility, package version, breaking change 기준을 분리한다. `PROTOCOL.md` 상단에는 현재 protocol version을 명시하고, README에서는 버전 정책 문서로 연결한다. 당장은 wire format을 바꾸지 않으므로 proto message에 version field를 추가하지 않는다.

```markdown
// After
# Versioning

- Protocol version: wire-format compatibility contract.
- Package version: language implementation release version.
- Breaking protocol changes require a new major protocol version.
- Backward-compatible message additions require crosstest updates before release.
```

**수정 파일 및 체크리스트**

- [ ] `VERSIONING.md` — 새 버전 정책 문서
  - [ ] protocol version과 package version의 차이 정의
  - [ ] breaking/non-breaking 예시 작성
  - [ ] 새 언어 구현체가 따라야 할 최소 호환성 기준 작성
- [ ] `PROTOCOL.md` — protocol version 명시
  - [ ] 문서 상단에 `Current protocol version: 0.1` 추가
  - [ ] `typeName`, `nonce`, `responseNonce`, heartbeat 변경은 breaking 후보임을 연결
- [ ] `README.md` — Versioning 섹션 추가
  - [ ] `VERSIONING.md` 링크
  - [ ] 패키지 배포 전에도 프로토콜 호환성 기준은 유지한다는 문구 추가
- [ ] `dart/pubspec.yaml` — 변경 없음
  - [ ] 실제 패키지 배포를 하지 않으므로 `publish_to: none`은 유지

**테스트 작성**

문서 정책 추가라 단위 테스트는 불필요하다. 다만 최종 검증에서 기존 테스트와 crosstest를 실행해 문서화한 현재 호환성 상태가 실제로 유지되는지 확인한다.

**중간 검증**

```bash
# 문서 변경 후 기본 검증
cd dart && dart analyze
cd go && go test ./...
```

Expected: analyzer 이슈 없음, Go 전체 테스트 통과

---

### [AI_FIRST-4] 새 언어 구현 scaffolding과 검증 체크리스트 추가

**문제**

`PORTING_GUIDE.md`는 C#, Kotlin, Swift, Python, Rust 구현 지침을 잘 설명하지만 구현 시작점과 완료 조건은 문서 안에 흩어져 있다. `skills/add-crosstest-language/SKILL.md`에는 crosstest 규칙이 있으나, 일반 구현자가 어디서 어떤 파일을 만들어 시작해야 하는지 scaffold가 없다.

```markdown
// Before — PORTING_GUIDE.md:23
## C# (Unity / .NET)

### 핵심 매핑
```

AI 에이전트가 새 언어를 추가할 때 매번 디렉터리 구조, README, crosstest runner, parser map 예제를 새로 설계하게 된다.

**해결 방법**

`templates/language/` 아래에 새 언어 구현체가 채워 넣을 README, protocol checklist, crosstest checklist 템플릿을 둔다. 실제 코드는 언어별로 달라 템플릿이 과하게 코드를 생성하지 않게 하고, 반드시 만족해야 하는 파일 구조와 검증 명령만 고정한다.

```text
// After
templates/language/README.md
templates/language/IMPLEMENTATION_CHECKLIST.md
templates/language/CROSSTEST_CHECKLIST.md
```

`PORTING_GUIDE.md`에는 "새 언어 추가 절차" 섹션을 추가해서 템플릿, proto generation, crosstest 작성 순서를 연결한다.

**수정 파일 및 체크리스트**

- [ ] `templates/language/README.md` — 새 언어 package README 템플릿
  - [ ] transport, parser map, client/server, tests 항목 placeholder 포함
- [ ] `templates/language/IMPLEMENTATION_CHECKLIST.md` — 구현 완료 조건
  - [ ] `Transport`, `Communicator`, TCP, WS, TLS/WSS, heartbeat, request-response 항목 포함
  - [ ] typeName compatibility 확인 항목 포함
- [ ] `templates/language/CROSSTEST_CHECKLIST.md` — 크로스언어 테스트 완료 조건
  - [ ] send-push, single request, concurrent request, TCP, WS 항목 포함
  - [ ] 양방향 테스트와 PASS/FAIL line format 요구
- [ ] `PORTING_GUIDE.md` — 새 언어 추가 절차 추가
  - [ ] templates 사용 방법 링크
  - [ ] `skills/add-crosstest-language/SKILL.md`의 runner 배치 규칙과 충돌하지 않게 설명
- [ ] `README.md` — Multi-language 확장 안내 추가
  - [ ] 새 언어는 `PORTING_GUIDE.md`와 templates를 먼저 따르도록 안내

**테스트 작성**

템플릿과 문서 추가이므로 단위 테스트는 불필요하다. 하지만 Markdown 링크와 경로가 실제로 존재하는지 `find`/`rg`로 확인하고, 기존 Dart/Go 테스트를 유지 검증으로 실행한다.

**중간 검증**

```bash
# 템플릿 경로 확인
find templates/language -maxdepth 1 -type f
# 기존 테스트
cd dart && dart test
cd go && go test ./...
```

Expected: 템플릿 3개 존재, Dart 40개 이상 통과, Go 전체 테스트 통과

---

## 의존 관계 및 구현 순서

```text
[AI_FIRST-1] → [AI_FIRST-2] → [AI_FIRST-3] → [AI_FIRST-4]
```

`AI_FIRST-1`은 Dart 구현을 기준 구조에 맞추는 작업이라 이후 문서와 템플릿의 예시가 흔들리지 않게 먼저 끝낸다. `AI_FIRST-2`는 proto 변경 루프를 안정화하고, `AI_FIRST-3`은 안정화된 현재 wire contract를 버전 정책으로 고정한다. `AI_FIRST-4`는 앞의 결정들을 새 언어 구현 템플릿에 반영한다.

---

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `dart/lib/src/transport.dart` | AI_FIRST-1 |
| `dart/lib/src/communicator.dart` | AI_FIRST-1 |
| `dart/lib/src/protobuf_client.dart` | AI_FIRST-1 |
| `dart/lib/src/ws_protobuf_client.dart` | AI_FIRST-1 |
| `dart/lib/toki_socket.dart` | AI_FIRST-1 |
| `dart/test/communicator_test.dart` | AI_FIRST-1 |
| `dart/test/socket_test.dart` | AI_FIRST-1 |
| `tools/check_proto_sync.sh` | AI_FIRST-2 |
| `tools/generate_proto.sh` | AI_FIRST-2 |
| `README.md` | AI_FIRST-2, AI_FIRST-3, AI_FIRST-4 |
| `VERSIONING.md` | AI_FIRST-3 |
| `PROTOCOL.md` | AI_FIRST-3 |
| `templates/language/README.md` | AI_FIRST-4 |
| `templates/language/IMPLEMENTATION_CHECKLIST.md` | AI_FIRST-4 |
| `templates/language/CROSSTEST_CHECKLIST.md` | AI_FIRST-4 |
| `PORTING_GUIDE.md` | AI_FIRST-4 |

---

## 최종 검증

```bash
# 1. Proto sync
tools/check_proto_sync.sh

# 2. Dart 정적 분석과 테스트
cd dart && dart analyze
cd dart && dart test

# 3. Go 테스트
cd go && go test ./...

# 4. Cross-language tests
cd dart && PATH=/config/go-sdk/go/bin:/config/go/bin:$PATH dart run crosstest/dart_go.dart
cd go && PATH=/config/go-sdk/go/bin:/config/go/bin:$PATH GOCACHE=/tmp/go-build GOMODCACHE=/tmp/go-mod go run ./crosstest
```

Expected outcome: proto sync 성공, Dart 기존 40개 + 신규 2개 이상 통과, Go 전체 테스트 통과, Dart-server/Go-client 및 Go-server/Dart-client crosstest 통과
