<!-- task=m-milestone-work-item-creation-sync/04+03_plane_todo_projection plan=0 tag=API -->

# Code Review Reference - API

> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
> Fill implementation-owned sections, run verification, keep active files in place, and report ready for review. Do not prompt the user directly during implementation.

## 개요

date=2026-06-13
task=m-milestone-work-item-creation-sync/04+03_plane_todo_projection, plan=0, tag=API

## Roadmap Targets

- Milestone: `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/milestone-work-item-creation-sync.md`
- Task ids:
  - `cycle-plane-todo`: 원본 Plane 본문을 `사용자 요청:` 댓글로 보존하고, `develop`의 Milestone 내용으로 Plane 본문/제목을 갱신한 뒤 `Todo`로 이동한다.
- Completion mode: check-on-pass

## 이 파일을 읽는 리뷰 에이전트에게

> **[REVIEW AGENT ONLY]** 종결 절차는 코드리뷰 에이전트 전용이다.

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| [API-1] Plane Body And Title Patch | [x] |
| [API-2] Projection Service Ordering | [x] |
| [API-3] Plane Body Formatter | [x] |

## 구현 체크리스트

- [x] Plane adapter에 work item body/title update capability를 additive로 추가한다.
- [x] Projection service가 댓글 보존 -> body/title patch -> Todo status 이동 순서로 실행하고 중간 실패 시 다음 단계를 건너뛰게 한다.
- [x] Milestone 내용을 Plane body로 변환하는 작은 formatter를 추가하고 제목은 `[milestone-id] 제목`으로 만든다.
- [x] develop projection candidate가 없으면 no-op 또는 explicit not-ready error를 반환한다.
- [x] `cd services/core && go test ./...`와 `git diff --check`를 실행한다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 코드리뷰 전용 체크리스트

- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [x] active 파일을 `.log`로 아카이브하고 PASS이면 `complete.log` 작성 후 task directory를 archive로 이동한다.
- [x] PASS이고 task group이 `m-<milestone-slug>`이면 런타임 완료 이벤트 메타데이터를 보고한다.

## 계획 대비 변경 사항

- 의존성 상태 차이: 계획 본문은 predecessor `03_develop_match`의 `complete.log`가 missing이라 명시하지만, 해당 산출물은 이후 `roadmapsync` 패키지로 커밋(`dde07f9`)되어 코드 의존성(`ProjectionCandidate`, `Identity`, `Revision`, `MatchDevelopMilestone`)이 이미 존재한다. 따라서 코드 차원 선행 의존성은 충족된 상태로 보고 구현을 진행했다. `03` task 디렉터리는 active tree에 없고 archive 접근은 하지 않았다.
- API-1 provider.go 반영 방식: adapter에 `UpdateWorkItem`만 추가하는 대신, provider-neutral 경계 유지를 위해 `workitem`에 `CapabilityBodyProjection`, `BodyProjector` 인터페이스, `BodyProjection` 타입을 추가하고 Plane `Client.ProjectBody`로 구현했다. 이는 status projection과 body projection을 독립 단계로 유지하라는 도메인 규칙(상태/본문 분리)에 맞춘 선택이다.
- 기존 `UpdateIssueStatus`는 제거 없이 `UpdateWorkItem` wrapper로 재구성해 호출부 호환을 유지했다(계획의 "wrapper로 유지" 허용 범위).

## 주요 설계 결정

- PATCH는 non-empty field만 담는다(`UpdateWorkItem`이 `name`/`description_html`/`state`를 개별 판단). blank title/body가 기존 값을 지우지 않으며, 모든 field가 비면 `ErrInvalidInput`으로 no-op patch를 막는다.
- `ProjectPlaneTodo`는 provider DTO를 직접 받지 않고 `PlaneTodoProvider`(Commenter+BodyProjector+StatusProjector) facet과 neutral 입력만 받는다. 순서는 comment(`사용자 요청:` 보존) → body/title → Todo status이며, 각 단계 실패 시 다음 단계를 호출하지 않는다.
- not-ready 후보는 provider를 한 번도 호출하지 않고 `ErrNotReady`를 반환한다. 필수 입력(ref/original body/todo state/milestone markdown) 누락은 `ErrInvalidProjectionInput`으로 조기 차단한다.
- formatter는 Milestone 제목 + `목표`/`범위` 섹션만 HTML-escape하여 본문에 싣는다. Provider identity 블록(provider, work item id 등 secret-like metadata)은 본문 섹션이 아니므로 투영에서 제외된다. 제목은 `[<milestone-slug>] 제목` 형식이며 slug는 Milestone 파일명에서 도출한다.
- 실제 Plane dev server Todo 이동 smoke는 plan 범위 밖이므로 adapter httptest와 projection fake로 보존 순서/patch body/status를 검증했다. raw token은 코드/테스트/문서에 기록하지 않았다.

