<!-- task=m-milestone-work-item-creation-sync/03+02_develop_match 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/03+02_develop_match, 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-develop-sync`: sync layer가 `branches/develop` 또는 remote `develop`에서 pushed Milestone을 감지하고 원래 Plane 티켓 identity와 매칭한다.
- Completion mode: check-on-pass

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

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

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| [API-1] Roadmap Sync Identity Model | [x] |
| [API-2] Milestone Identity Parser | [x] |
| [API-3] Develop Projection Candidate Gate | [x] |

## 구현 체크리스트

- [x] `develop` branch workspace 또는 remote revision scan 결과를 표현하는 `roadmapsync` domain model을 추가한다.
- [x] Milestone 파일에서 provider, tenant, project, work item id, milestone path/id를 추출하는 parser를 추가한다.
- [x] pushed/scanned Milestone이 원래 Plane work item identity와 일치할 때만 projection-ready result를 반환한다.
- [x] slot local dirty path나 push 실패 result는 projection-ready가 아니도록 테스트한다.
- [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로 이동한다.
- [ ] PASS이고 task group이 `m-<milestone-slug>`이면 런타임 완료 이벤트 메타데이터를 보고한다.

## 계획 대비 변경 사항

- 계획대로 `services/core/internal/roadmapsync` 패키지에 6개 파일(identity, milestone_parser, develop_match 및 각 test)을 추가했고 기존 파일 수정 없이 신규 패키지로 한정했다.
- 계획의 typed shape `Identity`, `Revision`, `ScanResult`, `ProjectionCandidate`를 모두 contract note 5.2 필드명에 맞춰 추가했다. 계획에 없던 `ErrNoProviderIdentity` sentinel을 parser에 추가했는데, "block 없음"과 "block은 있으나 필수 필드 누락"을 같은 not-ready 신호로 호출자에 전달하면서 generic `ErrInvalidIdentity`와 구분하기 위함이다.
- `MatchDevelopMilestone`은 계획대로 develop revision/branch, changed files, expected identity를 입력받아 `ProjectionCandidate{Ready, Reason}`을 반환한다. 계획 문구의 "slot local path만 들어온 경우 false"는 slot scan이 develop pushed revision을 갖지 못한다는 점을 이용해 `revision==""`를 `reasonNoRevision` not-ready로 처리하는 방식으로 구현했다.

## 주요 설계 결정

- **Identity 최소 match key**: 정규화는 `provider + work_item_id + roadmap_milestone_path`를 필수로 강제한다. milestone 마일스톤은 path를 외부(파일 경로)에서 주입하므로 parser가 block 내부에 path를 중복 기재하도록 요구하지 않는다. authoring_run_id는 milestone 문서 epic [provider-identity] 검증 문구대로 필수 매칭 키에서 제외했다.
- **Parser 엄격성**: ad hoc string scan을 `scanIdentityFields`/`parseFieldLine` 내부로 제한하고 외부 API는 typed `Identity`만 반환한다. heading(`## Provider identity`) block과 단일 `- Provider identity:` bullet block 두 형식을 지원하되, 다음 heading을 만나면 block을 닫아 `완료 리뷰` 같은 후속 섹션의 provider 라인을 흡수하지 않는다. 필수 필드가 빠진 block은 조용히 성공하지 않고 `ErrNoProviderIdentity`로 거부한다.
- **Develop gate**: `develop` 외 branch, pushed revision 없음(slot local/push 실패), milestone path 불일치, milestone 형식이 아닌 변경 파일을 모두 별도 stable reason 문자열로 분리해 sync layer가 원인별로 분기할 수 있게 했다. milestone 경로 판정은 `agent-roadmap/phase/<phase>/milestones/<file>.md` 5-segment 형태를 정확히 요구한다.
- **Revision 기본값**: Milestone sync source-of-truth branch는 contract note대로 `develop`을 기본값으로 둔다.

## 사용자 리뷰 요청

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

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

- slot local file이나 push 실패만으로 projection-ready가 되지 않는지 확인한다.
- Milestone parser가 provider identity 누락을 조용히 성공 처리하지 않는지 확인한다.
- sync identity field가 contract note와 충돌하지 않는지 확인한다.

## 검증 결과

### API-1/API-2/API-3 중간 검증
```bash
$ cd services/core && go test ./internal/roadmapsync
ok  	github.com/nomadcode/nomadcode-core/internal/roadmapsync	0.003s
$ gofmt -l ./internal/roadmapsync/
(no output)
$ go vet ./internal/roadmapsync
(no output)
```

### 최종 검증
```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	(cached)
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)
?   	github.com/nomadcode/nomadcode-core/internal/agent	[no test files]
ok  	github.com/nomadcode/nomadcode-core/internal/authoring	(cached)
ok  	github.com/nomadcode/nomadcode-core/internal/config	(cached)
?   	github.com/nomadcode/nomadcode-core/internal/db	[no test files]
ok  	github.com/nomadcode/nomadcode-core/internal/http	(cached)
?   	github.com/nomadcode/nomadcode-core/internal/model	[no test files]
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	(cached)
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	(cached)

$ git add -N services/core/internal/roadmapsync/ && git diff --check
(no output, exit 0)
```

---

## 코드리뷰 결과

- 종합 판정: FAIL
- 차원별 평가:
  - correctness: Fail
  - completeness: Fail
  - test coverage: Fail
  - API contract: Pass
  - code quality: Pass
  - plan deviation: Fail
  - verification trust: Pass
- 발견된 문제:
  - Required: `MatchDevelopMilestone`가 changed path와 expected identity 유효성만으로 `Ready=true`를 반환합니다. 현재 `ScanResult`에는 pushed Milestone 문서에서 파싱한 identity가 없고, `MatchDevelopMilestone`도 `ParseMilestoneIdentity` 결과나 `Identity.Matches`를 사용하지 않습니다. 따라서 `agent-roadmap/.../milestones/foo.md`가 변경되기만 하면 그 파일 안의 provider/work item id가 누락되었거나 다른 Plane 티켓이어도 projection-ready가 됩니다. `services/core/internal/roadmapsync/develop_match.go:38`에서 scanned Milestone identity(또는 milestone contents/parser result)를 입력받아 expected identity와 비교하고, missing/mismatched provider identity는 not-ready reason으로 반환하세요. 회귀 테스트는 "changed milestone path matches but parsed provider identity is missing/mismatched -> not ready"를 추가하세요.
  - Required: bullet형 `Provider identity` block parser가 block의 indent/end boundary를 지키지 않습니다. `scanIdentityFields`는 각 줄을 `strings.TrimSpace`한 뒤 `- Provider identity:`를 만나면 `inBlock=true`로 두고, 다음 heading 전까지 모든 `key: value` bullet을 field로 흡수합니다. 이 때문에 nested bullet이 아닌 뒤쪽 sibling bullet의 `provider`나 `work item id`도 identity로 오인할 수 있습니다. `services/core/internal/roadmapsync/milestone_parser.go:95`에서 raw indentation을 보존해 parent bullet보다 깊은 child bullet만 허용하고, blank/outdent/non-child 또는 다음 heading에서 bullet block을 닫도록 고치세요. 회귀 테스트는 `- Provider identity:` 아래에 nested 필드가 없고 뒤쪽 top-level bullet에 `work item id`가 있는 경우 `ErrNoProviderIdentity`가 나는지 확인해야 합니다.
- 다음 단계: FAIL follow-up으로 다음 active `PLAN-cloud-G07.md`와 `CODE_REVIEW-cloud-G07.md`를 작성한다.
