<!-- task=plane_foundation/02+01_plane_smoke plan=0 tag=PLANE -->

# Plane Real Smoke Plan

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

`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채우는 것이 필수입니다. 구현 후 검증 명령을 실행하고 실제 출력과 구현 메모를 review stub에 기록한 뒤 active 파일을 유지한 채 리뷰 준비 상태로 보고하세요. 최종 판정, log rename, `complete.log`, archive 이동은 code-review skill 전용입니다.

## 배경

Plane adapter는 unit test로 검증됐지만, 실제 `https://plane.toki-labs.com` against work item 조회, comment 생성, state update smoke는 아직 증거가 없습니다. 이 작업은 `01_state_metadata` 완료 뒤 실제 smoke를 안전하게 실행할 수 있는 진입점과 문서를 만들고, 가능한 환경이면 dev Plane에 실제 호출 결과를 남깁니다.

## 분석 결과

### 읽은 파일

- `agent-ops/rules/project/domain/core/rules.md`
- `services/core/internal/adapters/plane/client.go`
- `services/core/internal/adapters/plane/client_test.go`
- `services/core/internal/http/handlers.go`
- `services/core/internal/http/handlers_test.go`
- `services/core/internal/workflow/model.go`
- `services/core/internal/workflow/service.go`
- `services/core/README.md`
- `services/core/bin/build`
- `services/core/bin/docker-down`
- `services/core/bin/docker-up`
- `services/core/bin/migrate-up`
- `services/core/bin/run`
- `services/core/bin/sqlc`
- `services/core/bin/test`
- `agent-ops/roadmap/milestones/plane-thin-e2e-loop.md`

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

- `services/core/internal/adapters/plane/client_test.go`는 mock server로 path/header/body/error를 검증하지만 실제 Plane 인스턴스 호출을 검증하지 않는다.
- 실제 smoke command가 없으므로 `PLANE_TOKEN` 존재 여부, write smoke opt-in, stdout token redaction을 검증할 테스트가 없다.
- actual external smoke는 CI unit test가 아니라 수동/운영 검증이다. command validation과 dry-run/read-only behavior는 unit test로 덮고, write smoke는 env가 있을 때만 실행한다.

### 심볼 참조

- renamed/removed symbol: none.
- `rg --sort path -n "AddComment|UpdateIssueStatus|PLANE_TOKEN|PLANE_BASE_URL|Plane work item" services/core` 결과, adapter는 `client.go`와 `client_test.go`에 있으며 HTTP task 생성 경로는 `handlers.go`에 한정된다.

### 분할 판단

분할 정책을 먼저 평가했다. 공유 task group은 `plane_foundation`이고, 이 작업은 `02+01_plane_smoke`라서 `01_state_metadata`의 `complete.log`가 선행 조건이다. 이유는 smoke 문서와 sample payload가 `state_id` metadata 반영 결과를 전제로 해야 하고, 실제 외부 mutation은 독립 리뷰/실행 위험이 크기 때문이다.

### 범위 결정 근거

- token 생성/보관/교체 운영법은 사용자 지시로 제외한다. 구현은 token 값을 읽되 저장하거나 출력하지 않는다.
- Plane 자동 pipeline, task 완료/실패 결과 자동 발행, webhook 수집은 제외한다.
- adapter path/body 자체가 실제 Plane에서 틀린 것으로 확인되지 않는 한 adapter API를 대규모 재설계하지 않는다.
- smoke 결과를 repository에 secret이나 token이 포함된 형태로 남기지 않는다.

### 빌드 등급

build=`cloud-G07`, review=`cloud-G07`. 실제 외부 API, 인증, comment/state mutation이 포함되고 unit test만으로 완료를 확정할 수 없어 높은 검증 주의가 필요하다.

## 의존 관계 및 구현 순서

`agent-task/plane_foundation/02+01_plane_smoke/`는 디렉터리명 기준으로 `agent-task/plane_foundation/01_state_metadata/complete.log`가 필요하다. 이 의존성 외의 숨은 선행 조건을 추가하지 않는다.

## 구현 체크리스트