## 사용자 리뷰 요청

- 상태: 없음
- 사유 유형: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 자동 후속 불가 이유: 없음
- 재개 조건: 없음

## 리뷰어를 위한 체크포인트

- comment 보존 실패가 body/status mutation을 막는지 확인한다.
- Todo 이동이 develop projection candidate 없이 실행되지 않는지 확인한다.
- Plane custom field 전제를 넣지 않았는지 확인한다.

## 검증 결과

### API-1 중간 검증
```bash
$ cd services/core && go test ./internal/adapters/plane ./internal/workitem
ok  	github.com/nomadcode/nomadcode-core/internal/adapters/plane	0.007s
ok  	github.com/nomadcode/nomadcode-core/internal/workitem	0.003s
```

### API-2 / API-3 중간 검증
```bash
$ cd services/core && go test ./internal/roadmapsync ./internal/adapters/plane
ok  	github.com/nomadcode/nomadcode-core/internal/roadmapsync	0.003s
ok  	github.com/nomadcode/nomadcode-core/internal/adapters/plane	(cached)
```

### 최종 검증
```bash
$ cd services/core && go test ./...
ok  	github.com/nomadcode/nomadcode-core/cmd/plane-smoke	(cached)
?   	github.com/nomadcode/nomadcode-core/cmd/server	[no test files]
ok  	github.com/nomadcode/nomadcode-core/internal/adapters/a2a	(cached)
ok  	github.com/nomadcode/nomadcode-core/internal/adapters/jira	0.008s
ok  	github.com/nomadcode/nomadcode-core/internal/adapters/mattermost	(cached)
ok  	github.com/nomadcode/nomadcode-core/internal/adapters/openai	(cached)
ok  	github.com/nomadcode/nomadcode-core/internal/adapters/plane	(cached)
ok  	github.com/nomadcode/nomadcode-core/internal/authoring	(cached)
ok  	github.com/nomadcode/nomadcode-core/internal/config	(cached)
ok  	github.com/nomadcode/nomadcode-core/internal/http	0.004s
ok  	github.com/nomadcode/nomadcode-core/internal/notification	(cached)
ok  	github.com/nomadcode/nomadcode-core/internal/projectsync	(cached)
ok  	github.com/nomadcode/nomadcode-core/internal/protosocket	0.010s
ok  	github.com/nomadcode/nomadcode-core/internal/roadmapsync	(cached)
ok  	github.com/nomadcode/nomadcode-core/internal/scheduler	(cached)
ok  	github.com/nomadcode/nomadcode-core/internal/storage	(cached)
ok  	github.com/nomadcode/nomadcode-core/internal/workflow	(cached)
ok  	github.com/nomadcode/nomadcode-core/internal/workitem	(cached)
ok  	github.com/nomadcode/nomadcode-core/internal/workitempipeline	0.003s

$ git diff --check
OK: no whitespace errors
```

---

## 코드리뷰 결과

- 종합 판정: PASS
- 차원별 평가:
  - correctness: Pass
  - completeness: Pass
  - test coverage: Pass
  - API contract: Pass
  - code quality: Pass
  - plan deviation: Pass
  - verification trust: Pass
- 발견된 문제: 없음
- 리뷰 중 조치: `services/core/internal/roadmapsync/plane_format_test.go`의 formatting-only drift를 `gofmt`로 정리했다. 동작/API 변경은 없다.
- 검증:
  - `cd services/core && go test ./internal/adapters/plane ./internal/workitem` - PASS
  - `cd services/core && go test ./internal/roadmapsync ./internal/adapters/plane` - PASS
  - `cd services/core && go test ./...` - PASS
  - `cd services/core && gofmt -l ./internal/roadmapsync ./internal/adapters/plane ./internal/workitem` - PASS; output 없음
  - `git diff --check` - PASS; output 없음
- 다음 단계: PASS 절차로 active plan/review를 `.log`로 아카이브하고 `complete.log` 작성 후 task directory를 archive로 이동한다.
