기여하기
변경이 만족해야 할 원칙
섹션 제목: “변경이 만족해야 할 원칙”이것들은 취향이 아닙니다. 이 중 하나를 어기는 변경은 수정을 요청받습니다. 아키텍처 전체가 그 위에 서 있기 때문입니다.
- 극단적 객체지향. 이 설계는 데이터를 다루는 함수가 아니라, 메시지를 주고받는 객체입니다.
- 모든 것은 인스턴스다. plain object는 존재해서는 안 된다. 경계에서 들어오는 것은 무엇이든 그것이 구성되는 지점으로부터 설계상 올바른 최단거리에서 인스턴스로 변환됩니다. 메시지는 인스턴스로 전달되고 인스턴스로 전달받습니다 — 순수 객체는 함수로 넘길 수 없습니다. 1급 객체가 아닌 것으로 간주하기 때문입니다.
- 클래스를 타입으로 쓸 수 있는 곳에서는
interface보다class를 씁니다. - 네임스페이스와 네스팅을 적극적으로 활용하며, 폴더 구조가 이를 드러냅니다. 클래스 이름을 보면 파일 경로를 추론할 수 있어야 합니다.
- 실물로 검증합니다. 스크립트 목(mock)은 실제 CLI와 실제 운영체제가 무엇을 하는지 가립니다. 결함 셋은 오직 실제 CLI를 실행해서만 발견되었고, 그중 하나는 CI에서 실제 Windows를 돌려서만 발견되었습니다.
원칙 2가 실제로 만드는 배치
섹션 제목: “원칙 2가 실제로 만드는 배치”경계에서 들어오는 모든 것은 최단거리에서 인스턴스가 됩니다.
| 경계 | 들어오는 것 | 변환될 인스턴스 |
|---|---|---|
| CLI stdout 한 줄 | JSON 문자열 | Event 하위 클래스 |
| argv 조립 결과 | string[] |
Argv |
| 권한 요청 | control_request JSON |
PermissionRequest |
| 파일 경로 | string |
AbstractPath 하위 클래스 (WSL 변환을 아는 객체) |
경로가 특히 중요합니다. string으로 돌아다니면 “Windows 경로인가 WSL 경로인가”를 읽는 쪽마다 매번 추론해야 하지만, WindowsPath/WslPath 인스턴스면 타입이 답을 갖습니다.
클래스가 놓일 자리
섹션 제목: “클래스가 놓일 자리”이는 원칙 4가 결정하며, 협상 대상이 아닙니다. 이름이 곧 경로입니다.
ActiveCli.Adapter.Claude.ClaudeAdapter → src/ActiveCli/Adapter/Claude/ClaudeAdapter.ts파일 하나에 클래스 하나. 클래스를 추가하기 전에, 그것이 어느 네임스페이스의 문제를 푸는지 물으십시오.
| 그것이… | 놓일 자리 |
|---|---|
| CLI마다 다르게 표기되는 것이라면 | Adapter/<Cli>/ |
| 모든 CLI에 대해 같은 방식으로 운영체제 문제를 푸는 것이라면 | Process/ |
| 자기 자신임으로써 질문에 답하고, 아무것도 import하지 않는다면 | Path/, 또는 PermissionMode / SessionId 곁 |
| CLI가 우리에게 말한 것이라면 | Event/ |
| 우리가 CLI에게 말하는 것이라면 | Message/ |
어댑터는 실행 파일 탐색, PATH 보강, 트리 종료 같은 Process/의 관심사에 손을 뻗어서는 안 됩니다. 그것들은 보편적이며, 어댑터는 자기 CLI가 다르게 표기하는 것만 소유합니다.
트리 내부에는 barrel 파일이 없습니다. 모든 내부 import는 원하는 파일을 직접 지목하며, src/index.ts가 유일한 공개 표면이므로 새 공개 클래스는 그곳에 명시적으로 추가합니다.
테스트 실행
섹션 제목: “테스트 실행”npm test # vitest run — 132개 테스트, 12개 파일npm run typechecknpm run build # tsc, 그다음 copy-assets아키텍처가 이미 뒷받침하는 테스트 작성법
섹션 제목: “아키텍처가 이미 뒷받침하는 테스트 작성법”| 기법 | 쓰이는 곳 | 가능한 이유 |
|---|---|---|
| 실제 프로세스 | Session.test.ts가 node -e <script>를 스크립트 CLI로 실행 |
어댑터 계약이 테스트 안에서 구현할 만큼 작음 |
| 고정된 플랫폼 | 런처·트리·어댑터 테스트 | 플랫폼을 직접 읽지 않고 생성자 파라미터로 받음 |
| 순수 로직 추출 | WindowsLauncher.pick(), JobSpawner.buildCommandLine() |
Windows 호스트 없이도 규칙을 단언할 수 있도록 public으로 만듦 |
| 협력자 대체 | 레지스트리 테스트의 StubIdentity |
전반에 걸친 생성자 기본값 주입 |
| 임시 디렉토리 | CliRegistry 테스트 |
레지스트리 디렉토리는 호출자가 고르는 것 |
| 산문 픽스처 | McpOutputParser.test.ts가 실제 claude mcp list 출력을 상대로 단언 |
출력된 텍스트의 파싱은 샘플에 고정해둘 만한 계약임 |
새 클래스에 process.platform이 필요하다면 기본값이 있는 생성자 파라미터로 받으십시오. 그것이 Mac에서도 Windows 규칙을 단언할 수 있게 하는 장치입니다.
3-OS CI
섹션 제목: “3-OS CI”테스트 워크플로는 ubuntu, macOS, 그리고 windows-latest에서 실행되며, Windows 레그는 형식이 아닙니다.
첫 실행에서, mac에서는 결코 드러나지 않았을 결함을 잡았습니다. cmd.exe를 통과하는 인자 a&b|c<d>e가 a로 잘렸고, cmd는 'b' is not recognized as an internal or external command를 뱉었습니다. Node의 CommandLineToArgvW 인용이 &를 literal로 만든다는 통념은 틀렸습니다 — 코드의 원본 주석도 정확히 그렇게 적혀 있었습니다.
mcp add-json이 넘기는 JSON은 바로 그 문자들로 가득하므로, Windows 사용자의 MCP 설정이 저장 시 조용히 잘릴 뻔했습니다.
해법은 & | < >만 캐럿 이스케이프하고 그 밖에는 하지 않는 것입니다. 괄호까지 이스케이프한 첫 시도는 write("hi") 같은 평범한 인자를 망가뜨렸습니다 — 캐럿은 인용되지 않은 문맥에서만 유효하고, 괄호는 블록 문법에서만 특별하기 때문입니다.
여기서 얻는 교훈은 일반화됩니다. 프로세스 실행, 인용, 경로, 프로세스 트리에 손대는 변경은 Windows 레그가 초록이어야만 의미를 갖습니다.
문서와 논의는 한글로 합니다. 코드·주석·식별자·커밋 메시지는 공용언어인 영어로 씁니다.
풀 리퀘스트를 열기 전에
섹션 제목: “풀 리퀘스트를 열기 전에”-
npm test통과 -
npm run typecheck통과 -
copy-assets를 포함해npm run build통과 - 세 운영체제 모두에서 CI 초록
- 새 클래스의 경로가 그 이름과 일치
- 인스턴스를 만들었어야 할 경계를 plain object가 넘지 않음
- 새 공개 클래스는
src/index.ts에서 export - 실제 CLI로만 확인할 수 있는 동작은 실제 CLI로 확인함