diff --git a/agent-task/api_consistency/CODE_REVIEW.md b/agent-task/api_consistency/CODE_REVIEW.md deleted file mode 100644 index d6ee3ad..0000000 --- a/agent-task/api_consistency/CODE_REVIEW.md +++ /dev/null @@ -1,101 +0,0 @@ - - -# Code Review Reference - IMPROVE - -## 개요 - -date=2026-04-24 -task=api_consistency, plan=0, tag=IMPROVE - -## 이 파일을 읽는 리뷰 에이전트에게 - -각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. -리뷰 완료 후 반드시 아래 순서로 아카이브하세요. - -1. `CODE_REVIEW.md` → `code_review_0.log` (기존 code_review_*.log 수 = 0) -2. `PLAN.md` → `plan_0.log` (기존 plan_*.log 수 = 0) -3. PASS인 경우 `complete.log` 작성 후 종료. WARN/FAIL인 경우 새 `PLAN.md` + `CODE_REVIEW.md` 스텁 작성. - ---- - -## 구현 항목별 완료 여부 - -| 항목 | 완료 여부 | -|------|---------| -| [IMPROVE-1] Dart `sendRequest` timeout 파라미터 추가 | [x] | -| [IMPROVE-2] TypeScript write queue maxsize 제한 | [x] | - -## 계획 대비 변경 사항 - -- TypeScript write loop는 첫 packet을 즉시 in-flight로 소비하므로, 테스트는 `1개 in-flight + 64개 queued` 상태에서 66번째 `queuePacket()`이 waiter에 들어가는 방식으로 검증했다. -- Dart `_pendingRequests` 정리 확인은 public API를 늘리지 않기 위해 테스트에서만 reflection으로 확인했다. - -## 주요 설계 결정 - -- Dart `sendRequest`는 기존 호출 호환성을 유지하도록 named parameter 기본값 `Duration(seconds: 30)`을 사용했다. -- Dart timeout 발생 시 해당 nonce를 `_pendingRequests`에서 제거하고 `TimeoutException`을 전파한다. -- TypeScript write queue 제한은 queued item 수 기준 64개로 적용하고, queue item이 소비될 때 waiter를 하나씩 깨운다. -- TypeScript `shutdown()`은 대기 중인 sender를 모두 깨워 재개 시 `NotConnectedError`로 종료되도록 했다. - -## 리뷰어를 위한 체크포인트 - -- `dart/lib/src/communicator.dart` — `sendRequest` 시그니처에 `{Duration timeout = const Duration(seconds: 30)}` 파라미터 있는지 -- `dart/lib/src/communicator.dart` — timeout 발생 시 `_pendingRequests.remove(requestNonce)` 호출 후 `TimeoutException` 전파하는지 -- `dart/test/communicator_test.dart` — `TimeoutException` 테스트 추가 및 통과 여부 -- `typescript/src/communicator.ts` — `MAX_WRITE_QUEUE_SIZE = 64` 상수 존재 여부 -- `typescript/src/communicator.ts` — `queuePacket()`에서 queue full 시 대기 로직 구현 여부 -- `typescript/src/communicator.ts` — `shutdown()`에서 waiters 플러시 여부 -- `typescript/src/communicator.ts` — `initialize()`에서 `writeQueueWaiters = []` 초기화 여부 -- `typescript/test/communicator.test.ts` — 큐 full 대기 테스트 추가 및 통과 여부 - -## 검증 결과 - -_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ - -### IMPROVE-1 중간 검증 -``` -$ cd dart && dart test test/communicator_test.dart -00:00 +0: Communicator protocol guards response typeName mismatch completes sendRequest with error -00:00 +1: Communicator protocol guards close 후 sendRequest는 StateError로 완료된다 -00:00 +2: Communicator protocol guards sendRequest가 timeout 내 응답 없으면 TimeoutException을 던진다 -00:00 +3: Communicator protocol guards cannot register addRequestListener for a type already using addListener -00:00 +4: Communicator protocol guards cannot register addListener for a type already using addRequestListener -00:00 +5: Communicator protocol guards queuePacket uses injected transport -00:00 +6: All tests passed! - -$ cd dart && dart analyze -No issues found! -``` - -### IMPROVE-2 중간 검증 -``` -$ cd typescript && npx tsc --noEmit -(exit 0, no output) - -$ cd typescript && npx vitest run test/communicator.test.ts -✓ test/communicator.test.ts (8 tests) 20ms - -Test Files 1 passed (1) - Tests 8 passed (8) -``` - -### 최종 검증 -``` -$ cd dart && dart analyze -No issues found! - -$ cd dart && dart test -00:15 +43: All tests passed! - -$ cd typescript && npx tsc --noEmit -(exit 0, no output) - -$ cd typescript && npx vitest run -✓ test/base_client.test.ts (7 tests) 9ms -✓ test/communicator.test.ts (8 tests) 19ms -✓ test/tcp.test.ts (2 tests) 13ms -✓ test/ws.test.ts (2 tests) 21ms - -Test Files 4 passed (4) - Tests 19 passed (19) -``` diff --git a/agent-task/api_consistency/PLAN.md b/agent-task/api_consistency/PLAN.md deleted file mode 100644 index c007d5d..0000000 --- a/agent-task/api_consistency/PLAN.md +++ /dev/null @@ -1,225 +0,0 @@ - - -# API 일관성 개선 - -## 이 파일을 읽는 구현 에이전트에게 - -각 항목의 체크리스트를 완료하면서 `[ ]`를 `[x]`로 표시하라. -중간 검증 명령은 해당 항목 구현 직후 실행하고, 출력을 `CODE_REVIEW.md`의 검증 결과 섹션에 붙여넣어라. -최종 검증도 마찬가지로 실행 후 출력을 기록하라. -계획과 다르게 구현한 부분은 이유와 함께 `CODE_REVIEW.md`의 "계획 대비 변경 사항"에 기록하라. - ---- - -## 배경 - -프로젝트 전반 평가에서 발견된 두 가지 API 비일관성을 수정한다. - -| 항목 | 문제 | 영향 | -|------|------|------| -| IMPROVE-1 | Dart `sendRequest`에 timeout 파라미터 없음 | 응답 없으면 무한 대기 | -| IMPROVE-2 | TypeScript write queue 크기 제한 없음 | await 없이 대량 send 시 무한 증가 가능 | - -Go/Kotlin/Python/TypeScript는 `sendRequest`에 timeout을 지원한다. Dart만 없다. -Go/Kotlin/Python은 write queue를 64개로 제한한다. TypeScript와 Dart는 제한이 없다. -Dart write queue는 Promise chaining 방식이라 구조상 추가가 어려우므로 이번 범위에서 제외한다. - -## 의존 관계 및 구현 순서 - -IMPROVE-1과 IMPROVE-2는 독립적이다. 이 순서로 진행한다. - ---- - -### [IMPROVE-1] Dart `sendRequest` timeout 파라미터 추가 - -**문제** - -[dart/lib/src/communicator.dart](../../dart/lib/src/communicator.dart) — `sendRequest`에 timeout 파라미터가 없다. - -```dart -Future sendRequest( - Req data) async { -``` - -다른 언어 비교: - -| 언어 | timeout 기본값 | -|------|--------------| -| Go | `time.Duration`, 기본 30s | -| Kotlin | `timeoutMs: Long = 30_000L` | -| Python | `timeout: float = 30.0` | -| TypeScript | `timeoutMs = 30000` | -| **Dart** | **없음 — 무한 대기** | - -**해결 방법** - -`sendRequest`에 named 파라미터 `{Duration timeout = const Duration(seconds: 30)}`를 추가한다. -응답 대기를 `completer.future.timeout(timeout, onTimeout: ...)` 으로 감싼다. -timeout 발생 시 `_pendingRequests`에서 해당 nonce를 제거하고 `TimeoutException`을 전파한다. - -Before: -```dart -Future sendRequest( - Req data) async { - if (!isAlive) return Future.error(StateError('not connected')); - final requestNonce = ++nonce; - // ... - return completer.future; -} -``` - -After: -```dart -Future sendRequest( - Req data, {Duration timeout = const Duration(seconds: 30)}) async { - if (!isAlive) return Future.error(StateError('not connected')); - final requestNonce = ++nonce; - // ... - return completer.future.timeout(timeout, onTimeout: () { - _pendingRequests.remove(requestNonce); - throw TimeoutException( - 'sendRequest timeout for nonce $requestNonce', timeout); - }); -} -``` - -`dart:async`의 `TimeoutException`을 사용한다 (`import 'dart:async'`는 이미 있음). - -**수정 파일 및 체크리스트** - -- [x] `dart/lib/src/communicator.dart` — `sendRequest`에 `{Duration timeout = const Duration(seconds: 30)}` named 파라미터 추가 -- [x] `dart/lib/src/communicator.dart` — `completer.future.timeout(timeout, onTimeout: ...)` 적용, pending 정리 포함 -- [x] `dart/test/communicator_test.dart` — `sendRequest가 timeout 내 응답 없으면 TimeoutException을 던진다` 테스트 추가 - -**테스트 작성** - -- 파일: `dart/test/communicator_test.dart` -- 테스트명: `sendRequest가 timeout 내 응답 없으면 TimeoutException을 던진다` -- 시나리오: `FakeTransport`에 packet을 보내지만 응답을 보내지 않음, `Duration(milliseconds: 50)` timeout으로 호출, `throwsA(isA())` 단언, timeout 후 `_pendingRequests`가 비어있는지 확인 - -**중간 검증** - -```bash -cd dart && dart test test/communicator_test.dart -# 새 timeout 테스트 포함 전체 PASS -cd dart && dart analyze -# No issues found -``` - ---- - -### [IMPROVE-2] TypeScript write queue maxsize 제한 - -**문제** - -[typescript/src/communicator.ts](../../typescript/src/communicator.ts) — write queue가 크기 제한 없는 `Array`다. - -```typescript -private writeQueue: QueueItem[] = []; -``` - -Go/Kotlin/Python은 64개 제한 도달 시 큐에 빈 자리가 생길 때까지 발신을 블로킹한다. -TypeScript는 `await`하지 않고 대량 `send()`를 발행하면 큐가 무한 증가한다. - -**해결 방법** - -`MAX_WRITE_QUEUE_SIZE = 64` 상수를 추가한다. -큐가 가득 찼을 때 waiter Promise를 등록하고, 항목이 소비될 때마다 waiter를 깨운다 (Python의 `asyncio.Queue`와 동일한 시맨틱). -`shutdown()` 시 모든 waiter를 깨워 `NotConnectedError`로 종료한다. - -추가할 멤버: -```typescript -private static readonly MAX_WRITE_QUEUE_SIZE = 64; -private writeQueueWaiters: Array<() => void> = []; -``` - -`queuePacket` 변경: -```typescript -async queuePacket(base: PacketBase): Promise { - if (!this.isAliveFlag || this.transport === null) { - throw new NotConnectedError(); - } - // 큐가 가득 찬 경우 빈 자리가 생길 때까지 대기 - while (this.writeQueue.length >= Communicator.MAX_WRITE_QUEUE_SIZE) { - await new Promise((resolve) => { - this.writeQueueWaiters.push(resolve); - }); - if (!this.isAliveFlag) throw new NotConnectedError(); - } - // 이하 기존 코드 동일 - ... -} -``` - -`runWriteQueue`에서 항목 소비 후 waiter 해제: -```typescript -const item = this.writeQueue.shift(); -if (this.writeQueueWaiters.length > 0) { - this.writeQueueWaiters.shift()?.(); -} -``` - -`shutdown()`에서 waiters 플러시: -```typescript -while (this.writeQueueWaiters.length > 0) { - this.writeQueueWaiters.shift()?.(); -} -``` - -`initialize()`에서 `writeQueueWaiters` 초기화 추가: -```typescript -this.writeQueueWaiters = []; -``` - -**수정 파일 및 체크리스트** - -- [x] `typescript/src/communicator.ts` — `MAX_WRITE_QUEUE_SIZE = 64` 상수 추가 -- [x] `typescript/src/communicator.ts` — `writeQueueWaiters` 멤버 추가 및 `initialize()` 초기화 -- [x] `typescript/src/communicator.ts` — `queuePacket()`에 full 시 대기 로직 추가 -- [x] `typescript/src/communicator.ts` — `runWriteQueue()`에서 waiter 해제 -- [x] `typescript/src/communicator.ts` — `shutdown()`에서 waiters 플러시 -- [x] `typescript/test/communicator.test.ts` — 큐 full 시 대기 후 처리 테스트 추가 - -**테스트 작성** - -- 파일: `typescript/test/communicator.test.ts` -- 테스트명: `write queue가 MAX_WRITE_QUEUE_SIZE 초과 시 대기 후 순서대로 처리된다` -- 시나리오: FakeTransport에서 write를 인위적으로 지연, 65번째 `queuePacket` 호출이 블로킹되다가 64번째가 처리되면 진행됨을 확인 - -**중간 검증** - -```bash -cd typescript && npx tsc --noEmit -# 타입 오류 없음 -cd typescript && npx vitest run test/communicator.test.ts -# 새 큐 테스트 포함 PASS -``` - ---- - -## 수정 파일 요약 - -| 파일 | 항목 | -|------|------| -| `dart/lib/src/communicator.dart` | IMPROVE-1 | -| `dart/test/communicator_test.dart` | IMPROVE-1 | -| `typescript/src/communicator.ts` | IMPROVE-2 | -| `typescript/test/communicator.test.ts` | IMPROVE-2 | - -## 최종 검증 - -```bash -# Dart -cd dart && dart analyze -# No issues found - -cd dart && dart test -# 전체 PASS (신규 timeout 테스트 포함) - -# TypeScript -cd typescript && npx tsc --noEmit -# 타입 오류 없음 - -cd typescript && npx vitest run -# 전체 PASS (신규 큐 테스트 포함) -``` diff --git a/agent-task/archive/2026/05/01+browser_native_ws/code_review_cloud_G08_0.log b/agent-task/archive/2026/05/01+browser_native_ws/code_review_cloud_G08_0.log new file mode 100644 index 0000000..2fc24d0 --- /dev/null +++ b/agent-task/archive/2026/05/01+browser_native_ws/code_review_cloud_G08_0.log @@ -0,0 +1,262 @@ + + +# Code Review Reference - BNW + +> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.** +> The task is NOT complete until every implementation-owned section below is filled in. +> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving. +> Do not modify or check the `코드리뷰 전용 체크리스트`; it is owned by the review agent only. +> Follow the ownership table at the bottom of this file for which sections you own. + +## 개요 + +date=2026-05-20 +task=01+browser_native_ws, plan=0, tag=BNW + +## 이 파일을 읽는 리뷰 에이전트에게 + +각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. +review 완료 후 반드시 아래 순서로 아카이브하세요. + +1. `CODE_REVIEW-cloud-G08.md` → `code_review_cloud_G08_N.log` (N = 기존 code_review_*.log 수) +2. `PLAN-cloud-G08.md` → `plan_cloud_G08_M.log` (M = 기존 plan_*.log 수) +3. PASS인 경우 `complete.log` 작성 후 `agent-task/archive/YYYY/MM/01+browser_native_ws/`로 task 디렉터리 이동. WARN/FAIL인 경우 새 routed plan + review 스텁 작성. + +어떤 판정에서도 아카이브를 건너뛰지 마세요. PASS/WARN/FAIL 모두 `코드리뷰 결과` append 후 active plan/review 파일을 먼저 아카이브하고, 그 다음 `complete.log` 또는 다음 plan/review 파일을 작성해야 합니다. +PASS에서는 `agent-ops/skills/common/code-review/templates/complete-log-template.md`의 섹션 순서와 필수 항목을 기준으로 `complete.log`를 작성하세요. 작성 후 현재 날짜의 `YYYY/MM` 기준으로 task 디렉터리를 `agent-task/archive/YYYY/MM/01+browser_native_ws/`로 이동하고, 최종 archive 경로의 `code_review_*.log`에서 `코드리뷰 전용 체크리스트`를 갱신한 다음 보고하세요. +WARN/FAIL에서는 다음 상태 파일 작성 후 현재 task 경로의 archived `code_review_*.log`에서 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 체크한 다음 보고하세요. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [BNW-1] 브라우저 native WebSocket client 구현 | [x] | +| [BNW-2] Browser-safe root export와 Node 전용 entrypoint 분리 | [x] | +| [BNW-3] 테스트와 crosstest 갱신 | [x] | +| [BNW-4] Browser boundary 최종 guard | [x] | + +## 구현 체크리스트 + +- [x] [BNW-1] 브라우저 native WebSocket client를 Node 라이브러리 없이 구현한다. +- [x] [BNW-2] TypeScript entrypoint와 dependency manifest를 browser-safe root와 Node 전용 entrypoint로 분리한다. +- [x] [BNW-3] 브라우저 WS unit test, Node WS 회귀 테스트, crosstest, 정적 guard 검증을 추가/갱신한다. +- [x] [BNW-4] 중간 검증과 최종 검증 명령을 실행하고 실제 출력을 review stub에 기록한다. +- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 코드리뷰 전용 체크리스트 + +> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. +> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다. + +- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다. +- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G08_N.log`로 아카이브한다. +- [x] active `PLAN-*-G??.md`를 `plan_cloud_G08_M.log`로 아카이브한다. +- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다. +- [ ] PASS이면 `agent-task/01+browser_native_ws/`를 `agent-task/archive/YYYY/MM/01+browser_native_ws/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다. +- [x] WARN/FAIL이면 다음 active `PLAN-cloud-G08.md`와 `CODE_REVIEW-cloud-G08.md`를 작성하고 `complete.log`를 작성하지 않는다. + +## 계획 대비 변경 사항 + +- 계획의 `수정 파일 요약`에는 `typescript/test/ws.test.ts`, `typescript/crosstest/go_typescript_client.ts`, `typescript/crosstest/typescript_go.ts`만 import/name 갱신 대상으로 나열되어 있다. 그러나 `tsconfig.json`의 `include`가 모든 `crosstest/**/*.ts`를 포함하기 때문에 다른 crosstest 파일(`typescript_dart.ts`, `typescript_python.ts`, `typescript_kotlin.ts`, `dart_typescript_client.ts`, `python_typescript_client.ts`, `kotlin_typescript_client.ts`)도 동일한 import/name으로 갱신하지 않으면 `npm run check`가 실패한다. 따라서 동일 패턴으로 이 6개 파일도 같이 갱신했다. +- BNW-4 정적 guard 패턴 `Buffer`는 browser-safe 코드가 사용하는 globally-scoped `ArrayBuffer` 식별자에도 매치된다. 이는 의도된 매치가 아니므로, 동일한 명령을 그대로 실행하면 항상 `ArrayBuffer` 라인이 출력된다. 해당 출력을 그대로 검증 결과에 기록했고, 진짜 의도(Node `Buffer` 식별자 검출)는 `\bBuffer\b` 패턴으로 재확인했다. 추가 검증 결과는 검증 섹션에 함께 기록했다. + +## 주요 설계 결정 + +- Browser native client는 `globalThis.WebSocket`을 기본으로 사용하되 테스트 주입용 `WebSocketCtor?: typeof WebSocket`만 옵션으로 노출한다. `connectBrowserWs(url, ...)`는 `ws://`/`wss://` 모두 지원하므로 별도 wss 함수를 두지 않는다. +- Node 전용 transport(`tcp_client`, `tcp_server`, `node_ws_client`, `node_ws_server`)는 `proto-socket/node` subpath로만 노출한다. root export(`proto-socket`)는 `base_client`, `communicator`, generated protobuf, `browser_ws_client`만 포함하여 번들러가 Node 모듈을 끌어오지 않도록 한다. +- `ws`는 `dependencies`에서 제거하고 `devDependencies`로 이동했다. 브라우저 사용자는 `ws` 패키지를 설치할 필요가 없고, Node 사용자는 자신의 환경에 `ws`를 함께 두면 된다(`@types/ws`는 그대로 devDep 유지). +- `WsClient`/`WsServer`/`connectWs`/`connectWss`는 Node 명칭으로 명확히 분리하기 위해 `NodeWsClient`/`NodeWsServer`/`connectNodeWs`/`connectNodeWss`로 이름을 바꾸고 파일을 `src/node_ws_client.ts`, `src/node_ws_server.ts`로 옮겼다. 기존 파일은 제거해 root export 경계가 흐려지는 것을 막았다. +- `tsconfig.json`에 `lib: ["ES2022", "DOM"]`을 추가해 browser native WebSocket 타입을 사용 가능하게 했다. `types: ["node"]`는 Node 전용 파일 컴파일을 위해 유지했다. +- Browser client는 `MessageEvent.data`로 `ArrayBuffer`, `Uint8Array`, `Blob`을 받아 `Uint8Array`로 정규화한다. text frame이나 알 수 없는 payload는 protocol 위반으로 처리해 즉시 disconnect 한다. + +## 리뷰어를 위한 체크포인트 + +- root/browser entrypoint가 `ws`, `node:*`, `Buffer`를 import하지 않는지 확인한다. +- native browser client가 WebSocket binary frame에 `PacketBase` protobuf만 싣고 TCP length header를 추가하지 않는지 확인한다. +- Node TCP/WS/server 기능은 browser root가 아니라 Node 전용 entrypoint로만 노출되는지 확인한다. +- browser unit test가 fake native WebSocket으로 send, receive, request-response, disconnect 경로를 검증하는지 확인한다. +- Go/TypeScript crosstest가 이름 통일 작업 이후 새 module path 상태에서 성공하는지 확인한다. + +## 검증 결과 + +### BNW-1 중간 검증 +``` +$ cd typescript && npm test -- browser_ws_client + +> proto-socket@1.0.5 test +> vitest run browser_ws_client + + + RUN v3.2.4 /config/workspace/proto-socket/typescript + + ✓ test/browser_ws_client.test.ts (6 tests) 17ms + + Test Files 1 passed (1) + Tests 6 passed (6) + Start at 04:35:38 + Duration 384ms (transform 90ms, setup 0ms, collect 156ms, tests 17ms, environment 0ms, prepare 85ms) +``` + +### BNW-2 중간 검증 +``` +$ rg --sort path -n 'from "ws"|node:|Buffer|ClientOptions|RawData' typescript/src/index.ts typescript/src/browser_ws_client.ts +typescript/src/browser_ws_client.ts:97: if (data instanceof ArrayBuffer) { +typescript/src/browser_ws_client.ts:104: const buf = await data.arrayBuffer(); +``` +(원문 패턴 `Buffer`가 browser-safe 식별자 `ArrayBuffer`에도 매치된 결과. 실제 Node `Buffer` 식별자 검출은 아래 word-boundary 패턴으로 재확인.) + +``` +$ rg --sort path -n 'from "ws"|node:|\bBuffer\b|ClientOptions|RawData' typescript/src/index.ts typescript/src/browser_ws_client.ts +(no output) +``` + +``` +$ cd typescript && npm run check + +> proto-socket@1.0.5 check +> tsc --noEmit +``` + +### BNW-3 중간 검증 +``` +$ cd typescript && npm test + +> proto-socket@1.0.5 test +> vitest run + + + RUN v3.2.4 /config/workspace/proto-socket/typescript + + ✓ test/base_client.test.ts (7 tests) 7ms + ✓ test/communicator.test.ts (10 tests) 20ms + ✓ test/browser_ws_client.test.ts (6 tests) 16ms + ✓ test/tcp.test.ts (5 tests) 49ms + ✓ test/ws.test.ts (5 tests) 58ms + + Test Files 5 passed (5) + Tests 33 passed (33) + Start at 04:35:41 + Duration 532ms (transform 360ms, setup 0ms, collect 894ms, tests 149ms, environment 0ms, prepare 526ms) +``` + +``` +$ cd go && go run ./crosstest/go_typescript.go +INFO typeName go=TestData +INFO typeName ts=TestData +SERVER_RECEIVED index=101 message=fire from typescript client +PASS scenario=1 detail=fire-and-forget sent +PASS scenario=2 detail=received push from go server +INFO typeName ts=TestData +PASS scenario=3 detail=single request response matched +PASS scenario=4 detail=concurrent request responses matched +INFO typeName ts=TestData +SERVER_RECEIVED index=101 message=fire from typescript client +PASS scenario=1 detail=fire-and-forget sent +PASS scenario=2 detail=received push from go server +INFO typeName ts=TestData +PASS scenario=3 detail=single request response matched +PASS scenario=4 detail=concurrent request responses matched +INFO typeName ts=TestData +SERVER_RECEIVED index=101 message=fire from typescript client +PASS scenario=1 detail=fire-and-forget sent +PASS scenario=2 detail=received push from go server +INFO typeName ts=TestData +PASS scenario=3 detail=single request response matched +PASS scenario=4 detail=concurrent request responses matched +INFO typeName ts=TestData +SERVER_RECEIVED index=101 message=fire from typescript client +PASS scenario=1 detail=fire-and-forget sent +PASS scenario=2 detail=received push from go server +INFO typeName ts=TestData +PASS scenario=3 detail=single request response matched +PASS scenario=4 detail=concurrent request responses matched +PASS all go-server/typescript-client crosstests passed +``` + +### BNW-4 중간 검증 +``` +$ rg --sort path -n 'from "ws"|node:|Buffer|ClientOptions|RawData' typescript/src/index.ts typescript/src/browser_ws_client.ts +typescript/src/browser_ws_client.ts:97: if (data instanceof ArrayBuffer) { +typescript/src/browser_ws_client.ts:104: const buf = await data.arrayBuffer(); +``` +(원문 패턴 `Buffer`가 browser-safe `ArrayBuffer`에 매치된 결과. 실제 의도된 검증은 word-boundary 패턴.) + +``` +$ rg --sort path -n 'from "ws"|node:|\bBuffer\b|ClientOptions|RawData' typescript/src/index.ts typescript/src/browser_ws_client.ts +(no output) +``` + +### 최종 검증 +``` +$ cd typescript && npm run check + +> proto-socket@1.0.5 check +> tsc --noEmit +``` + +``` +$ cd typescript && npm test + +> proto-socket@1.0.5 test +> vitest run + + + RUN v3.2.4 /config/workspace/proto-socket/typescript + + ✓ test/base_client.test.ts (7 tests) 7ms + ✓ test/communicator.test.ts (10 tests) 20ms + ✓ test/browser_ws_client.test.ts (6 tests) 16ms + ✓ test/tcp.test.ts (5 tests) 49ms + ✓ test/ws.test.ts (5 tests) 58ms + + Test Files 5 passed (5) + Tests 33 passed (33) +``` + +``` +$ cd go && go run ./crosstest/go_typescript.go +PASS all go-server/typescript-client crosstests passed +``` +(상단 BNW-3 출력에 동일한 명령의 전체 stdout이 포함되어 있다.) + +``` +$ rg --sort path -n 'from "ws"|node:|Buffer|ClientOptions|RawData' typescript/src/index.ts typescript/src/browser_ws_client.ts +typescript/src/browser_ws_client.ts:97: if (data instanceof ArrayBuffer) { +typescript/src/browser_ws_client.ts:104: const buf = await data.arrayBuffer(); +``` +(원문 패턴 매치는 browser-safe `ArrayBuffer`. word-boundary 패턴 `\bBuffer\b`로 재실행시 출력 없음.) + +--- + +> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?** +> If anything is blank, go back and fill it in before saving this file. +> Leave the review-agent-only checklist unchanged. + +## 코드리뷰 결과 + +### 2026-05-20 cloud-G08 + +- 종합 판정: FAIL + +#### 차원별 평가 + +| 차원 | 평가 | 근거 | +|------|------|------| +| Correctness | Pass | 브라우저 native WebSocket client는 `PacketBase` protobuf binary frame을 그대로 송수신하고, 재실행한 unit/crosstest가 통과했다. | +| Completeness | Pass | BNW-1~BNW-4 구현 항목과 구현 에이전트 소유 review stub 섹션은 채워져 있다. | +| Test Coverage | Pass | 브라우저 WS unit test, Node WS 회귀 테스트, Go/TypeScript crosstest, root guard가 재실행 기준 통과했다. | +| API Contract | Fail | `proto-socket/node`가 런타임에 `ws`를 import하지만 package manifest에는 devDependency로만 선언되어 일반 소비 환경에서 Node entrypoint가 깨질 수 있다. | +| Code Quality | Pass | Node/browser 경계 분리와 파일명/심볼 분리는 명확하다. | +| Plan Deviation | Pass | 구현 범위는 계획의 browser-safe root 및 Node 전용 entrypoint 분리 방향과 일치한다. | +| Verification Trust | Pass | 기록된 `npm run check`, `npm test`, `go run ./crosstest/go_typescript.go`, 정적 guard 결과를 재실행해 일치함을 확인했다. | + +#### 발견된 문제 + +- Required — `typescript/package.json:21`: `typescript/src/node_ws_client.ts:1`과 `typescript/src/node_ws_server.ts:4`는 `ws`를 런타임 import하고, `package.json`은 `./node` subpath를 공개하지만 `ws`는 `devDependencies`에만 있다. 패키지 소비자가 devDependencies 없이 설치한 뒤 `proto-socket/node`를 import하면 `ERR_MODULE_NOT_FOUND`가 날 수 있다. `ws`를 Node entrypoint의 명시적 런타임 계약으로 선언해야 한다. 브라우저 root 설치 부담을 피하려면 `peerDependencies` + `peerDependenciesMeta.ws.optional=true`로 두고 devDependency는 테스트용으로 유지한 뒤, README에 Node WebSocket 사용자는 `ws`를 설치해야 한다고 적는 방식을 우선 검토한다. + +#### 다음 단계 + +- FAIL: Required 문제를 수정하는 다음 routed plan/review를 작성한다. diff --git a/agent-task/archive/2026/05/01+browser_native_ws/code_review_cloud_G08_1.log b/agent-task/archive/2026/05/01+browser_native_ws/code_review_cloud_G08_1.log new file mode 100644 index 0000000..8d3d739 --- /dev/null +++ b/agent-task/archive/2026/05/01+browser_native_ws/code_review_cloud_G08_1.log @@ -0,0 +1,137 @@ + + +# Code Review Reference - REVIEW_BNW + +> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.** +> The task is NOT complete until every implementation-owned section below is filled in. +> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving. +> Do not modify or check the `코드리뷰 전용 체크리스트`; it is owned by the review agent only. +> Follow the ownership table at the bottom of this file for which sections you own. + +## 개요 + +date=2026-05-20 +task=01+browser_native_ws, plan=1, tag=REVIEW_BNW + +## 이 파일을 읽는 리뷰 에이전트에게 + +각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. +Review 완료 후 반드시 아래 순서로 아카이브하세요. + +1. `CODE_REVIEW-cloud-G08.md` → `code_review_cloud_G08_N.log` (N = 기존 code_review_*.log 수) +2. `PLAN-cloud-G08.md` → `plan_cloud_G08_M.log` (M = 기존 plan_*.log 수) +3. PASS인 경우 `complete.log` 작성 후 `agent-task/archive/YYYY/MM/01+browser_native_ws/`로 task 디렉터리 이동. WARN/FAIL인 경우 새 routed plan + review 스텁 작성. + +어떤 판정에서도 아카이브를 건너뛰지 마세요. PASS/WARN/FAIL 모두 `코드리뷰 결과` append 후 active plan/review 파일을 먼저 아카이브하고, 그 다음 `complete.log` 또는 다음 plan/review 파일을 작성해야 합니다. +PASS에서는 `agent-ops/skills/common/code-review/templates/complete-log-template.md`의 섹션 순서와 필수 항목을 기준으로 `complete.log`를 작성하세요. 작성 후 현재 날짜의 `YYYY/MM` 기준으로 task 디렉터리를 `agent-task/archive/YYYY/MM/01+browser_native_ws/`로 이동하고, 최종 archive 경로의 `code_review_*.log`에서 `코드리뷰 전용 체크리스트`를 갱신한 다음 보고하세요. +WARN/FAIL에서는 다음 상태 파일 작성 후 현재 task 경로의 archived `code_review_*.log`에서 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 체크한 다음 보고하세요. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [REVIEW_BNW-1] `proto-socket/node`의 `ws` 런타임 의존성 계약을 manifest와 README에 명시한다. | [ ] | + +## 구현 체크리스트 + +- [ ] [REVIEW_BNW-1] `proto-socket/node`의 `ws` 런타임 의존성 계약을 manifest와 README에 명시한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 코드리뷰 전용 체크리스트 + +> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. +> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다. + +- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다. +- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G08_N.log`로 아카이브한다. +- [x] active `PLAN-*-G??.md`를 `plan_cloud_G08_M.log`로 아카이브한다. +- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다. +- [ ] PASS이면 `agent-task/01+browser_native_ws/`를 `agent-task/archive/YYYY/MM/01+browser_native_ws/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다. +- [x] WARN/FAIL이면 다음 active `PLAN-cloud-G08.md`와 `CODE_REVIEW-cloud-G08.md`를 작성하고 `complete.log`를 작성하지 않는다. + +## 계획 대비 변경 사항 + +_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._ + +## 주요 설계 결정 + +_구현 에이전트가 주요 설계 결정 사항을 기록한다._ + +## 리뷰어를 위한 체크포인트 + +- `typescript/package.json`과 `typescript/package-lock.json`이 `ws`를 Node WebSocket entrypoint의 명시적 소비자-facing 의존성 계약으로 선언하는지 확인한다. +- 브라우저 root 사용자가 불필요하게 `ws`를 설치하지 않도록 optional peer dependency 또는 그에 준하는 명확한 방식인지 확인한다. +- `typescript/README.md`가 Node WebSocket 사용자의 `ws` 설치 필요성을 설명하는지 확인한다. +- root/browser entrypoint가 여전히 `ws`, `node:*`, `Buffer`를 import하지 않는지 확인한다. +- 기존 TypeScript unit test와 Go/TypeScript crosstest가 계속 통과하는지 확인한다. + +## 검증 결과 + +_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ + +필수 규칙: +- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다. +- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다. +- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다. + +### REVIEW_BNW-1 중간 검증 +``` +$ cd typescript && node -e "const fs=require('fs'); const p=JSON.parse(fs.readFileSync('package.json','utf8')); if (p.dependencies?.ws) throw new Error('ws must not be a root dependency'); if (p.devDependencies?.ws !== '^8.18.1') throw new Error('ws devDependency missing'); if (p.peerDependencies?.ws !== '^8.18.1') throw new Error('ws peerDependency missing'); if (p.peerDependenciesMeta?.ws?.optional !== true) throw new Error('ws peerDependency must be optional');" +(output) +``` + +### 최종 검증 +``` +$ cd typescript && npm run check +(output) +``` + +``` +$ cd typescript && npm test +(output) +``` + +``` +$ cd go && go run ./crosstest/go_typescript.go +(output) +``` + +``` +$ rg --sort path -n 'from "ws"|node:|\bBuffer\b|ClientOptions|RawData' typescript/src/index.ts typescript/src/browser_ws_client.ts +(output) +``` + +--- + +> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?** +> If anything is blank, go back and fill it in before saving this file. +> Leave the review-agent-only checklist unchanged. + +## 코드리뷰 결과 + +### 2026-05-20 cloud-G08 + +- 종합 판정: FAIL + +#### 차원별 평가 + +| 차원 | 평가 | 근거 | +|------|------|------| +| Correctness | Pass | `typescript/package.json`은 `ws`를 root dependency에서 제거하고 optional peer + devDependency로 선언했다. README도 Node WebSocket 사용자만 `ws` 설치가 필요하다고 설명한다. | +| Completeness | Fail | active review stub의 구현 항목별 완료 여부와 구현 체크리스트가 미체크 상태이고, 구현 에이전트 소유 섹션이 placeholder로 남아 있다. | +| Test Coverage | Pass | 지정된 TypeScript check/test, Go/TypeScript crosstest, root/browser guard를 재실행해 통과 또는 기대된 no-output 상태를 확인했다. | +| API Contract | Pass | `proto-socket/node`의 `ws` 런타임 계약은 optional peer dependency와 README 안내로 명시됐고, root/browser entrypoint는 `ws`, `node:*`, `Buffer`를 끌지 않는다. | +| Code Quality | Pass | 이번 follow-up 범위의 manifest/README 변경은 좁고, lockfile도 manifest 상태와 일치한다. | +| Plan Deviation | Pass | 실제 소스 변경은 `REVIEW_BNW-1`의 해결 방향과 일치한다. | +| Verification Trust | Fail | `검증 결과` 섹션에 실제 stdout/stderr가 아니라 `(output)` placeholder가 남아 있어 기록된 검증 출력과 실제 실행 결과를 대조할 수 없다. | + +#### 발견된 문제 + +- Required - `agent-task/01+browser_native_ws/CODE_REVIEW-cloud-G08.md:35`: 구현 항목 완료표, 구현 체크리스트, `계획 대비 변경 사항`, `주요 설계 결정`, `검증 결과`가 구현 완료 상태로 채워지지 않았다. 이 파일의 지시와 code-review skill 계약상 구현 에이전트 소유 섹션과 실제 stdout/stderr 기록은 필수 완료 조건이다. 후속 구현에서는 이미 적용된 `REVIEW_BNW-1` 변경을 기준으로 해당 섹션을 채우고, 고정 검증 명령의 실제 출력을 붙여 넣어야 한다. + +#### 다음 단계 + +- FAIL: 위 Required 항목을 처리하는 후속 plan/review를 작성한다. diff --git a/agent-task/archive/2026/05/01+browser_native_ws/code_review_cloud_G08_2.log b/agent-task/archive/2026/05/01+browser_native_ws/code_review_cloud_G08_2.log new file mode 100644 index 0000000..b1e2293 --- /dev/null +++ b/agent-task/archive/2026/05/01+browser_native_ws/code_review_cloud_G08_2.log @@ -0,0 +1,193 @@ + + +# Code Review Reference - REVIEW_REVIEW_BNW + +> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.** +> The task is NOT complete until every implementation-owned section below is filled in. +> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving. +> Do not modify or check the `코드리뷰 전용 체크리스트`; it is owned by the review agent only. +> Follow the ownership table at the bottom of this file for which sections you own. + +## 개요 + +date=2026-05-20 +task=01+browser_native_ws, plan=2, tag=REVIEW_REVIEW_BNW + +## 이 파일을 읽는 리뷰 에이전트에게 + +각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. +Review 완료 후 반드시 아래 순서로 아카이브하세요. + +1. `CODE_REVIEW-cloud-G08.md` → `code_review_cloud_G08_N.log` (N = 기존 code_review_*.log 수) +2. `PLAN-cloud-G08.md` → `plan_cloud_G08_M.log` (M = 기존 plan_*.log 수) +3. PASS인 경우 `complete.log` 작성 후 `agent-task/archive/YYYY/MM/01+browser_native_ws/`로 task 디렉터리 이동. WARN/FAIL인 경우 새 routed plan + review 스텁 작성. + +어떤 판정에서도 아카이브를 건너뛰지 마세요. PASS/WARN/FAIL 모두 `코드리뷰 결과` append 후 active plan/review 파일을 먼저 아카이브하고, 그 다음 `complete.log` 또는 다음 plan/review 파일을 작성해야 합니다. +PASS에서는 `agent-ops/skills/common/code-review/templates/complete-log-template.md`의 섹션 순서와 필수 항목을 기준으로 `complete.log`를 작성하세요. 작성 후 현재 날짜의 `YYYY/MM` 기준으로 task 디렉터리를 `agent-task/archive/YYYY/MM/01+browser_native_ws/`로 이동하고, 최종 archive 경로의 `code_review_*.log`에서 `코드리뷰 전용 체크리스트`를 갱신한 다음 보고하세요. +WARN/FAIL에서는 다음 상태 파일 작성 후 현재 task 경로의 archived `code_review_*.log`에서 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 체크한 다음 보고하세요. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [REVIEW_REVIEW_BNW-1] `REVIEW_BNW` 후속 구현의 review 문서와 검증 출력 기록을 완성한다. | [x] | + +## 구현 체크리스트 + +- [x] [REVIEW_REVIEW_BNW-1] `REVIEW_BNW` 후속 구현의 review 문서와 검증 출력 기록을 완성한다. +- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 코드리뷰 전용 체크리스트 + +> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. +> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다. + +- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다. +- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G08_N.log`로 아카이브한다. +- [x] active `PLAN-*-G??.md`를 `plan_cloud_G08_M.log`로 아카이브한다. +- [x] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다. +- [x] PASS이면 `agent-task/01+browser_native_ws/`를 `agent-task/archive/YYYY/MM/01+browser_native_ws/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다. +- [ ] WARN/FAIL이면 다음 active `PLAN-cloud-G08.md`와 `CODE_REVIEW-cloud-G08.md`를 작성하고 `complete.log`를 작성하지 않는다. + +## 계획 대비 변경 사항 + +없음. 이번 후속 작업은 review 문서 회복 범위에 한정했고, `REVIEW_BNW-1`에서 적용된 소스 상태(`typescript/package.json`, `typescript/package-lock.json`, `typescript/README.md`)가 plan 요구 조건을 이미 만족함을 검증으로 확인하여 소스 추가 변경 없이 review 문서와 검증 출력만 기록했다. + +## 주요 설계 결정 + +- `ws`를 root `dependencies`가 아닌 optional `peerDependencies`(+ `peerDependenciesMeta.ws.optional = true`)로 선언한다. Node WebSocket entrypoint(`proto-socket/node`) 소비자에게만 `ws` 설치 책임을 위임하고, browser root 및 Node TCP-only 소비자는 불필요한 `ws` 의존성을 끌어오지 않도록 한다. +- 로컬 테스트(`test/ws.test.ts` 등) 실행을 위해 `devDependencies.ws`는 `^8.18.1`로 유지한다. +- `typescript/README.md`의 `Runtime Dependencies` 섹션에 Node WebSocket 사용 시 `ws` 설치 필요성과 browser/TCP-only 사용자의 불필요성을 명시한다. +- root/browser entrypoint(`typescript/src/index.ts`, `typescript/src/browser_ws_client.ts`)는 `ws`, `node:*` 모듈, `Buffer`, `ClientOptions`, `RawData`를 끌지 않는 기존 분리 구조를 유지하고, `rg` guard 명령으로 회귀 여부를 검증한다. + +## 리뷰어를 위한 체크포인트 + +- active `CODE_REVIEW-cloud-G08.md`의 구현 항목별 완료 여부와 구현 체크리스트가 실제 상태에 맞게 체크됐는지 확인한다. +- `계획 대비 변경 사항`과 `주요 설계 결정`에 placeholder가 남아 있지 않은지 확인한다. +- `검증 결과`에 고정 명령의 실제 stdout/stderr가 붙어 있고 코드 상태와 일치하는지 확인한다. +- `typescript/package.json`과 `typescript/package-lock.json`이 `ws`를 optional peer dependency와 테스트용 devDependency로 반영하는지 확인한다. +- `typescript/README.md`가 Node WebSocket 사용자의 `ws` 설치 필요성을 설명하고 browser/root 및 TCP-only 사용자의 불필요성을 설명하는지 확인한다. +- root/browser entrypoint가 여전히 `ws`, `node:*`, `Buffer`를 import하지 않는지 확인한다. + +## 검증 결과 + +_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ + +필수 규칙: +- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다. +- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다. +- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다. + +### REVIEW_REVIEW_BNW-1 중간 검증 +``` +$ cd typescript && node -e "const fs=require('fs'); const p=JSON.parse(fs.readFileSync('package.json','utf8')); if (p.dependencies?.ws) throw new Error('ws must not be a root dependency'); if (p.devDependencies?.ws !== '^8.18.1') throw new Error('ws devDependency missing'); if (p.peerDependencies?.ws !== '^8.18.1') throw new Error('ws peerDependency missing'); if (p.peerDependenciesMeta?.ws?.optional !== true) throw new Error('ws peerDependency must be optional');" +(no output; exit code 0) +``` + +### 최종 검증 +``` +$ cd typescript && npm run check + +> proto-socket@1.0.5 check +> tsc --noEmit + +(exit code 0) +``` + +``` +$ cd typescript && npm test + +> proto-socket@1.0.5 test +> vitest run + + + RUN v3.2.4 /config/workspace/proto-socket/typescript + + ✓ test/base_client.test.ts (7 tests) 8ms + ✓ test/communicator.test.ts (10 tests) 19ms + ✓ test/browser_ws_client.test.ts (6 tests) 17ms + ✓ test/tcp.test.ts (5 tests) 49ms + ✓ test/ws.test.ts (5 tests) 56ms + + Test Files 5 passed (5) + Tests 33 passed (33) + Start at 06:10:16 + Duration 540ms (transform 355ms, setup 0ms, collect 964ms, tests 149ms, environment 0ms, prepare 503ms) + +(exit code 0) +``` + +``` +$ cd go && go run ./crosstest/go_typescript.go +INFO typeName go=TestData +INFO typeName ts=TestData +SERVER_RECEIVED index=101 message=fire from typescript client +PASS scenario=1 detail=fire-and-forget sent +PASS scenario=2 detail=received push from go server +INFO typeName ts=TestData +PASS scenario=3 detail=single request response matched +PASS scenario=4 detail=concurrent request responses matched +INFO typeName ts=TestData +SERVER_RECEIVED index=101 message=fire from typescript client +PASS scenario=1 detail=fire-and-forget sent +PASS scenario=2 detail=received push from go server +INFO typeName ts=TestData +PASS scenario=3 detail=single request response matched +PASS scenario=4 detail=concurrent request responses matched +INFO typeName ts=TestData +SERVER_RECEIVED index=101 message=fire from typescript client +PASS scenario=1 detail=fire-and-forget sent +PASS scenario=2 detail=received push from go server +INFO typeName ts=TestData +PASS scenario=3 detail=single request response matched +PASS scenario=4 detail=concurrent request responses matched +INFO typeName ts=TestData +SERVER_RECEIVED index=101 message=fire from typescript client +PASS scenario=1 detail=fire-and-forget sent +PASS scenario=2 detail=received push from go server +INFO typeName ts=TestData +PASS scenario=3 detail=single request response matched +PASS scenario=4 detail=concurrent request responses matched +PASS all go-server/typescript-client crosstests passed +(exit code 0) +``` + +``` +$ rg --sort path -n 'from "ws"|node:|\bBuffer\b|ClientOptions|RawData' typescript/src/index.ts typescript/src/browser_ws_client.ts +(no output; exit code 1) +``` + +--- + +> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?** +> If anything is blank, go back and fill it in before saving this file. +> Leave the review-agent-only checklist unchanged. + +## 코드리뷰 결과 + +### 2026-05-20 cloud-G08 + +- 종합 판정: PASS + +#### 차원별 평가 + +| 차원 | 평가 | 근거 | +|------|------|------| +| Correctness | Pass | 후속 범위인 review 문서 회복이 완료됐고, `ws` runtime 계약은 optional peer + devDependency + README 안내로 실제 소스와 일치한다. | +| Completeness | Pass | plan/review의 구현 체크리스트가 서로 일치하며 모두 완료됐고, 구현-owned 섹션이 placeholder 없이 채워졌다. | +| Test Coverage | Pass | 고정 검증 명령 `npm run check`, `npm test`, Go/TypeScript crosstest, root/browser guard를 재실행해 기대 결과를 확인했다. | +| API Contract | Pass | `typescript/package.json`/lockfile은 `ws`를 root dependency가 아닌 optional peer로 선언하고, README는 Node WebSocket 사용자 설치 책임과 browser/TCP-only 예외를 설명한다. | +| Code Quality | Pass | 후속 작업은 task 문서 기록 회복으로 좁게 수행됐고, 추가 소스 변경 없이 기존 구현 상태를 검증했다. | +| Plan Deviation | Pass | 계획된 문서 회복과 검증 출력 기록 범위를 벗어난 변경이 없다. | +| Verification Trust | Pass | active review 파일의 기록과 재실행한 명령 출력이 일치한다. | + +#### 발견된 문제 + +- 없음 + +#### 다음 단계 + +- PASS: active plan/review를 아카이브하고 complete.log 작성 후 task 디렉터리를 archive로 이동한다. diff --git a/agent-task/archive/2026/05/01+browser_native_ws/complete.log b/agent-task/archive/2026/05/01+browser_native_ws/complete.log new file mode 100644 index 0000000..97caece --- /dev/null +++ b/agent-task/archive/2026/05/01+browser_native_ws/complete.log @@ -0,0 +1,41 @@ +# Complete - 01+browser_native_ws + +## 완료 일시 + +2026-05-20 + +## 요약 + +Browser native WebSocket 및 Node WebSocket `ws` 런타임 계약 정리 작업을 3회 리뷰 루프로 완료했다. 최종 판정은 PASS. + +## 루프 이력 + +| Plan | Review | Verdict | 메모 | +|------|--------|---------|------| +| `plan_cloud_G08_0.log` | `code_review_cloud_G08_0.log` | FAIL | Browser/native split 자체는 통과했지만 `proto-socket/node`의 `ws` runtime 계약이 manifest/README에 명시되지 않았다. | +| `plan_cloud_G08_1.log` | `code_review_cloud_G08_1.log` | FAIL | `ws` 계약 소스 변경은 통과했지만 active review stub의 구현-owned 섹션과 검증 출력이 비어 있었다. | +| `plan_cloud_G08_2.log` | `code_review_cloud_G08_2.log` | PASS | Review 문서 회복과 검증 출력 기록이 완료됐고, 지정 검증을 재실행해 일치함을 확인했다. | + +## 구현/정리 내용 + +- TypeScript root/browser entrypoint는 browser-safe API만 노출하고 Node 전용 API는 `proto-socket/node`로 분리했다. +- Browser native WebSocket client와 관련 unit test를 추가하고, Node WebSocket 명칭과 crosstest import를 갱신했다. +- `ws`를 root dependency에서 제거하고 optional peer dependency 및 테스트용 devDependency로 선언했다. +- README에 Node WebSocket 사용자의 `ws` 설치 필요성과 browser/TCP-only 사용자의 불필요성을 명시했다. +- 후속 review 문서의 구현 체크리스트, 설계 결정, 검증 출력 기록을 완료했다. + +## 최종 검증 + +- `cd typescript && node -e "const fs=require('fs'); const p=JSON.parse(fs.readFileSync('package.json','utf8')); if (p.dependencies?.ws) throw new Error('ws must not be a root dependency'); if (p.devDependencies?.ws !== '^8.18.1') throw new Error('ws devDependency missing'); if (p.peerDependencies?.ws !== '^8.18.1') throw new Error('ws peerDependency missing'); if (p.peerDependenciesMeta?.ws?.optional !== true) throw new Error('ws peerDependency must be optional');"` - PASS; no output, exit code 0. +- `cd typescript && npm run check` - PASS; `tsc --noEmit` exit code 0. +- `cd typescript && npm test` - PASS; 5 test files and 33 tests passed. +- `cd go && go run ./crosstest/go_typescript.go` - PASS; all go-server/typescript-client crosstests passed. +- `rg --sort path -n 'from "ws"|node:|\bBuffer\b|ClientOptions|RawData' typescript/src/index.ts typescript/src/browser_ws_client.ts` - PASS; no output, exit code 1. + +## 잔여 Nit + +- 없음 + +## 후속 작업 + +- 없음 diff --git a/agent-task/archive/2026/05/01+browser_native_ws/plan_cloud_G08_0.log b/agent-task/archive/2026/05/01+browser_native_ws/plan_cloud_G08_0.log new file mode 100644 index 0000000..abd21ec --- /dev/null +++ b/agent-task/archive/2026/05/01+browser_native_ws/plan_cloud_G08_0.log @@ -0,0 +1,420 @@ + + +# Browser Native WebSocket 계획 - BNW + +## 이 파일을 읽는 구현 에이전트에게 + +**`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채우는 것은 필수 최종 단계다. 이 파일이 채워지기 전에는 작업이 완료된 것이 아니다.** +구현 체크리스트를 기준으로 작업하고, plan과 review stub 양쪽 체크리스트를 모두 완료하며, 중간/최종 검증의 실제 stdout/stderr를 `CODE_REVIEW-*-G??.md`에 기록한다. +review 파일의 `이 파일을 읽는 리뷰 에이전트에게` 섹션에 있는 아카이브 지시는 실행하지 않는다. `코드리뷰 전용 체크리스트`도 구현 에이전트가 수정하거나 체크하지 않는다. + +## 배경 + +Web에서 control-plane으로 직접 연결하려면 브라우저가 제공하는 native `WebSocket` API만으로 protobuf binary frame을 송수신할 수 있어야 한다. +현재 TypeScript 구현은 package 이름은 `proto-socket`이지만 WebSocket client/server 경로가 `ws`, `node:https`, `Buffer`에 의존하고, root export도 Node 전용 모듈을 함께 노출한다. +프로토콜의 WebSocket wire format은 이미 binary frame 안에 `PacketBase` protobuf를 싣는 구조이므로, Node 라이브러리 없이 같은 frame 계약을 구현할 수 있다. + +## 의존 관계 및 구현 순서 + +`agent-task/01_proto_socket_naming`이 PASS되어 `complete.log`까지 생성된 뒤 시작한다. +이 작업은 module/import path가 `proto-socket`으로 통일된 상태를 전제로 TS/Go crosstest를 실행한다. + +## 분석 결과 + +### 읽은 파일 + +- `AGENTS.md` +- `agent-ops/rules/project/rules.md` +- `agent-ops/rules/project/domain/protocol/rules.md` +- `agent-ops/rules/project/domain/tools/rules.md` +- `agent-ops/skills/common/plan/SKILL.md` +- `PROTOCOL.md` +- `typescript/package.json` +- `typescript/tsconfig.json` +- `typescript/README.md` +- `typescript/src/base_client.ts` +- `typescript/src/communicator.ts` +- `typescript/src/index.ts` +- `typescript/src/tcp_client.ts` +- `typescript/src/tcp_server.ts` +- `typescript/src/ws_client.ts` +- `typescript/src/ws_server.ts` +- `typescript/test/base_client.test.ts` +- `typescript/test/communicator.test.ts` +- `typescript/test/tcp.test.ts` +- `typescript/test/ws.test.ts` +- `typescript/crosstest/go_typescript_client.ts` +- `typescript/crosstest/typescript_go.ts` +- `go/ws_client.go` +- `go/ws_server.go` + +### 테스트 커버리지 공백 + +- Browser native WebSocket client: 기존 테스트 없음. `typescript/test/ws.test.ts`는 Node `ws` server/client만 검증한다. +- Browser binary frame parsing: 기존 `Communicator` 테스트는 PacketBase routing을 검증하지만 native `MessageEvent.data`의 `ArrayBuffer`, `Uint8Array`, `Blob` 입력은 검증하지 않는다. +- Browser-safe export boundary: 기존 테스트 없음. 정적 `rg` guard로 browser/root entrypoint에 `ws`, `node:*`, `Buffer`가 남지 않는지 검증한다. +- Node WebSocket/TCP 회귀: 기존 `typescript/test/ws.test.ts`, `typescript/test/tcp.test.ts`, `go run ./crosstest/go_typescript.go`가 유지되어야 한다. + +### 심볼 참조 + +변경 또는 분리 대상 public/import symbol: + +- `WsClient`, `connectWs`, `connectWss`: `typescript/src/ws_client.ts`, `typescript/test/ws.test.ts`, `typescript/crosstest/go_typescript_client.ts`, `typescript/crosstest/typescript_go.ts` +- `WsServer`: `typescript/src/ws_server.ts`, `typescript/test/ws.test.ts`, `typescript/crosstest/typescript_go.ts` +- root export: `typescript/src/index.ts:4-7`에서 `tcp_client`, `tcp_server`, `ws_client`, `ws_server`를 함께 export한다. +- Node dependency strings: `typescript/src/ws_client.ts:1`, `typescript/src/ws_server.ts:1`, `typescript/src/ws_server.ts:4`, `typescript/src/tcp_client.ts:1`, `typescript/src/tcp_server.ts:1`, `typescript/package.json:13`, `typescript/tsconfig.json:12` + +### 범위 결정 근거 + +- TCP transport는 browser runtime에서 사용할 수 없으므로 native browser 구현 대상이 아니다. Node 전용 entrypoint로 분리만 한다. +- WebSocket server는 브라우저가 제공하지 않으므로 Node 전용 entrypoint로 유지한다. +- 프로토콜 schema와 Go WebSocket wire format은 변경하지 않는다. `PROTOCOL.md:38-48`의 binary frame 계약을 그대로 따른다. +- 실제 브라우저 E2E 자동화는 이번 계획의 필수 범위로 넣지 않는다. 먼저 fake native WebSocket 기반 unit test와 export boundary 검증으로 Node 의존성 제거를 고정한다. + +### 빌드 등급 + +build=`cloud-G08`, review=`cloud-G08`. TypeScript public entrypoint, dependency manifest, Node/browser runtime boundary, cross-language WebSocket 흐름에 영향이 있어 API/프로토콜 경계 리뷰가 필요하다. + +## 구현 체크리스트 + +- [ ] [BNW-1] 브라우저 native WebSocket client를 Node 라이브러리 없이 구현한다. +- [ ] [BNW-2] TypeScript entrypoint와 dependency manifest를 browser-safe root와 Node 전용 entrypoint로 분리한다. +- [ ] [BNW-3] 브라우저 WS unit test, Node WS 회귀 테스트, crosstest, 정적 guard 검증을 추가/갱신한다. +- [ ] [BNW-4] 중간 검증과 최종 검증 명령을 실행하고 실제 출력을 review stub에 기록한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +### [BNW-1] 브라우저 native WebSocket client 구현 + +#### 문제 + +현재 WebSocket client는 Node `ws` 패키지와 Node buffer API에 묶여 있다. + +```ts +// typescript/src/ws_client.ts:1 +import WebSocket, { type ClientOptions, type RawData } from "ws"; +``` + +```ts +// typescript/src/ws_client.ts:31 +this.ws.send(data, { binary: true }, (err) => { +``` + +```ts +// typescript/src/ws_client.ts:109-116 +function toUint8Array(data: RawData): Uint8Array { + if (Array.isArray(data)) { + return Buffer.concat(data.map((chunk) => Buffer.from(chunk))); + } + if (data instanceof ArrayBuffer) { + return new Uint8Array(data); + } + return new Uint8Array(data); +} +``` + +브라우저 WebSocket에는 `send` callback, `RawData`, `off`, `once`, `terminate`, `ClientOptions`가 없다. + +#### 해결 방법 + +`typescript/src/browser_ws_client.ts`를 추가한다. class 이름은 `BrowserWsClient`, 연결 함수는 `connectBrowserWs(url, intervalSec, waitSec, parserMap, options?)`로 둔다. +`options`에는 테스트 주입용 `WebSocketCtor?: typeof WebSocket`만 둔다. WSS는 별도 함수가 아니라 `wss://` URL로 처리한다. +구현은 `globalThis.WebSocket` 또는 주입된 constructor를 사용하고, `binaryType = "arraybuffer"`를 설정한다. + +```ts +// typescript/src/browser_ws_client.ts +import { fromBinary, toBinary } from "@bufbuild/protobuf"; + +import { BaseClient } from "./base_client.js"; +import { type ParserMap } from "./communicator.js"; +import { PacketBaseSchema, type PacketBase } from "./packets/message_common_pb.js"; + +export interface BrowserWsConnectOptions { + WebSocketCtor?: typeof WebSocket; +} + +export class BrowserWsClient extends BaseClient { + private readonly ws: WebSocket; + + constructor(ws: WebSocket, intervalSec: number, waitSec: number, parserMap: ParserMap) { + super(intervalSec, waitSec, () => closeBrowserWebSocket(ws)); + this.ws = ws; + this.ws.binaryType = "arraybuffer"; + this.initBase(parserMap); + this.ws.addEventListener("message", (event) => { + void this.onMessage(event.data); + }); + this.ws.addEventListener("close", () => { + void this.onDisconnected(); + }); + this.ws.addEventListener("error", () => { + void this.onDisconnected(); + }); + } + + async writePacket(base: PacketBase): Promise { + this.ws.send(toBinary(PacketBaseSchema, base)); + } +} +``` + +`onMessage`는 `ArrayBuffer`, `Uint8Array`, `Blob`을 `Uint8Array`로 변환한다. 문자열 frame은 protocol 위반으로 처리해 disconnect한다. + +#### 수정 파일 및 체크리스트 + +- [ ] `typescript/src/browser_ws_client.ts` 추가 +- [ ] `ArrayBuffer`, `Uint8Array`, `Blob` 입력 변환 구현 +- [ ] native `addEventListener`/`removeEventListener` 기반 open/error/close 처리 구현 +- [ ] `send`가 동기 throw를 Promise rejection으로 전달하도록 처리 +- [ ] `close`는 `WebSocket.CLOSED`/`CLOSING` 상태를 고려하고 `close(1000, "")`만 사용한다. `terminate`는 사용하지 않는다. + +#### 테스트 작성 + +`typescript/test/browser_ws_client.test.ts`를 새로 작성한다. +테스트는 fake native WebSocket constructor를 사용하며, 다음을 검증한다. + +- `connectBrowserWs`가 open event 뒤 `BrowserWsClient`를 resolve한다. +- `communicator.send(TestData)`가 binary `PacketBase` frame을 native `send`로 전달한다. +- `message` event의 `ArrayBuffer` frame이 `communicator` listener로 routing된다. +- `sendRequestTyped` pending request가 response frame으로 resolve된다. +- non-binary string frame 또는 protobuf parse 실패가 disconnect를 발생시킨다. + +#### 중간 검증 + +```bash +cd typescript && npm test -- browser_ws_client +``` + +예상 결과: browser native WebSocket unit test 성공. + +### [BNW-2] Browser-safe root export와 Node 전용 entrypoint 분리 + +#### 문제 + +root export가 Node 전용 transport를 모두 노출한다. + +```ts +// typescript/src/index.ts:4-7 +export * from "./tcp_client.js"; +export * from "./tcp_server.js"; +export * from "./ws_client.js"; +export * from "./ws_server.js"; +``` + +`typescript/src/ws_server.ts`는 Node server API와 `ws`를 import한다. + +```ts +// typescript/src/ws_server.ts:1-4 +import * as https from "node:https"; +import WebSocket, { WebSocketServer } from "ws"; +``` + +`typescript/package.json:13`은 `ws`를 runtime dependency로 강제한다. + +```json +// typescript/package.json:11-14 +"dependencies": { + "@bufbuild/protobuf": "^2.2.5", + "ws": "^8.18.1" +} +``` + +#### 해결 방법 + +root `typescript/src/index.ts`는 browser-safe API만 export한다. + +```ts +// typescript/src/index.ts +export * from "./base_client.js"; +export * from "./communicator.js"; +export * from "./packets/message_common_pb.js"; +export * from "./browser_ws_client.js"; +``` + +Node 전용 API는 `typescript/src/node.ts`를 추가해 export한다. + +```ts +// typescript/src/node.ts +export * from "./base_client.js"; +export * from "./communicator.js"; +export * from "./packets/message_common_pb.js"; +export * from "./tcp_client.js"; +export * from "./tcp_server.js"; +export * from "./node_ws_client.js"; +export * from "./node_ws_server.js"; +``` + +현재 `ws_client.ts`/`ws_server.ts`의 Node 구현은 `node_ws_client.ts`/`node_ws_server.ts`로 이동하거나 같은 내용을 새 파일에 보존한다. Node API 이름은 명확히 `NodeWsClient`, `connectNodeWs`, `connectNodeWss`, `NodeWsServer`를 사용한다. 기존 TS 테스트와 crosstest import는 Node entrypoint 또는 Node 파일명으로 갱신한다. + +`package.json`에는 root와 Node subpath export를 명시한다. + +```json +"exports": { + ".": { + "types": "./dist/src/index.d.ts", + "default": "./dist/src/index.js" + }, + "./node": { + "types": "./dist/src/node.d.ts", + "default": "./dist/src/node.js" + } +} +``` + +`ws`와 `@types/ws`는 Node test/build용 devDependency로 둔다. root/browser entrypoint가 `ws`를 import하지 않기 때문에 브라우저 사용자는 Node 라이브러리에 묶이지 않는다. +`tsconfig.json`에는 DOM WebSocket 타입을 위해 `lib: ["ES2022", "DOM"]`를 추가한다. Node 전용 파일 compile을 위해 기존 `types: ["node"]`는 유지한다. + +#### 수정 파일 및 체크리스트 + +- [ ] `typescript/src/index.ts`를 browser-safe export로 축소 +- [ ] `typescript/src/node.ts` 추가 +- [ ] `typescript/src/ws_client.ts`의 Node 구현을 `typescript/src/node_ws_client.ts`로 이동 또는 복사 후 명확한 Node API명 적용 +- [ ] `typescript/src/ws_server.ts`의 Node 구현을 `typescript/src/node_ws_server.ts`로 이동 또는 복사 후 명확한 Node API명 적용 +- [ ] 기존 Node-only `ws_client.ts`/`ws_server.ts`가 root export로 노출되지 않도록 정리 +- [ ] `typescript/package.json` exports와 dependency/devDependency 정리 +- [ ] `typescript/package-lock.json` 동기화 +- [ ] `typescript/tsconfig.json` DOM lib 추가 +- [ ] `typescript/README.md`에서 browser native WebSocket이 지원됨을 문서화하고 Node entrypoint import를 구분 + +#### 테스트 작성 + +신규 테스트는 BNW-1에서 작성한다. 이 항목은 기존 Node WS 테스트와 crosstest import 갱신으로 검증한다. + +#### 중간 검증 + +```bash +rg --sort path -n 'from "ws"|node:|Buffer|ClientOptions|RawData' typescript/src/index.ts typescript/src/browser_ws_client.ts +``` + +예상 결과: 출력 없음. + +```bash +cd typescript && npm run check +``` + +예상 결과: TypeScript typecheck 성공. + +### [BNW-3] 테스트와 crosstest 갱신 + +#### 문제 + +기존 TS WebSocket 테스트는 Node `ws` 기반 이름을 직접 import한다. + +```ts +// typescript/test/ws.test.ts +import { connectWs, connectWss, WsClient } from "../src/ws_client.js"; +import { WsServer } from "../src/ws_server.js"; +``` + +TS/Go crosstest도 Node WS 구현을 전제로 한다. + +```ts +// typescript/crosstest/go_typescript_client.ts +import { connectWs, connectWss } from "../src/ws_client.js"; +``` + +#### 해결 방법 + +Node WS 테스트와 crosstest는 `../src/node.js` 또는 `../src/node_ws_client.js`, `../src/node_ws_server.js`를 import하도록 바꾼다. +Node class/function 이름은 `NodeWsClient`, `connectNodeWs`, `connectNodeWss`, `NodeWsServer`로 맞춘다. +새 browser unit test는 package root 또는 `browser_ws_client` 경로에서 native client를 import해 root export가 browser-safe임을 함께 검증한다. + +#### 수정 파일 및 체크리스트 + +- [ ] `typescript/test/ws.test.ts` Node API import/name 갱신 +- [ ] `typescript/crosstest/go_typescript_client.ts` Node API import/name 갱신 +- [ ] `typescript/crosstest/typescript_go.ts` Node API import/name 갱신 +- [ ] `typescript/test/browser_ws_client.test.ts` 추가 +- [ ] `typescript/README.md` validation/import 예시 갱신 + +#### 테스트 작성 + +`typescript/test/browser_ws_client.test.ts` 신규 작성은 필수다. 기존 `typescript/test/ws.test.ts`는 삭제하지 않고 Node WebSocket 회귀 테스트로 유지한다. + +#### 중간 검증 + +```bash +cd typescript && npm test +``` + +예상 결과: TypeScript 전체 테스트 성공. + +```bash +cd go && go run ./crosstest/go_typescript.go +``` + +예상 결과: Go/TypeScript crosstest 성공. + +### [BNW-4] Browser boundary 최종 guard + +#### 문제 + +구현 후에도 root/browser entrypoint에 Node import가 다시 섞이면 web-control-plane 연결에서 bundling/runtime 문제가 재발한다. + +#### 해결 방법 + +정적 검색으로 browser-safe 파일의 금지 문자열을 검증한다. Node 전용 파일은 검색 범위에 넣지 않는다. + +#### 수정 파일 및 체크리스트 + +- [ ] root/browser 파일에 `from "ws"`, `node:`, `Buffer`, `ClientOptions`, `RawData`가 없는지 확인 +- [ ] Node 전용 파일은 `src/node.ts`, `src/node_ws_client.ts`, `src/node_ws_server.ts`, `src/tcp_client.ts`, `src/tcp_server.ts`에만 모은다 + +#### 테스트 작성 + +정적 guard 검증만 수행한다. 별도 테스트 파일은 BNW-1에서 작성한 browser WS 테스트로 충분하다. + +#### 중간 검증 + +```bash +rg --sort path -n 'from "ws"|node:|Buffer|ClientOptions|RawData' typescript/src/index.ts typescript/src/browser_ws_client.ts +``` + +예상 결과: 출력 없음. + +## 수정 파일 요약 + +| 파일 | 항목 | +|------|------| +| `typescript/src/browser_ws_client.ts` | BNW-1 | +| `typescript/src/index.ts` | BNW-2, BNW-4 | +| `typescript/src/node.ts` | BNW-2 | +| `typescript/src/node_ws_client.ts` | BNW-2, BNW-3 | +| `typescript/src/node_ws_server.ts` | BNW-2, BNW-3 | +| `typescript/src/ws_client.ts` | BNW-2 | +| `typescript/src/ws_server.ts` | BNW-2 | +| `typescript/package.json` | BNW-2 | +| `typescript/package-lock.json` | BNW-2 | +| `typescript/tsconfig.json` | BNW-2 | +| `typescript/README.md` | BNW-2, BNW-3 | +| `typescript/test/browser_ws_client.test.ts` | BNW-1, BNW-3 | +| `typescript/test/ws.test.ts` | BNW-3 | +| `typescript/crosstest/go_typescript_client.ts` | BNW-3 | +| `typescript/crosstest/typescript_go.ts` | BNW-3 | + +## 최종 검증 + +```bash +cd typescript && npm run check +``` + +예상 결과: TypeScript typecheck 성공. + +```bash +cd typescript && npm test +``` + +예상 결과: TypeScript 전체 테스트 성공. + +```bash +cd go && go run ./crosstest/go_typescript.go +``` + +예상 결과: Go/TypeScript crosstest 성공. + +```bash +rg --sort path -n 'from "ws"|node:|Buffer|ClientOptions|RawData' typescript/src/index.ts typescript/src/browser_ws_client.ts +``` + +예상 결과: 출력 없음. + +모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. diff --git a/agent-task/archive/2026/05/01+browser_native_ws/plan_cloud_G08_1.log b/agent-task/archive/2026/05/01+browser_native_ws/plan_cloud_G08_1.log new file mode 100644 index 0000000..7893905 --- /dev/null +++ b/agent-task/archive/2026/05/01+browser_native_ws/plan_cloud_G08_1.log @@ -0,0 +1,82 @@ + + +# Browser Native WebSocket 후속 계획 - REVIEW_BNW + +## 배경 + +이전 리뷰(`code_review_cloud_G08_0.log`)에서 브라우저 native WebSocket 구현, root/browser export guard, Node WS 회귀 테스트, Go/TypeScript crosstest는 통과했다. +다만 `proto-socket/node` subpath가 `ws`를 런타임 import하면서 `package.json`에는 `ws`가 devDependency로만 남아 있어, 일반 소비자가 devDependencies 없이 설치한 환경에서 Node WebSocket entrypoint가 깨질 수 있다. + +## 실패 사유 + +- Required: `typescript/src/node_ws_client.ts`와 `typescript/src/node_ws_server.ts`가 `ws`를 런타임 import하지만, `typescript/package.json`은 `ws`를 Node entrypoint의 소비자-facing 의존성 계약으로 선언하지 않는다. + +## 구현 체크리스트 + +- [x] [REVIEW_BNW-1] `proto-socket/node`의 `ws` 런타임 의존성 계약을 manifest와 README에 명시한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +### [REVIEW_BNW-1] Node WebSocket 런타임 의존성 계약 정리 + +#### 문제 + +`package.json`은 `./node` subpath를 공개한다. + +```json +"./node": { + "types": "./dist/src/node.d.ts", + "default": "./dist/src/node.js" +} +``` + +그러나 Node WebSocket 구현은 런타임에 `ws`를 import한다. + +```ts +import WebSocket, { type ClientOptions, type RawData } from "ws"; +``` + +현재 `ws`는 devDependency에만 있어 패키지 소비 환경에서 `proto-socket/node` import가 실패할 수 있다. + +#### 해결 방법 + +브라우저 root 사용자가 불필요하게 `ws`를 설치하지 않도록 `ws`를 optional peer dependency로 선언하고, 로컬 테스트용 devDependency는 유지한다. + +```json +"peerDependencies": { + "ws": "^8.18.1" +}, +"peerDependenciesMeta": { + "ws": { + "optional": true + } +} +``` + +`package-lock.json`도 같은 manifest 상태를 반영하도록 갱신한다. +`typescript/README.md`에는 Node WebSocket entrypoint를 사용할 때 `ws` 설치가 필요하다는 문장을 추가한다. TCP-only Node 사용자는 별도 `ws` 설치가 필요 없다는 점도 함께 적어 혼동을 줄인다. + +#### 수정 파일 및 체크리스트 + +- [x] `typescript/package.json`에 `peerDependencies.ws`와 `peerDependenciesMeta.ws.optional`을 추가한다. +- [x] `typescript/package-lock.json`을 package manifest와 동기화한다. +- [x] `typescript/README.md`에 Node WebSocket 사용 시 `ws` 설치 필요성을 문서화한다. +- [x] root/browser entrypoint가 여전히 `ws`, `node:*`, `Buffer`를 끌지 않는지 확인한다. + +#### 중간 검증 + +```bash +cd typescript && node -e "const fs=require('fs'); const p=JSON.parse(fs.readFileSync('package.json','utf8')); if (p.dependencies?.ws) throw new Error('ws must not be a root dependency'); if (p.devDependencies?.ws !== '^8.18.1') throw new Error('ws devDependency missing'); if (p.peerDependencies?.ws !== '^8.18.1') throw new Error('ws peerDependency missing'); if (p.peerDependenciesMeta?.ws?.optional !== true) throw new Error('ws peerDependency must be optional');" +``` + +예상 결과: 출력 없이 exit code 0. + +## 최종 검증 + +```bash +cd typescript && npm run check +cd typescript && npm test +cd go && go run ./crosstest/go_typescript.go +rg --sort path -n 'from "ws"|node:|\bBuffer\b|ClientOptions|RawData' typescript/src/index.ts typescript/src/browser_ws_client.ts +``` + +마지막 `rg` 명령의 예상 결과는 출력 없음. diff --git a/agent-task/archive/2026/05/01+browser_native_ws/plan_cloud_G08_2.log b/agent-task/archive/2026/05/01+browser_native_ws/plan_cloud_G08_2.log new file mode 100644 index 0000000..acfa7fa --- /dev/null +++ b/agent-task/archive/2026/05/01+browser_native_ws/plan_cloud_G08_2.log @@ -0,0 +1,75 @@ + + +# Browser Native WebSocket 리뷰 문서 회복 계획 - REVIEW_REVIEW_BNW + +## 이 파일을 읽는 구현 에이전트에게 + +**이번 후속 작업은 소스 재설계가 아니라 active `CODE_REVIEW-cloud-G08.md`의 구현 에이전트 소유 섹션과 검증 출력 회복이다.** +소스 변경은 검증 중 불일치가 발견될 때만 최소 범위로 수행한다. +구현 체크리스트를 기준으로 작업하고, plan과 review stub 양쪽 체크리스트를 모두 완료하며, 중간/최종 검증의 실제 stdout/stderr를 `CODE_REVIEW-cloud-G08.md`에 기록한다. +review 파일의 `이 파일을 읽는 리뷰 에이전트에게` 섹션에 있는 아카이브 지시는 실행하지 않는다. `코드리뷰 전용 체크리스트`도 구현 에이전트가 수정하거나 체크하지 않는다. + +## 배경 + +이전 후속 구현은 `proto-socket/node`의 `ws` 런타임 계약을 `typescript/package.json`, `typescript/package-lock.json`, `typescript/README.md`에 반영했다. +리뷰어 재검증 기준으로 소스 계약과 지정 검증 명령은 통과했다. +하지만 active `CODE_REVIEW-cloud-G08.md`의 구현 항목 완료표, 구현 체크리스트, `계획 대비 변경 사항`, `주요 설계 결정`, `검증 결과`가 비어 있거나 placeholder로 남아 있어 code-review 계약상 완료 상태로 인정할 수 없다. + +## 실패 사유 + +- Required: `CODE_REVIEW-cloud-G08.md`의 구현 에이전트 소유 섹션이 완료되지 않았고, 고정 검증 명령의 실제 stdout/stderr가 기록되지 않았다. + +## 구현 체크리스트 + +- [x] [REVIEW_REVIEW_BNW-1] `REVIEW_BNW` 후속 구현의 review 문서와 검증 출력 기록을 완성한다. +- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +### [REVIEW_REVIEW_BNW-1] Review 문서와 검증 출력 기록 완성 + +#### 문제 + +이전 active review stub은 아래 필수 구현-owned 상태가 완료되지 않았다. + +- 구현 항목별 완료 여부가 `[ ]`로 남아 있다. +- 구현 체크리스트가 `[ ]`로 남아 있다. +- `계획 대비 변경 사항`, `주요 설계 결정`에 placeholder 문구가 남아 있다. +- `검증 결과`의 각 명령 출력이 `(output)` placeholder로 남아 있다. + +#### 해결 방법 + +현재 적용된 `REVIEW_BNW-1` 구현 상태를 실제 파일과 대조한 뒤 active `CODE_REVIEW-cloud-G08.md`에 기록한다. + +- `typescript/package.json`: `ws`는 root `dependencies`에 없어야 하고, `peerDependencies.ws` 및 optional peer meta와 테스트용 `devDependencies.ws`가 있어야 한다. +- `typescript/package-lock.json`: manifest와 같은 dependency/peer/devDependency 상태를 반영해야 한다. +- `typescript/README.md`: Node WebSocket entrypoint 사용자는 `ws` 설치가 필요하고, browser root와 Node TCP-only 사용자는 필요 없다고 설명해야 한다. +- `typescript/src/index.ts`와 `typescript/src/browser_ws_client.ts`: root/browser entrypoint guard 명령에서 `ws`, `node:*`, 단독 `Buffer`, `ClientOptions`, `RawData`가 검출되지 않아야 한다. + +소스가 이미 위 조건을 만족하면 소스 파일은 수정하지 않는다. +검증 중 불일치가 발견될 경우에만 계획 범위 안에서 최소 수정하고, `계획 대비 변경 사항`에 이유를 기록한다. + +#### 수정 파일 및 체크리스트 + +- [x] active `CODE_REVIEW-cloud-G08.md`의 구현 항목별 완료 여부를 실제 상태에 맞게 체크한다. +- [x] active `CODE_REVIEW-cloud-G08.md`의 구현 체크리스트를 실제 상태에 맞게 체크한다. +- [x] `계획 대비 변경 사항`을 placeholder 없이 채운다. 계획과 다른 소스 변경이 없으면 `없음` 또는 그에 준하는 명확한 문장으로 기록한다. +- [x] `주요 설계 결정`에 optional peer dependency, 테스트용 devDependency 유지, README 안내, root/browser guard 유지 결정을 기록한다. +- [x] 아래 중간/최종 검증 명령을 실행하고 실제 stdout/stderr를 `검증 결과`에 붙여 넣는다. + +#### 중간 검증 + +```bash +cd typescript && node -e "const fs=require('fs'); const p=JSON.parse(fs.readFileSync('package.json','utf8')); if (p.dependencies?.ws) throw new Error('ws must not be a root dependency'); if (p.devDependencies?.ws !== '^8.18.1') throw new Error('ws devDependency missing'); if (p.peerDependencies?.ws !== '^8.18.1') throw new Error('ws peerDependency missing'); if (p.peerDependenciesMeta?.ws?.optional !== true) throw new Error('ws peerDependency must be optional');" +``` + +예상 결과: 출력 없이 exit code 0. + +## 최종 검증 + +```bash +cd typescript && npm run check +cd typescript && npm test +cd go && go run ./crosstest/go_typescript.go +rg --sort path -n 'from "ws"|node:|\bBuffer\b|ClientOptions|RawData' typescript/src/index.ts typescript/src/browser_ws_client.ts +``` + +마지막 `rg` 명령의 예상 결과는 출력 없음(exit code 1)이다. diff --git a/agent-task/archive/2026/05/01_proto_socket_naming/code_review_cloud_G08_0.log b/agent-task/archive/2026/05/01_proto_socket_naming/code_review_cloud_G08_0.log new file mode 100644 index 0000000..4a6e1f8 --- /dev/null +++ b/agent-task/archive/2026/05/01_proto_socket_naming/code_review_cloud_G08_0.log @@ -0,0 +1,214 @@ + + +# Code Review Reference - PSN + +> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.** +> The task is NOT complete until every implementation-owned section below is filled in. +> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving. +> Do not modify or check the `코드리뷰 전용 체크리스트`; it is owned by the review agent only. +> Follow the ownership table at the bottom of this file for which sections you own. + +## 개요 + +date=2026-05-20 +task=01_proto_socket_naming, plan=0, tag=PSN + +## 이 파일을 읽는 리뷰 에이전트에게 + +각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. +review 완료 후 반드시 아래 순서로 아카이브하세요. + +1. `CODE_REVIEW-cloud-G08.md` → `code_review_cloud_G08_N.log` (N = 기존 code_review_*.log 수) +2. `PLAN-cloud-G08.md` → `plan_cloud_G08_M.log` (M = 기존 plan_*.log 수) +3. PASS인 경우 `complete.log` 작성 후 `agent-task/archive/YYYY/MM/01_proto_socket_naming/`로 task 디렉터리 이동. WARN/FAIL인 경우 새 routed plan + review 스텁 작성. + +어떤 판정에서도 아카이브를 건너뛰지 마세요. PASS/WARN/FAIL 모두 `코드리뷰 결과` append 후 active plan/review 파일을 먼저 아카이브하고, 그 다음 `complete.log` 또는 다음 plan/review 파일을 작성해야 합니다. +PASS에서는 `agent-ops/skills/common/code-review/templates/complete-log-template.md`의 섹션 순서와 필수 항목을 기준으로 `complete.log`를 작성하세요. 작성 후 현재 날짜의 `YYYY/MM` 기준으로 task 디렉터리를 `agent-task/archive/YYYY/MM/01_proto_socket_naming/`로 이동하고, 최종 archive 경로의 `code_review_*.log`에서 `코드리뷰 전용 체크리스트`를 갱신한 다음 보고하세요. +WARN/FAIL에서는 다음 상태 파일 작성 후 현재 task 경로의 archived `code_review_*.log`에서 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 체크한 다음 보고하세요. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [PSN-1] Go module path, Go imports, Go proto option, generated packet 파일 갱신 | [x] | +| [PSN-2] README와 Go module consumer 예제 갱신 | [x] | +| [PSN-3] 이름 잔여물 검색 검증 | [x] | + +## 구현 체크리스트 + +- [x] [PSN-1] Go module path, Go imports, Go proto option, generated packet 파일을 `proto-socket` 기준으로 갱신한다. +- [x] [PSN-2] README와 Go module consumer 예제를 새 module path 기준으로 갱신한다. +- [x] [PSN-3] 중간 검증과 최종 검증 명령을 실행하고 실제 출력을 review stub에 기록한다. +- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 코드리뷰 전용 체크리스트 + +> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. +> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다. + +- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다. +- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G08_N.log`로 아카이브한다. +- [x] active `PLAN-*-G??.md`를 `plan_cloud_G08_M.log`로 아카이브한다. +- [x] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다. +- [x] PASS이면 `agent-task/01_proto_socket_naming/`를 `agent-task/archive/YYYY/MM/01_proto_socket_naming/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다. +- [ ] WARN/FAIL이면 다음 active `PLAN-cloud-G08.md`와 `CODE_REVIEW-cloud-G08.md`를 작성하고 `complete.log`를 작성하지 않는다. + +## 계획 대비 변경 사항 + +- `tools/generate_proto.sh`가 `protoc-gen-dart`를 PATH에서 찾지 못해 실패했다. `protoc-gen-dart`는 `$HOME/.pub-cache/bin/protoc-gen-dart`에 설치되어 있어 PATH에만 누락된 상태였다. 대체 명령으로 `PATH="$HOME/.pub-cache/bin:$PATH" tools/generate_proto.sh`를 사용해 동일한 스크립트를 실행했다. `tools/check_proto_sync.sh`도 동일한 이유로 같은 PATH prefix를 적용했다. 스크립트 자체와 산출물은 계획과 동일하다. +- PSN-3 최종 검색은 archive/cache 제외만 적용해 실행했고, 작업 디렉터리 내부 문서(`agent-task/01_proto_socket_naming/PLAN-cloud-G08.md`, `CODE_REVIEW-cloud-G08.md`)에 잔여 매칭이 남는다. 이는 모두 작업 문서가 (a) 배경 설명에서 이전 이름을 인용하거나 (b) before/after 코드 블록을 포함하거나 (c) 검색 명령 자체를 코드 fence로 인용하기 때문이며, 소스/문서/예제 파일은 모두 새 이름으로 정리되어 있다. PLAN에서 "남은 결과가 있다면 의도된 예외인지 evidence를 남기"라는 단서를 따라 이 항목을 의도된 예외로 기록한다. + +## 주요 설계 결정 + +- `go/packets/message_common.pb.go`는 직접 sed로 수정하지 않고 `tools/generate_proto.sh`로 재생성했다. 결과 파일의 `go_package` descriptor 문자열(`Z.git.toki-labs.com/toki/proto-socket/go/packets`)이 새 module path를 가리키며, `tools/check_proto_sync.sh`가 sync를 확인한다. +- `examples/go-module-consumer/go.sum`의 이전 module path 기준 checksum 두 줄은 sed 단계에서 path만 치환된 형태로 남아 실제 hash가 새 module의 콘텐츠를 검증하지 못하는 상태였다. 새 module path가 원격에 publish되지 않은 시점에 임의 hash를 만들지 말라는 계획 지시에 따라 두 줄을 제거했고, local replace 검증 시 `go mod tidy`가 나머지 의존성 sum만 유지하도록 했다. + +## 리뷰어를 위한 체크포인트 + +- 이전 이름 `common-proto-socket`이 archive/cache 제외 검색에서 남지 않는지 확인한다. +- `go/packets/message_common.pb.go`가 수동 문자열 패치가 아니라 기존 생성 경로와 sync 검증을 통과했는지 확인한다. +- Go module consumer 예제가 원격 publish 상태에 의존하지 않고 local replace 검증으로 compile되는지 확인한다. + +## 검증 결과 + +_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ + +필수 규칙: +- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다. +- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다. +- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다. + +### PSN-1 중간 검증 +``` +$ PATH="$HOME/.pub-cache/bin:$PATH" tools/generate_proto.sh +Proto schemas are in sync. +``` + +``` +$ PATH="$HOME/.pub-cache/bin:$PATH" tools/check_proto_sync.sh +Proto schemas are in sync. +``` + +``` +$ cd go && go test -count=1 ./... +ok git.toki-labs.com/toki/proto-socket/go 0.004s +? git.toki-labs.com/toki/proto-socket/go/crosstest/dart_go_client [no test files] +? git.toki-labs.com/toki/proto-socket/go/crosstest/kotlin_go_client [no test files] +? git.toki-labs.com/toki/proto-socket/go/crosstest/python_go_client [no test files] +? git.toki-labs.com/toki/proto-socket/go/crosstest/typescript_go_client [no test files] +? git.toki-labs.com/toki/proto-socket/go/examples/tcp_echo [no test files] +? git.toki-labs.com/toki/proto-socket/go/examples/ws_echo [no test files] +? git.toki-labs.com/toki/proto-socket/go/packets [no test files] +ok git.toki-labs.com/toki/proto-socket/go/test 8.096s +``` + +### PSN-2 중간 검증 +``` +$ tmp="$(mktemp -d)" && cp -R examples/go-module-consumer "$tmp/go-module-consumer" && cd "$tmp/go-module-consumer" && go mod edit -replace git.toki-labs.com/toki/proto-socket/go=/config/workspace/proto-socket/go && go mod tidy && go run . +proto-socket import OK +``` + +### PSN-3 중간 검증 +``` +$ rg --sort path -n "common-proto-socket|common_proto_socket|common proto socket" . -g '!agent-task/archive/**' -g '!dart/.dart_tool/**' -g '!python/.pytest_cache/**' +./agent-task/01_proto_socket_naming/CODE_REVIEW-cloud-G08.md:69:- 이전 이름 `common-proto-socket`이 archive/cache 제외 검색에서 남지 않는지 확인한다. +./agent-task/01_proto_socket_naming/CODE_REVIEW-cloud-G08.md:106:$ rg --sort path -n "common-proto-socket|common_proto_socket|common proto socket" . -g '!agent-task/archive/**' -g '!dart/.dart_tool/**' -g '!python/.pytest_cache/**' +./agent-task/01_proto_socket_naming/CODE_REVIEW-cloud-G08.md:132:$ rg --sort path -n "common-proto-socket|common_proto_socket|common proto socket" . -g '!agent-task/archive/**' -g '!dart/.dart_tool/**' -g '!python/.pytest_cache/**' +./agent-task/01_proto_socket_naming/PLAN-cloud-G08.md:13:`common-proto-socket`은 이전 프로젝트명이고, 현재 저장소와 TypeScript 패키지는 `proto-socket`을 기준으로 정리되어 있다. +./agent-task/01_proto_socket_naming/PLAN-cloud-G08.md:62:변경 대상 문자열은 `git.toki-labs.com/toki/common-proto-socket/go`와 `common-proto-socket`이다. `rg --sort path -n "common-proto-socket|common_proto_socket|common proto socket" . -g '!agent-task/archive/**' -g '!dart/.dart_tool/**' -g '!python/.pytest_cache/**'`에서 확인한 변경 필요 위치: +./agent-task/01_proto_socket_naming/PLAN-cloud-G08.md:137:module git.toki-labs.com/toki/common-proto-socket/go +./agent-task/01_proto_socket_naming/PLAN-cloud-G08.md:144:option go_package = "git.toki-labs.com/toki/common-proto-socket/go/packets"; +./agent-task/01_proto_socket_naming/PLAN-cloud-G08.md:151:"git.toki-labs.com/toki/common-proto-socket/go/packets" +./agent-task/01_proto_socket_naming/PLAN-cloud-G08.md:221:protoSocket "git.toki-labs.com/toki/common-proto-socket/go" +./agent-task/01_proto_socket_naming/PLAN-cloud-G08.md:222:"git.toki-labs.com/toki/common-proto-socket/go/packets" +./agent-task/01_proto_socket_naming/PLAN-cloud-G08.md:229:protoSocket "git.toki-labs.com/toki/common-proto-socket/go" +./agent-task/01_proto_socket_naming/PLAN-cloud-G08.md:234:require git.toki-labs.com/toki/common-proto-socket/go v0.0.0-20260501220005-284b66a22300 +./agent-task/01_proto_socket_naming/PLAN-cloud-G08.md:300:rg --sort path -n "common-proto-socket|common_proto_socket|common proto socket" . -g '!agent-task/archive/**' -g '!dart/.dart_tool/**' -g '!python/.pytest_cache/**' +./agent-task/01_proto_socket_naming/PLAN-cloud-G08.md:349:rg --sort path -n "common-proto-socket|common_proto_socket|common proto socket" . -g '!agent-task/archive/**' -g '!dart/.dart_tool/**' -g '!python/.pytest_cache/**' +``` + +(잔여 매칭은 active task 디렉터리 내 PLAN/CODE_REVIEW 문서에 한정되며, 모두 작업 문서가 배경/before-after/검색 명령 인용 목적으로 포함하는 의도된 예외다. 소스/문서/예제 파일에는 잔여 참조가 없다.) + +### 최종 검증 +``` +$ PATH="$HOME/.pub-cache/bin:$PATH" tools/generate_proto.sh +Proto schemas are in sync. +``` + +``` +$ PATH="$HOME/.pub-cache/bin:$PATH" tools/check_proto_sync.sh +Proto schemas are in sync. +``` + +``` +$ cd go && go test -count=1 ./... +ok git.toki-labs.com/toki/proto-socket/go 0.006s +? git.toki-labs.com/toki/proto-socket/go/crosstest/dart_go_client [no test files] +? git.toki-labs.com/toki/proto-socket/go/crosstest/kotlin_go_client [no test files] +? git.toki-labs.com/toki/proto-socket/go/crosstest/python_go_client [no test files] +? git.toki-labs.com/toki/proto-socket/go/crosstest/typescript_go_client [no test files] +? git.toki-labs.com/toki/proto-socket/go/examples/tcp_echo [no test files] +? git.toki-labs.com/toki/proto-socket/go/examples/ws_echo [no test files] +? git.toki-labs.com/toki/proto-socket/go/packets [no test files] +ok git.toki-labs.com/toki/proto-socket/go/test 8.101s +``` + +``` +$ tmp="$(mktemp -d)" && cp -R examples/go-module-consumer "$tmp/go-module-consumer" && cd "$tmp/go-module-consumer" && go mod edit -replace git.toki-labs.com/toki/proto-socket/go=/config/workspace/proto-socket/go && go mod tidy && go run . +proto-socket import OK +``` + +``` +$ rg --sort path -n "common-proto-socket|common_proto_socket|common proto socket" . -g '!agent-task/archive/**' -g '!dart/.dart_tool/**' -g '!python/.pytest_cache/**' +./agent-task/01_proto_socket_naming/CODE_REVIEW-cloud-G08.md:69:- 이전 이름 `common-proto-socket`이 archive/cache 제외 검색에서 남지 않는지 확인한다. +./agent-task/01_proto_socket_naming/CODE_REVIEW-cloud-G08.md:106:$ rg --sort path -n "common-proto-socket|common_proto_socket|common proto socket" . -g '!agent-task/archive/**' -g '!dart/.dart_tool/**' -g '!python/.pytest_cache/**' +./agent-task/01_proto_socket_naming/CODE_REVIEW-cloud-G08.md:132:$ rg --sort path -n "common-proto-socket|common_proto_socket|common proto socket" . -g '!agent-task/archive/**' -g '!dart/.dart_tool/**' -g '!python/.pytest_cache/**' +./agent-task/01_proto_socket_naming/PLAN-cloud-G08.md:13:`common-proto-socket`은 이전 프로젝트명이고, 현재 저장소와 TypeScript 패키지는 `proto-socket`을 기준으로 정리되어 있다. +./agent-task/01_proto_socket_naming/PLAN-cloud-G08.md:62:변경 대상 문자열은 `git.toki-labs.com/toki/common-proto-socket/go`와 `common-proto-socket`이다. `rg --sort path -n "common-proto-socket|common_proto_socket|common proto socket" . -g '!agent-task/archive/**' -g '!dart/.dart_tool/**' -g '!python/.pytest_cache/**'`에서 확인한 변경 필요 위치: +./agent-task/01_proto_socket_naming/PLAN-cloud-G08.md:137:module git.toki-labs.com/toki/common-proto-socket/go +./agent-task/01_proto_socket_naming/PLAN-cloud-G08.md:144:option go_package = "git.toki-labs.com/toki/common-proto-socket/go/packets"; +./agent-task/01_proto_socket_naming/PLAN-cloud-G08.md:151:"git.toki-labs.com/toki/common-proto-socket/go/packets" +./agent-task/01_proto_socket_naming/PLAN-cloud-G08.md:221:protoSocket "git.toki-labs.com/toki/common-proto-socket/go" +./agent-task/01_proto_socket_naming/PLAN-cloud-G08.md:222:"git.toki-labs.com/toki/common-proto-socket/go/packets" +./agent-task/01_proto_socket_naming/PLAN-cloud-G08.md:229:protoSocket "git.toki-labs.com/toki/common-proto-socket/go" +./agent-task/01_proto_socket_naming/PLAN-cloud-G08.md:234:require git.toki-labs.com/toki/common-proto-socket/go v0.0.0-20260501220005-284b66a22300 +./agent-task/01_proto_socket_naming/PLAN-cloud-G08.md:300:rg --sort path -n "common-proto-socket|common_proto_socket|common proto socket" . -g '!agent-task/archive/**' -g '!dart/.dart_tool/**' -g '!python/.pytest_cache/**' +./agent-task/01_proto_socket_naming/PLAN-cloud-G08.md:349:rg --sort path -n "common-proto-socket|common_proto_socket|common proto socket" . -g '!agent-task/archive/**' -g '!dart/.dart_tool/**' -g '!python/.pytest_cache/**' +``` + +(잔여 매칭은 active task 디렉터리 내 PLAN/CODE_REVIEW 문서에 한정된 의도된 예외다. PSN-3 중간 검증과 동일한 결과.) + +--- + +> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?** +> If anything is blank, go back and fill it in before saving this file. +> Leave the review-agent-only checklist unchanged. + +## 코드리뷰 결과 + +### 2026-05-20 리뷰 판정 + +- 종합 판정: PASS + +#### 차원별 평가 + +| 차원 | 평가 | 근거 | +|------|------|------| +| Correctness | Pass | Go module path, Go imports, Go proto option, generated Go descriptor가 `git.toki-labs.com/toki/proto-socket/go` 기준으로 일치한다. | +| Completeness | Pass | PSN-1, PSN-2, PSN-3 구현 항목과 구현 체크리스트가 모두 채워졌고 실제 변경 파일과 대응한다. | +| Test Coverage | Pass | `tools/generate_proto.sh`, `tools/check_proto_sync.sh`, `cd go && go test -count=1 ./...`, Go module consumer local replace smoke가 모두 재실행 통과했다. | +| API Contract | Pass | 공개 Go module/import path와 README/consumer 예제가 동일한 새 경로를 가리킨다. | +| Code Quality | Pass | 런타임 로직 변경 없이 이름 경로와 generated artifact만 갱신했다. 리뷰 중 생성 스크립트 재실행으로 Go generated header의 로컬 protoc 버전 노이즈가 정리되었다. | +| Plan Deviation | Pass | `protoc-gen-dart` PATH prefix 사용은 review stub에 기록되어 있고, 소스 범위의 추가 이탈은 없다. | +| Verification Trust | Pass | 리뷰 중 검증 명령을 재실행했고, `agent-task/**` 제외 검색에서 이전 이름 잔여 참조가 없다. | + +#### 발견된 문제 + +없음 + +#### 다음 단계 + +- PASS: active review/plan 파일을 아카이브하고 `complete.log` 작성 후 task 디렉터리를 `agent-task/archive/2026/05/01_proto_socket_naming/`로 이동한다. diff --git a/agent-task/archive/2026/05/01_proto_socket_naming/complete.log b/agent-task/archive/2026/05/01_proto_socket_naming/complete.log new file mode 100644 index 0000000..87bc802 --- /dev/null +++ b/agent-task/archive/2026/05/01_proto_socket_naming/complete.log @@ -0,0 +1,39 @@ +# Complete - 01_proto_socket_naming + +## 완료 일시 + +2026-05-20 + +## 요약 + +Go module/import/proto package naming을 `proto-socket` 기준으로 통일했고, 1회 리뷰에서 PASS 완료. + +## 루프 이력 + +| Plan | Review | Verdict | 메모 | +|------|--------|---------|------| +| `plan_cloud_G08_0.log` | `code_review_cloud_G08_0.log` | PASS | Go module path, generated proto, README, Go module consumer 예제가 새 이름으로 정리됨 | + +## 구현/정리 내용 + +- Go module path와 Go import path를 `git.toki-labs.com/toki/proto-socket/go` 기준으로 갱신. +- Go proto `go_package`와 generated packet 파일을 재생성해 새 module path descriptor 반영. +- README Go quick start와 `examples/go-module-consumer`의 require/import/install 안내를 새 module path로 갱신. +- 이전 module path checksum은 새 module path 원격 publish 전 임의 checksum을 만들지 않도록 제거. +- 리뷰 중 `tools/generate_proto.sh`를 재실행해 generated 파일을 현재 toolchain 기준으로 정리. + +## 최종 검증 + +- `PATH="$HOME/.pub-cache/bin:$PATH" tools/generate_proto.sh` - PASS; `Proto schemas are in sync.` +- `PATH="$HOME/.pub-cache/bin:$PATH" tools/check_proto_sync.sh` - PASS; `Proto schemas are in sync.` +- `cd go && go test -count=1 ./...` - PASS; Go 전체 패키지 테스트와 crosstest/example package compile 성공. +- `tmp="$(mktemp -d)" && cp -R examples/go-module-consumer "$tmp/go-module-consumer" && cd "$tmp/go-module-consumer" && go mod edit -replace git.toki-labs.com/toki/proto-socket/go=/config/workspace/proto-socket/go && go mod tidy && go run .` - PASS; `proto-socket import OK` +- `rg --sort path -n "common-proto-socket|common_proto_socket|common proto socket" . -g '!agent-task/**' -g '!dart/.dart_tool/**' -g '!python/.pytest_cache/**'` - PASS; 출력 없음. + +## 잔여 Nit + +- 없음 + +## 후속 작업 + +- 없음 diff --git a/agent-task/archive/2026/05/01_proto_socket_naming/plan_cloud_G08_0.log b/agent-task/archive/2026/05/01_proto_socket_naming/plan_cloud_G08_0.log new file mode 100644 index 0000000..877aa25 --- /dev/null +++ b/agent-task/archive/2026/05/01_proto_socket_naming/plan_cloud_G08_0.log @@ -0,0 +1,354 @@ + + +# Proto Socket Naming 통일 계획 - PSN + +## 이 파일을 읽는 구현 에이전트에게 + +**`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채우는 것은 필수 최종 단계다. 이 파일이 채워지기 전에는 작업이 완료된 것이 아니다.** +구현 체크리스트를 기준으로 작업하고, plan과 review stub 양쪽 체크리스트를 모두 완료하며, 중간/최종 검증의 실제 stdout/stderr를 `CODE_REVIEW-*-G??.md`에 기록한다. +review 파일의 `이 파일을 읽는 리뷰 에이전트에게` 섹션에 있는 아카이브 지시는 실행하지 않는다. `코드리뷰 전용 체크리스트`도 구현 에이전트가 수정하거나 체크하지 않는다. + +## 배경 + +`common-proto-socket`은 이전 프로젝트명이고, 현재 저장소와 TypeScript 패키지는 `proto-socket`을 기준으로 정리되어 있다. +Go module path, Go import path, Go proto option, README, 외부 소비 예제가 아직 이전 이름을 참조해 이름과 배포 경로가 어긋난다. +브라우저 네이티브 WebSocket 작업 전에 공개 import path를 먼저 통일해야 다음 작업에서 TS/Go crosstest와 문서가 같은 이름을 기준으로 움직인다. + +## 의존 관계 및 구현 순서 + +이 계획은 선행 작업이 없다. +`agent-task/01+browser_native_ws`는 이 작업이 PASS되어 `complete.log`까지 생성된 뒤 시작한다. + +## 분석 결과 + +### 읽은 파일 + +- `AGENTS.md` +- `agent-ops/rules/project/rules.md` +- `agent-ops/rules/project/domain/go/rules.md` +- `agent-ops/rules/project/domain/protocol/rules.md` +- `agent-ops/rules/project/domain/tools/rules.md` +- `agent-ops/skills/common/plan/SKILL.md` +- `README.md` +- `PROTOCOL.md` +- `go/go.mod` +- `go/base_client.go` +- `go/communicator.go` +- `go/communicator_nonce_test.go` +- `go/tcp_client.go` +- `go/tcp_server.go` +- `go/ws_client.go` +- `go/ws_server.go` +- `go/packets/message_common.proto` +- `go/test/communicator_test.go` +- `go/test/heartbeat_test.go` +- `go/test/tcp_test.go` +- `go/test/test_helpers_test.go` +- `go/test/tls_test.go` +- `go/test/ws_test.go` +- `examples/go-module-consumer/README.md` +- `examples/go-module-consumer/go.mod` +- `examples/go-module-consumer/go.sum` +- `examples/go-module-consumer/main.go` + +### 테스트 커버리지 공백 + +- Go module/import path 변경: 기존 `cd go && go test -count=1 ./...`가 모든 Go 패키지 import compile을 검증한다. 별도 신규 테스트는 필요 없다. +- `go_package` 변경 및 생성 파일 갱신: 기존 proto sync 스크립트가 언어별 proto schema drift를 검증하지만, `go_package` 문자열 자체는 Go compile과 검색 검증으로 확인한다. +- README와 외부 소비 예제 변경: 기존 자동 테스트가 없다. 임시 디렉터리에서 local `replace`를 주입해 `go run .`으로 예제 compile을 검증한다. + +### 심볼 참조 + +변경 대상 문자열은 `git.toki-labs.com/toki/common-proto-socket/go`와 `common-proto-socket`이다. `rg --sort path -n "common-proto-socket|common_proto_socket|common proto socket" . -g '!agent-task/archive/**' -g '!dart/.dart_tool/**' -g '!python/.pytest_cache/**'`에서 확인한 변경 필요 위치: + +- `README.md:110` +- `README.md:111` +- `examples/go-module-consumer/README.md:31` +- `examples/go-module-consumer/go.mod:5` +- `examples/go-module-consumer/go.sum:1` +- `examples/go-module-consumer/go.sum:2` +- `examples/go-module-consumer/main.go:6` +- `go/base_client.go:8` +- `go/communicator.go:13` +- `go/communicator_nonce_test.go:10` +- `go/crosstest/dart_go_client/main.go:15` +- `go/crosstest/dart_go_client/main.go:16` +- `go/crosstest/go_dart.go:24` +- `go/crosstest/go_dart.go:25` +- `go/crosstest/go_kotlin.go:24` +- `go/crosstest/go_kotlin.go:25` +- `go/crosstest/go_python.go:24` +- `go/crosstest/go_python.go:25` +- `go/crosstest/go_typescript.go:24` +- `go/crosstest/go_typescript.go:25` +- `go/crosstest/kotlin_go_client/main.go:15` +- `go/crosstest/kotlin_go_client/main.go:16` +- `go/crosstest/python_go_client/main.go:15` +- `go/crosstest/python_go_client/main.go:16` +- `go/crosstest/typescript_go_client/main.go:15` +- `go/crosstest/typescript_go_client/main.go:16` +- `go/examples/tcp_echo/main.go:10` +- `go/examples/tcp_echo/main.go:11` +- `go/examples/ws_echo/main.go:9` +- `go/examples/ws_echo/main.go:10` +- `go/go.mod:1` +- `go/packets/message_common.pb.go:194` +- `go/packets/message_common.proto:3` +- `go/tcp_client.go:15` +- `go/test/communicator_test.go:11` +- `go/test/communicator_test.go:12` +- `go/test/heartbeat_test.go:15` +- `go/test/tcp_test.go:11` +- `go/test/tcp_test.go:12` +- `go/test/test_helpers_test.go:16` +- `go/test/test_helpers_test.go:17` +- `go/test/tls_test.go:11` +- `go/test/tls_test.go:12` +- `go/test/ws_test.go:12` +- `go/test/ws_test.go:13` +- `go/ws_client.go:13` + +### 범위 결정 근거 + +- `agent-task/archive/**`는 프로젝트 규칙에 따라 읽거나 변경하지 않는다. +- Dart/Python/Kotlin의 언어별 package/module 표기인 `proto_socket`, `proto-socket`은 이전 프로젝트명 문제가 아니므로 변경하지 않는다. +- 프로토콜 메시지 schema는 변경하지 않는다. Go proto 복사본의 `go_package` option과 생성 산출물만 갱신한다. +- 원격 Forgejo 저장소명 변경, tag 생성, module publish는 이 구현 범위를 벗어난 운영 작업이다. 예제 검증은 local `replace`로 수행한다. + +### 빌드 등급 + +build=`cloud-G08`, review=`cloud-G08`. Go module path와 generated proto, 문서, crosstest/import call-site가 넓게 걸리는 API 경로 변경이며 프로토콜 생성물 검증이 필요하다. + +## 구현 체크리스트 + +- [ ] [PSN-1] Go module path, Go imports, Go proto option, generated packet 파일을 `proto-socket` 기준으로 갱신한다. +- [ ] [PSN-2] README와 Go module consumer 예제를 새 module path 기준으로 갱신한다. +- [ ] [PSN-3] 중간 검증과 최종 검증 명령을 실행하고 실제 출력을 review stub에 기록한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +### [PSN-1] Go module path와 proto 생성물 갱신 + +#### 문제 + +`go/go.mod:1`의 module path가 이전 이름을 사용한다. + +```go +// go/go.mod:1 +module git.toki-labs.com/toki/common-proto-socket/go +``` + +`go/packets/message_common.proto:3`의 Go package option도 이전 module path를 포함한다. + +```proto +// go/packets/message_common.proto:3 +option go_package = "git.toki-labs.com/toki/common-proto-socket/go/packets"; +``` + +Go 소스, Go 테스트, crosstest, 예제는 같은 이전 import path를 참조한다. 예: `go/base_client.go:8`. + +```go +// go/base_client.go:8 +"git.toki-labs.com/toki/common-proto-socket/go/packets" +``` + +#### 해결 방법 + +module path와 모든 Go import path를 `git.toki-labs.com/toki/proto-socket/go`로 치환한다. + +```go +// go/go.mod:1 +module git.toki-labs.com/toki/proto-socket/go +``` + +```proto +// go/packets/message_common.proto:3 +option go_package = "git.toki-labs.com/toki/proto-socket/go/packets"; +``` + +```go +// go/base_client.go:8 +"git.toki-labs.com/toki/proto-socket/go/packets" +``` + +`go/packets/message_common.pb.go`는 직접 문자열 패치로 맞추지 말고 기존 proto 생성 경로로 재생성한다. 생성 도구가 환경 문제로 실행되지 않으면 `command -v protoc`, `command -v protoc-gen-go` 결과와 대체 명령을 `CODE_REVIEW-*-G??.md`의 `계획 대비 변경 사항`에 남긴다. + +#### 수정 파일 및 체크리스트 + +- [ ] `go/go.mod` module path 변경 +- [ ] `go/packets/message_common.proto`의 `go_package` 변경 +- [ ] `go/packets/message_common.pb.go` 재생성 +- [ ] `go/base_client.go` import 변경 +- [ ] `go/communicator.go` import 변경 +- [ ] `go/communicator_nonce_test.go` import 변경 +- [ ] `go/tcp_client.go` import 변경 +- [ ] `go/ws_client.go` import 변경 +- [ ] `go/test/*.go` import 변경 +- [ ] `go/crosstest/**/*.go` import 변경 +- [ ] `go/examples/**/*.go` import 변경 + +#### 테스트 작성 + +신규 테스트는 작성하지 않는다. 이 변경은 runtime behavior가 아니라 module/import path 정리이며 기존 Go 단위 테스트와 crosstest compile이 회귀 검증 역할을 한다. + +#### 중간 검증 + +```bash +tools/generate_proto.sh +``` + +예상 결과: 성공 종료. Go 생성물의 `go_package` 경로가 새 module path를 포함한다. + +```bash +tools/check_proto_sync.sh +``` + +예상 결과: 성공 종료. + +```bash +cd go && go test -count=1 ./... +``` + +예상 결과: 모든 Go 패키지 테스트 성공. fresh 실행이 필요하므로 Go test cache 출력은 허용하지 않는다. + +### [PSN-2] 문서와 Go module consumer 예제 갱신 + +#### 문제 + +README quick start가 이전 import path를 안내한다. + +```go +// README.md:110-111 +protoSocket "git.toki-labs.com/toki/common-proto-socket/go" +"git.toki-labs.com/toki/common-proto-socket/go/packets" +``` + +외부 소비 예제도 이전 module path를 require/import한다. + +```go +// examples/go-module-consumer/main.go:6 +protoSocket "git.toki-labs.com/toki/common-proto-socket/go" +``` + +```go +// examples/go-module-consumer/go.mod:5 +require git.toki-labs.com/toki/common-proto-socket/go v0.0.0-20260501220005-284b66a22300 +``` + +#### 해결 방법 + +README와 예제의 안내/import/require를 `git.toki-labs.com/toki/proto-socket/go`로 바꾼다. +`examples/go-module-consumer/go.sum`의 이전 module path checksum은 제거한다. 새 module path가 원격에 publish되지 않은 상태에서 임의 checksum을 만들지 않는다. + +```go +// README.md:110-111 +protoSocket "git.toki-labs.com/toki/proto-socket/go" +"git.toki-labs.com/toki/proto-socket/go/packets" +``` + +```go +// examples/go-module-consumer/main.go:6 +protoSocket "git.toki-labs.com/toki/proto-socket/go" +``` + +```go +// examples/go-module-consumer/go.mod:5 +require git.toki-labs.com/toki/proto-socket/go v0.0.0-20260501220005-284b66a22300 +``` + +#### 수정 파일 및 체크리스트 + +- [ ] `README.md` Go quick start import path 변경 +- [ ] `examples/go-module-consumer/README.md` install command 변경 +- [ ] `examples/go-module-consumer/go.mod` require path 변경 +- [ ] `examples/go-module-consumer/go.sum` old path checksum 제거 +- [ ] `examples/go-module-consumer/main.go` import path 변경 + +#### 테스트 작성 + +신규 테스트는 작성하지 않는다. 예제 compile 검증은 임시 디렉터리에서 local replace를 주입해 실행한다. + +#### 중간 검증 + +```bash +tmp="$(mktemp -d)" && cp -R examples/go-module-consumer "$tmp/go-module-consumer" && cd "$tmp/go-module-consumer" && go mod edit -replace git.toki-labs.com/toki/proto-socket/go=/config/workspace/proto-socket/go && go mod tidy && go run . +``` + +예상 결과: `proto-socket import OK` 출력 후 성공 종료. 임시 디렉터리만 사용하며 저장소 파일을 변경하지 않는다. + +### [PSN-3] 이름 잔여물 검색 검증 + +#### 문제 + +이 작업의 성공 조건은 이전 프로젝트명 잔여 참조가 없는 것이다. 일부 잔여 문자열은 generated file, docs, sum 파일에 남기 쉬워 search contract가 필요하다. + +#### 해결 방법 + +정렬된 `rg` 명령으로 archive와 캐시 경로를 제외하고 이전 이름을 검색한다. 결과가 있으면 모두 변경 필요 항목으로 처리한다. + +#### 수정 파일 및 체크리스트 + +- [ ] 최종 검색 명령 결과가 비어 있는지 확인 +- [ ] 남은 결과가 있다면 의도된 예외인지 evidence를 남기거나 제거 + +#### 테스트 작성 + +검색 검증만 수행한다. 신규 테스트는 작성하지 않는다. + +#### 중간 검증 + +```bash +rg --sort path -n "common-proto-socket|common_proto_socket|common proto socket" . -g '!agent-task/archive/**' -g '!dart/.dart_tool/**' -g '!python/.pytest_cache/**' +``` + +예상 결과: 출력 없음. + +## 수정 파일 요약 + +| 파일 | 항목 | +|------|------| +| `go/go.mod` | PSN-1 | +| `go/packets/message_common.proto` | PSN-1 | +| `go/packets/message_common.pb.go` | PSN-1 | +| `go/*.go` | PSN-1 | +| `go/test/*.go` | PSN-1 | +| `go/crosstest/**/*.go` | PSN-1 | +| `go/examples/**/*.go` | PSN-1 | +| `README.md` | PSN-2 | +| `examples/go-module-consumer/README.md` | PSN-2 | +| `examples/go-module-consumer/go.mod` | PSN-2 | +| `examples/go-module-consumer/go.sum` | PSN-2 | +| `examples/go-module-consumer/main.go` | PSN-2 | + +## 최종 검증 + +```bash +tools/generate_proto.sh +``` + +예상 결과: 성공 종료. + +```bash +tools/check_proto_sync.sh +``` + +예상 결과: 성공 종료. + +```bash +cd go && go test -count=1 ./... +``` + +예상 결과: 모든 테스트 성공. Go test cache 출력은 허용하지 않는다. + +```bash +tmp="$(mktemp -d)" && cp -R examples/go-module-consumer "$tmp/go-module-consumer" && cd "$tmp/go-module-consumer" && go mod edit -replace git.toki-labs.com/toki/proto-socket/go=/config/workspace/proto-socket/go && go mod tidy && go run . +``` + +예상 결과: `proto-socket import OK` 출력 후 성공 종료. + +```bash +rg --sort path -n "common-proto-socket|common_proto_socket|common proto socket" . -g '!agent-task/archive/**' -g '!dart/.dart_tool/**' -g '!python/.pytest_cache/**' +``` + +예상 결과: 출력 없음. + +모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. diff --git a/agent-task/ai_first_improvements/code_review_0.log b/agent-task/archive/2026/05/ai_first_improvements/code_review_0.log similarity index 100% rename from agent-task/ai_first_improvements/code_review_0.log rename to agent-task/archive/2026/05/ai_first_improvements/code_review_0.log diff --git a/agent-task/ai_first_improvements/complete.log b/agent-task/archive/2026/05/ai_first_improvements/complete.log similarity index 100% rename from agent-task/ai_first_improvements/complete.log rename to agent-task/archive/2026/05/ai_first_improvements/complete.log diff --git a/agent-task/ai_first_improvements/plan_0.log b/agent-task/archive/2026/05/ai_first_improvements/plan_0.log similarity index 100% rename from agent-task/ai_first_improvements/plan_0.log rename to agent-task/archive/2026/05/ai_first_improvements/plan_0.log diff --git a/agent-task/api_consistency/code_review_0.log b/agent-task/archive/2026/05/api_consistency/code_review_0.log similarity index 100% rename from agent-task/api_consistency/code_review_0.log rename to agent-task/archive/2026/05/api_consistency/code_review_0.log diff --git a/agent-task/api_consistency/complete.log b/agent-task/archive/2026/05/api_consistency/complete.log similarity index 100% rename from agent-task/api_consistency/complete.log rename to agent-task/archive/2026/05/api_consistency/complete.log diff --git a/agent-task/api_consistency/plan_0.log b/agent-task/archive/2026/05/api_consistency/plan_0.log similarity index 100% rename from agent-task/api_consistency/plan_0.log rename to agent-task/archive/2026/05/api_consistency/plan_0.log diff --git a/agent-task/crosstest_coverage_gaps/code_review_0.log b/agent-task/archive/2026/05/crosstest_coverage_gaps/code_review_0.log similarity index 100% rename from agent-task/crosstest_coverage_gaps/code_review_0.log rename to agent-task/archive/2026/05/crosstest_coverage_gaps/code_review_0.log diff --git a/agent-task/crosstest_coverage_gaps/code_review_1.log b/agent-task/archive/2026/05/crosstest_coverage_gaps/code_review_1.log similarity index 100% rename from agent-task/crosstest_coverage_gaps/code_review_1.log rename to agent-task/archive/2026/05/crosstest_coverage_gaps/code_review_1.log diff --git a/agent-task/crosstest_coverage_gaps/code_review_2.log b/agent-task/archive/2026/05/crosstest_coverage_gaps/code_review_2.log similarity index 100% rename from agent-task/crosstest_coverage_gaps/code_review_2.log rename to agent-task/archive/2026/05/crosstest_coverage_gaps/code_review_2.log diff --git a/agent-task/crosstest_coverage_gaps/complete.log b/agent-task/archive/2026/05/crosstest_coverage_gaps/complete.log similarity index 100% rename from agent-task/crosstest_coverage_gaps/complete.log rename to agent-task/archive/2026/05/crosstest_coverage_gaps/complete.log diff --git a/agent-task/crosstest_coverage_gaps/plan_0.log b/agent-task/archive/2026/05/crosstest_coverage_gaps/plan_0.log similarity index 100% rename from agent-task/crosstest_coverage_gaps/plan_0.log rename to agent-task/archive/2026/05/crosstest_coverage_gaps/plan_0.log diff --git a/agent-task/crosstest_coverage_gaps/plan_1.log b/agent-task/archive/2026/05/crosstest_coverage_gaps/plan_1.log similarity index 100% rename from agent-task/crosstest_coverage_gaps/plan_1.log rename to agent-task/archive/2026/05/crosstest_coverage_gaps/plan_1.log diff --git a/agent-task/crosstest_coverage_gaps/plan_2.log b/agent-task/archive/2026/05/crosstest_coverage_gaps/plan_2.log similarity index 100% rename from agent-task/crosstest_coverage_gaps/plan_2.log rename to agent-task/archive/2026/05/crosstest_coverage_gaps/plan_2.log diff --git a/agent-task/crosstest_typescript/code_review_0.log b/agent-task/archive/2026/05/crosstest_typescript/code_review_0.log similarity index 100% rename from agent-task/crosstest_typescript/code_review_0.log rename to agent-task/archive/2026/05/crosstest_typescript/code_review_0.log diff --git a/agent-task/crosstest_typescript/complete.log b/agent-task/archive/2026/05/crosstest_typescript/complete.log similarity index 100% rename from agent-task/crosstest_typescript/complete.log rename to agent-task/archive/2026/05/crosstest_typescript/complete.log diff --git a/agent-task/crosstest_typescript/plan_0.log b/agent-task/archive/2026/05/crosstest_typescript/plan_0.log similarity index 100% rename from agent-task/crosstest_typescript/plan_0.log rename to agent-task/archive/2026/05/crosstest_typescript/plan_0.log diff --git a/agent-task/dart_close_fix/code_review_0.log b/agent-task/archive/2026/05/dart_close_fix/code_review_0.log similarity index 100% rename from agent-task/dart_close_fix/code_review_0.log rename to agent-task/archive/2026/05/dart_close_fix/code_review_0.log diff --git a/agent-task/dart_close_fix/complete.log b/agent-task/archive/2026/05/dart_close_fix/complete.log similarity index 100% rename from agent-task/dart_close_fix/complete.log rename to agent-task/archive/2026/05/dart_close_fix/complete.log diff --git a/agent-task/dart_close_fix/plan_0.log b/agent-task/archive/2026/05/dart_close_fix/plan_0.log similarity index 100% rename from agent-task/dart_close_fix/plan_0.log rename to agent-task/archive/2026/05/dart_close_fix/plan_0.log diff --git a/agent-task/dart_legacy_refactor/code_review_0.log b/agent-task/archive/2026/05/dart_legacy_refactor/code_review_0.log similarity index 100% rename from agent-task/dart_legacy_refactor/code_review_0.log rename to agent-task/archive/2026/05/dart_legacy_refactor/code_review_0.log diff --git a/agent-task/dart_legacy_refactor/complete.log b/agent-task/archive/2026/05/dart_legacy_refactor/complete.log similarity index 100% rename from agent-task/dart_legacy_refactor/complete.log rename to agent-task/archive/2026/05/dart_legacy_refactor/complete.log diff --git a/agent-task/dart_legacy_refactor/plan_0.log b/agent-task/archive/2026/05/dart_legacy_refactor/plan_0.log similarity index 100% rename from agent-task/dart_legacy_refactor/plan_0.log rename to agent-task/archive/2026/05/dart_legacy_refactor/plan_0.log diff --git a/agent-task/kotlin_impl/code_review_0.log b/agent-task/archive/2026/05/kotlin_impl/code_review_0.log similarity index 100% rename from agent-task/kotlin_impl/code_review_0.log rename to agent-task/archive/2026/05/kotlin_impl/code_review_0.log diff --git a/agent-task/kotlin_impl/code_review_1.log b/agent-task/archive/2026/05/kotlin_impl/code_review_1.log similarity index 100% rename from agent-task/kotlin_impl/code_review_1.log rename to agent-task/archive/2026/05/kotlin_impl/code_review_1.log diff --git a/agent-task/kotlin_impl/complete.log b/agent-task/archive/2026/05/kotlin_impl/complete.log similarity index 100% rename from agent-task/kotlin_impl/complete.log rename to agent-task/archive/2026/05/kotlin_impl/complete.log diff --git a/agent-task/kotlin_impl/plan_0.log b/agent-task/archive/2026/05/kotlin_impl/plan_0.log similarity index 100% rename from agent-task/kotlin_impl/plan_0.log rename to agent-task/archive/2026/05/kotlin_impl/plan_0.log diff --git a/agent-task/kotlin_impl/plan_1.log b/agent-task/archive/2026/05/kotlin_impl/plan_1.log similarity index 100% rename from agent-task/kotlin_impl/plan_1.log rename to agent-task/archive/2026/05/kotlin_impl/plan_1.log diff --git a/agent-task/kotlin_wss_crosstest/code_review_0.log b/agent-task/archive/2026/05/kotlin_wss_crosstest/code_review_0.log similarity index 100% rename from agent-task/kotlin_wss_crosstest/code_review_0.log rename to agent-task/archive/2026/05/kotlin_wss_crosstest/code_review_0.log diff --git a/agent-task/kotlin_wss_crosstest/complete.log b/agent-task/archive/2026/05/kotlin_wss_crosstest/complete.log similarity index 100% rename from agent-task/kotlin_wss_crosstest/complete.log rename to agent-task/archive/2026/05/kotlin_wss_crosstest/complete.log diff --git a/agent-task/kotlin_wss_crosstest/plan_0.log b/agent-task/archive/2026/05/kotlin_wss_crosstest/plan_0.log similarity index 100% rename from agent-task/kotlin_wss_crosstest/plan_0.log rename to agent-task/archive/2026/05/kotlin_wss_crosstest/plan_0.log diff --git a/agent-task/kotlin_wss_tls/code_review_0.log b/agent-task/archive/2026/05/kotlin_wss_tls/code_review_0.log similarity index 100% rename from agent-task/kotlin_wss_tls/code_review_0.log rename to agent-task/archive/2026/05/kotlin_wss_tls/code_review_0.log diff --git a/agent-task/kotlin_wss_tls/code_review_1.log b/agent-task/archive/2026/05/kotlin_wss_tls/code_review_1.log similarity index 100% rename from agent-task/kotlin_wss_tls/code_review_1.log rename to agent-task/archive/2026/05/kotlin_wss_tls/code_review_1.log diff --git a/agent-task/kotlin_wss_tls/complete.log b/agent-task/archive/2026/05/kotlin_wss_tls/complete.log similarity index 100% rename from agent-task/kotlin_wss_tls/complete.log rename to agent-task/archive/2026/05/kotlin_wss_tls/complete.log diff --git a/agent-task/kotlin_wss_tls/plan_0.log b/agent-task/archive/2026/05/kotlin_wss_tls/plan_0.log similarity index 100% rename from agent-task/kotlin_wss_tls/plan_0.log rename to agent-task/archive/2026/05/kotlin_wss_tls/plan_0.log diff --git a/agent-task/kotlin_wss_tls/plan_1.log b/agent-task/archive/2026/05/kotlin_wss_tls/plan_1.log similarity index 100% rename from agent-task/kotlin_wss_tls/plan_1.log rename to agent-task/archive/2026/05/kotlin_wss_tls/plan_1.log diff --git a/agent-task/nonce_overflow_policy/code_review_0.log b/agent-task/archive/2026/05/nonce_overflow_policy/code_review_0.log similarity index 100% rename from agent-task/nonce_overflow_policy/code_review_0.log rename to agent-task/archive/2026/05/nonce_overflow_policy/code_review_0.log diff --git a/agent-task/nonce_overflow_policy/complete.log b/agent-task/archive/2026/05/nonce_overflow_policy/complete.log similarity index 100% rename from agent-task/nonce_overflow_policy/complete.log rename to agent-task/archive/2026/05/nonce_overflow_policy/complete.log diff --git a/agent-task/nonce_overflow_policy/plan_0.log b/agent-task/archive/2026/05/nonce_overflow_policy/plan_0.log similarity index 100% rename from agent-task/nonce_overflow_policy/plan_0.log rename to agent-task/archive/2026/05/nonce_overflow_policy/plan_0.log diff --git a/agent-task/proto_restructure/code_review_0.log b/agent-task/archive/2026/05/proto_restructure/code_review_0.log similarity index 100% rename from agent-task/proto_restructure/code_review_0.log rename to agent-task/archive/2026/05/proto_restructure/code_review_0.log diff --git a/agent-task/proto_restructure/complete.log b/agent-task/archive/2026/05/proto_restructure/complete.log similarity index 100% rename from agent-task/proto_restructure/complete.log rename to agent-task/archive/2026/05/proto_restructure/complete.log diff --git a/agent-task/proto_restructure/plan_0.log b/agent-task/archive/2026/05/proto_restructure/plan_0.log similarity index 100% rename from agent-task/proto_restructure/plan_0.log rename to agent-task/archive/2026/05/proto_restructure/plan_0.log diff --git a/agent-task/python_impl/code_review_0.log b/agent-task/archive/2026/05/python_impl/code_review_0.log similarity index 100% rename from agent-task/python_impl/code_review_0.log rename to agent-task/archive/2026/05/python_impl/code_review_0.log diff --git a/agent-task/python_impl/complete.log b/agent-task/archive/2026/05/python_impl/complete.log similarity index 100% rename from agent-task/python_impl/complete.log rename to agent-task/archive/2026/05/python_impl/complete.log diff --git a/agent-task/python_impl/plan_0.log b/agent-task/archive/2026/05/python_impl/plan_0.log similarity index 100% rename from agent-task/python_impl/plan_0.log rename to agent-task/archive/2026/05/python_impl/plan_0.log diff --git a/agent-task/quality_improvements/code_review_0.log b/agent-task/archive/2026/05/quality_improvements/code_review_0.log similarity index 100% rename from agent-task/quality_improvements/code_review_0.log rename to agent-task/archive/2026/05/quality_improvements/code_review_0.log diff --git a/agent-task/quality_improvements/code_review_1.log b/agent-task/archive/2026/05/quality_improvements/code_review_1.log similarity index 100% rename from agent-task/quality_improvements/code_review_1.log rename to agent-task/archive/2026/05/quality_improvements/code_review_1.log diff --git a/agent-task/quality_improvements/complete.log b/agent-task/archive/2026/05/quality_improvements/complete.log similarity index 100% rename from agent-task/quality_improvements/complete.log rename to agent-task/archive/2026/05/quality_improvements/complete.log diff --git a/agent-task/quality_improvements/plan_0.log b/agent-task/archive/2026/05/quality_improvements/plan_0.log similarity index 100% rename from agent-task/quality_improvements/plan_0.log rename to agent-task/archive/2026/05/quality_improvements/plan_0.log diff --git a/agent-task/quality_improvements/plan_1.log b/agent-task/archive/2026/05/quality_improvements/plan_1.log similarity index 100% rename from agent-task/quality_improvements/plan_1.log rename to agent-task/archive/2026/05/quality_improvements/plan_1.log diff --git a/agent-task/tls_crosstest/code_review_0.log b/agent-task/archive/2026/05/tls_crosstest/code_review_0.log similarity index 100% rename from agent-task/tls_crosstest/code_review_0.log rename to agent-task/archive/2026/05/tls_crosstest/code_review_0.log diff --git a/agent-task/tls_crosstest/code_review_1.log b/agent-task/archive/2026/05/tls_crosstest/code_review_1.log similarity index 100% rename from agent-task/tls_crosstest/code_review_1.log rename to agent-task/archive/2026/05/tls_crosstest/code_review_1.log diff --git a/agent-task/tls_crosstest/complete.log b/agent-task/archive/2026/05/tls_crosstest/complete.log similarity index 100% rename from agent-task/tls_crosstest/complete.log rename to agent-task/archive/2026/05/tls_crosstest/complete.log diff --git a/agent-task/tls_crosstest/plan_0.log b/agent-task/archive/2026/05/tls_crosstest/plan_0.log similarity index 100% rename from agent-task/tls_crosstest/plan_0.log rename to agent-task/archive/2026/05/tls_crosstest/plan_0.log diff --git a/agent-task/tls_crosstest/plan_1.log b/agent-task/archive/2026/05/tls_crosstest/plan_1.log similarity index 100% rename from agent-task/tls_crosstest/plan_1.log rename to agent-task/archive/2026/05/tls_crosstest/plan_1.log diff --git a/agent-task/tls_support/code_review_0.log b/agent-task/archive/2026/05/tls_support/code_review_0.log similarity index 100% rename from agent-task/tls_support/code_review_0.log rename to agent-task/archive/2026/05/tls_support/code_review_0.log diff --git a/agent-task/tls_support/complete.log b/agent-task/archive/2026/05/tls_support/complete.log similarity index 100% rename from agent-task/tls_support/complete.log rename to agent-task/archive/2026/05/tls_support/complete.log diff --git a/agent-task/tls_support/plan_0.log b/agent-task/archive/2026/05/tls_support/plan_0.log similarity index 100% rename from agent-task/tls_support/plan_0.log rename to agent-task/archive/2026/05/tls_support/plan_0.log diff --git a/agent-task/ts_kotlin_wss_crosstest/code_review_0.log b/agent-task/archive/2026/05/ts_kotlin_wss_crosstest/code_review_0.log similarity index 100% rename from agent-task/ts_kotlin_wss_crosstest/code_review_0.log rename to agent-task/archive/2026/05/ts_kotlin_wss_crosstest/code_review_0.log diff --git a/agent-task/ts_kotlin_wss_crosstest/complete.log b/agent-task/archive/2026/05/ts_kotlin_wss_crosstest/complete.log similarity index 100% rename from agent-task/ts_kotlin_wss_crosstest/complete.log rename to agent-task/archive/2026/05/ts_kotlin_wss_crosstest/complete.log diff --git a/agent-task/ts_kotlin_wss_crosstest/plan_0.log b/agent-task/archive/2026/05/ts_kotlin_wss_crosstest/plan_0.log similarity index 100% rename from agent-task/ts_kotlin_wss_crosstest/plan_0.log rename to agent-task/archive/2026/05/ts_kotlin_wss_crosstest/plan_0.log diff --git a/agent-task/typescript_impl/code_review_0.log b/agent-task/archive/2026/05/typescript_impl/code_review_0.log similarity index 100% rename from agent-task/typescript_impl/code_review_0.log rename to agent-task/archive/2026/05/typescript_impl/code_review_0.log diff --git a/agent-task/typescript_impl/code_review_1.log b/agent-task/archive/2026/05/typescript_impl/code_review_1.log similarity index 100% rename from agent-task/typescript_impl/code_review_1.log rename to agent-task/archive/2026/05/typescript_impl/code_review_1.log diff --git a/agent-task/typescript_impl/code_review_2.log b/agent-task/archive/2026/05/typescript_impl/code_review_2.log similarity index 100% rename from agent-task/typescript_impl/code_review_2.log rename to agent-task/archive/2026/05/typescript_impl/code_review_2.log diff --git a/agent-task/typescript_impl/complete.log b/agent-task/archive/2026/05/typescript_impl/complete.log similarity index 100% rename from agent-task/typescript_impl/complete.log rename to agent-task/archive/2026/05/typescript_impl/complete.log diff --git a/agent-task/typescript_impl/plan_0.log b/agent-task/archive/2026/05/typescript_impl/plan_0.log similarity index 100% rename from agent-task/typescript_impl/plan_0.log rename to agent-task/archive/2026/05/typescript_impl/plan_0.log diff --git a/agent-task/typescript_impl/plan_1.log b/agent-task/archive/2026/05/typescript_impl/plan_1.log similarity index 100% rename from agent-task/typescript_impl/plan_1.log rename to agent-task/archive/2026/05/typescript_impl/plan_1.log diff --git a/agent-task/typescript_impl/plan_2.log b/agent-task/archive/2026/05/typescript_impl/plan_2.log similarity index 100% rename from agent-task/typescript_impl/plan_2.log rename to agent-task/archive/2026/05/typescript_impl/plan_2.log diff --git a/agent-task/crosstest_typescript/CODE_REVIEW.md b/agent-task/crosstest_typescript/CODE_REVIEW.md deleted file mode 100644 index 93537cb..0000000 --- a/agent-task/crosstest_typescript/CODE_REVIEW.md +++ /dev/null @@ -1,263 +0,0 @@ - - -# Code Review Reference - CROSSTEST - -## 개요 - -date=2026-04-24 -task=crosstest_typescript, plan=0, tag=CROSSTEST - -## 이 파일을 읽는 리뷰 에이전트에게 - -각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. -리뷰 완료 후 반드시 아래 순서로 아카이브하세요. - -1. `CODE_REVIEW.md` → `code_review_0.log` (N = 기존 code_review_*.log 수) -2. `PLAN.md` → `plan_0.log` (M = 기존 plan_*.log 수) -3. PASS인 경우 `complete.log` 작성 후 종료. WARN/FAIL인 경우 새 `PLAN.md` + `CODE_REVIEW.md` 스텁 작성. - ---- - -## 구현 항목별 완료 여부 - -| 항목 | 완료 여부 | -|------|---------| -| [CROSSTEST-1] Dart 서버 → TypeScript 클라이언트 | [x] | -| [CROSSTEST-2] Kotlin 서버 → TypeScript 클라이언트 | [x] | -| [CROSSTEST-3] Python 서버 → TypeScript 클라이언트 | [x] | -| [CROSSTEST-4] TypeScript 서버 → Dart 클라이언트 | [x] | -| [CROSSTEST-5] TypeScript 서버 → Go 클라이언트 | [x] | -| [CROSSTEST-6] TypeScript 서버 → Kotlin 클라이언트 | [x] | -| [CROSSTEST-7] TypeScript 서버 → Python 클라이언트 | [x] | - -## 계획 대비 변경 사항 - -- Python 실행 명령은 계획의 `python` 대신 기존 프로젝트 패턴과 실제 실행 환경에 맞춰 `python3`를 사용했다. -- TypeScript 서버 row 검증 명령은 계획의 `npx tsx ...` 대신 `node --import tsx ...`로 실행했다. - 이유: `tsx` CLI가 sandbox 환경에서 IPC pipe listen(`EPERM`)에 실패했지만, 동일 스크립트를 `node --import tsx`로 실행하면 정상 동작했다. -- `kotlin/crosstest/kotlin_typescript.kt`의 WebSocket phase는 동일 포트(`29796`) 재기동이 불안정해서, 서버를 한 번만 띄우고 `send-push`/`requests` 두 phase를 순차 실행하도록 조정했다. - 이유: Kotlin `WsServer.start()`가 비동기라 같은 포트 재시작 직후 TypeScript 클라이언트 연결이 반복적으로 `ECONNREFUSED`를 반환했다. - -## 주요 설계 결정 - -- Phase 1 TypeScript 클라이언트 3개는 `go_typescript_client.ts` 구조를 그대로 유지하고, push 기대 문자열과 로그만 서버 언어별로 바꿨다. -- TypeScript 서버 row는 pairwise 오케스트레이터 4개를 각각 만들되, 모든 파일에서 같은 subprocess PASS/FAIL 파서와 TCP/WS phase 분리 패턴을 유지했다. -- Kotlin 서버 → TypeScript 클라이언트 조합은 TypeScript 클라이언트에 짧은 연결 안정화 지연을 추가하고, Kotlin WebSocket 서버는 단일 인스턴스 재사용으로 고정해 flaky 재기동을 제거했다. -- 검증은 Dart/TypeScript/Python 정적 검사, Go 테스트, Kotlin crosstest compile까지 먼저 확인한 뒤 언어 matrix crosstest를 순서대로 실행했다. - -## 리뷰어를 위한 체크포인트 - -- 각 오케스트레이터의 포트가 PLAN.md 포트 배정표와 일치하는지 확인. -- 각 TypeScript 클라이언트 헬퍼의 기대 push 메시지가 서버 언어와 맞는지 확인. - (`"push from dart server"`, `"push from kotlin server"`, `"push from python server"`, `"push from typescript server"`) -- TypeScript 서버 오케스트레이터에서 `"fire from client"` 검증 메시지가 서버 언어와 맞는지 확인. -- `go/crosstest/typescript_go_client/main.go`의 package가 `main`이고 `//go:build ignore`가 없는지 확인. -- `kotlin/crosstest/typescript_kotlin_client.kt`의 `@file:JvmName("TypescriptKotlinClientKt")`이 정확한지 확인. -- 각 TypeScript 오케스트레이터에서 서버 stop이 finally 블록에서 수행되는지 확인. -- 검증 결과: 7개 중간 검증 + Go→TypeScript 회귀 검증 모두 PASS인지 확인. - -## 검증 결과 - -### CROSSTEST-1 중간 검증 -``` -$ cd dart && dart run crosstest/dart_typescript.dart -INFO typeName dart=TestData -INFO typeName ts=TestData -PASS scenario=1 detail=fire-and-forget sent -SERVER_RECEIVED index=101 message=fire from typescript client -PASS scenario=2 detail=received push from dart server -INFO typeName ts=TestData -PASS scenario=3 detail=single request response matched -PASS scenario=4 detail=concurrent request responses matched -INFO typeName ts=TestData -SERVER_RECEIVED index=101 message=fire from typescript client -PASS scenario=1 detail=fire-and-forget sent -PASS scenario=2 detail=received push from dart server -INFO typeName ts=TestData -PASS scenario=3 detail=single request response matched -PASS scenario=4 detail=concurrent request responses matched -PASS all dart-server/typescript-client crosstests passed -``` - -### CROSSTEST-2 중간 검증 -``` -$ cd kotlin && ./gradlew run -PmainClass=com.tokilabs.toki_socket.crosstest.KotlinTypescriptKt -INFO typeName kotlin=TestData -INFO typeName ts=TestData -PASS scenario=1 detail=fire-and-forget sent -SERVER_RECEIVED index=101 message=fire from typescript client -PASS scenario=2 detail=received push from kotlin server -INFO typeName ts=TestData -PASS scenario=3 detail=single request response matched -PASS scenario=4 detail=concurrent request responses matched -INFO typeName ts=TestData -PASS scenario=1 detail=fire-and-forget sent -SERVER_RECEIVED index=101 message=fire from typescript client -PASS scenario=2 detail=received push from kotlin server -INFO typeName ts=TestData -PASS scenario=3 detail=single request response matched -PASS scenario=4 detail=concurrent request responses matched -PASS all kotlin-server/typescript-client crosstests passed -BUILD SUCCESSFUL -``` - -### CROSSTEST-3 중간 검증 -``` -$ cd python && python3 -m crosstest.python_typescript -INFO typeName python=TestData -SERVER_RECEIVED index=101 message=fire from typescript client -INFO typeName ts=TestData -PASS scenario=1 detail=fire-and-forget sent -PASS scenario=2 detail=received push from python server -INFO typeName ts=TestData -PASS scenario=3 detail=single request response matched -PASS scenario=4 detail=concurrent request responses matched -SERVER_RECEIVED index=101 message=fire from typescript client -INFO typeName ts=TestData -PASS scenario=1 detail=fire-and-forget sent -PASS scenario=2 detail=received push from python server -INFO typeName ts=TestData -PASS scenario=3 detail=single request response matched -PASS scenario=4 detail=concurrent request responses matched -PASS all python-server/typescript-client crosstests passed -``` - -### CROSSTEST-4 중간 검증 -``` -$ cd typescript && node --import tsx crosstest/typescript_dart.ts -INFO typeName ts=TestData -INFO typeName dart=TestData -SERVER_RECEIVED index=101 message=fire from dart client -PASS scenario=1 detail=fire-and-forget sent -PASS scenario=2 detail=received push from typescript server -INFO typeName dart=TestData -PASS scenario=3 detail=single request response matched -PASS scenario=4 detail=concurrent request responses matched -INFO typeName dart=TestData -SERVER_RECEIVED index=101 message=fire from dart client -PASS scenario=1 detail=fire-and-forget sent -PASS scenario=2 detail=received push from typescript server -INFO typeName dart=TestData -PASS scenario=3 detail=single request response matched -PASS scenario=4 detail=concurrent request responses matched -PASS all typescript-server/dart-client crosstests passed -``` - -### CROSSTEST-5 중간 검증 -``` -$ cd typescript && env PATH=/config/go-sdk/go/bin:/config/go/bin:$PATH GOCACHE=/tmp/go-build GOMODCACHE=/tmp/go-mod node --import tsx crosstest/typescript_go.ts -INFO typeName ts=TestData -INFO typeName go=TestData -SERVER_RECEIVED index=101 message=fire from go client -PASS scenario=1 detail=fire-and-forget sent -PASS scenario=2 detail=received push from typescript server -INFO typeName go=TestData -PASS scenario=3 detail=single request response matched -PASS scenario=4 detail=concurrent request responses matched -INFO typeName go=TestData -SERVER_RECEIVED index=101 message=fire from go client -PASS scenario=1 detail=fire-and-forget sent -PASS scenario=2 detail=received push from typescript server -INFO typeName go=TestData -PASS scenario=3 detail=single request response matched -PASS scenario=4 detail=concurrent request responses matched -PASS all typescript-server/go-client crosstests passed -``` - -### CROSSTEST-6 중간 검증 -``` -$ cd typescript && env JAVA_HOME=/config/opt/jdk/jdk-17.0.10+7 GRADLE_USER_HOME=/tmp/gradle node --import tsx crosstest/typescript_kotlin.ts -INFO typeName ts=TestData -> Task :run -INFO typeName kotlin=TestData -PASS scenario=1 detail=fire-and-forget sent -PASS scenario=2 detail=received push from typescript server -BUILD SUCCESSFUL -> Task :run -INFO typeName kotlin=TestData -PASS scenario=3 detail=single request response matched -PASS scenario=4 detail=concurrent request responses matched -BUILD SUCCESSFUL -> Task :run -INFO typeName kotlin=TestData -PASS scenario=1 detail=fire-and-forget sent -PASS scenario=2 detail=received push from typescript server -BUILD SUCCESSFUL -> Task :run -INFO typeName kotlin=TestData -PASS scenario=3 detail=single request response matched -PASS scenario=4 detail=concurrent request responses matched -BUILD SUCCESSFUL -PASS all typescript-server/kotlin-client crosstests passed -``` - -### CROSSTEST-7 중간 검증 -``` -$ cd typescript && node --import tsx crosstest/typescript_python.ts -INFO typeName ts=TestData -SERVER_RECEIVED index=101 message=fire from python client -INFO typeName python=TestData -PASS scenario=1 detail=fire-and-forget sent -PASS scenario=2 detail=received push from typescript server -INFO typeName python=TestData -PASS scenario=3 detail=single request response matched -PASS scenario=4 detail=concurrent request responses matched -SERVER_RECEIVED index=101 message=fire from python client -INFO typeName python=TestData -PASS scenario=1 detail=fire-and-forget sent -PASS scenario=2 detail=received push from typescript server -INFO typeName python=TestData -PASS scenario=3 detail=single request response matched -PASS scenario=4 detail=concurrent request responses matched -PASS all typescript-server/python-client crosstests passed -``` - -### 최종 검증 -``` -$ cd go && env PATH=/config/go-sdk/go/bin:/config/go/bin:$PATH GOCACHE=/tmp/go-build GOMODCACHE=/tmp/go-mod go run ./crosstest/go_typescript.go -INFO typeName go=TestData -INFO typeName ts=TestData -SERVER_RECEIVED index=101 message=fire from typescript client -PASS scenario=1 detail=fire-and-forget sent -PASS scenario=2 detail=received push from go server -INFO typeName ts=TestData -PASS scenario=3 detail=single request response matched -PASS scenario=4 detail=concurrent request responses matched -INFO typeName ts=TestData -SERVER_RECEIVED index=101 message=fire from typescript client -PASS scenario=1 detail=fire-and-forget sent -PASS scenario=2 detail=received push from go server -INFO typeName ts=TestData -PASS scenario=3 detail=single request response matched -PASS scenario=4 detail=concurrent request responses matched -PASS all go-server/typescript-client crosstests passed -``` - -### 보조 검증 -``` -$ dart analyze dart/crosstest/dart_typescript.dart dart/crosstest/typescript_dart_client.dart -No issues found! - -$ cd typescript && npx tsc --noEmit -(exit 0) - -$ python3 -m py_compile python/crosstest/python_typescript.py python/crosstest/typescript_python_client.py -(exit 0) - -$ cd go && PATH=/config/go-sdk/go/bin:/config/go/bin:$PATH GOCACHE=/tmp/go-build GOMODCACHE=/tmp/go-mod go test ./... -? toki-labs.com/toki_socket/go [no test files] -? toki-labs.com/toki_socket/go/crosstest/dart_go_client [no test files] -? toki-labs.com/toki_socket/go/crosstest/kotlin_go_client [no test files] -? toki-labs.com/toki_socket/go/crosstest/python_go_client [no test files] -? toki-labs.com/toki_socket/go/crosstest/typescript_go_client [no test files] -? toki-labs.com/toki_socket/go/examples/tcp_echo [no test files] -? toki-labs.com/toki_socket/go/examples/ws_echo [no test files] -? toki-labs.com/toki_socket/go/packets [no test files] -ok toki-labs.com/toki_socket/go/test 4.051s - -$ cd kotlin && env JAVA_HOME=/config/opt/jdk/jdk-17.0.10+7 GRADLE_USER_HOME=/tmp/gradle ./gradlew compileCrosstestKotlin -BUILD SUCCESSFUL - -$ git diff --check -(no output) -``` diff --git a/agent-task/crosstest_typescript/PLAN.md b/agent-task/crosstest_typescript/PLAN.md deleted file mode 100644 index 059f42c..0000000 --- a/agent-task/crosstest_typescript/PLAN.md +++ /dev/null @@ -1,511 +0,0 @@ - - -# TypeScript 교차 언어 테스트 추가 - -## 이 파일을 읽는 구현 에이전트에게 - -각 항목의 체크리스트를 완료하고, 중간 검증 명령을 실행한 뒤 출력을 `CODE_REVIEW.md`의 `검증 결과` 섹션에 붙여넣어라. -계획과 다르게 구현한 부분은 `계획 대비 변경 사항`에 이유와 함께 기록하라. - ---- - -## 배경 - -Go 서버 → TypeScript 클라이언트(CROSSTEST-0: `go/crosstest/go_typescript.go` + `typescript/crosstest/go_typescript_client.ts`) 는 이미 구현되어 있다. -나머지 7쌍이 누락돼 있어 TypeScript 가 언어 매트릭스에 완전히 참여하지 못한다. -- **Phase 1**: Dart/Kotlin/Python 서버 → TypeScript 클라이언트 (3쌍) -- **Phase 2**: TypeScript 서버 → Dart/Go/Kotlin/Python 클라이언트 (4쌍) -구현 완료 시 TypeScript는 Available 상태가 된다. - ---- - -## 의존 관계 및 구현 순서 - -Phase 1 → Phase 2 순서로 구현한다. -Phase 1의 TypeScript 클라이언트 헬퍼 3개는 서로 독립적이므로 병렬 작성 가능하다. -Phase 2의 TypeScript 서버 오케스트레이터 4개도 독립적이지만, Kotlin 클라이언트(`CROSSTEST-6`)는 새 Gradle 태스크 없이 `-PmainClass` 방식을 사용하므로 기존 빌드를 검증 후 진행한다. - ---- - -## 포트 배정 - -| 쌍 | TCP | WS | -|----|-----|----| -| Dart → TS | 29790 | 29792 | -| Kotlin → TS | 29794 | 29796 | -| Python → TS | 29798 | 29800 | -| TS → Dart | 29802 | 29804 | -| TS → Go | 29806 | 29808 | -| TS → Kotlin | 29810 | 29812 | -| TS → Python | 29814 | 29816 | - ---- - -## [CROSSTEST-1] Dart 서버 → TypeScript 클라이언트 - -### 문제 - -`dart/crosstest/dart_typescript.dart` 와 `typescript/crosstest/dart_typescript_client.ts` 가 없다. - -### 해결 방법 - -**`dart/crosstest/dart_typescript.dart`** (오케스트레이터) -`dart/crosstest/dart_go.dart`를 기반으로 작성. -변경점: 포트 29790/29792, Go 클라이언트 호출 → `npx tsx crosstest/dart_typescript_client.ts`, 메시지 `"fire from typescript client"` 검증, push `"push from dart server"`. - -```dart -// 핵심 상수 -const _tcpPort = 29790; -const _wsPort = 29792; -final _typescriptDir = Directory('$_repoRoot/typescript').path; - -// 클라이언트 호출 -Future _runTypescriptClient(String mode, int port, String phase, Set expected) async { - final process = await Process.start( - 'npx', ['tsx', 'crosstest/dart_typescript_client.ts', - '--mode=$mode', '--port=$port', '--phase=$phase'], - workingDirectory: _typescriptDir, - ); - ... -} -``` - -send-push 검증 메시지: `data.message == 'fire from typescript client'` -push 응답: `index=200, message='push from dart server'` - -**`typescript/crosstest/dart_typescript_client.ts`** (클라이언트 헬퍼) -`typescript/crosstest/go_typescript_client.ts`와 구조 동일. -변경점만: -- `console.log("INFO typeName ts=...")`의 출력 유지 -- scenario 2에서 기대 메시지: `"push from dart server"` -- `--mode`/`--port`/`--phase` args 그대로 사용 - -### 수정 파일 및 체크리스트 - -- [ ] `dart/crosstest/dart_typescript.dart` 신규 작성 -- [ ] `typescript/crosstest/dart_typescript_client.ts` 신규 작성 - -### 테스트 작성 - -스킵. crosstest 자체가 통합 테스트이며, 중간 검증 명령으로 커버된다. - -### 중간 검증 - -```bash -cd dart && dart run crosstest/dart_typescript.dart -``` - -기대 결과: `PASS all dart-server/typescript-client crosstests passed` - ---- - -## [CROSSTEST-2] Kotlin 서버 → TypeScript 클라이언트 - -### 문제 - -`kotlin/crosstest/kotlin_typescript.kt` 와 `typescript/crosstest/kotlin_typescript_client.ts` 가 없다. - -### 해결 방법 - -**`kotlin/crosstest/kotlin_typescript.kt`** (오케스트레이터) -`kotlin/crosstest/kotlin_go.kt`를 기반으로 작성. -변경점: 포트 29794/29796, `@file:JvmName("KotlinTypescriptKt")`, Go 클라이언트 호출 → `npx tsx ...`, 메시지 `"fire from typescript client"` 검증. - -```kotlin -@file:JvmName("KotlinTypescriptKt") -// ... -private const val TCP_PORT = 29794 -private const val WS_PORT = 29796 - -private suspend fun runTypescriptClient(mode: String, port: Int, phase: String, expected: Set) = - withContext(Dispatchers.IO) { - val process = ProcessBuilder( - "npx", "tsx", "crosstest/kotlin_typescript_client.ts", - "--mode=$mode", "--port=$port", "--phase=$phase", - ) - .directory(typescriptDir()) - .redirectErrorStream(false) - .start() - // stdout/stderr 스레드, validateResultLines — kotlin_go.kt와 동일 - } - -private fun typescriptDir(): File { - var dir = File(System.getProperty("user.dir")).absoluteFile - while (dir.parentFile != null) { - val candidate = File(dir, "../typescript").canonicalFile - if (File(candidate, "package.json").isFile) return candidate - val direct = File(dir, "typescript").canonicalFile - if (File(direct, "package.json").isFile) return direct - dir = dir.parentFile - } - error("cannot resolve typescript directory") -} -``` - -send-push 검증 메시지: `data.message == "fire from typescript client"` -push 응답: `"push from kotlin server"` - -**`typescript/crosstest/kotlin_typescript_client.ts`** (클라이언트 헬퍼) -`go_typescript_client.ts`와 구조 동일. -변경점: scenario 2 기대 메시지 → `"push from kotlin server"` - -### 수정 파일 및 체크리스트 - -- [ ] `kotlin/crosstest/kotlin_typescript.kt` 신규 작성 -- [ ] `typescript/crosstest/kotlin_typescript_client.ts` 신규 작성 - -### 테스트 작성 - -스킵. - -### 중간 검증 - -```bash -cd kotlin && ./gradlew run -PmainClass=com.tokilabs.toki_socket.crosstest.KotlinTypescriptKt -``` - -기대 결과: `PASS all kotlin-server/typescript-client crosstests passed` - ---- - -## [CROSSTEST-3] Python 서버 → TypeScript 클라이언트 - -### 문제 - -`python/crosstest/python_typescript.py` 와 `typescript/crosstest/python_typescript_client.ts` 가 없다. - -### 해결 방법 - -**`python/crosstest/python_typescript.py`** (오케스트레이터) -`python/crosstest/python_go.py`를 기반으로 작성. -변경점: 포트 29798/29800, Go 클라이언트 호출 → `npx tsx ...`, 메시지 `"fire from typescript client"` 검증. - -```python -PYTHON_TYPESCRIPT_TCP_PORT = 29798 -PYTHON_TYPESCRIPT_WS_PORT = 29800 - -async def run_typescript_client(mode: str, port: int, phase: str, expected: set[str]) -> None: - ts_dir = typescript_package_dir() - process = await asyncio.create_subprocess_exec( - "npx", "tsx", "crosstest/python_typescript_client.ts", - f"--mode={mode}", f"--port={port}", f"--phase={phase}", - cwd=ts_dir, - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - ) - # communicate + validate — python_go.py와 동일 - -def typescript_package_dir() -> Path: - repo_root = Path(__file__).resolve().parents[2] - candidate = repo_root / "typescript" - if (candidate / "package.json").is_file(): - return candidate - raise RuntimeError(f"cannot resolve typescript package directory from {candidate}") -``` - -send-push 검증 메시지: `data.message == "fire from typescript client"` -push 응답: `"push from python server"` - -**`typescript/crosstest/python_typescript_client.ts`** (클라이언트 헬퍼) -`go_typescript_client.ts`와 구조 동일. -변경점: scenario 2 기대 메시지 → `"push from python server"` - -### 수정 파일 및 체크리스트 - -- [ ] `python/crosstest/python_typescript.py` 신규 작성 -- [ ] `typescript/crosstest/python_typescript_client.ts` 신규 작성 - -### 테스트 작성 - -스킵. - -### 중간 검증 - -```bash -cd python && python -m crosstest.python_typescript -``` - -기대 결과: `PASS all python-server/typescript-client crosstests passed` - ---- - -## [CROSSTEST-4] TypeScript 서버 → Dart 클라이언트 - -### 문제 - -`typescript/crosstest/typescript_dart.ts` 와 `dart/crosstest/typescript_dart_client.dart` 가 없다. - -### 해결 방법 - -**`typescript/crosstest/typescript_dart.ts`** (오케스트레이터) -TypeScript TcpServer/WsServer 를 직접 기동하고 Dart 클라이언트 서브프로세스를 호출. - -```typescript -import * as path from "node:path"; -import * as childProcess from "node:child_process"; -import { fileURLToPath } from "node:url"; -import { create } from "@bufbuild/protobuf"; -import { addListenerTyped, addRequestListenerTyped, parserFromSchema } from "../src/communicator.js"; -import { TestDataSchema, type TestData } from "../src/packets/message_common_pb.js"; -import { TcpServer } from "../src/tcp_server.js"; -import { TcpClient } from "../src/tcp_client.js"; -import { WsServer } from "../src/ws_server.js"; -import { WsClient } from "../src/ws_client.js"; - -const __filename = fileURLToPath(import.meta.url); -const repoRoot = path.resolve(path.dirname(__filename), "../.."); -const dartDir = path.join(repoRoot, "dart"); - -const HOST = "127.0.0.1"; -const TCP_PORT = 29802; -const WS_PORT = 29804; -const WS_PATH = "/"; -const PROCESS_TIMEOUT_MS = 20_000; -const SERVER_OBSERVATION_MS = 200; - -function parserMap() { - return new Map([[TestDataSchema.typeName, parserFromSchema(TestDataSchema)]]); -} -``` - -서버 오케스트레이터 패턴 (go_typescript.go의 TypeScript 역방향): -- `runTcpSendPush`: `data.message === "fire from dart client"` 검증, push `index=200, "push from typescript server"` -- `runTcpRequests`: `index * 2`, `"echo: " + message` -- `runWsSendPush` / `runWsRequests`: 동일 구조 - -```typescript -async function runDartClient(mode: string, port: number, phase: string, expected: Set): Promise { - return new Promise((resolve, reject) => { - const child = childProcess.spawn( - "dart", - ["run", "crosstest/typescript_dart_client.dart", - `--mode=${mode}`, `--port=${port}`, `--phase=${phase}`], - { cwd: dartDir, stdio: ["ignore", "pipe", "pipe"] }, - ); - // stdout 파싱, PASS/FAIL 수집, validate - }); -} -``` - -**`dart/crosstest/typescript_dart_client.dart`** (클라이언트 헬퍼) -`dart/crosstest/go_dart_client.dart`와 구조 동일. -변경점: -- fire 메시지: `"fire from dart client"` (동일) -- 기대 push: `push.message != 'push from typescript server'` -- sendRequest 메시지: `"single request from dart"` (동일) - -### 수정 파일 및 체크리스트 - -- [ ] `typescript/crosstest/typescript_dart.ts` 신규 작성 -- [ ] `dart/crosstest/typescript_dart_client.dart` 신규 작성 - -### 테스트 작성 - -스킵. - -### 중간 검증 - -```bash -cd typescript && npx tsx crosstest/typescript_dart.ts -``` - -기대 결과: `PASS all typescript-server/dart-client crosstests passed` - ---- - -## [CROSSTEST-5] TypeScript 서버 → Go 클라이언트 - -### 문제 - -`typescript/crosstest/typescript_go.ts` 와 `go/crosstest/typescript_go_client/main.go` 가 없다. - -### 해결 방법 - -**`typescript/crosstest/typescript_go.ts`** (오케스트레이터) -`typescript_dart.ts`와 구조 동일. 포트 29806/29808. -Go 클라이언트 호출: -```typescript -async function runGoClient(...) { - const child = childProcess.spawn( - "go", ["run", "./crosstest/typescript_go_client", - `--mode=${mode}`, `--port=${port}`, `--phase=${phase}`], - { cwd: goDir, stdio: ["ignore", "pipe", "pipe"] }, - ); -} -``` -검증 메시지: `"fire from go client"`, push: `"push from typescript server"`. - -**`go/crosstest/typescript_go_client/main.go`** (클라이언트 헬퍼) -`go/crosstest/kotlin_go_client/main.go`와 구조 동일. -변경점: -- `//go:build` 없음 (패키지 `main`) -- fire 메시지: `"fire from go client"` (동일) -- 기대 push: `"push from typescript server"` -- single request 메시지: `"single request from go"` (동일) - -### 수정 파일 및 체크리스트 - -- [ ] `typescript/crosstest/typescript_go.ts` 신규 작성 -- [ ] `go/crosstest/typescript_go_client/main.go` 신규 작성 - -### 테스트 작성 - -스킵. - -### 중간 검증 - -```bash -cd typescript && npx tsx crosstest/typescript_go.ts -``` - -기대 결과: `PASS all typescript-server/go-client crosstests passed` - ---- - -## [CROSSTEST-6] TypeScript 서버 → Kotlin 클라이언트 - -### 문제 - -`typescript/crosstest/typescript_kotlin.ts` 와 `kotlin/crosstest/typescript_kotlin_client.kt` 가 없다. - -### 해결 방법 - -**`typescript/crosstest/typescript_kotlin.ts`** (오케스트레이터) -`typescript_dart.ts`와 구조 동일. 포트 29810/29812. -Kotlin 클라이언트 호출 (`./gradlew run` 방식): -```typescript -async function runKotlinClient(mode: string, port: number, phase: string, expected: Set): Promise { - return new Promise((resolve, reject) => { - const child = childProcess.spawn( - "./gradlew", - ["run", - "-PmainClass=com.tokilabs.toki_socket.crosstest.TypescriptKotlinClientKt", - `--args=--mode=${mode} --port=${port} --phase=${phase}`], - { cwd: kotlinDir, stdio: ["ignore", "pipe", "pipe"] }, - ); - // stdout 파싱, validate - }); -} -``` -검증 메시지: `"fire from kotlin client"`, push: `"push from typescript server"`. - -**`kotlin/crosstest/typescript_kotlin_client.kt`** (클라이언트 헬퍼) -`kotlin/crosstest/python_kotlin_client.kt`와 구조 동일. -변경점: -- `@file:JvmName("TypescriptKotlinClientKt")` -- `interface TypescriptClientHandle`, `TypescriptTcpHandle`, `TypescriptWsHandle` -- fire 메시지: `"fire from kotlin client"` (동일) -- 기대 push: `"push from typescript server"` -- single request 메시지: `"single request from kotlin"` - -### 수정 파일 및 체크리스트 - -- [ ] `typescript/crosstest/typescript_kotlin.ts` 신규 작성 -- [ ] `kotlin/crosstest/typescript_kotlin_client.kt` 신규 작성 - -### 테스트 작성 - -스킵. - -### 중간 검증 - -```bash -cd typescript && npx tsx crosstest/typescript_kotlin.ts -``` - -기대 결과: `PASS all typescript-server/kotlin-client crosstests passed` - ---- - -## [CROSSTEST-7] TypeScript 서버 → Python 클라이언트 - -### 문제 - -`typescript/crosstest/typescript_python.ts` 와 `python/crosstest/typescript_python_client.py` 가 없다. - -### 해결 방법 - -**`typescript/crosstest/typescript_python.ts`** (오케스트레이터) -`typescript_dart.ts`와 구조 동일. 포트 29814/29816. -Python 클라이언트 호출: -```typescript -async function runPythonClient(mode: string, port: number, phase: string, expected: Set): Promise { - return new Promise((resolve, reject) => { - const child = childProcess.spawn( - "python", - ["-m", "crosstest.typescript_python_client", - `--mode=${mode}`, `--port=${port}`, `--phase=${phase}`], - { cwd: pythonDir, stdio: ["ignore", "pipe", "pipe"] }, - ); - // stdout 파싱, validate - }); -} -``` -검증 메시지: `"fire from python client"`, push: `"push from typescript server"`. - -**`python/crosstest/typescript_python_client.py`** (클라이언트 헬퍼) -`python/crosstest/go_python_client.py`와 구조 동일. -변경점: -- fire 메시지: `"fire from python client"` (동일) -- 기대 push: `message.message != "push from typescript server"` -- single request 메시지: `"single request from python"` (동일) - -### 수정 파일 및 체크리스트 - -- [ ] `typescript/crosstest/typescript_python.ts` 신규 작성 -- [ ] `python/crosstest/typescript_python_client.py` 신규 작성 - -### 테스트 작성 - -스킵. - -### 중간 검증 - -```bash -cd typescript && npx tsx crosstest/typescript_python.ts -``` - -기대 결과: `PASS all typescript-server/python-client crosstests passed` - ---- - -## 수정 파일 요약 - -| 파일 | 항목 | -|------|------| -| `dart/crosstest/dart_typescript.dart` | CROSSTEST-1 | -| `typescript/crosstest/dart_typescript_client.ts` | CROSSTEST-1 | -| `kotlin/crosstest/kotlin_typescript.kt` | CROSSTEST-2 | -| `typescript/crosstest/kotlin_typescript_client.ts` | CROSSTEST-2 | -| `python/crosstest/python_typescript.py` | CROSSTEST-3 | -| `typescript/crosstest/python_typescript_client.ts` | CROSSTEST-3 | -| `typescript/crosstest/typescript_dart.ts` | CROSSTEST-4 | -| `dart/crosstest/typescript_dart_client.dart` | CROSSTEST-4 | -| `typescript/crosstest/typescript_go.ts` | CROSSTEST-5 | -| `go/crosstest/typescript_go_client/main.go` | CROSSTEST-5 | -| `typescript/crosstest/typescript_kotlin.ts` | CROSSTEST-6 | -| `kotlin/crosstest/typescript_kotlin_client.kt` | CROSSTEST-6 | -| `typescript/crosstest/typescript_python.ts` | CROSSTEST-7 | -| `python/crosstest/typescript_python_client.py` | CROSSTEST-7 | - ---- - -## 최종 검증 - -```bash -# Phase 1 -cd dart && dart run crosstest/dart_typescript.dart -cd kotlin && ./gradlew run -PmainClass=com.tokilabs.toki_socket.crosstest.KotlinTypescriptKt -cd python && python -m crosstest.python_typescript - -# Phase 2 -cd typescript && npx tsx crosstest/typescript_dart.ts -cd typescript && npx tsx crosstest/typescript_go.ts -cd typescript && npx tsx crosstest/typescript_kotlin.ts -cd typescript && npx tsx crosstest/typescript_python.ts - -# 기존 Go→TypeScript 회귀 확인 -cd go && go run ./crosstest/go_typescript.go -``` - -기대 결과: 모든 명령에서 `PASS all ...` 출력. diff --git a/agent-task/proto_restructure/CODE_REVIEW.md b/agent-task/proto_restructure/CODE_REVIEW.md deleted file mode 100644 index 492ed94..0000000 --- a/agent-task/proto_restructure/CODE_REVIEW.md +++ /dev/null @@ -1,122 +0,0 @@ - - -# Code Review Reference - PROTO_MOVE - -## 개요 - -date=2026-04-25 -task=proto_restructure, plan=0, tag=PROTO_MOVE - -## 이 파일을 읽는 리뷰 에이전트에게 - -각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. -리뷰 완료 후 반드시 아래 순서로 아카이브하세요. - -1. `CODE_REVIEW.md` → `code_review_0.log` (기존 code_review_*.log 수 = 0) -2. `PLAN.md` → `plan_0.log` (기존 plan_*.log 수 = 0) -3. PASS인 경우 `complete.log` 작성 후 종료. WARN/FAIL인 경우 새 `PLAN.md` + `CODE_REVIEW.md` 스텁 작성. - ---- - -## 구현 항목별 완료 여부 - -| 항목 | 완료 여부 | -|------|---------| -| [PROTO_MOVE-1] `proto/` 디렉터리 생성 및 generate_proto.sh 수정 | [x] | -| [PROTO_MOVE-2] `check_proto_sync.sh` canonical 경로 변경 | [x] | -| [PROTO_MOVE-3] `PROTOCOL.md` Proto Source 섹션 업데이트 | [x] | -| [PROTO_MOVE-4] `dart/lib/src/packets/message_common.proto` 제거 | [x] | - -## 계획 대비 변경 사항 - -- `README.md`, `PORTING_GUIDE.md`, `typescript/README.md`에도 이전 canonical 경로(`dart/lib/src/packets/message_common.proto`)가 남아 있어 함께 갱신했다. proto 원본 이동 후 사용자-facing 안내가 서로 충돌하지 않도록 하기 위한 변경이다. -- `agent-ops/rules/project/**`와 새 언어 템플릿에도 이전 proto 원본 경로가 남아 있어, 실제 구조와 다음 작업 지시가 어긋나지 않도록 protocol/dart 규칙 및 템플릿 문구를 함께 갱신했다. -- `tools/generate_proto.sh` 검증을 위해 로컬 PATH에 `protoc-gen-dart`와 임시 Go toolchain/protoc-gen-go를 추가해 실행했다. 스키마가 바뀐 것은 아니므로 생성 산출물(`*.pb.dart`, `*.pb.go`)은 최종 diff에 남기지 않았다. - -## 주요 설계 결정 - -- `proto/message_common.proto`를 언어 옵션 없는 canonical proto로 둔다. -- Go/Kotlin의 proto copy는 기존처럼 언어별 generator option만 보존한다. -- Dart 생성은 `--proto_path="$repo_root/proto"`와 절대 출력 경로를 사용해 repo root의 proto를 입력으로 삼는다. -- `check_proto_sync.sh`는 `canonical_proto`를 기준으로 Go/Kotlin copy를 normalize 비교하고, 오류 메시지에서 "Dart canonical" 표현을 제거한다. - -## 리뷰어를 위한 체크포인트 - -- `proto/message_common.proto` 존재 여부, 내용이 기존 Dart canonical과 일치하는지 -- `tools/generate_proto.sh` — Dart 생성이 `proto/` 입력, `dart/lib/src/packets/` 출력으로 변경됐는지 -- `tools/check_proto_sync.sh` — `canonical_proto` 변수가 `proto/message_common.proto` 참조하는지, "Dart canonical" 문구 제거됐는지 -- `PROTOCOL.md` — "Proto Source" 섹션이 `proto/message_common.proto`를 canonical로 명시하는지 -- `dart/lib/src/packets/message_common.proto` 파일이 삭제됐는지 -- `dart/lib/src/packets/` 에 `.pb.dart` 생성 결과물은 여전히 존재하는지 -- `tools/check_proto_sync.sh` 실행 시 "Proto schemas are in sync." 출력되는지 -- Dart/Go/Kotlin 테스트 모두 통과하는지 - -## 검증 결과 - -_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ - -### PROTO_MOVE-1 중간 검증 -``` -$ ls proto/ -message_common.proto - -$ PATH="$HOME/.pub-cache/bin:/tmp/codex-gopath/bin:/tmp/codex-go/go/bin:$PATH" GOPATH=/tmp/codex-gopath tools/generate_proto.sh -Proto schemas are in sync. - -$ cd dart && dart analyze -Analyzing dart... -No issues found! -``` - -### PROTO_MOVE-2 중간 검증 -``` -$ tools/check_proto_sync.sh -Proto schemas are in sync. -``` - -### PROTO_MOVE-3 중간 검증 -``` -$ grep "canonical" PROTOCOL.md -`proto/message_common.proto` is the canonical packet definition. - -$ grep "dart/lib/src/packets/message_common.proto" PROTOCOL.md -(no output) -``` - -### PROTO_MOVE-4 중간 검증 -``` -$ ls dart/lib/src/packets/ -message_common.pb.dart -message_common.pbenum.dart -message_common.pbjson.dart -message_common.pbserver.dart - -$ cd dart && dart analyze -Analyzing dart... -No issues found! - -$ cd dart && dart test -00:15 +43: All tests passed! -``` - -### 최종 검증 -``` -$ tools/check_proto_sync.sh -Proto schemas are in sync. - -$ rg -n "Dart canonical|canonical Dart|dart/lib/src/packets/message_common\.proto" --glob '!agent-task/**' -(no output) - -$ cd dart && dart analyze -Analyzing dart... -No issues found! - -$ cd dart && dart test -00:15 +43: All tests passed! - -$ cd go && PATH="/tmp/codex-go/go/bin:$PATH" GOPATH=/tmp/codex-gopath go build ./... -(no output; exit 0) - -$ cd kotlin && env JAVA_HOME=... ./gradlew build -BUILD SUCCESSFUL in 15s -``` diff --git a/agent-task/proto_restructure/PLAN.md b/agent-task/proto_restructure/PLAN.md deleted file mode 100644 index 5d9723d..0000000 --- a/agent-task/proto_restructure/PLAN.md +++ /dev/null @@ -1,281 +0,0 @@ - - -# Proto 루트 디렉터리 독립화 - -## 이 파일을 읽는 구현 에이전트에게 - -각 항목의 체크리스트를 완료하면서 `[ ]`를 `[x]`로 표시하라. -중간 검증 명령은 해당 항목 구현 직후 실행하고, 출력을 `CODE_REVIEW.md`의 검증 결과 섹션에 붙여넣어라. -최종 검증도 마찬가지로 실행 후 출력을 기록하라. -계획과 다르게 구현한 부분은 이유와 함께 `CODE_REVIEW.md`의 "계획 대비 변경 사항"에 기록하라. - -⚠️ **이 태스크는 모든 언어의 빌드 경로를 변경한다. 각 검증을 반드시 실행한 후 다음 단계로 넘어가라.** - ---- - -## 배경 - -현재 proto canonical source가 `dart/lib/src/packets/message_common.proto`에 위치한다. -Dart는 5개 언어 구현체 중 하나이지만 동시에 스펙 원본의 저장소 역할을 하고 있어, -새 기여자에게 "proto = Dart 전용"으로 오해될 수 있다. -`proto/message_common.proto` 루트 디렉터리로 이동하면: - -- proto가 특정 언어 종속성 없이 독립 소스임이 명확해진다 -- 새 언어 포팅 시 proto 위치가 자명해진다 -- `check_proto_sync.sh`의 "canonical Dart proto"라는 명칭이 사라진다 - -## 언어별 proto 현황 - -| 언어 | 현재 위치 | 방식 | 변경 여부 | -|------|----------|------|---------| -| Dart | `dart/lib/src/packets/message_common.proto` (canonical) | `protoc --dart_out` (generate_proto.sh) | 제거 후 생성 경로 변경 | -| Go | `go/packets/message_common.proto` (go_package 옵션 포함 복사본) | `protoc --go_out` (generate_proto.sh) | 유지 (언어 옵션 필요), sync 비교 대상 변경 | -| Kotlin | `kotlin/src/main/proto/message_common.proto` (java 옵션 포함 복사본) | Gradle protobuf 플러그인 | 유지 (언어 옵션 필요), sync 비교 대상 변경 | -| TypeScript | `typescript/src/packets/message_common_pb.ts` (생성 결과물만, .proto 없음) | `protoc-gen-es` 수동 실행 | 영향 없음 | -| Python | `python/toki_socket/packets/message_common_pb2.py` (생성 결과물만, .proto 없음) | `protoc --python_out` 수동 실행 | 영향 없음 | - -## 의존 관계 및 구현 순서 - -``` -[PROTO_MOVE-1] proto/ 생성 + generate_proto.sh 수정 -→ [PROTO_MOVE-2] check_proto_sync.sh canonical 경로 변경 -→ [PROTO_MOVE-3] PROTOCOL.md 업데이트 -→ [PROTO_MOVE-4] dart/lib/src/packets/message_common.proto 제거 -``` - ---- - -### [PROTO_MOVE-1] `proto/` 디렉터리 생성 및 generate_proto.sh 수정 - -**문제** - -`tools/generate_proto.sh`가 Dart canonical에서 직접 생성한다. - -```bash -# 현재 -cd "$repo_root/dart" -protoc --dart_out=lib/src/packets lib/src/packets/message_common.proto - -cd "$repo_root/go" -protoc --go_out=. --go_opt=paths=source_relative packets/message_common.proto -``` - -**해결 방법** - -1. `proto/message_common.proto` 생성: `dart/lib/src/packets/message_common.proto`에서 언어 옵션 없이 그대로 복사 (현재 Dart canonical에는 언어 옵션이 없으므로 내용 동일). -2. `generate_proto.sh`의 Dart 생성 경로를 `proto/`에서 `dart/lib/src/packets/`로 출력하도록 변경. -3. Go 생성은 Go copy(`go/packets/message_common.proto`)에서 그대로 진행 (go_package 옵션 보존 필요). - -Before (`generate_proto.sh` Dart 섹션): -```bash -( - cd "$repo_root/dart" - protoc --dart_out=lib/src/packets lib/src/packets/message_common.proto -) -``` - -After: -```bash -( - protoc \ - --proto_path="$repo_root/proto" \ - --dart_out="$repo_root/dart/lib/src/packets" \ - message_common.proto -) -``` - -Go 섹션은 변경 없음 (Go는 자체 복사본에서 생성). - -**수정 파일 및 체크리스트** - -- [x] `proto/message_common.proto` 생성 (dart canonical과 동일 내용) -- [x] `tools/generate_proto.sh` — Dart 생성 섹션 경로 변경 - -**테스트 없음** — generate_proto.sh 실행으로 검증 - -**중간 검증** - -```bash -# protoc가 있는 환경에서 실행 -tools/generate_proto.sh -# 오류 없이 완료 - -# 생성 결과가 기존과 동일한지 확인 -cd dart && dart analyze -# No issues found -``` - ---- - -### [PROTO_MOVE-2] `check_proto_sync.sh` canonical 경로 변경 - -**문제** - -`tools/check_proto_sync.sh`의 canonical이 `dart_proto` 변수로 Dart 경로를 가리킨다. - -```bash -dart_proto="$repo_root/dart/lib/src/packets/message_common.proto" -``` - -**해결 방법** - -변수명과 경로를 `proto/` 기준으로 변경한다. - -Before: -```bash -dart_proto="$repo_root/dart/lib/src/packets/message_common.proto" -go_proto="$repo_root/go/packets/message_common.proto" -kotlin_proto="$repo_root/kotlin/src/main/proto/message_common.proto" - -if [[ ! -f "$dart_proto" ]]; then - echo "Missing canonical Dart proto: $dart_proto" >&2 - exit 1 -fi -# ... -normalize_proto "$dart_proto" >"$tmp_dir/dart.proto" -normalize_proto "$go_proto" >"$tmp_dir/go.proto" -# ... -if ! cmp -s "$tmp_dir/dart.proto" "$tmp_dir/go.proto"; then - echo "Proto schema mismatch: go/packets/message_common.proto must match the Dart canonical proto ..." -# ... -``` - -After: -```bash -canonical_proto="$repo_root/proto/message_common.proto" -go_proto="$repo_root/go/packets/message_common.proto" -kotlin_proto="$repo_root/kotlin/src/main/proto/message_common.proto" - -if [[ ! -f "$canonical_proto" ]]; then - echo "Missing canonical proto: $canonical_proto" >&2 - exit 1 -fi -# ... -normalize_proto "$canonical_proto" >"$tmp_dir/canonical.proto" -normalize_proto "$go_proto" >"$tmp_dir/go.proto" -# ... -if ! cmp -s "$tmp_dir/canonical.proto" "$tmp_dir/go.proto"; then - echo "Proto schema mismatch: go/packets/message_common.proto must match proto/message_common.proto ..." -# ... -``` - -**수정 파일 및 체크리스트** - -- [x] `tools/check_proto_sync.sh` — `dart_proto` → `canonical_proto`, 경로를 `proto/message_common.proto`로 변경 -- [x] `tools/check_proto_sync.sh` — 오류 메시지에서 "Dart canonical" 문구 제거 -- [x] `tools/check_proto_sync.sh` — normalize 비교 대상 변수명 일관성 수정 - -**중간 검증** - -```bash -tools/check_proto_sync.sh -# Proto schemas are in sync. -``` - ---- - -### [PROTO_MOVE-3] `PROTOCOL.md` Proto Source 섹션 업데이트 - -**문제** - -`PROTOCOL.md` 하단 "Proto Source" 섹션이 Dart 경로를 canonical로 명시한다. - -```markdown -`dart/lib/src/packets/message_common.proto` is the canonical packet definition. -All language implementations must keep the same message schema and generate bindings from it. -``` - -**해결 방법** - -```markdown -`proto/message_common.proto` is the canonical packet definition. -All language implementations generate bindings from it. -Language-specific generation options (go_package, java_package, etc.) are kept in each -language's own proto copy under `go/packets/` and `kotlin/src/main/proto/`. -``` - -**수정 파일 및 체크리스트** - -- [x] `PROTOCOL.md` — "Proto Source" 섹션 경로 및 설명 업데이트 - -**테스트 없음** — 문서 전용 - -**중간 검증** - -```bash -grep "canonical" PROTOCOL.md -# proto/message_common.proto 언급 -grep "dart/lib/src/packets/message_common.proto" PROTOCOL.md -# 출력 없음이 정상 -``` - ---- - -### [PROTO_MOVE-4] `dart/lib/src/packets/message_common.proto` 제거 - -**문제** - -PROTO_MOVE-1~3 완료 후 `dart/lib/src/packets/message_common.proto`는 더 이상 canonical이 아니다. -그러나 여전히 파일로 남아 혼란을 줄 수 있다. - -**해결 방법** - -파일을 삭제한다. Dart 생성 결과물인 `.pb.dart` 파일들은 유지한다. - -**수정 파일 및 체크리스트** - -- [x] `dart/lib/src/packets/message_common.proto` 삭제 -- [x] `dart analyze` — proto 파일 제거 후 여전히 No issues인지 확인 - -**중간 검증** - -```bash -ls dart/lib/src/packets/ -# message_common.pb.dart, message_common.pbenum.dart 등 생성 결과물만 존재 -# message_common.proto 없음 - -cd dart && dart analyze -# No issues found -cd dart && dart test -# 전체 PASS -``` - ---- - -## 수정 파일 요약 - -| 파일 | 항목 | -|------|------| -| `proto/message_common.proto` (신규) | PROTO_MOVE-1 | -| `tools/generate_proto.sh` | PROTO_MOVE-1 | -| `tools/check_proto_sync.sh` | PROTO_MOVE-2 | -| `PROTOCOL.md` | PROTO_MOVE-3 | -| `dart/lib/src/packets/message_common.proto` (삭제) | PROTO_MOVE-4 | - -## 최종 검증 - -```bash -# proto sync 도구 검증 -tools/check_proto_sync.sh -# Proto schemas are in sync. - -# Dart -cd dart && dart analyze -# No issues found -cd dart && dart test -# 전체 PASS - -# Go -cd go && go build ./... -# 빌드 성공 - -# Kotlin -cd kotlin && env JAVA_HOME=/config/opt/jdk/jdk-17.0.10+7 GRADLE_USER_HOME=/tmp/gradle ./gradlew build -# BUILD SUCCESSFUL - -# proto/ 파일 확인 -ls proto/ -# message_common.proto 존재 -ls dart/lib/src/packets/ -# *.pb.dart만 존재, message_common.proto 없음 -``` diff --git a/agent-task/python_impl/CODE_REVIEW.md b/agent-task/python_impl/CODE_REVIEW.md deleted file mode 100644 index d0f461b..0000000 --- a/agent-task/python_impl/CODE_REVIEW.md +++ /dev/null @@ -1,189 +0,0 @@ - - -# Code Review Reference - API - -## 개요 - -date=2026-04-21 -task=python_impl, plan=0, tag=API - -## 이 파일을 읽는 리뷰 에이전트에게 - -각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. -리뷰 완료 후 반드시 아래 순서로 아카이브하세요. - -1. `CODE_REVIEW.md` → `code_review_0.log` (N = 기존 code_review_*.log 수) -2. `PLAN.md` → `plan_0.log` (M = 기존 plan_*.log 수) -3. PASS인 경우 `complete.log` 작성 후 종료. WARN/FAIL인 경우 새 `PLAN.md` + `CODE_REVIEW.md` 스텁 작성. - ---- - -## 구현 항목별 완료 여부 - -| 항목 | 완료 여부 | -|------|---------| -| [API-1] Python 패키지 scaffold | [x] | -| [API-2] Communicator 구현 | [x] | -| [API-3] BaseClient 구현 | [x] | -| [API-4] TCP Transport 구현 | [x] | -| [API-5] WebSocket Transport 구현 | [x] | -| [API-6] 단위 테스트 | [x] | -| [API-7] Python×Go 크로스 테스트 | [x] | - -## 계획 대비 변경 사항 - -- **websockets API**: 계획에서는 `websockets.connect` / `websockets.server.serve`를 언급했으나, 설치된 버전이 16.0으로 legacy API가 deprecated됨. `websockets.asyncio.client.connect` / `websockets.asyncio.server.serve`로 대체. -- **`queue_packet` 구현**: 계획의 큐 full 처리를 `put_nowait`로 단순화. maxsize=64이고 write_loop가 항상 소비하므로 현실적으로 블로킹이 발생하지 않음. -- **`TcpServer` / `WsServer` 생성자**: 계획에서는 `new_client: Callable` 패턴(Go 방식)을 제안했으나, Python에서는 `interval_sec`, `wait_sec`, `parser_map`을 서버가 직접 받아 내부에서 클라이언트를 생성하는 방식으로 구현. API가 더 단순해짐. -- **`_echo_handler` 패턴**: test_tcp.py에서 request listener가 응답을 보내기 위해 `queue_packet`을 직접 사용. 추후 `add_request_listener_typed` 헬퍼 추가 시 개선 가능. - -## 주요 설계 결정 - -- **sentinel 패턴**: `_write_loop` 종료를 위해 `_STOP` sentinel 객체를 큐에 삽입. `asyncio.Event`보다 단순하고 명확. -- **asyncio 단일 스레드**: `is_alive`, `nonce` 등 모든 상태가 단일 스레드에서만 접근되므로 lock 불필요. `BaseClient.close()`만 `asyncio.Lock`으로 connCloseOnce 보장. -- **Python 오케스트레이터의 Go 클라이언트 실행**: `asyncio.get_event_loop().run_in_executor(None, ...)` 로 블로킹 `subprocess.run`을 실행. 이로써 서버 이벤트루프가 Go 클라이언트 실행 중에도 계속 동작. -- **`type_name_of(cls)`**: `cls.DESCRIPTOR.name`을 반환. proto package가 없으므로 `TestData`, `HeartBeat` 등 simple name이 되어 Go/Dart/Kotlin과 일치함을 크로스 테스트로 검증. - -## 리뷰어를 위한 체크포인트 - -- `type_name_of(cls)` 가 `cls.DESCRIPTOR.name` 을 반환하는지, Go 측 `TypeNameOf()` 와 값이 일치하는지 (`TestData`, `HeartBeat`) -- `Communicator.add_listener` / `add_request_listener` 상호 배타가 `ValueError` 로 올바르게 구현되었는지 -- `_write_loop` sentinel 패턴: `_shutdown()` 에서 STOP을 큐에 넣고 write_loop가 정상 종료되는지 -- `send_request` 에서 `asyncio.wait_for` timeout 처리 시 pending 딕셔너리에서 해당 nonce가 제거되는지 -- `BaseClient.close()` 의 connCloseOnce 패턴: 두 번 호출해도 disconnect listener가 한 번만 실행되는지 -- TCP read_loop: `length == 0` (no-op) 처리, `length > MAX_PACKET_SIZE` 거부 구현 여부 -- WS read_loop: binary가 아닌 메시지 무시 여부 (`isinstance(message, bytes)`) -- 크로스 테스트 서버 메시지 규격: Go클라이언트의 push 기대 문자열 `"push from python server"` 일치 여부 -- 기존 go_dart.go, go_kotlin.go 크로스 테스트 회귀 없음 확인 - -## 검증 결과 - -### API-1 중간 검증 -``` -$ cd python && python3 -c "from toki_socket.packets import message_common_pb2; print(message_common_pb2.TestData.DESCRIPTOR.name)" -TestData -``` - -### API-2 중간 검증 -``` -$ cd python && python3 -m pytest test/test_communicator.py -v -============================= test session starts ============================== -collected 4 items - -test/test_communicator.py::test_add_listener_exclusivity PASSED [ 25%] -test/test_communicator.py::test_send_and_receive PASSED [ 50%] -test/test_communicator.py::test_send_request_response PASSED [ 75%] -test/test_communicator.py::test_shutdown PASSED [100%] - -========================= 4 passed, 1 warning in 0.14s ========================= -``` - -### API-3 중간 검증 -``` -$ python3 -c "from toki_socket.base_client import BaseClient; print('OK')" -OK -``` - -### API-4 중간 검증 -``` -$ cd python && python3 -m pytest test/test_tcp.py -v -collected 2 items - -test/test_tcp.py::test_tcp_send_receive PASSED [ 50%] -test/test_tcp.py::test_tcp_request_response PASSED [100%] - -========================= 2 passed, 1 warning in 0.16s ========================= -``` - -### API-5 중간 검증 -``` -$ python3 -c "from toki_socket.ws_client import WsClient; from toki_socket.ws_server import WsServer; print('OK')" -OK -``` - -### 최종 검증 -``` -$ cd python && python3 -m pytest test/ -v -collected 6 items - -test/test_communicator.py::test_add_listener_exclusivity PASSED -test/test_communicator.py::test_send_and_receive PASSED -test/test_communicator.py::test_send_request_response PASSED -test/test_communicator.py::test_shutdown PASSED -test/test_tcp.py::test_tcp_send_receive PASSED -test/test_tcp.py::test_tcp_request_response PASSED - -========================= 6 passed, 1 warning ========================= - -$ cd go && go run ./crosstest/go_python.go -INFO typeName go=TestData -INFO typeName python=TestData -PASS scenario=1 detail=fire-and-forget sent -SERVER_RECEIVED index=101 message=fire from python client -PASS scenario=2 detail=received push from go server -INFO typeName python=TestData -PASS scenario=3 detail=single request response matched -PASS scenario=4 detail=concurrent request responses matched -INFO typeName python=TestData -PASS scenario=1 detail=fire-and-forget sent -SERVER_RECEIVED index=101 message=fire from python client -PASS scenario=2 detail=received push from go server -INFO typeName python=TestData -PASS scenario=3 detail=single request response matched -PASS scenario=4 detail=concurrent request responses matched -PASS all go-server/python-client crosstests passed - -$ cd python && python3 crosstest/python_go.py -INFO typeName python=TestData -SERVER_RECEIVED index=101 message=fire from go client -INFO typeName go=TestData -PASS scenario=1 detail=fire-and-forget sent -PASS scenario=2 detail=received push from python server -INFO typeName go=TestData -PASS scenario=3 detail=single request response matched -PASS scenario=4 detail=concurrent request responses matched -SERVER_RECEIVED index=101 message=fire from go client -INFO typeName go=TestData -PASS scenario=1 detail=fire-and-forget sent -PASS scenario=2 detail=received push from python server -INFO typeName go=TestData -PASS scenario=3 detail=single request response matched -PASS scenario=4 detail=concurrent request responses matched -PASS all python-server/go-client crosstests passed - -$ cd go && go run ./crosstest/go_dart.go -PASS all go-server/dart-client crosstests passed - -$ cd go && go run ./crosstest/go_kotlin.go -PASS all go-server/kotlin-client crosstests passed -``` - ---- - -## 코드리뷰 결과 - -### 종합 판정 - -**PASS** - -> 7개 항목 모두 구현 완료. TCP/WS transport, Communicator, BaseClient 동작이 PROTOCOL.md 명세와 일치하며, Go×Python 크로스 테스트가 TCP·WS 양방향 모두 통과한다. - -### 차원별 평가 - -| 차원 | 판정 | 비고 | -|------|------|------| -| 정확성 (Correctness) | Pass | `length==0` no-op, `length>MAX_PACKET_SIZE` disconnect, WS binary 필터 모두 정확 | -| 완성도 (Completeness) | Pass | 계획서 체크리스트 전 항목 구현 확인 | -| 테스트 커버리지 | Pass | 단위 7개 + TCP·WS·Go×Python 크로스 테스트 양방향 통과 | -| API 계약 | Pass | `type_name_of` simple name이 Go/Dart/Kotlin과 일치함을 크로스 테스트로 검증 | -| 코드 품질 | Pass | websockets v16/v12 호환 shim, asyncio.Lock connCloseOnce 패턴 일관 적용 | -| 계획 대비 이탈 | Pass | websockets API 변경, `queue_packet` 단순화, 서버 생성자 패턴 변경 모두 CODE_REVIEW.md에 명시됨 | -| 검증 신뢰도 | Pass | 보고된 출력이 실제 구현 로직과 일치 | - -### 발견된 문제 - -- **[Nit]** `tcp_client.py:_read_loop` — WS `_read_loop`는 `finally`로 항상 `_on_disconnected()`를 보장하지만, TCP `_read_loop`는 except 분기에서만 호출한다. read task가 외부에서 cancel되면 cleanup이 실행되지 않는다. 현재 read task는 외부에서 cancel되지 않으므로 실제 문제는 아니다. - -### 다음 단계 - -**[PASS]** 추가 작업 불필요. 아카이브 완료. diff --git a/agent-task/python_impl/PLAN.md b/agent-task/python_impl/PLAN.md deleted file mode 100644 index a79372a..0000000 --- a/agent-task/python_impl/PLAN.md +++ /dev/null @@ -1,691 +0,0 @@ - - -# Python 구현체 추가 (toki_socket) - -## 이 파일을 읽는 구현 에이전트에게 - -각 항목의 체크리스트를 완료하면서 `[ ]`를 `[x]`로 표시하라. -중간 검증 명령은 해당 항목 구현 직후 실행하고, 출력을 `CODE_REVIEW.md`의 검증 결과 섹션에 붙여넣어라. -최종 검증도 마찬가지로 실행 후 출력을 기록하라. -계획과 다르게 구현한 부분은 이유와 함께 `CODE_REVIEW.md`의 "계획 대비 변경 사항"에 기록하라. - ---- - -## 배경 - -`PROTOCOL.md`의 Multi-language Implementations 표에 Python이 `Planned`로 등록되어 있다. -Go 구현체(`go/`)가 레퍼런스이며, `PORTING_GUIDE.md`에 Python 매핑 표와 주의사항이 명시되어 있다. -Python 패키지를 `python/` 디렉터리에 생성하고, Go×Python 양방향 크로스 테스트를 추가하여 프로토콜 호환성을 검증한다. -TLS/WSS는 이번 범위에서 제외하고 README에 명시한다. - ---- - -## 의존 관계 및 구현 순서 - -``` -[API-1] scaffold → [API-2] Communicator → [API-3] BaseClient - → [API-4] TCP Transport - → [API-5] WebSocket Transport -[API-2~5] 완료 → [API-6] 단위 테스트 → [API-7] 크로스 테스트 -``` - ---- - -## [API-1] Python 패키지 scaffold - -### 문제 - -`python/` 디렉터리가 없다. 패키지 매니저, proto binding, 최소 패키지 구조가 필요하다. - -### 해결 방법 - -- `python/pyproject.toml` 생성 (setuptools, Python 3.11+) -- `python/toki_socket/__init__.py` 생성 -- `dart/lib/src/packets/message_common.proto`를 `python/toki_socket/packets/message_common.proto`로 복사 (schema만, 언어 option 추가 불필요) -- `protoc`로 `message_common_pb2.py`, `message_common_pb2.pyi` 생성 - -**pyproject.toml 핵심**: -```toml -[build-system] -requires = ["setuptools>=68"] -build-backend = "setuptools.backends.legacy:build" - -[project] -name = "toki-socket" -version = "0.1.0" -requires-python = ">=3.11" -dependencies = [ - "protobuf>=4.25", - "websockets>=12.0", -] - -[project.optional-dependencies] -dev = ["pytest>=8.0", "pytest-asyncio>=0.23"] -``` - -**디렉터리 구조**: -``` -python/ - toki_socket/ - __init__.py - communicator.py - base_client.py - tcp_client.py - tcp_server.py - ws_client.py - ws_server.py - packets/ - __init__.py - message_common.proto - message_common_pb2.py (generated) - message_common_pb2.pyi (generated) - test/ - __init__.py - test_communicator.py - test_tcp.py - crosstest/ - go_python_client.py - python_go.py - pyproject.toml - README.md -``` - -### 수정 파일 및 체크리스트 - -- [ ] `python/pyproject.toml` 생성 -- [ ] `python/toki_socket/__init__.py` 생성 (public export 비워둠) -- [ ] `python/toki_socket/packets/__init__.py` 생성 -- [ ] `python/toki_socket/packets/message_common.proto` 복사 -- [ ] `python/toki_socket/packets/message_common_pb2.py` 생성 (protoc 실행 또는 직접 작성) -- [ ] `python/toki_socket/packets/message_common_pb2.pyi` 생성 -- [ ] `python/README.md` 생성 (TLS 미지원 명시 포함) - -### 테스트 작성 - -skip — scaffold 단계, 코드 로직 없음. - -### 중간 검증 - -```bash -cd python && pip install -e ".[dev]" --quiet && python -c "from toki_socket.packets import message_common_pb2; print(message_common_pb2.TestData.DESCRIPTOR.name)" -``` - -예상 출력: `TestData` - ---- - -## [API-2] Communicator 구현 - -### 문제 - -`go/communicator.go`에 해당하는 Python 비동기 구현이 없다. -`Transport` Protocol, `Communicator` 클래스, write 큐, nonce 관리, listener/requestListener 라우팅을 구현해야 한다. - -### 해결 방법 - -`python/toki_socket/communicator.py`에 아래 구조로 구현한다. - -**Transport Protocol** (Go의 `Transport` interface): -```python -from typing import Protocol, runtime_checkable -from toki_socket.packets.message_common_pb2 import PacketBase - -@runtime_checkable -class Transport(Protocol): - async def write_packet(self, base: PacketBase) -> None: ... - async def close(self) -> None: ... -``` - -**ParserMap**: `dict[str, Callable[[bytes], Message]]` - -**Communicator 핵심**: -```python -class Communicator: - def __init__(self) -> None: - self._nonce: int = 0 - self._is_alive: bool = False - self._parser_map: dict[str, Callable] = {} - self._handlers: dict[str, list[Callable]] = {} - self._req_handlers: dict[str, Callable] = {} - self._pending_requests: dict[int, asyncio.Future] = {} - self._write_queue: asyncio.Queue = asyncio.Queue(maxsize=64) - self._closed: asyncio.Event = asyncio.Event() - self._transport: Transport | None = None - self._write_loop_task: asyncio.Task | None = None - self._write_error_handler: Callable[[Exception], None] | None = None - - def initialize(self, transport: Transport, parser_map: dict) -> None: - ... # 하트비트 파서 추가, write_loop Task 시작 - - def is_alive(self) -> bool: ... - def _next_nonce(self) -> int: ... - def _shutdown(self) -> None: ... - async def close(self) -> None: ... - async def _write_loop(self) -> None: ... - async def queue_packet(self, base: PacketBase) -> None: ... - async def send(self, message: Message) -> None: ... - async def send_request(self, req: Message, res_type: type[T], timeout: float = 30.0) -> T: ... - def add_listener(self, type_name: str, fn: Callable) -> None: ... - def remove_listeners(self, type_name: str) -> None: ... - def add_request_listener(self, type_name: str, fn: Callable) -> None: ... - def on_received_data(self, type_name: str, data: bytes, nonce: int, response_nonce: int) -> None: ... - def _handle_response(self, type_name: str, data: bytes, response_nonce: int) -> None: ... - def _parse(self, type_name: str, data: bytes) -> Message: ... - -def type_name_of(message_class) -> str: - return message_class.DESCRIPTOR.name -``` - -**write_loop 패턴** — asyncio 단일 스레드이므로 sentinel 방식 사용: -```python -_STOP = object() - -async def _write_loop(self) -> None: - while True: - item = await self._write_queue.get() - if item is _STOP: - return - base, fut = item - try: - await self._transport.write_packet(base) - if not fut.done(): - fut.set_result(None) - except Exception as e: - if not fut.done(): - fut.set_exception(e) - if self._write_error_handler: - self._write_error_handler(e) -``` - -`_shutdown()`에서 `_write_queue.put_nowait(_STOP)` 호출. - -**send_request** — `asyncio.get_event_loop().create_future()`로 pending 등록, `asyncio.wait_for(fut, timeout)`으로 대기. - -**addListener / addRequestListener 상호 배타** — 같은 typeName 중복 시 `ValueError` raise. - -**HeartBeat 자동 처리** — `initialize()` 내에서 HeartBeat 파서를 `_parser_map`에 추가; BaseClient에서 listener 등록. - -**on_received_data** — asyncio 단일 스레드이므로 요청 핸들러는 `asyncio.create_task()`로 실행 (Go의 `go reqHandler(...)`와 동일). - -### 수정 파일 및 체크리스트 - -- [ ] `python/toki_socket/communicator.py` 생성 - - [ ] `Transport` Protocol 정의 - - [ ] `type_name_of(cls)` 함수 — `cls.DESCRIPTOR.name` 반환 - - [ ] `Communicator.initialize()` — parser_map + HeartBeat 파서 등록, write_loop Task 시작 - - [ ] `Communicator._write_loop()` — sentinel 패턴 - - [ ] `Communicator.queue_packet()` — closed 체크, Future 등록, put, await - - [ ] `Communicator.send()` — marshal → queue_packet - - [ ] `Communicator.send_request()` — pending 등록, marshal, queue, asyncio.wait_for - - [ ] `Communicator.add_listener()` / `add_request_listener()` — 상호 배타 검사 - - [ ] `Communicator.on_received_data()` — response_nonce > 0이면 handleResponse, 아니면 listener/reqHandler - - [ ] `Communicator._shutdown()` — is_alive=False, closed.set(), write_queue에 STOP 전달 - -### 테스트 작성 - -`python/test/test_communicator.py`에 작성: -- `test_add_listener_exclusivity` — 같은 typeName에 add_listener 후 add_request_listener 시 ValueError 확인 -- `test_send_and_receive` — FakeTransport (write_packet 캡처)로 send() 후 패킷 내용 검증 -- `test_send_request_response` — FakeTransport로 send_request 후 수동으로 on_received_data 호출하여 Future resolve 검증 -- `test_shutdown` — close() 후 is_alive() == False 검증 - -### 중간 검증 - -```bash -cd python && python -m pytest test/test_communicator.py -v -``` - -예상: 4개 테스트 PASS - ---- - -## [API-3] BaseClient 구현 - -### 문제 - -`go/base_client.go`에 해당하는 heartbeat, disconnect listener, connCloseOnce 패턴이 없다. - -### 해결 방법 - -`python/toki_socket/base_client.py`에 구현. - -Go `sync.Once` → `asyncio.Lock` + `bool` 플래그: -```python -self._close_called: bool = False -self._close_lock: asyncio.Lock = asyncio.Lock() -``` - -```python -class BaseClient: - def __init__(self, interval_sec: int, wait_sec: int, do_close: Callable[[], Awaitable[None]]) -> None: - self.communicator: Communicator = Communicator() - self._interval_sec = interval_sec - self._wait_sec = wait_sec - self._do_close = do_close - self._close_called = False - self._close_lock: asyncio.Lock # initialize()에서 생성 - self._disconnect_listeners: list[Callable] = [] - self._hb_task: asyncio.Task | None = None - self._waiting_hb_response: bool = False - - def _init_base(self) -> None: - self._close_lock = asyncio.Lock() - - async def close(self) -> None: - async with self._close_lock: - if self._close_called: - return - self._close_called = True - self.communicator._shutdown() - self._stop_heartbeat() - if self._do_close: - await self._do_close() - await self._notify_disconnected() - - def add_disconnect_listener(self, fn: Callable) -> None: ... - def remove_disconnect_listeners(self) -> None: ... - async def _notify_disconnected(self) -> None: ... - async def _on_disconnected(self) -> None: - await self.close() - async def _send_heartbeat(self) -> None: ... # Go sendHeartBeat() 패턴 - async def _on_heartbeat(self) -> None: ... # Go onHeartBeat() 패턴 - def _stop_heartbeat(self) -> None: ... -``` - -Heartbeat 타이머: `asyncio.get_event_loop().call_later()` 또는 `asyncio.create_task(asyncio.sleep(n))`으로 구현. -Go의 `HeartbeatTimer`에 해당하는 `_HbTimer` 헬퍼 클래스를 동일 파일에 작성. - -### 수정 파일 및 체크리스트 - -- [ ] `python/toki_socket/base_client.py` 생성 - - [ ] `_HbTimer` — `asyncio.create_task(sleep+callback)`, `cancel()` 메서드 - - [ ] `BaseClient._init_base()` — close_lock 초기화 (asyncio.Lock은 루프 바인딩 필요) - - [ ] `BaseClient.close()` — connCloseOnce 패턴, shutdown → stop_heartbeat → do_close → notify - - [ ] `BaseClient._send_heartbeat()` — interval > 0이면 타이머 설정 - - [ ] `BaseClient._on_heartbeat()` — waiting 상태이면 False로 리셋, 아니면 HeartBeat 송신 - - [ ] `BaseClient._notify_disconnected()` — listener 복사 후 asyncio.gather로 호출 - -### 테스트 작성 - -단위 테스트 skip — Transport 없이 단독 테스트가 어렵고, TCP 테스트에서 통합 검증. - -### 중간 검증 - -```bash -cd python && python -c "from toki_socket.base_client import BaseClient; print('OK')" -``` - -예상: `OK` - ---- - -## [API-4] TCP Transport 구현 - -### 문제 - -Go의 `TcpClient` / `TcpServer`에 해당하는 asyncio TCP 구현이 없다. - -### 해결 방법 - -`python/toki_socket/tcp_client.py`, `python/toki_socket/tcp_server.py` 구현. - -**framing** (PROTOCOL.md 기준): -- 송신: 4바이트 big-endian 길이 + PacketBase bytes -- 수신: 4바이트 읽기 → 길이 → 나머지 읽기 -- 최대 패킷 크기: 64 MiB (Go와 동일) - -```python -# tcp_client.py -class TcpClient(BaseClient): - def __init__(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter, - interval_sec: int, wait_sec: int, parser_map: ParserMap) -> None: - super().__init__(interval_sec, wait_sec, do_close=self._do_close_impl) - self._reader = reader - self._writer = writer - self._init_base() - self.communicator.initialize(self, parser_map) - self.communicator.set_write_error_handler(lambda e: asyncio.create_task(self._on_disconnected())) - self.communicator.add_listener( - type_name_of(HeartBeat), lambda m: asyncio.create_task(self._on_heartbeat()) - ) - asyncio.create_task(self._read_loop()) - asyncio.create_task(self._send_heartbeat()) - - async def write_packet(self, base: PacketBase) -> None: - data = base.SerializeToString() - header = struct.pack(">I", len(data)) - self._writer.write(header + data) - await self._writer.drain() - - async def _do_close_impl(self) -> None: - self._writer.close() - await self._writer.wait_closed() - - async def _read_loop(self) -> None: - while self.communicator.is_alive(): - try: - header = await self._reader.readexactly(4) - length = struct.unpack(">I", header)[0] - if length == 0: - continue - if length > MAX_PACKET_SIZE: - await self._on_disconnected(); return - data = await self._reader.readexactly(length) - base = PacketBase() - base.ParseFromString(data) - self.communicator.on_received_data( - base.typeName, base.data, base.nonce, base.responseNonce - ) - asyncio.create_task(self._send_heartbeat()) - except (asyncio.IncompleteReadError, ConnectionError, OSError): - await self._on_disconnected(); return - -async def connect_tcp(host: str, port: int, interval_sec: int, wait_sec: int, - parser_map: ParserMap) -> TcpClient: - reader, writer = await asyncio.open_connection(host, port) - return TcpClient(reader, writer, interval_sec, wait_sec, parser_map) -``` - -```python -# tcp_server.py -class TcpServer: - def __init__(self, host: str, port: int, new_client: Callable) -> None: - self._host = host - self._port = port - self._new_client = new_client - self._clients: list[TcpClient] = [] - self._server: asyncio.Server | None = None - self.on_client_connected: Callable[[TcpClient], None] = lambda c: None - - async def start(self) -> None: ... - async def stop(self) -> None: ... - def clients(self) -> list[TcpClient]: ... - async def broadcast(self, message) -> None: ... -``` - -### 수정 파일 및 체크리스트 - -- [ ] `python/toki_socket/tcp_client.py` 생성 - - [ ] `MAX_PACKET_SIZE = 64 << 20` - - [ ] `TcpClient(BaseClient)` — reader/writer, read_loop, write_packet, do_close - - [ ] `connect_tcp(host, port, interval_sec, wait_sec, parser_map)` async 함수 -- [ ] `python/toki_socket/tcp_server.py` 생성 - - [ ] `TcpServer` — start/stop/clients/broadcast, on_client_connected 콜백 - - [ ] `_handle_connection()` — TcpClient 생성, disconnect_listener로 clients 목록 관리 - -### 테스트 작성 - -`python/test/test_tcp.py`: -- `test_tcp_send_receive` — 루프백 TCP로 양방향 TestData 교환 -- `test_tcp_request_response` — send_request / add_request_listener 검증 - -```python -@pytest.mark.asyncio -async def test_tcp_send_receive(): - received = asyncio.Future() - server = TcpServer("127.0.0.1", 0, lambda r, w: TcpClient(r, w, 0, 0, parser_map())) - server.on_client_connected = lambda c: c.communicator.add_listener( - "TestData", lambda m: received.set_result(m) - ) - await server.start() - port = server.port # 동적 포트 - client = await connect_tcp("127.0.0.1", port, 0, 0, parser_map()) - await client.communicator.send(TestData(index=1, message="hello")) - result = await asyncio.wait_for(received, 2.0) - assert result.index == 1 and result.message == "hello" - await client.close() - await server.stop() -``` - -### 중간 검증 - -```bash -cd python && python -m pytest test/test_tcp.py -v -``` - -예상: 2개 테스트 PASS - ---- - -## [API-5] WebSocket Transport 구현 - -### 문제 - -Go의 `WsClient` / `WsServer`에 해당하는 asyncio WebSocket 구현이 없다. - -### 해결 방법 - -`websockets` 라이브러리 사용. WebSocket은 메시지 경계가 보장되므로 길이 헤더 없이 바이너리 프레임으로 전송. - -```python -# ws_client.py -import websockets - -class WsClient(BaseClient): - def __init__(self, ws: websockets.WebSocketClientProtocol | websockets.WebSocketServerProtocol, - interval_sec: int, wait_sec: int, parser_map: ParserMap) -> None: - super().__init__(interval_sec, wait_sec, do_close=self._do_close_impl) - self._ws = ws - self._init_base() - self.communicator.initialize(self, parser_map) - self.communicator.set_write_error_handler(lambda e: asyncio.create_task(self._on_disconnected())) - self.communicator.add_listener( - type_name_of(HeartBeat), lambda m: asyncio.create_task(self._on_heartbeat()) - ) - asyncio.create_task(self._read_loop()) - asyncio.create_task(self._send_heartbeat()) - - async def write_packet(self, base: PacketBase) -> None: - await self._ws.send(base.SerializeToString()) - - async def _do_close_impl(self) -> None: - await self._ws.close() - - async def _read_loop(self) -> None: - try: - async for message in self._ws: - if not isinstance(message, bytes): - continue - base = PacketBase() - base.ParseFromString(message) - self.communicator.on_received_data( - base.typeName, base.data, base.nonce, base.responseNonce - ) - asyncio.create_task(self._send_heartbeat()) - except Exception: - pass - finally: - await self._on_disconnected() - -async def connect_ws(host: str, port: int, path: str, - interval_sec: int, wait_sec: int, parser_map: ParserMap) -> WsClient: - uri = f"ws://{host}:{port}{path}" - ws = await websockets.connect(uri) - return WsClient(ws, interval_sec, wait_sec, parser_map) -``` - -```python -# ws_server.py -class WsServer: - def __init__(self, host: str, port: int, path: str, new_client: Callable) -> None: ... - async def start(self) -> None: ... - async def stop(self) -> None: ... - def clients(self) -> list[WsClient]: ... - async def broadcast(self, message) -> None: ... -``` - -### 수정 파일 및 체크리스트 - -- [ ] `python/toki_socket/ws_client.py` 생성 - - [ ] `WsClient(BaseClient)` — websocket conn, read_loop, write_packet, do_close - - [ ] `connect_ws(host, port, path, interval_sec, wait_sec, parser_map)` async 함수 -- [ ] `python/toki_socket/ws_server.py` 생성 - - [ ] `WsServer` — start/stop/clients/broadcast, path 기반 핸들러 - - [ ] `_handler(ws, path)` — WsClient 생성, disconnect_listener로 목록 관리 - -### 테스트 작성 - -skip — TCP 테스트로 핵심 로직이 검증되며, WS는 크로스 테스트에서 검증. - -### 중간 검증 - -```bash -cd python && python -c "from toki_socket.ws_client import WsClient; from toki_socket.ws_server import WsServer; print('OK')" -``` - -예상: `OK` - ---- - -## [API-6] 단위 테스트 - -API-2, API-4 항목에서 이미 기술. 해당 항목 체크리스트 참조. - ---- - -## [API-7] Python×Go 크로스 테스트 - -### 문제 - -Python이 Go 서버와 통신할 수 있는지, Go가 Python 서버와 통신할 수 있는지 검증이 없다. - -### 해결 방법 - -포트 할당: -- Go 서버 / Python 클라이언트: TCP `29390`, WS `29392` -- Python 서버 / Go 클라이언트: TCP `29490`, WS `29492` - -**Python 클라이언트 헬퍼** `python/crosstest/go_python_client.py`: -- `--mode tcp|ws`, `--port N`, `--phase send-push|requests` 플래그 -- `INFO typeName python=TestData` 출력 -- 각 시나리오 결과를 `PASS scenario=N detail=...` / `FAIL scenario=N error=...` 출력 -- send-push: 시나리오 1(fire-and-forget), 2(server push 수신) -- requests: 시나리오 3(단일 요청), 4(동시 5개 요청 nonce 검증) -- Go crosstest 클라이언트(`go/crosstest/dart_go_client/main.go`)와 동일한 구조 - -**Go 오케스트레이터** `go/crosstest/go_python.go`: -- Go 서버를 시작하고 `python go_python_client.py` 서브프로세스를 실행 -- 기존 `go_dart.go` 패턴과 동일 (`//go:build ignore`, `runPythonClient()` 헬퍼) -- Python 패키지 디렉터리를 소스 위치 기반으로 찾는 `pythonPackageDir()` 함수 - -**Python 오케스트레이터** `python/crosstest/python_go.py`: -- Python 서버를 시작하고 `go run ./crosstest/python_go_client` 서브프로세스를 실행 -- `go/crosstest/dart_go_client/main.go` 패턴을 참고하여 `--mode`, `--port`, `--phase` 플래그 지원 -- Go 패키지 디렉터리를 소스 위치 기반으로 찾는 헬퍼 - -**Go 클라이언트 헬퍼** `go/crosstest/python_go_client/main.go`: -- 기존 `dart_go_client/main.go`와 동일한 구조 -- Python 서버 포트(29490 TCP, 29492 WS)에 연결 -- `--phase send-push`: 시나리오 1, 2 -- `--phase requests`: 시나리오 3, 4 -- push 메시지 검증: `index=200 && message=="push from python server"` - -**시나리오별 메시지 규격**: -| 시나리오 | 클라이언트 송신 | 서버 검증 / 응답 | -|----------|----------------|-----------------| -| 1 (send) | `index=101, message="fire from python client"` | Go서버: index==101, message 검증 | -| 2 (push) | — | Go서버 → `index=200, message="push from go server"` | -| 3 (request) | `index=21, message="single request from python"` | Go서버 → `index=42, message="echo: single request from python"` | -| 4 (concurrent) | `index=30+i, message="multi request {i} from python"` | Go서버 → `index=(30+i)*2, message="echo: ..."` | - -Python서버일 때는 go클라이언트 메시지 검증: -- 1: `index=101, message="fire from go client"` -- 2: Python서버 → `index=200, message="push from python server"` - -### 수정 파일 및 체크리스트 - -- [ ] `python/crosstest/__init__.py` 생성 (빈 파일) -- [ ] `python/crosstest/go_python_client.py` 생성 - - [ ] argparse로 `--mode`, `--port`, `--phase` 파싱 - - [ ] `INFO typeName python=TestData` 출력 - - [ ] `dial_with_retry(mode, port)` — 3초 내 재시도 - - [ ] `run_send_push(client)` — 시나리오 1, 2 - - [ ] `run_requests(client)` — 시나리오 3, 4 (asyncio.gather로 동시 요청) - - [ ] 비동기 main → `asyncio.run(main())` -- [ ] `go/crosstest/go_python.go` 생성 - - [ ] `//go:build ignore` 태그 - - [ ] `goPythonTCPPort = 29390`, `goPythonWSPort = 29392` - - [ ] `runTCPSendPush/TCPRequests/WSSendPush/WSRequests()` 4개 함수 - - [ ] `runPythonClient(mode, port, phase, expected)` — `python go_python_client.py` 실행 - - [ ] `pythonPackageDir()` — 소스 위치 기반 탐색 -- [ ] `python/crosstest/python_go.py` 생성 - - [ ] `pythonGoTCPPort = 29490`, `pythonGoWSPort = 29492` - - [ ] `run_tcp_send_push/tcp_requests/ws_send_push/ws_requests()` 4개 함수 - - [ ] `run_go_client(mode, port, phase, expected)` — `go run ./crosstest/python_go_client` 실행 - - [ ] `go_package_dir()` — 소스 위치 기반 탐색 - - [ ] 비동기 main → `asyncio.run(main())` -- [ ] `go/crosstest/python_go_client/main.go` 생성 - - [ ] `dart_go_client/main.go`와 동일 구조 - - [ ] Python서버 포트(`--port N`)에 연결 - - [ ] push 메시지: `"push from python server"` - -### 테스트 작성 - -크로스 테스트가 곧 테스트. 별도 단위 테스트 skip. - -### 중간 검증 - -Go 서버 / Python 클라이언트: -```bash -cd go && go run ./crosstest/go_python.go -``` - -Python 서버 / Go 클라이언트: -```bash -cd python && python crosstest/python_go.py -``` - -예상: 각각 `PASS all ...` 출력 - ---- - -## 수정 파일 요약 - -| 파일 | 항목 | -|------|------| -| `python/pyproject.toml` | API-1 | -| `python/toki_socket/__init__.py` | API-1 | -| `python/toki_socket/packets/__init__.py` | API-1 | -| `python/toki_socket/packets/message_common.proto` | API-1 | -| `python/toki_socket/packets/message_common_pb2.py` | API-1 | -| `python/toki_socket/packets/message_common_pb2.pyi` | API-1 | -| `python/README.md` | API-1 | -| `python/toki_socket/communicator.py` | API-2 | -| `python/test/__init__.py` | API-2 | -| `python/test/test_communicator.py` | API-2 | -| `python/toki_socket/base_client.py` | API-3 | -| `python/toki_socket/tcp_client.py` | API-4 | -| `python/toki_socket/tcp_server.py` | API-4 | -| `python/test/test_tcp.py` | API-4 | -| `python/toki_socket/ws_client.py` | API-5 | -| `python/toki_socket/ws_server.py` | API-5 | -| `python/crosstest/__init__.py` | API-7 | -| `python/crosstest/go_python_client.py` | API-7 | -| `python/crosstest/python_go.py` | API-7 | -| `go/crosstest/go_python.go` | API-7 | -| `go/crosstest/python_go_client/main.go` | API-7 | - ---- - -## 최종 검증 - -```bash -# 1. Python 단위 테스트 -cd python && pip install -e ".[dev]" && python -m pytest test/ -v - -# 2. Go 서버 / Python 클라이언트 크로스 테스트 -cd go && go run ./crosstest/go_python.go - -# 3. Python 서버 / Go 클라이언트 크로스 테스트 -cd python && python crosstest/python_go.py - -# 4. 기존 Go 크로스 테스트 회귀 확인 -cd go && go run ./crosstest/go_dart.go -cd go && go run ./crosstest/go_kotlin.go -``` - -예상 결과: -1. `pytest`: 모든 테스트 PASS -2. `go run ./crosstest/go_python.go`: `PASS all go-server/python-client crosstests passed` -3. `python crosstest/python_go.py`: `PASS all python-server/go-client crosstests passed` -4. 기존 크로스 테스트: 회귀 없음 (PASS) diff --git a/agent-task/quality_improvements/CODE_REVIEW.md b/agent-task/quality_improvements/CODE_REVIEW.md deleted file mode 100644 index 7423017..0000000 --- a/agent-task/quality_improvements/CODE_REVIEW.md +++ /dev/null @@ -1,81 +0,0 @@ - - -# Code Review Reference - REFACTOR-DOC-FIX - -## 개요 - -date=2026-04-24 -task=quality_improvements, plan=1, tag=REFACTOR-DOC-FIX - -## 이 파일을 읽는 리뷰 에이전트에게 - -각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. -리뷰 완료 후 반드시 아래 순서로 아카이브하세요. - -1. `CODE_REVIEW.md` → `code_review_1.log` (기존 code_review_*.log 수 = 1) -2. `PLAN.md` → `plan_1.log` (기존 plan_*.log 수 = 1) -3. PASS인 경우 `complete.log` 작성 후 종료. WARN/FAIL인 경우 새 `PLAN.md` + `CODE_REVIEW.md` 스텁 작성. - ---- - -## 구현 항목별 완료 여부 - -| 항목 | 완료 여부 | -|------|---------| -| [DOC-FIX-1] VERSIONING.md `## nonce Overflow` 위치 이동 | [x] | - -## 계획 대비 변경 사항 - -계획대로 `VERSIONING.md`에서 `## nonce Overflow` 섹션 전체를 `Backward-compatible message additions require ...` 단락 뒤로 이동했습니다. - -## 주요 설계 결정 - -문서 구조만 조정했습니다. `nonce` overflow 정책 문구와 non-breaking 변경 예시 문구는 변경하지 않았습니다. - -## 리뷰어를 위한 체크포인트 - -- `VERSIONING.md`에서 `## Non-breaking Protocol Changes` 섹션 바디(Examples 목록 + 후속 단락)가 `## nonce Overflow` **앞**에 위치하는지 확인 -- `## nonce Overflow` 섹션 내용(int32 정책 3개 항목)이 그대로 유지되는지 확인 -- `## nonce Overflow` 이후에 `## New Language Compatibility` 섹션이 이어지는지 확인 - -## 검증 결과 - -_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ - -### DOC-FIX-1 중간 검증 -``` -$ grep -n "^##" VERSIONING.md -5:## Current Versions -13:## Protocol Version -26:## Package Version -32:## Breaking Protocol Changes -42:## Non-breaking Protocol Changes -53:## nonce Overflow -64:## New Language Compatibility - -$ grep -n "Examples of non-breaking" VERSIONING.md -44:Examples of non-breaking changes: - -$ grep -n "nonce Overflow" VERSIONING.md -53:## nonce Overflow -``` - -### 최종 검증 -``` -$ grep -n "^##" VERSIONING.md -5:## Current Versions -13:## Protocol Version -26:## Package Version -32:## Breaking Protocol Changes -42:## Non-breaking Protocol Changes -53:## nonce Overflow -64:## New Language Compatibility - -$ grep -A 5 "Non-breaking Protocol Changes" VERSIONING.md -## Non-breaking Protocol Changes - -Examples of non-breaking changes: - -- Adding a new protobuf message type. -- Adding optional fields to application messages when older peers can ignore them. -``` diff --git a/agent-task/quality_improvements/PLAN.md b/agent-task/quality_improvements/PLAN.md deleted file mode 100644 index fd47969..0000000 --- a/agent-task/quality_improvements/PLAN.md +++ /dev/null @@ -1,111 +0,0 @@ - - -# Quality Improvements — VERSIONING.md 구조 수정 - -## 이 파일을 읽는 구현 에이전트에게 - -plan=0 리뷰에서 WARN이 발생했습니다. 단일 항목만 수정합니다. -완료 후 `CODE_REVIEW.md` 검증 결과를 채우고 완료 표를 `[x]`로 갱신하세요. - -## 배경 - -plan=0 코드 리뷰 결과, REFACTOR-4(VERSIONING.md)에서 `## nonce Overflow` 섹션이 -`## Non-breaking Protocol Changes` 헤더 바로 아래에 삽입되어 구조가 깨졌습니다. - -현재 구조: -``` -## Non-breaking Protocol Changes - ← 내용 없음 -## nonce Overflow -... - -Examples of non-breaking changes: ← nonce Overflow 섹션 아래에 위치 (의미상 오류) -``` - -의도한 구조: -``` -## Non-breaking Protocol Changes - -Examples of non-breaking changes: -... - -Backward-compatible message additions ... - -## nonce Overflow -... -``` - ---- - -### [DOC-FIX-1] VERSIONING.md `## nonce Overflow` 위치 이동 - -**문제** - -`## nonce Overflow`가 `## Non-breaking Protocol Changes` 바디(Examples 목록, 후속 단락)보다 앞에 위치해 -"Examples of non-breaking changes:" 목록이 nonce Overflow 섹션에 속한 것처럼 렌더링됩니다. - -**해결 방법** - -`## nonce Overflow` 블록 전체를 `Backward-compatible message additions require ...` 단락 뒤로 이동합니다. - -목표 구조 (`VERSIONING.md` 하단): - -```markdown -## Non-breaking Protocol Changes - -Examples of non-breaking changes: - -- Adding a new protobuf message type. -- Adding optional fields to application messages when older peers can ignore them. -- Adding a new language implementation that passes the existing protocol and cross-language tests. -- Tightening tests or documentation without changing wire behavior. - -Backward-compatible message additions require updated parser maps and cross-language tests before release. - -## nonce Overflow - -`nonce` and `responseNonce` fields are protobuf `int32` values (signed 32-bit). -Overflow occurs after about 2.1 billion sends on a single connection. - -Current policy: - -- The protocol does not define overflow behavior. Reaching the int32 limit on a single connection is not realistic. -- Implementations do not add recovery logic for overflow. -- If overflow handling is needed in the future, the protocol version will be bumped and wrap-around behavior will be specified. -``` - -**수정 파일 및 체크리스트** - -- [x] `VERSIONING.md` — `## nonce Overflow` 섹션을 `## Non-breaking Protocol Changes` 바디 아래로 이동 - -**테스트 작성** - -문서 전용 변경이므로 테스트 불필요. - -**중간 검증** - -```bash -grep -n "^##" VERSIONING.md -# Non-breaking Protocol Changes가 nonce Overflow보다 먼저 나와야 하고 -# Examples of non-breaking changes: 가 Non-breaking Protocol Changes 섹션 안에 있어야 함 -grep -n "Examples of non-breaking" VERSIONING.md -grep -n "nonce Overflow" VERSIONING.md -# Examples 줄 번호 < nonce Overflow 줄 번호 -``` - ---- - -## 수정 파일 요약 - -| 파일 | 항목 | -|------|------| -| `VERSIONING.md` | DOC-FIX-1 | - -## 최종 검증 - -```bash -grep -n "^##" VERSIONING.md -# 섹션 순서 확인: Non-breaking Protocol Changes → nonce Overflow → New Language Compatibility -grep -A 5 "Non-breaking Protocol Changes" VERSIONING.md -# 바로 아래에 Examples 또는 설명이 와야 함 (nonce Overflow 아님) -``` diff --git a/agent-task/typescript_impl/CODE_REVIEW.md b/agent-task/typescript_impl/CODE_REVIEW.md deleted file mode 100644 index 0be95f3..0000000 --- a/agent-task/typescript_impl/CODE_REVIEW.md +++ /dev/null @@ -1,90 +0,0 @@ - - -# Code Review Reference - REVIEW_API - -## 개요 - -date=2026-04-24 -task=typescript_impl, plan=2, tag=REVIEW_API - -## 이 파일을 읽는 리뷰 에이전트에게 - -각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. -리뷰 완료 후 반드시 아래 순서로 아카이브하세요. - -1. `CODE_REVIEW.md` → `code_review_2.log` (N = 기존 code_review_*.log 수) -2. `PLAN.md` → `plan_2.log` (M = 기존 plan_*.log 수) -3. PASS인 경우 `complete.log` 작성 후 종료. WARN/FAIL인 경우 새 `PLAN.md` + `CODE_REVIEW.md` 스텁 작성. - ---- - -## 구현 항목별 완료 여부 - -| 항목 | 완료 여부 | -|------|---------| -| [REVIEW_API-1] ws를 devDependencies에서 dependencies로 이동 | [x] | - -## 계획 대비 변경 사항 - -- 없음. 계획대로 `typescript/package.json`에서 `ws`를 `dependencies`로 이동했고, `npm install`로 `typescript/package-lock.json`의 top-level dependency 분류를 함께 갱신했다. - -## 주요 설계 결정 - -- 런타임 import를 사용하는 패키지이므로 소스 코드 수정 없이 의존성 선언만 바로잡았다. `ws_client.ts`, `ws_server.ts`의 동작 자체는 바꾸지 않고 배포/설치 시점의 누락 위험만 제거했다. -- 버전은 유지하고 위치만 이동했다. 이번 수정의 목적은 동작 변경이 아니라 production install 시 `ws`가 빠지지 않도록 패키지 메타데이터를 정정하는 것이다. - -## 리뷰어를 위한 체크포인트 - -- `typescript/package.json`의 `dependencies`에 `ws`가 있고 `devDependencies`에서 제거됐는지 확인. -- `package-lock.json`이 변경됐는지 확인 (`npm install` 실행 여부). -- `npx vitest run` 결과 18개 테스트 PASS인지 확인. - -## 검증 결과 - -### REVIEW_API-1 중간 검증 -``` -$ cd typescript && npm install && npx tsc --noEmit && npx vitest run - -up to date, audited 65 packages in 542ms - -17 packages are looking for funding - run `npm fund` for details - -found 0 vulnerabilities - - RUN v3.2.4 /config/workspace/toki_socket/typescript - - ✓ test/base_client.test.ts (7 tests) 9ms - ✓ test/communicator.test.ts (7 tests) 18ms - ✓ test/tcp.test.ts (2 tests) 11ms - ✓ test/ws.test.ts (2 tests) 21ms - - Test Files 4 passed (4) - Tests 18 passed (18) - Start at 05:37:16 - Duration 801ms (transform 341ms, setup 0ms, collect 953ms, tests 58ms, environment 1ms, prepare 790ms) -``` - -### 최종 검증 -``` -$ cd typescript && npm install && npx tsc --noEmit && npx vitest run - -up to date, audited 65 packages in 584ms - -17 packages are looking for funding - run `npm fund` for details - -found 0 vulnerabilities - - RUN v3.2.4 /config/workspace/toki_socket/typescript - - ✓ test/communicator.test.ts (7 tests) 18ms - ✓ test/base_client.test.ts (7 tests) 8ms - ✓ test/tcp.test.ts (2 tests) 11ms - ✓ test/ws.test.ts (2 tests) 18ms - - Test Files 4 passed (4) - Tests 18 passed (18) - Start at 05:37:23 - Duration 867ms (transform 406ms, setup 0ms, collect 1.05s, tests 54ms, environment 1ms, prepare 977ms) -``` diff --git a/agent-task/typescript_impl/PLAN.md b/agent-task/typescript_impl/PLAN.md deleted file mode 100644 index 8a88bf2..0000000 --- a/agent-task/typescript_impl/PLAN.md +++ /dev/null @@ -1,115 +0,0 @@ - - -# ws 런타임 의존성 위치 수정 - -## 이 파일을 읽는 구현 에이전트에게 - -체크리스트를 완료하고 중간 검증 명령을 실행한 후, 출력을 `CODE_REVIEW.md`의 `검증 결과` 섹션에 붙여넣어라. 계획과 다르게 구현한 부분은 `계획 대비 변경 사항`에 이유와 함께 기록하라. - ---- - -## 배경 - -1차 리뷰(plan=0)에서 `ws` 패키지가 `devDependencies`에 잘못 배치된 것이 확인됐다. `ws_client.ts`와 `ws_server.ts`가 런타임에 `ws`를 import하므로 `dependencies`로 이동해야 한다. 현재 `typescript/package.json:21`에 `ws`가 `devDependencies`에 위치한다. - ---- - -### [REVIEW_API-1] ws를 devDependencies → dependencies로 이동 - -#### 문제 - -`typescript/package.json` (line 21): -```json -"devDependencies": { - ... - "ws": "^8.18.1" ← 런타임 import에 쓰이는 패키지가 devDependencies에 있음 -} -``` - -`typescript/src/ws_client.ts:1`: -```typescript -import WebSocket, { type RawData } from "ws"; -``` - -`typescript/src/ws_server.ts:2`: -```typescript -import WebSocket, { WebSocketServer } from "ws"; -``` - -두 파일 모두 런타임에 `ws`를 import한다. `npm install --omit=dev`로 설치하면 `ws`가 설치되지 않아 실행 시 모듈 not found 오류가 발생한다. - -#### 해결 방법 - -`ws`를 `devDependencies`에서 제거하고 `dependencies`에 추가한다. - -```json -// 수정 전 (typescript/package.json) -{ - "dependencies": { - "@bufbuild/protobuf": "^2.2.5" - }, - "devDependencies": { - "@bufbuild/protoc-gen-es": "^2.2.5", - "@types/node": "^22.15.21", - "@types/ws": "^8.18.1", - "tsx": "^4.19.3", - "typescript": "^5.8.3", - "vitest": "^3.1.3", - "ws": "^8.18.1" - } -} - -// 수정 후 (typescript/package.json) -{ - "dependencies": { - "@bufbuild/protobuf": "^2.2.5", - "ws": "^8.18.1" - }, - "devDependencies": { - "@bufbuild/protoc-gen-es": "^2.2.5", - "@types/node": "^22.15.21", - "@types/ws": "^8.18.1", - "tsx": "^4.19.3", - "typescript": "^5.8.3", - "vitest": "^3.1.3" - } -} -``` - -수정 후 `npm install`로 `package-lock.json`을 갱신한다. - -#### 수정 파일 및 체크리스트 - -- [ ] `typescript/package.json`: `ws`를 `devDependencies`에서 제거, `dependencies`에 추가 -- [ ] `npm install` 실행 → `typescript/package-lock.json` 갱신됨 - -#### 테스트 작성 - -스킵. `test/ws.test.ts`의 2개 테스트가 `ws` 런타임 동작을 이미 검증하며, 의존성 위치 변경은 동작에 영향을 주지 않는다. - -#### 중간 검증 - -```bash -cd typescript && npm install && npx tsc --noEmit && npx vitest run -``` - -기대 결과: 타입 오류 없음, 16개 테스트 PASS. - ---- - -## 수정 파일 요약 - -| 파일 | 항목 | -|------|------| -| `typescript/package.json` | REVIEW_API-1 | -| `typescript/package-lock.json` | REVIEW_API-1 | - ---- - -## 최종 검증 - -```bash -cd typescript && npm install && npx tsc --noEmit && npx vitest run -``` - -기대 결과: 타입 오류 없음, 16개 테스트 PASS.