<!-- task=crosstest_typescript plan=0 tag=CROSSTEST -->

# 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<void> _runTypescriptClient(String mode, int port, String phase, Set<String> 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<String>) =
    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<string>): Promise<void> {
  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<string>): Promise<void> {
  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<string>): Promise<void> {
  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 ...` 출력.