- [ ] `01_state_metadata`가 PASS 처리되어 `complete.log`가 있는지 확인한다.
- [ ] 실제 Plane smoke용 Go command와 bin wrapper를 추가한다.
- [ ] smoke command의 env validation, read-only lookup, write opt-in, token 미출력 동작을 테스트한다.
- [ ] `services/core/README.md`에 read-only smoke와 write smoke 절차를 추가한다.
- [ ] 실제 `PLANE_TOKEN`과 test work item이 있으면 lookup/comment/state update smoke를 실행하고 token 없는 출력만 기록한다.
- [ ] smoke 결과에 따라 `agent-ops/roadmap/milestones/plane-thin-e2e-loop.md` 체크리스트를 갱신한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [PLANE-1] 실제 Plane smoke 진입점 추가

#### 문제

`services/core/internal/adapters/plane/client.go:81`, `:89`, `:105`에 조회/comment/state update method는 있지만, 실제 Plane 인스턴스를 대상으로 세 method를 순서대로 실행하는 명령이 없다. 현재 bin script들은 server/test/build/migration 중심이며 smoke entrypoint가 없다.

현재 adapter 호출 표면:

```go
// services/core/internal/adapters/plane/client.go:81
func (c *Client) GetWorkItem(ctx context.Context, ref WorkItemRef) (WorkItem, error)

// services/core/internal/adapters/plane/client.go:89
func (c *Client) AddComment(ctx context.Context, input AddCommentInput) error

// services/core/internal/adapters/plane/client.go:105
func (c *Client) UpdateIssueStatus(ctx context.Context, input UpdateIssueStatusInput) error
```

#### 해결 방법

새 command `services/core/cmd/plane-smoke/main.go`와 wrapper `services/core/bin/plane-smoke`를 추가한다. command는 env에서 값을 읽고 token을 출력하지 않는다.

필수 env:

```text
PLANE_BASE_URL
PLANE_TOKEN
PLANE_WORKSPACE_SLUG
PLANE_PROJECT_ID
PLANE_WORK_ITEM_ID
```

write smoke opt-in env:

```text
PLANE_SMOKE_APPLY=1
PLANE_SMOKE_COMMENT
PLANE_SMOKE_STATE_ID
```

동작:

1. 항상 `GetWorkItem`을 실행하고 `lookup ok: id=<id> name=<name>`을 출력한다.
2. `PLANE_SMOKE_APPLY=1`이면 `AddComment`를 실행하고 `comment ok`를 출력한다.
3. `PLANE_SMOKE_APPLY=1`이면 `UpdateIssueStatus`를 실행하고 `state update ok: state_id=<id>`를 출력한다.
4. apply가 아니면 write 호출을 skip하고 `write smoke skipped: set PLANE_SMOKE_APPLY=1`을 출력한다.

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

- [ ] `services/core/cmd/plane-smoke/main.go`: env loading, validation, client creation, smoke execution.
- [ ] `services/core/cmd/plane-smoke/main_test.go`: missing env, read-only mode, apply validation, token redaction을 helper 단위로 테스트.
- [ ] `services/core/bin/plane-smoke`: 기존 bin script 패턴과 동일하게 core root로 이동 후 `go run ./cmd/plane-smoke "$@"` 실행.

#### 테스트 작성

작성한다. `main_test.go`는 network 없이 env parsing/validation과 output builder를 검증한다. 실제 HTTP는 adapter unit test가 이미 mock server로 덮고 있으므로 smoke command test가 real Plane을 호출하지 않게 한다.

#### 중간 검증

```bash
cd services/core && go test -count=1 ./cmd/plane-smoke ./internal/adapters/plane
```

예상 결과: smoke command와 Plane adapter package 테스트가 통과한다.

### [PLANE-2] 실제 smoke 절차 문서화와 실행

#### 문제

`services/core/README.md:93`에는 Plane work item에서 pending task를 만드는 curl 예시만 있고, 실제 adapter comment/state update smoke 절차는 없다. 마일스톤 완료 기준의 실제 200/comment/state update 증거도 아직 없다.

현재 문서:

