383 lines
15 KiB
Text
383 lines
15 KiB
Text
<!-- 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 통과
|