# Plan - API ## 이 파일을 읽는 구현 에이전트에게 구현 후 active `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 반드시 실제 구현 내용과 검증 출력으로 채운다. 검증을 실행하고, active 파일은 그대로 둔 뒤 리뷰 준비를 보고한다. 최종 판정, log rename, `complete.log`, archive 이동은 code-review 스킬 전용이다. 선택된 Milestone의 `구현 잠금 > 결정 필요` 항목이 실구현을 막을 때만 review stub의 `사용자 리뷰 요청` 섹션을 채우고 멈춘다. 구현 중 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 환경, secret, 외부 서비스, 검증 evidence 공백은 사용자 리뷰 요청이 아니라 검증 결과나 follow-up 대상이다. ## 배경 Agent-origin push는 Gito `branch.updated`를 wakeup으로 받지만, Plane mutation 전에 `after` revision을 fetch/scan해서 active Milestone 상태를 다시 확인해야 한다. 현재 scanner, bridge, pipeline은 부분 구현되어 있으나 `before..after` diff hint와 전체 active index의 결합, missing-create idempotency, identity conflict evidence를 하나의 완료 단위로 고정해야 한다. ## 사용자 리뷰 요청 흐름 사용자 리뷰 요청은 선택된 Milestone lock decision만 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 직접 사용자 prompt는 금지이며, code-review가 정당성을 검증하고 실제 `USER_REVIEW.md` 작성을 소유한다. ## Roadmap Targets - Milestone: `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/agent-origin-milestone-creation-sync.md` - Task ids: - `scan-diff`: Gito webhook의 `before..after` 변경 목록을 wakeup 힌트로 사용하되, `after` SHA 검증 뒤 active Milestone diff와 전체 active index를 함께 스캔한다. - `missing-create`: provider identity가 없는 active Milestone을 agent-origin 생성 후보로 판별하고 Plane work item을 생성한다. - `identity-ledger`: 생성된 Plane work item id를 `roadmap_sync_identities`에 filename slug 기준으로 upsert하고 revision/idempotency conflict를 구분한다. - Completion mode: check-on-pass ## 분석 결과 ### 읽은 파일 - `agent-roadmap/current.md` - `agent-roadmap/phase/agent-ops-mcp-control-plane/PHASE.md` - `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/agent-origin-milestone-creation-sync.md` - `agent-roadmap/sdd/agent-ops-mcp-control-plane/agent-origin-milestone-creation-sync/SDD.md` - `agent-contract/index.md` - `agent-contract/outer/gito-branch-webhook-consumer-v1.md` - `agent-test/local/rules.md` - `agent-test/local/core-smoke.md` - `agent-test/local/workspace-ops-smoke.md` - `services/core/internal/gitoevents/events.go` - `services/core/internal/gitoevents/events_test.go` - `services/core/internal/gitosync/scanner.go` - `services/core/internal/gitosync/scanner_test.go` - `services/core/internal/gitosync/bridge.go` - `services/core/internal/gitosync/bridge_test.go` - `services/core/internal/gitosync/runner.go` - `services/core/internal/gitosync/runner_test.go` - `services/core/internal/roadmapsync/identity.go` - `services/core/internal/roadmapsync/milestone_parser.go` - `services/core/internal/roadmapsync/develop_match.go` - `services/core/internal/roadmapsync/develop_match_test.go` - `services/core/internal/roadmapsync/retry.go` - `services/core/internal/roadmapsync/retry_test.go` - `services/core/internal/roadmapsyncpipeline/service.go` - `services/core/internal/roadmapsyncpipeline/service_test.go` - `services/core/internal/storage/roadmap_sync_identities.go` - `services/core/internal/storage/roadmap_sync_identities_test.go` - `services/core/internal/storage/roadmap_sync_steps.go` - `services/core/internal/storage/roadmap_sync_steps_test.go` - `services/core/internal/adapters/plane/client.go` - `services/core/internal/adapters/plane/client_test.go` - `services/core/internal/workitem/provider.go` - `services/core/internal/http/gito_webhook.go` - `services/core/internal/http/gito_webhook_test.go` - `services/core/internal/config/config.go` - `services/core/internal/config/config_test.go` - `services/core/internal/scheduler/roadmap_sync_jobs.go` - `services/core/internal/scheduler/jobs.go` - `services/core/queries/roadmap_sync_identities.sql` - `services/core/queries/roadmap_sync_steps.sql` - `services/core/migrations/00006_create_roadmap_sync_identities.sql` - `services/core/migrations/00007_create_roadmap_sync_steps.sql` - `services/core/migrations/00009_add_roadmap_sync_milestone_id.sql` ### SDD 기준 - SDD: `agent-roadmap/sdd/agent-ops-mcp-control-plane/agent-origin-milestone-creation-sync/SDD.md` - 상태: `[승인됨]`, SDD 잠금 해제, 사용자 리뷰 없음. - Targeted scenarios: S01=`scan-diff`, S02=`missing-create`, S03=`identity-ledger`. - Evidence Map rows: S01 requires gitosync scanner/bridge unit and webhook handler tests; S02 requires roadmapsyncpipeline missing-create idempotency test; S03 requires storage identity conflict/upsert test. - 구현 checklist는 `changed_files` 단독 신뢰 금지, `after` SHA verification, missing identity create idempotency, slug/provider conflict evidence 순서로 역산한다. ### 테스트 환경 규칙 - test_env: `local`. - `agent-test/local/rules.md` present/read. - Matched profiles read: `agent-test/local/core-smoke.md`, `agent-test/local/workspace-ops-smoke.md`. - 적용 명령: `git diff --check`, `cd services/core && go test ./...`, 필요 시 `cd services/core && go vet ./...`. - Current preflight: repo root `/config/workspace/nomadcode`, branch `main`, HEAD `d80abe83579dd0d6fbc86b1a74c83db6d18c756d`, `go version go1.26.2 linux/arm64`, `git version 2.43.0`. - 원격 runner 검증이 필요하면 `toki@toki-labs.com:$HOME/agent-work/nomadcode`를 현재 변경과 동기화한 뒤 같은 명령을 실행한다. 현재 계획 작성 시에는 local checkout에서 `git diff --check`, focused `go test`, `go vet`를 PASS로 확인했다. ### 테스트 커버리지 공백 - `scan-diff`: existing scanner tests cover `after` mismatch, active index, archive exclusion, duplicate active slug. Gap: event `changed_files` or `before..after` hint is not carried through HTTP handler/scanner as an explicit relevance hint. - `missing-create`: bridge and pipeline tests cover identity-less docs, Plane create, and create result ledger upsert. Gap: same slug+same revision duplicate retry should assert no duplicate create after ledger exists. - `identity-ledger`: storage tests cover slug upsert and provider-work-item unique conflict. Gap: pipeline should expose conflict/not-ready reason without provider mutation for existing slug bound to a different provider item. ### 심볼 참조 - No renamed or removed symbols planned in this subtask. New fields or helper symbols must be grepped before use. ### 분할 판단 - Split decision policy evaluated before file selection. - Shared task group: `agent-task/m-agent-origin-milestone-creation-sync/`. - Sibling subtasks: - `01_scan_create_ledger`: independent foundation for S01-S03. - `02+01_identity_backfill`: depends on 01 because it needs created identity rows and work item ids. - `03+01,02_self_loop_update_conflict`: depends on 01 and 02 because self-loop is meaningful after backfill. - `04+01,02,03_field_smoke`: depends on all prior subtasks. - This plan is the first independent subtask; no predecessor completion is required. ### 범위 결정 근거 - Exclude identity backfill commit/push, self-loop no-op, provider identity update projection, conflict policy hardening, and field smoke. Those are separate dependent plans. - Exclude Plane `Todo -> In Progress` lifecycle and child ticket sync per Milestone 범위 제외. ### 빌드 등급 - `cloud-G07`: core/provider/DB/webhook behavior spans multiple packages and external contract semantics; local tests are strong, but a wrong implementation could pass unit tests while violating Gito/Plane sync promises. ## 구현 체크리스트 - [ ] `scan-diff` 경로에서 Gito `changed_files`/`before..after`는 wakeup hint로만 보존하고, `after` SHA fetch/verify와 active Milestone scan 없이는 enqueue하지 않는다. 검증: `changed_files`만 신뢰하지 않고 `origin/develop`의 `after` SHA가 일치할 때만 sync job이 enqueue된다. - [ ] `missing-create` 경로에서 identity-less active Milestone이 Plane create 후보가 되고, 같은 milestone slug와 revision 재처리에서 duplicate create가 발생하지 않도록 ledger/idempotency 테스트를 보강한다. 검증: 같은 milestone slug와 revision을 재처리해도 중복 Plane work item을 만들지 않는다. - [ ] `identity-ledger` 경로에서 filename slug 기준 upsert와 provider work item conflict가 provider mutation 없이 `conflict`로 구분되는지 pipeline/storage 테스트를 보강한다. 검증: 기존 slug가 다른 provider work item에 묶여 있으면 provider mutation 없이 conflict로 멈춘다. - [ ] `git diff --check`, focused `go test -count=1`, focused `go vet`를 실행한다. - [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. ## [API-1] Scan Hint And Revision Gate ### 문제 `services/core/internal/http/gito_webhook.go:22`의 HTTP event shape는 `changed_files`를 handler seam으로 전달하지 않고, `services/core/internal/gitosync/scanner.go:133`은 전체 active/archive tree만 읽는다. SDD S01은 `before..after` 변경 목록을 wakeup hint로 보존하되 `after` 검증 뒤 scan해야 한다. ### 해결 방법 HTTP/proto-socket event에서 이미 존재하는 `gitoevents.ChangedFile` 형태를 bridge/scanner 입력까지 유지하고, scanner는 hint를 로그/결정 근거로만 사용한다. enqueue 여부는 계속 `git fetch`, `git rev-parse refs/remotes//`, target revision active index scan 결과로만 결정한다. Before: ```go // services/core/internal/http/gito_webhook.go:22 type GitoBranchUpdatedEvent struct { RepoID string Branch string Before string After string } ``` After: ```go type GitoBranchUpdatedEvent struct { RepoID string Branch string Before string After string ChangedFiles []gitoevents.ChangedFile } ``` ### 수정 파일 및 체크리스트 - [ ] `services/core/internal/http/gito_webhook.go`: validated `changed_files`를 typed event로 전달한다. - [ ] `services/core/internal/http/gito_webhook_test.go`: changed_files가 handler로 전달되고 malformed shape는 400인 테스트를 유지/보강한다. - [ ] `services/core/internal/gitosync/scanner.go`: hint와 full active scan의 책임을 주석/결과 타입에 명시한다. - [ ] `services/core/internal/gitosync/scanner_test.go`: hint가 비어 있거나 다른 파일만 있어도 after verified active index가 source of truth임을 테스트한다. - [ ] `services/core/internal/gitosync/runner_test.go`: wire event changed_files가 scanner로 유지되는 assertion을 추가한다. ### 테스트 작성 Regression tests required for S01. Add or update tests named around `ChangedFilesAreHintOnly` in HTTP/gitosync packages. ### 중간 검증 ```sh cd services/core && go test -count=1 ./internal/http ./internal/gitoevents ./internal/gitosync ``` Expected: PASS, no duplicate enqueue when `after` is not verified. ## [API-2] Missing Create And Ledger Idempotency ### 문제 `services/core/internal/gitosync/bridge.go:137` builds missing-create jobs, and `services/core/internal/roadmapsyncpipeline/service.go:158` creates Plane work items when no existing identity row exists. The existing path needs explicit same-slug/same-revision duplicate retry coverage and conflict evidence for S02/S03. ### 해결 방법 Keep the existing missing-create branch, but make tests assert that a persisted slug identity prevents a second create and that provider-work-item unique conflicts return `SyncActionConflict` before any additional provider mutation. Before: ```go // services/core/internal/roadmapsyncpipeline/service.go:158 } else if in.Expected.WorkItemID == "" && in.Ref.ID == "" { creator, ok := s.provider.(workitem.Creator) ... createRes, err := creator.CreateWorkItem(ctx, workitem.CreateInput{...}) ``` After: ```go // same branch, but covered by an existing-row retry test: // found=true with completed develop_matched must not call CreateWorkItem again. ``` ### 수정 파일 및 체크리스트 - [ ] `services/core/internal/roadmapsyncpipeline/service_test.go`: add duplicate retry test for missing-create existing identity. - [ ] `services/core/internal/roadmapsyncpipeline/service_test.go`: assert conflict result does not call create/body/status after upsert conflict. - [ ] `services/core/internal/storage/roadmap_sync_identities_test.go`: keep provider work item unique conflict coverage aligned with slug upsert. ### 테스트 작성 Regression tests required for S02/S03. No production behavior change is needed if existing code already satisfies the tests; if a test fails, fix the minimal production path. ### 중간 검증 ```sh cd services/core && go test -count=1 ./internal/roadmapsyncpipeline ./internal/storage ./internal/gitosync ``` Expected: PASS; fake provider `CreateWorkItem` call count stays one across duplicate retry. ## [API-3] Review Stub Completion ### 문제 Roadmap completion requires review evidence copied from active `CODE_REVIEW-*-G??.md`; implementation is incomplete until the review stub is filled. ### 해결 방법 Fill completion table, checklist, plan deviations, design decisions, and exact command outputs in `CODE_REVIEW-cloud-G07.md`. ### 수정 파일 및 체크리스트 - [ ] `agent-task/m-agent-origin-milestone-creation-sync/01_scan_create_ledger/CODE_REVIEW-cloud-G07.md`: fill implementation-owned sections only. ### 테스트 작성 No test file; this is required implementation evidence. ### 중간 검증 ```sh git diff --check ``` Expected: no output. ## 수정 파일 요약 | 파일 | 항목 | |------|------| | `services/core/internal/http/gito_webhook.go` | API-1 | | `services/core/internal/http/gito_webhook_test.go` | API-1 | | `services/core/internal/gitosync/scanner.go` | API-1 | | `services/core/internal/gitosync/scanner_test.go` | API-1 | | `services/core/internal/gitosync/runner_test.go` | API-1 | | `services/core/internal/roadmapsyncpipeline/service.go` | API-2 | | `services/core/internal/roadmapsyncpipeline/service_test.go` | API-2 | | `services/core/internal/storage/roadmap_sync_identities_test.go` | API-2 | | `agent-task/m-agent-origin-milestone-creation-sync/01_scan_create_ledger/CODE_REVIEW-cloud-G07.md` | API-3 | ## 최종 검증 ```sh git diff --check cd services/core && go test -count=1 ./internal/http ./internal/gitoevents ./internal/gitosync ./internal/roadmapsync ./internal/roadmapsyncpipeline ./internal/storage cd services/core && go vet ./internal/http ./internal/gitoevents ./internal/gitosync ./internal/roadmapsync ./internal/roadmapsyncpipeline ./internal/storage ``` Expected: all commands exit 0. Fresh `-count=1` is required for Go tests; cached output is not acceptable for this plan. 모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.