```markdown
<!-- services/core/README.md:93 -->
Plane work item에서 pending task를 수동 생성하는 예시:
```

#### 해결 방법

README에 `Plane smoke` 섹션을 추가한다. read-only lookup과 write smoke를 분리하고, write smoke는 test work item에서만 `PLANE_SMOKE_APPLY=1`로 실행하도록 쓴다. token 운영법은 쓰지 않는다.

문서 명령 예:

```bash
PLANE_BASE_URL="https://plane.toki-labs.com" \
PLANE_TOKEN="$PLANE_TOKEN" \
PLANE_WORKSPACE_SLUG="general" \
PLANE_PROJECT_ID="a6beb42f-7a8a-410c-b50f-ea3ca94828f3" \
PLANE_WORK_ITEM_ID="<test-work-item-id>" \
./bin/plane-smoke
```

write smoke 명령 예:

```bash
PLANE_BASE_URL="https://plane.toki-labs.com" \
PLANE_TOKEN="$PLANE_TOKEN" \
PLANE_WORKSPACE_SLUG="general" \
PLANE_PROJECT_ID="a6beb42f-7a8a-410c-b50f-ea3ca94828f3" \
PLANE_WORK_ITEM_ID="<test-work-item-id>" \
PLANE_SMOKE_COMMENT="NomadCode core Plane smoke" \
PLANE_SMOKE_STATE_ID="ea2e5b48-8bf1-4723-b749-de7723be41e9" \
PLANE_SMOKE_APPLY=1 \
./bin/plane-smoke
```

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

- [ ] `services/core/README.md`: read-only/write smoke 절차 추가.
- [ ] `agent-ops/roadmap/milestones/plane-thin-e2e-loop.md`: 실제 smoke 성공 시 인증/comment/state 하위 항목과 완료 기준을 체크한다. token 운영법은 범위 제외에 유지한다.
- [ ] `CODE_REVIEW-cloud-G07.md`: 실제 smoke를 실행했다면 token 없는 stdout/stderr를 붙이고, 실행하지 못했다면 `test -n "${PLANE_TOKEN:-}"` 결과와 blocker를 기록한다.

#### 테스트 작성

문서 자체 테스트는 작성하지 않는다. 대신 `PLANE-1` command test와 실제 smoke command output을 검증 결과로 남긴다.

#### 중간 검증

```bash
cd services/core && test -x ./bin/plane-smoke && go test -count=1 ./cmd/plane-smoke
```

예상 결과: wrapper가 executable이고 command package 테스트가 통과한다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `services/core/cmd/plane-smoke/main.go` | PLANE-1 |
| `services/core/cmd/plane-smoke/main_test.go` | PLANE-1 |
| `services/core/bin/plane-smoke` | PLANE-1 |
| `services/core/README.md` | PLANE-2 |
| `agent-ops/roadmap/milestones/plane-thin-e2e-loop.md` | PLANE-2 |

## 최종 검증

```bash
cd services/core && go test -count=1 ./...
```

예상 결과: 모든 core package 테스트가 통과한다. cache output은 허용하지 않는다.

```bash
cd services/core && test -n "${PLANE_TOKEN:-}" && PLANE_BASE_URL="https://plane.toki-labs.com" PLANE_TOKEN="$PLANE_TOKEN" PLANE_WORKSPACE_SLUG="general" PLANE_PROJECT_ID="a6beb42f-7a8a-410c-b50f-ea3ca94828f3" PLANE_WORK_ITEM_ID="<test-work-item-id>" PLANE_SMOKE_COMMENT="NomadCode core Plane smoke" PLANE_SMOKE_STATE_ID="ea2e5b48-8bf1-4723-b749-de7723be41e9" PLANE_SMOKE_APPLY=1 ./bin/plane-smoke
```

예상 결과: `lookup ok`, `comment ok`, `state update ok`가 출력되고 token은 출력되지 않는다. `PLANE_TOKEN` 또는 test work item이 없으면 이 task는 완료 처리하지 말고 blocker를 `CODE_REVIEW-cloud-G07.md`에 기록한다.

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