<!-- task=03_typescript_crosstest_process_cleanup plan=0 tag=TEST -->

# TypeScript Crosstest Process Cleanup

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

**구현 완료의 마지막 단계는 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 모두 채우는 것이다.**
아래 구현 체크리스트와 리뷰 stub의 동일한 체크리스트를 끝까지 수행하고, 중간/최종 검증을 실행한 실제 출력으로 `CODE_REVIEW-cloud-G07.md`를 채운다. 리뷰 파일의 `이 파일을 읽는 리뷰 에이전트에게` 아카이브 지시는 실행하지 말고, `코드리뷰 전용 체크리스트`도 수정하지 않는다.

## 배경

TypeScript 서버 크로스테스트 러너는 child process timeout 시 direct child만 `SIGKILL`하고 stdout/stderr stream completion을 무제한 대기한다. Go/Dart/Python/Kotlin client command가 wrapper 또는 toolchain child를 만들 수 있으므로 process group 종료와 bounded stream wait가 필요하다.

## 분석 결과

### 읽은 파일

- `agent-ops/rules/project/rules.md`
- `typescript/crosstest/typescript_dart.ts`
- `typescript/crosstest/typescript_go.ts`
- `typescript/crosstest/typescript_python.ts`
- `typescript/crosstest/typescript_kotlin.ts`
- `typescript/package.json`

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

- Node `ChildProcess` timeout 후 descendant cleanup: 별도 unit test 없음. 실제 `tsx crosstest/typescript_*.ts` 명령이 회귀 검증이다.
- TypeScript compile coverage: `npm run check`가 import/type 오류를 검증한다.

### 심볼 참조

- renamed/removed symbol: none.
- 관련 참조 검색: `rg --sort path -n "function runClientProcess|child\\.kill\\(|Promise\\.all\\(\\[stdoutDone, stderrDone\\]\\)|collectLines\\(" typescript/crosstest`.

### 분할 판단

분할 정책을 먼저 평가했다. 이 작업은 Go/Dart/TypeScript 런타임별 process-control 구현이 달라 분할한다.

- `01_go_crosstest_process_cleanup`: 의존성 없음.
- `02_dart_crosstest_process_cleanup`: 의존성 없음.
- `03_typescript_crosstest_process_cleanup`: 의존성 없음.

### 범위 결정 근거

이 계획은 `typescript/crosstest/typescript_dart.ts`, `typescript/crosstest/typescript_go.ts`, `typescript/crosstest/typescript_python.ts`, `typescript/crosstest/typescript_kotlin.ts`만 수정한다. `typescript/src/**`, 상대 언어 client helpers, `agent-task/archive/**`는 제외한다.

### 빌드 등급

build=`cloud-G07`, review=`cloud-G07`. Node child process lifecycle, stdout/stderr parsing, long-running crosstest command의 종료 계약을 다룬다.

## 구현 체크리스트

- [ ] 네 TypeScript server runner의 `runClientProcess`에 process group-aware kill helper를 적용한다.
- [ ] stdout/stderr `Promise.all`에 bounded stream join timeout을 적용한다.
- [ ] `npm run check`로 TypeScript 타입 오류가 없는지 확인한다.
- [ ] 중간 검증과 최종 검증 명령을 실행하고 실제 출력을 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [TEST-1] Harden TypeScript child process cleanup

#### 문제

- [typescript_dart.ts](/config/workspace/proto-socket/typescript/crosstest/typescript_dart.ts:340)는 timeout 시 direct child만 kill하고 [typescript_dart.ts](/config/workspace/proto-socket/typescript/crosstest/typescript_dart.ts:355)에서 stream completion을 무제한 대기한다.
- [typescript_go.ts](/config/workspace/proto-socket/typescript/crosstest/typescript_go.ts:340), [typescript_python.ts](/config/workspace/proto-socket/typescript/crosstest/typescript_python.ts:340), [typescript_kotlin.ts](/config/workspace/proto-socket/typescript/crosstest/typescript_kotlin.ts:344)도 같은 구조다.

Before:

```ts
// typescript/crosstest/typescript_dart.ts:339
let timedOut = false;
const timer = setTimeout(() => {
  timedOut = true;
  child.kill("SIGKILL");
}, PROCESS_TIMEOUT_MS);

await Promise.all([stdoutDone, stderrDone]);
```

#### 해결 방법

`runClientProcess`에서 POSIX process group을 만들 수 있도록 `detached: process.platform !== "win32"`를 사용한다. timeout 시 `killProcessTree(child)` helper가 POSIX에서는 `process.kill(-child.pid, "SIGKILL")`를 먼저 시도하고 실패하면 `child.kill("SIGKILL")`로 fallback한다. stream join은 `withTimeout(Promise.all(...), STREAM_JOIN_TIMEOUT_MS, "... streams did not finish")` 형태로 제한한다.

After:

```ts
const STREAM_JOIN_TIMEOUT_MS = 5_000;

const child = childProcess.spawn(command, args, {
  cwd,
  detached: process.platform !== "win32",
  stdio: ["ignore", "pipe", "pipe"],
});

const timer = setTimeout(() => {
  timedOut = true;
  killProcessTree(child);
}, PROCESS_TIMEOUT_MS);

await withTimeout(
  Promise.all([stdoutDone, stderrDone]),
  STREAM_JOIN_TIMEOUT_MS,
  `${label} streams did not finish`,
);
```

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

- [ ] `typescript/crosstest/typescript_dart.ts`: process group kill helper와 stream join timeout 추가.
- [ ] `typescript/crosstest/typescript_go.ts`: 같은 패턴 적용.
- [ ] `typescript/crosstest/typescript_python.ts`: 같은 패턴 적용.
- [ ] `typescript/crosstest/typescript_kotlin.ts`: 같은 패턴 적용.

#### 테스트 작성

별도 unit test는 작성하지 않는다. 이 파일들은 executable crosstest runner이고 timeout/descendant cleanup은 실제 subprocess command로 검증한다.

#### 중간 검증

```bash
cd typescript && npm run check
```

기대 결과: exit code 0.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `typescript/crosstest/typescript_dart.ts` | TEST-1 |
| `typescript/crosstest/typescript_go.ts` | TEST-1 |
| `typescript/crosstest/typescript_python.ts` | TEST-1 |
| `typescript/crosstest/typescript_kotlin.ts` | TEST-1 |

## 최종 검증

```bash
cd typescript && npm run check && npm test
cd typescript && ./node_modules/.bin/tsx crosstest/typescript_dart.ts
cd typescript && ./node_modules/.bin/tsx crosstest/typescript_go.ts
cd typescript && ./node_modules/.bin/tsx crosstest/typescript_python.ts
cd typescript && ./node_modules/.bin/tsx crosstest/typescript_kotlin.ts
rg --sort path -n "STREAM_JOIN_TIMEOUT_MS|killProcessTree|detached: process\\.platform|streams did not finish|Promise\\.all\\(\\[stdoutDone, stderrDone\\]\\)" typescript/crosstest/typescript_dart.ts typescript/crosstest/typescript_go.ts typescript/crosstest/typescript_python.ts typescript/crosstest/typescript_kotlin.ts
```

기대 결과: 모든 실행 명령 exit code 0. `rg` 결과에서 네 파일 모두 `STREAM_JOIN_TIMEOUT_MS`, `killProcessTree`, `detached: process.platform`, `streams did not finish`를 포함한다.

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