E2E Playbook (AI 에이전트용 단일 가이드)
docs/E2E_PLAYBOOK.md
E2E Playbook (AI 에이전트용 단일 가이드)
이 문서는 Claude/Codex 에이전트가 MAKITT 환경에서 E2E 작업을 시작하기 전에 반드시 먼저 읽는 단일 진입점이다. 모든 skill, 인증, 우회 헤더, 표준 시나리오, 포트, 트러블슈팅이 한 자리에 있다.
갱신 규칙: 새로운 skill 추가 / 인증 surface 변경 / dev URL 변경 시 이 문서를 즉시 업데이트한다. 이 문서가 stale 되면 모든 후속 세션이 잘못된 가이드를 따른다.
0. 1분 cheat sheet
# 1) admin token 발급 (셀러 사칭, makitt-api 대상) curl -sS -X POST http://localhost:8080/api/v1/admin/developer/token/email \ -H 'Content-Type: application/json' \ -d '{"email":"chance@makitt.io"}' # → { accessToken, refreshToken, userId, email } # 2) shop-api dev-bypass (스토어프론트 검증, 고객 인증 우회) curl -sS http://localhost:10000/shop/<shopId>/products/by-slug/<slug> \ -H 'Authorization: Bearer dev-bypass' \ -H 'X-Dev-Language: ja' \ -H 'X-Dev-Currency: JPY' # 3) HCS 라이브 변경 — compiler /save 단일 통로 (절대 우회 금지) # → /hcs-save skill 사용 # 4) 표준 E2E 템플릿 node scripts/e2e/product-i18n-form-submit.mjs
핵심 룰:
- HCS 변경은
/hcs-save단일 통로 (CLAUDE.md HCS 섹션 +/hcs-saveskill 참조) - 코드 수정 전 사용자 명시 승인 필수 (CLAUDE.md "코드 수정 원칙")
- e2e.md 핸드오프 보드 우선 — spec 폴더에
e2e.md있으면 세션 시작 시 먼저 읽기
1. 인증 / 우회 (정확히 2 surface)
1.1 makitt-api admin developer token (Bearer JWT)
용도: 셀러 어드민(web/builder) API 를 셀러 권한으로 호출. E2E 스크립트가 셀러를 사칭할 때 사용.
엔드포인트 (makitt-server/makitt-api/src/main/java/com/makitt/api/controller/admin/DeveloperController.java):
| Method | Path | 동작 |
|---|---|---|
POST | /api/v1/admin/developer/token | 하드코딩 dev 유저 (00000000-0000-0000-0000-000000000001, developer@makitt.com) 의 JWT 발급 |
POST | /api/v1/admin/developer/token/email | body {"email":"<email>"} → 해당 유저 JWT 발급 (E2E 에서 가장 많이 씀) |
사용:
TOKEN=$(curl -sS -X POST http://localhost:8080/api/v1/admin/developer/token/email \ -H 'Content-Type: application/json' \ -d '{"email":"chance@makitt.io"}' | jq -r '.accessToken') curl -sS http://localhost:8080/api/v1/products/<productId>/detail \ -H "Authorization: Bearer $TOKEN"
스코프: 발급된 유저 권한 전체 (해당 셀러의 shop, product, translations, orders 등). 7-day refresh token 함께 발급되어 DDB 에 저장됨.
스크립트 표준: MAKITT_ADMIN_TOKEN env 또는 /tmp/argo-e2e-token 파일을 fallback 으로 읽는 패턴이 일부 셸 스크립트에 있음 (예: scripts/seed-argo-partner-meta.sh). 신규 .mjs 스크립트는 매 실행마다 /token/email 로 새로 받는 게 권장 패턴.
1.2 makitt-shop-api dev-bypass header (고객 인증 우회)
용도: 스토어프론트 (/shop/{shopId}/**) API 를 진짜 고객 로그인 없이 호출. PDP / cart / checkout / 검색 검증.
구현: makitt-server/makitt-shop-api/src/main/java/com/makitt/shop/filter/ShopContextFilter.java (lines 60-64 상수, 121 분기, 277-344 dev-bypass 처리, 346-348 profile gate).
활성 조건: spring.profiles.active 가 local 또는 dev 를 포함할 때만. prod 에서는 silently 무시되고 익명 흐름으로 떨어짐 — 고객 인증 우회로 prod 노출 위험 없음.
헤더:
| 헤더 | 형식 | 효과 |
|---|---|---|
Authorization | Bearer dev-bypass | (필수) 우회 활성화 |
X-Dev-Customer-Id | cust_xxx | DB 의 진짜 고객 로딩 → 인증된 컨텍스트 |
X-Dev-Email | test@example.com | customer-id 없을 때 simulated 컨텍스트의 식별자 |
X-Dev-Language | ko | en | ja | i18n 추론 override (translation overlay 적용) |
X-Dev-Currency | KRW | USD | JPY | 가격 컨텍스트 + featuredPriceLabel override |
예시 (henriette i18n 검증):
curl -sS http://localhost:10000/shop/71261f65-ee4e-4238-9b4f-d56cee9be523/products/by-slug/cephira-shot-jellyfish-pore-serum \ -H 'Authorization: Bearer dev-bypass' \ -H 'X-Dev-Language: ja' \ -H 'X-Dev-Currency: JPY'
스코프: /shop/{shopId}/** 모든 라우트. Admin makitt-api 에는 무효.
참고 메모: .agents/memory/shop_api_dev_bypass_i18n.md
중요: MAKITT_DEV_BYPASS / DEV_TOKEN 같은 env var 는 존재하지 않는다. 우회는 오직 위 헤더로만.
1.3 기타 인증 surface
- AWS SSO:
aws sso login --profile makitt-dev— DDB/S3/SES/OpenSearch/Secrets Manager 직접 접근 시 필수.run-server.sh가 만료 자동 갱신. - Argo internal
x-api-token(Sugar/Verification):D606D91A99B6458A2AB277BA6E28ABA157200B3D371BC8FF21206C05EB3B83CD—/argo-testskill 내부에서만 사용. - Argo dev PG: Cluster A
argo/xpzmxkzk2021!(소문자 x), Cluster B<dbname>/Xpzmxkzk2020!(대문자 X,2020)./argo-dev-dbskill 통해서만. - 고객 진짜 로그인 (cookie):
POST /shop/{shopId}/auth/{login,signup,guest}/email-password→ HttpOnly cookieaccessToken설정. 프론트 SSR 검증 시 사용.
1.4 Admin order/checkout backdoor (E2E 픽스처 강제 전이)
용도: E2E 시나리오에서 주문을 임의 상태로 빠르게 만들어내기 위한 admin 강제 전이. 모두 makitt-api 의 admin 토큰 (§1.1) 으로 호출. 셀러 권한이므로 본인 샵의 주문만 조작 가능.
소스: makitt-api/src/main/java/com/makitt/api/controller/order/OrderController.java. Base path: /api/v1/shops/{shopId}/orders.
| 동작 | Method | Path | 노트 |
|---|---|---|---|
| 체크아웃 강제 결제완료 + Order 생성 | POST | /manual-payment/{checkoutId} | body { "processorType": "MANUAL", "processorInfo": "..." } 선택. checkout → PAID + Order 생성. E2E 결제 우회의 정공법 (mock processor reconcile 불필요) |
| 결제 대기 (PAYMENT_PENDING) Order 시드 | POST | /manual-payment-pending/{checkoutId} | (007 신설) Order 만 생성 (결제 단계 우회 — 상태 PAYMENT_PENDING). dev 한정 OrderApplication.createPendingOrder. 진짜 PG 안 거치고 PAYMENT_PENDING 케이스 fixture 가 필요할 때 (예: 본인 cancel 매트릭스). |
| 처리 시작 | POST | /{orderId}/process | PAID → PROCESSING |
| 배송 출발 | POST | /{orderId}/ship | body { trackingNumber, trackingCarrier, trackingUrl }. PROCESSING → SHIPPED |
| 배송 완료 | POST | /{orderId}/deliver | SHIPPED → DELIVERED |
| 셀러 취소 | POST | /{orderId}/cancel | ?reason= query. (셀러측 강제 취소 — 고객 cancel 과 분리) |
| 환불 | POST | /{orderId}/refund | body { amount, reason } |
| 통합 이벤트 dispatch | POST | /{orderId}/event | event 필드 + 파라미터로 모든 전이 처리 (가장 generic) |
E2E 표준 시퀀스 (Henriette 기준 — 한 주문을 PAID/PROCESSING/SHIPPED/DELIVERED 까지 빠르게 진행):
TOKEN=$(curl -sS -X POST http://localhost:8080/api/v1/admin/developer/token/email \ -H 'Content-Type: application/json' \ -d '{"email":"chance@makitt.io"}' | jq -r '.accessToken') # checkoutId 는 shop-api dev-bypass 흐름으로 미리 만들어 둠 SHOP_ID=71261f65-ee4e-4238-9b4f-d56cee9be523 H="-H Authorization:\ Bearer\ $TOKEN -H Content-Type:application/json" # 1) 결제완료 ORDER_ID=$(curl -sS -X POST "http://localhost:8080/api/v1/shops/$SHOP_ID/orders/manual-payment/$CHECKOUT_ID" \ $H -d '{"processorType":"MANUAL"}' | jq -r '.orderId') # 2) 처리 시작 curl -sS -X POST "http://localhost:8080/api/v1/shops/$SHOP_ID/orders/$ORDER_ID/process" $H # 3) 배송 시작 curl -sS -X POST "http://localhost:8080/api/v1/shops/$SHOP_ID/orders/$ORDER_ID/ship" $H \ -d '{"trackingNumber":"E2E-1","trackingCarrier":"CJ","trackingUrl":"https://example.com/track/E2E-1"}' # 4) 배송 완료 curl -sS -X POST "http://localhost:8080/api/v1/shops/$SHOP_ID/orders/$ORDER_ID/deliver" $H
주의:
- 위 admin 흐름은 Argo fulfillment 콜백 인프라 (
finished/argo-fulfillment-callbacks) 와 별개. Argo 풀루프 검증이 필요하면/argo-test로 partner shipment 이벤트를 발사하고 callback consumer 가 status 전이시키게 해야 함. - 고객 액션 (cancel/return/exchange) 은 위 admin endpoint 로 하지 말 것 —
shop-api의/shop/{shopId}/orders/{id}/...흐름 (§1.5) 으로 검증해야 진짜 E2E.
1.5 Customer order action endpoints (shop-api, 007 신설)
용도: 고객 본인의 주문에 대한 self-service 액션. admin backdoor (§1.4) 와 구분 — 이쪽이 진짜 고객 E2E. 6 액션 (그 중 review 는 별 흐름).
소스: makitt-server/makitt-shop-api/src/main/java/com/makitt/shop/controller/OrderController.java. Base path: /shop/{shopId}/orders/{orderId} (review 는 /shop/{shopId}/products/{productId}/reviews).
| 액션 | Method | Path | body 핵심 | 가용 status |
|---|---|---|---|---|
| 본인 cancel | POST | /{orderId}/cancel | { reason, reasonDetail? } | PAYMENT_PENDING / PAID / PROCESSING |
| 배송 확인 | POST | /{orderId}/confirm-delivery | (없음) | DELIVERED |
| 반품 신청 | POST | /{orderId}/return | { storeItemId, reason, reasonDetail? } | DELIVERED (확인 전 또는 일정 기간 내) |
| 교환 신청 | POST | /{orderId}/exchange | { storeItemId, reason, reasonDetail? } | DELIVERED (반품과 같은 윈도우) |
| 재주문 | POST | /{orderId}/reorder | (없음) | 모든 status — { checkoutId } 응답 → /checkout/{checkoutId} 로 navigate |
| 리뷰 작성 | POST | /products/{productId}/reviews | { orderId, rating, comment, images? } | DELIVERED 이후 본인 |
모두 OrderClaim 엔티티에 audit log 적재 + outbox event (OrderClaimRequested) 발행.
인증: 진짜 고객 cookie 또는 §1.2 dev-bypass + X-Dev-Customer-Id.
E2E 자동화 매트릭스: scripts/e2e/order-customer-actions.mjs (§3.4 참조) — 7 status × 6 액션 가용성 풀세트 검증.
2. Skill 카탈로그 (13개)
모든 skill 의 SSOT 는 .claude/commands/<name>.md. .agents/skills/<name>/SKILL.md 는 Codex 미러.
HCS 작업 (서버 SSOT, 최우선)
| Skill | 용도 | 핵심 |
|---|---|---|
/hcs-save | HCS 라이브 변경 저장 (단일 통로) | POST {compiler}/save atomic: 3단 검증 + multi-lang 컴파일 + S3 PUT + makitt-api PUT /save. 422 면 절대 우회 금지. |
/hcs-editing | HCS shop.json 안전 편집 헬퍼 | JSON parse + object mutate (text Edit 금지). 로컬 template = 시드/디버그 only, 라이브 SSOT 아님. |
/hcs-coverage | sales_channel × fragment 커버리지 SQLite | data/hcs-coverage.db, UI http://localhost:3007/coverage. 2,639채널 추적. |
/archive-shop | published 샵 git 시점 스냅샷 (v2 누적 모드) | makitt-shop-archive repo 에 매번 archives/{ts}__{name}/ 신규 디렉토리 + latest 심볼릭. 사용자 명시 호출시만. v1 in-place 폐기 (2026-05-06). |
데이터 직접 접근
| Skill | 용도 | 핵심 |
|---|---|---|
/makitt-db | DDB + OpenSearch | --profile makitt-dev, table makitt-dev-table, region ap-northeast-2. OpenSearch 는 VPC-only → Proxy API /api/v1/admin/opensearch. |
/argo-dev-db | Argo dev PostgreSQL | service-per-DB. VPN 필수. write 는 사용자 확인 필수. prod hard-block. |
테스트 환경
| Skill | 용도 | 핵심 |
|---|---|---|
/test | 테스트 환경 scaffold | --epic <id> / --pr <ref>... / --branch <name> <repo>... / --run <services> / --stop. iTerm2 split panes. |
/reset | test-user 리셋 | <userId> [full | account | shop <id>]. makitt-api :8080 의 /api/v1/admin/developer/test-users/... 사용. |
/argo-test | Argo dev fixture E2E | shipper/SKU/stock/order 풀 사이클. argoport.co 전용, argoport.com (prod) hard-block. |
시각 회귀 검증 (Visual Snapshot)
| Skill | 용도 | 핵심 |
|---|---|---|
/snapshot-node | 라이브 샵의 임의 노드(컴포넌트 / fragment / 모달 / drawer / 페이지 / 단일 카드) 격리 PNG 캡처 | POST {compiler}/node-snapshot 단일 통로. shop SSR + persistent Playwright Chromium. 라이브 픽셀 동등. 인증/모달/페이지 state/cursor/cropping 전부 한 endpoint 에서 처리. 상세: §3.4. |
유틸
| Skill | 용도 | 핵심 |
|---|---|---|
/upload-cdn | image → S3 → CDN URL | bucket makitt-dev-assets, CDN https://cdn.dev.makitt.io, key {path}/{yyyy}/{mm}/{uuid}.{ext}. |
/generate-image | Gemini/Imagen 이미지 생성 | GOOGLE_API_KEY 필수. fallback chain: Gemini 2.5 Flash → Imagen 4.0 Fast → 3.0 → 4.0. |
3. 표준 E2E 시나리오
3.1 admin form submit → shop-api PDP 검증 (canonical 패턴)
참조 구현: scripts/e2e/product-i18n-form-submit.mjs (299 lines).
파이프라인:
getDeveloperToken(email)→ admin JWTgetProductDetail(productId)(Bearer JWT) → 현재 상태 확인submitProductUpdate(productId, payload)→PUT /api/v1/products/{productId}으로 ProductFormV2 와 동일한 payload 전송loadTranslationMap(shopId, productId)→ 기존 번역 로드upsertTranslations(shopId, productId, lang, body)→ 언어별PUT /api/v1/shops/{shopId}/products/{productId}/translations/{lang}- for
(lang, currency)in[(ko,KRW),(en,USD),(ja,JPY)]:getShopPdp(shopId, slug, lang, currency)← 여기서 dev-bypass 헤더assertPdpTranslation(name/shortDescription/description/categoryName/brandName/seoTitle/seoDescription)assertPdpPrice(currencyCode 일치, featuredPrice > 0, featuredPriceLabel 비어있지 않음)
신규 E2E 작성 시 이 shape 그대로 복사 — admin 으로 변경 → shop-api 로 검증 의 일관 패턴.
3.2 e2e.md 핸드오프 보드 (멀티세션 SSOT)
작업 중인 spec 에 e2e.md 있으면 세션 시작시 무조건 먼저 읽음. 3 섹션:
## Inbox to AI - 처리할 항목 (AI 가 끝내면 Resolved 로 이동) ## Resolved - 완료된 항목 + 결과 1~2줄 ## Inbox to Human - [ ] 사용자 검증 필요한 체크박스 항목
UI: apps/docs (port 3007) /handoff — 마크다운 파일이 SSOT 라 직접 편집해도 동일.
작업 끝낼 때: 사용자에게 "e2e.md 업데이트했습니다 — Inbox to Human N건" 으로 짧게 보고.
새 spec 시작 시: docs/spec/ongoing/{spec}/e2e.md 생성 → 자동으로 보드 인덱스에 등장.
3.3 HCS 변경 풀 사이클
CLAUDE.md "HCS 작업 플로우" 섹션 + /hcs-save skill 의 5-step 절차를 그대로 따른다. 요약:
GET /api/v1/shops/{shopId}/versions/published→ 메타 + source URL 4종- 4개 source 다운로드 → 메모리에서 JSON parse + 객체 변경
POST {compiler}/save한 번에 atomic 처리- 422 →
validations.errors그대로 보고, 사용자와 baseline 청소 합의 후 재시도. 절대 우회 금지. - 200 → dev 즉시 반영, 추가 publish 호출 불필요.
금지 (CLAUDE.md 에 박혀있음):
- 로컬
apps/builder/data/templates/{name}/shop.json직접 편집해서 dev 자동 반영 가정 aws s3 cp/ presigned URL 직접 PUTupload-template.ts(검증 게이트 없음)PUT /versions/{vid}분리 호출/compile직접 호출 (검증 단계 빠짐)
흔한 silent typo — system namespace 키 카탈로그:
| namespace | 알려진 키 |
|---|---|
form | values, errors, touched, dirty, isDirty, isSubmitting, isValid, result, schema |
modal | id, payload, isOpen, dismissDays |
url | params, query |
특히 form 응답값은 form.result (NOT form.response). validator 가 이제 BINDING_UNKNOWN_NAMESPACE_KEY 로 잡지만 작성 시점에 알면 round-trip 1회 절약. 새 갭 발견 시 docs/spec/ongoing/henriette-perfection/README.md "Validator/Authoring Gap Log" 에 누적.
3.4 시각 회귀 검증 — /snapshot-node 풀 가이드
핵심: 한 HTTP 호출 = 라이브 픽셀 동등 PNG. 헤더/푸터/모달/drawer/PDP/주문 상세/검색/결제 — 사용자 인증/모달 state/페이지 state/cursor pagination/카드 cropping 까지 단일 endpoint 로 통합.
3.4.1 단일 통로
POST {compiler}/node-snapshot
local: http://localhost:3004/node-snapshot / dev: https://compiler.dev.makitt.io/node-snapshot
전제 조건:
- 로컬 MAKITT 서비스는
makitt devctl단일 통로 (전체 fresh start:makitt devctl console/ 상태:makitt devctl status/ 재시작:makitt devctl restart <service>/ 로그:makitt devctl logs <service>). agent 세션에서 직접pnpm dev/bootRun금지. - 필요한 서비스:
compiler(:3004),shop(:3003, prod build OK — snapshot route 가 NODE_ENV=production 에서도 응답),api(:8080),shop-api(:10000) - 코드 수정 후 표준 흐름:
makitt devctl restart <service>(예: api 변경 시makitt devctl restart api, shop 페이지 수정 시makitt devctl restart shop) - Playwright Chromium 1회 설치 (
cd makitt-client/apps/compiler && npx playwright install chromium)
3.4.2 요청 페이로드 — 전체 옵션
{ // [필수] "shopUrl": "henriette", // shop slug (NOT shopId) "nodeId": "global-header_root", // compiled-shop.json 의 노드 id // [국제화] "locale": "ko", // 'ko'|'en'|'ja'. compiled translation overlay 선택 "currency": "KRW", // 'KRW'|'USD'|'JPY' // [페이지 라우팅 / dynamic resource] "urlParams": { "slug": "cephira-..." }, // PDP `:slug` / order detail `:id` 등 "urlQuery": { "tab": "reviews" }, // optional, 거의 안 씀 // [인증] "customerId": "cust_65e02935aa77", // 인증 고객 컨텍스트 (orders, mypage). 없으면 guest // [컴포넌트 input override] "inputs": { "open": true, "items": [...] },// 노드 props 에 merge // [페이지 상태 override — tab / accordion 등] "pageState": { "pdpActiveTab": "reviews" }, // owner page state defaultValue 덮어쓰기 // [binding context override — 부모 resource alias / repeat source 주입] "bindingOverrides": { // bindingDataInitial 의 LOWEST precedence "product": { "id": "...", "reviewSummary": { "totalReviewCount": 7 } }, "reviews": { "items": [ { "rating": 5, "content": "..." } ] } }, // [캡처 형태] "width": 1280, // 뷰포트 폭 (default 1280, 첫 호출에 고정) "padding": 40, // 격리 wrapper padding (default 40, 풀폭이면 0) "background": "white", // wrapper 배경 "fullPage": true, // 명시적 full page (overlay 는 자동 fullPage) "cropSelector": "[id$=_card_card]:has-text('ORD-...')", // 단일 반복 iteration / 특정 자식 cropping // [응답 포맷] "format": "binary" // 'binary' (default, image/png) | 'json' (base64+meta) }
응답 (default binary):
Content-Type: image/png- 헤더:
X-Snapshot-Shop-Id,X-Snapshot-Version-Id,X-Snapshot-Duration-Ms
응답 (json):
{ "shopId", "versionId", "nodeId", "locale", "currency", "bytes", "durationMs", "screenshotBase64" }
3.4.3 시나리오별 한 줄 사용법
COMPILER=http://localhost:3004 # 헤더 (full-width, padding 0) curl -X POST $COMPILER/node-snapshot \ -d '{"shopUrl":"henriette","nodeId":"global-header_root","locale":"ko","currency":"KRW","width":1280,"padding":0}' \ -o header.png # 푸터 curl -X POST $COMPILER/node-snapshot \ -d '{"shopUrl":"henriette","nodeId":"global-footer_root","locale":"ko","currency":"KRW"}' \ -o footer.png # Drawer (cart) — overlay 자동 감지 + fullPage curl -X POST $COMPILER/node-snapshot \ -d '{"shopUrl":"henriette","nodeId":"commerce_header_drawer_drawer","locale":"ko","currency":"KRW"}' \ -o cart-drawer.png # Modal (지역/언어 선택) curl -X POST $COMPILER/node-snapshot \ -d '{"shopUrl":"henriette","nodeId":"i18n_modal_default_i18n_modal","locale":"ko","currency":"KRW"}' \ -o i18n-modal.png # PDP 풀 페이지 (urlParams.slug 필수) curl -X POST $COMPILER/node-snapshot \ -d '{"shopUrl":"henriette","nodeId":"pdp_pds-root","locale":"ko","currency":"KRW","urlParams":{"slug":"cephira-shot-jellyfish-pore-serum"},"fullPage":true}' \ -o pdp.png # 인증 고객 컨텍스트 — 주문 상세 curl -X POST $COMPILER/node-snapshot \ -d '{"shopUrl":"henriette","nodeId":"order-detail-page_detail-root","customerId":"cust_65e02935aa77","urlParams":{"id":"order_35590d706d44"},"locale":"ko","currency":"KRW","fullPage":true}' \ -o order-detail.png # 인증 고객 컨텍스트 — 주문 목록 풀 (cursor 강제 prefetch 자동) curl -X POST $COMPILER/node-snapshot \ -d '{"shopUrl":"henriette","nodeId":"order-list-page_list-root","customerId":"cust_65e02935aa77","locale":"ko","currency":"KRW","fullPage":true}' \ -o orders.png # 단일 카드 cropping — 특정 주문 카드만 curl -X POST $COMPILER/node-snapshot \ -d '{"shopUrl":"henriette","nodeId":"order-list-page_list-root","customerId":"cust_65e02935aa77","locale":"ko","currency":"KRW","fullPage":true,"cropSelector":"[id=\"order-list-page_card_card\"]:has-text(\"ORD-20260503-C9CE\")"}' \ -o order-card.png # 빈 카트 페이지 (non-auth) curl -X POST $COMPILER/node-snapshot \ -d '{"shopUrl":"henriette","nodeId":"cart-page-bg","locale":"ko","currency":"KRW"}' \ -o cart-empty.png
3.4.4 동작 흐름 (한 호출 안에서)
- compiler 가
makitt-api /api/v1/public/shops/url/{shopUrl}로 shopId 해석 customerId없으면 → shop-api/auth/guest호출 → guest accessToken/refreshToken 쿠키customerId있으면 → 쿠키accessToken=dev-bypass:{customerId}(sentinel) 주입- Playwright context 에
x-shop-url+ auth cookie 세팅 - shop
/snapshot?...URL navigate (JS-on, networkidle + fonts.ready 대기) - shop 의
/snapshotpage route:- root layout 이 cookie 의 shopUrl 로 AppShell 마운트 (modals/drawers/toast 항상 마운트)
- search params 파싱 (urlParams, customerId 흐름은 cookie 로)
getShopData(shopUrl, locale)→ published shop 메타 + compiled HCSfindInShop(nodeId)→ owner page 까지 같이 추출prefetchShopResources+prefetchNodeResources(forceCursor: true)→ cursor pagination 도 SSR 1페이지 hydrate- overlay (modal/drawer) 면:
<SnapshotOpener>client 컴포넌트 마운트 →useDrawerStore.openDrawer()/useModalStore.openModal()디스패치 - 일반 노드면:
<HydrationBoundary>+<PageRenderer>로 격리 wrapper 안 단일 노드 SSR - body 에
<div data-snapshot-mode="modal|drawer|inline">마커 박음
- Playwright capture:
data-snapshot-mode === "modal"|"drawer"→ fullPage screenshotcropSelector명시 → 그 selector 의 첫 매칭 element screenshot (scrollIntoViewIfNeeded)fullPage: true→ fullPage- 그 외 →
[data-snapshot-root]locator screenshot (실패 시 fullPage fallback)
- PNG 응답
3.4.5 노드 ID 발견 (compiled-shop.json walk)
# Henriette compiled-shop 샘플 curl -sS "https://s3.ap-northeast-2.amazonaws.com/makitt-dev-storage/shops/compiled/{shopId}/{versionId}/ko/compiled-shop.json" \ > /tmp/shop.json # 자주 쓰는 헤더/모달/드로워 (Henriette) node -e "const s=require('/tmp/shop.json'); console.log('header:', s.header?.id); console.log('footer:', s.footer?.id); console.log('modals:', (s.modals||[]).map(m=>m.id)); console.log('drawers:', (s.drawers||[]).map(d=>d.id)); console.log('pages:'); for (const p of s.pages) console.log(' -', p.pageId, p.path, p.content[0]?.id);"
Henriette 자주 쓰는 노드:
global-header_root,global-footer_rootcommerce_header_drawer_drawer(cart/menu drawer)i18n_modal_default_i18n_modal,product_option_modal_root,order_cancel_modal_root,order_return_modal_root,order_exchange_modal_root- 페이지 root:
pdp_pds-root(PDP),cart-page-bg(cart),checkout-page_chk-root(checkout),order-list-page_list-root(order list),order-detail-page_detail-root(order detail),main-hero_root/featured-product_root/best-sellers_root(home sections)
3.4.6 인증 모드 매트릭스
| 시나리오 | customerId | 인증 효과 |
|---|---|---|
| 헤더/푸터/PDP/카트(빈)/홈/모달 | (없음) | guest 토큰 자동 발급 → 익명 customer 로 SSR |
| 주문 상세 / 주문 목록 / mypage | cust_xxx | dev-bypass sentinel cookie → SSR + CSR 모두 Authorization: Bearer dev-bypass + X-Dev-Customer-Id 헤더로 rewrite |
| 빌더 시점 / 실제 JWT 가 필요한 시나리오 | (수동 cookie 주입) | 별도 phase, 현재 미구현 |
customerId 찾기:
# 주문 → 고객 aws dynamodb scan --profile makitt-dev --region ap-northeast-2 \ --table-name makitt-dev-table \ --filter-expression "begins_with(PK, :p) AND order_number = :n" \ --expression-attribute-values '{":p":{"S":"ORDER#"},":n":{"S":"ORD-...."}}' \ --projection-expression "PK, owner_id, shop_id, order_number"
3.4.7 cropSelector 패턴
repeat (반복) 안의 자식 노드는 같은 base id 가 여러 인스턴스로 렌더됨. Playwright locator 로 정확히 한 인스턴스 cropping:
| 의도 | selector |
|---|---|
| 특정 주문 카드 | [id$=_card_card]:has-text("ORD-20260503-C9CE") |
| 특정 상품 카드 | [id$=_product-card]:has-text("세피라샷") |
| 특정 nodeId 의 첫 매칭 | #exact-id (단일 인스턴스면 굳이 selector 안 써도 됨) |
| 텍스트로 위치 찾기 | text=특정 문구 (Playwright 의 텍스트 셀렉터) |
selector 가 미매칭이면 fullPage 로 fallback (silent — log 만).
3.4.8 격리 한계 돌파 — pageState + bindingOverrides
자식 노드를 격리 캡처할 때 끊기는 3가지 컨텍스트가 있고, 두 surface 로 모두 메울 수 있다:
| 끊기는 것 | 증상 | 해결책 |
|---|---|---|
| 페이지 상태 변수 | tab/accordion 분기가 default 값으로 고정 → 다른 분기는 못 그림 | pageState: {pdpActiveTab: 'reviews'} |
| 부모 resource alias | {{product.id}} 같은 부모 자원의 binding 미해결 → resource fetch 실패 | bindingOverrides: {product: {...}} |
| repeat iteration scope | repeat 노드 단독 격리 시 source = undefined → 0 iter | bindingOverrides: {reviews: {items: [singleItem]}} (source 자체 주입) |
주의: repeat 격리 시 {item} 또는 {review} 직접 주입은 안 된다 — renderer 가 repeat.source 에서 iterate 하므로 source binding (reviews.items) 자체를 단일 원소 배열로 주입해야 한다.
예시 — PDP review fragment 풀 매트릭스 (henriette):
COMPILER=http://localhost:3004 SLUG=cephira-shot-jellyfish-pore-serum PREFIX=pdp_detail_slot_pdp-detail_panels_slot_2_pdp_detail_slot_panels_slot_2_pdp-panel-reviews_ # 1. 리뷰 탭 panel root (visible=pdpActiveTab=='reviews') curl -X POST $COMPILER/node-snapshot -d "{ \"shopUrl\":\"henriette\", \"nodeId\":\"${PREFIX}tppr-root\", \"urlParams\":{\"slug\":\"$SLUG\"}, \"pageState\":{\"pdpActiveTab\":\"reviews\"}, \"locale\":\"ko\",\"currency\":\"KRW\",\"fullPage\":true }" -o tab-panel-reviews.png # 2. has-reviews 분기 (visible=reviewSummary.totalReviewCount > 0) curl -X POST $COMPILER/node-snapshot -d "{ \"shopUrl\":\"henriette\", \"nodeId\":\"${PREFIX}tppr-has-reviews\", \"urlParams\":{\"slug\":\"$SLUG\"}, \"bindingOverrides\":{\"product\":{\"id\":\"...\",\"reviewSummary\":{\"totalReviewCount\":7}}}, \"locale\":\"ko\",\"currency\":\"KRW\",\"fullPage\":true }" -o has-reviews.png # 3. rl-empty 분기 (빈 리뷰 fallback) curl -X POST $COMPILER/node-snapshot -d "{ \"shopUrl\":\"henriette\", \"nodeId\":\"${PREFIX}tppr-list_rl-empty\", \"urlParams\":{\"slug\":\"$SLUG\"}, \"bindingOverrides\":{\"reviews\":{\"items\":[]}}, \"locale\":\"ko\",\"currency\":\"KRW\" }" -o rl-empty.png # 4. rl-item 단일 iteration (repeat source 주입) curl -X POST $COMPILER/node-snapshot -d "{ \"shopUrl\":\"henriette\", \"nodeId\":\"${PREFIX}tppr-list_rl-item\", \"urlParams\":{\"slug\":\"$SLUG\"}, \"bindingOverrides\":{\"reviews\":{\"items\":[{\"id\":\"r1\",\"rating\":5,\"authorName\":\"체크어\",\"content\":\"단일 review\"}]}}, \"locale\":\"ko\",\"currency\":\"KRW\" }" -o rl-item.png
precedence: bindingOverrides 는 bindingDataInitial 의 LOWEST precedence — 진짜 prefetched data 가 있으면 그게 win. ancestor-context 누락만 채움 (override 가 의도치 않게 라이브 데이터를 가리지 않음).
3.4.9 빈 PNG 디버깅 표준 절차
빈/작은 PNG (15KB ~ 25KB 정도, 텍스트 한 줄 수준) 가 나오면 보통 node 의 binding cond 가 mock fixture 와 mismatch 가 원인. 30초 안에 해결되는 표준 절차:
Step 1 — 노드의 binding 문자열 직접 추출 (compiled-shop walk):
node -e " const s = JSON.parse(require('fs').readFileSync('/tmp/henriette-compiled-shop.json','utf8')); function walk(n, target) { if (!n) return null; if (n.id === target) return n; for (const c of n.children || []) { const r = walk(c, target); if (r) return r; } return null; } const node = walk(s.pages.find(p=>p.pageId==='product-detail').content[0], '<targetNodeId>'); console.log(JSON.stringify(node, null, 2).slice(0, 1500)); "
확인할 필드:
visible: "summary.mediaReviewCount > 0"→ fixture 의summary객체에 정확히mediaReviewCount키 (camelCase 그대로) 필요repeat.source: "reviews.items"→ fixture 에reviews.items: [...](배열) 필요resource.params: { productId: "{{product.id}}" }→ 부모 binding 의product.id필요 →bindingOverrides.product = {id: '...'}children: "{{summary.totalCount}}"→ fixture 의summary.totalCount
Step 2 — fixture 키 정확히 정렬:
| 노드의 binding 문자열 | mock fixture 에 들어갈 키 |
|---|---|
summary.mediaReviewCount | { summary: { mediaReviewCount: 12 } } |
summary.verifiedPurchaseCount | { summary: { verifiedPurchaseCount: 30 } } |
reviews.items (repeat source) | { reviews: { items: [item1, item2] } } (배열) |
review.rating (repeat 안 자식) | repeat 가 source 에서 iter 하므로 source 자체 주입 (위의 reviews.items), 자식이 직접 review 받음 |
product.reviewSummary.totalReviewCount | { product: { reviewSummary: { totalReviewCount: 47 } } } |
Step 3 — 분기 가지 매핑:
| 분기 종류 | 사용할 surface |
|---|---|
페이지 상태 변수 (pdpActiveTab 등) | pageState: { pdpActiveTab: 'reviews' } |
부모가 제공하는 자원 alias (product, summary, reviews) | bindingOverrides: { ... } |
repeat iteration 의 as alias 자식 (review/item) | source binding 자체 주입 (bindingOverrides.reviews.items=[item]) — as alias 직접 X |
Step 4 — 실패 시 raw SSR HTML 확인:
# bindingOverrides + urlParams 를 base64 로 인코딩해서 직접 GET B64=$(echo -n '{"summary":{"mediaReviewCount":12}}' | base64) curl -sS "http://localhost:3003/snapshot?shopUrl=henriette&nodeId=<node>&bindingOverrides=$B64" \ | grep -oE 'id="<node>[^"]*"' | head -3
요소가 SSR 에 있으면 → 클라 hydration 단계 race 가능성 (Playwright wait 늘리기). 요소가 SSR 에도 없으면 → fixture 키 mismatch 또는 visible cond 미충족.
Step 5 — 표준 fixture 패턴 (현재 검증된 PDP review 도메인):
{ "product": { "id": "<real productId>", "reviewSummary": { "totalReviewCount": 47, "averageRating": 4.7 } }, "summary": { "totalCount": 47, "averageRating": 4.7, "distribution": { "5": 32, "4": 10, "3": 3, "2": 1, "1": 1 }, "mediaReviewCount": 12, "verifiedPurchaseCount": 30 }, "review": { "id": "r1", "rating": 5, "authorName": "체크어", "content": "...", "verifiedPurchase": true, "productName": "...", "thumbnailUrl": "https://cdn.dev.makitt.io/...", "mediaList": [] }, "reviews": { "items": [/* review 배열 */], "pagination": { "totalItems": N } } }
다른 도메인 (orders / customer / cart 등) 의 fixture 패턴은 같은 절차로 발견 — 노드의 binding cond 가 정답 사전.
3.4.11 결정론 / 캐싱
- 같은 입력은 byte-identical PNG 가 기대됨 — 시간/카운트다운/random 컴포넌트가 없으면.
- compiler 는 현재 캐싱 없음 (매번
13s). 필요시 (versionId, nodeId, locale, currency, customerId, urlParams) tuple 로 LRU 캐시 추가 가능 —/save호출 시 invalidate. - baseline 저장은 별도 spec (
node-snapshot-baselines— TODO).
3.4.12 한계 / Phase 2 후보
- 시간 의존 콘텐츠 (카운트다운, 카드 시간 표시) → 같은 입력에도 PNG 다름 → 결정론 깨짐
- 스크롤 트리거 sticky / parallax / lazy load 무한 스크롤 → 초기 paint 만 잡힘
- toast 표시 → trigger 가 store dispatch → 현재 SnapshotOpener 는 modal/drawer 만 지원
- 실제 customer JWT 필요한 시나리오 (시드된 데이터로 dev-bypass 안 충분한 경우) → 별도 surface 필요
3.4.13 spec / 구현 참조
- spec:
docs/spec/finished/node-snapshot-rendering/README.md(✅ closed 2026-05-11) - e2e 핸드오프:
docs/spec/finished/node-snapshot-rendering/e2e.md— 9가지 hard-won 깨달음 + 매트릭스 통과 기록 - shop 라우트:
makitt-client/apps/shop/app/snapshot/page.tsx+SnapshotOpener.tsx - compiler 서비스:
makitt-client/apps/compiler/src/services/snapshot.service.ts+routes/node-snapshot.ts - api 클라이언트 dev-bypass rewrite:
makitt-client/packages/api/src/client/api-client.ts - prefetch forceCursor:
makitt-client/apps/shop/src/features/resource-loader/prefetch.ts
3.5 Order customer actions 7-slot 매트릭스 (007 표준)
참조 구현: scripts/e2e/order-customer-actions.mjs
7 status (PAYMENT_PENDING / PAID / PROCESSING / SHIPPED / DELIVERED-fresh / DELIVERED-confirmed / CANCELED) 각각에 대해 §1.5 6 액션의 가용성 매트릭스 풀세트 검증. Henriette (71261f65-...) 기준 baseline PASS.
파이프라인:
- §1.4 backdoor 로 7 status 의 fixture 주문 시드 (PAYMENT_PENDING 은 신설
manual-payment-pending, 나머지는 manual-payment + 전이 chain) - 각 슬롯에서 §1.5 액션 6종 호출 → 응답 status code 가 기대 매트릭스와 일치하는지 검증
OrderClaimDDB row + outboxOrderClaimRequestedevent 도 같이 검사
신규 액션 추가 시 매트릭스에 행/열 추가하고 fixture 시드 chain 만 늘리면 됨.
4. 포트 / URL / 프로필 cheat sheet
4.1 로컬 포트
| 서비스 | 포트 | 비고 |
|---|---|---|
| web | 3002 | --filter=@makitt/web |
| builder | 3001 | --filter=@makitt/builder |
| shop | 3003 | --filter=@makitt/shop |
| compiler | 3004 | --filter=@makitt/compiler |
| docs | 3007 | apps/docs, /handoff, /coverage 등 |
| makitt-api | 8080 | makitt devctl restart api |
| makitt-shop-api | 10000 | makitt devctl restart shop-api |
| makitt-ai-agent | 8000 | local; dev cluster 는 4000 |
| Local OpenSearch | 9200 | docker-compose |
| Chrome DevTools | 9222 | makitt chrome, --user-data-dir=/tmp/chrome-debug-profile |
4.2 Dev URLs
| Surface | URL |
|---|---|
| makitt-api | https://api.dev.makitt.io |
| compiler | https://compiler.dev.makitt.io |
| CDN | https://cdn.dev.makitt.io |
| docs viewer | https://root.dev.makitt.io |
4.3 Argo dev (모두 argoport.co — .com 은 prod, hard-block)
vms-api / cms-api / sugar-api / verification-api / oms-api / wms-{inbound,outbound,inventory,job}-api / wms-outbound-batch-api — 전체 https://<service>.argoport.co
4.4 AWS / 프로필
- Profile:
makitt-dev - Region:
ap-northeast-2 - DDB table:
makitt-dev-table - S3 buckets:
makitt-dev-assets(CDN, public),makitt-dev-storage(HCS sources/compiled) - Secrets Manager:
makitt-dev-infra,makitt-dev-opensearch,makitt-dev-alert(자동 로드는scripts/load-dev-env.sh)
4.5 Java / Gradle (macOS)
JAVA_HOME=/Library/Java/JavaVirtualMachines/temurin-21.jdk/Contents/Home \ ./gradlew compileJava \ -Dorg.gradle.java.home=/Library/Java/JavaVirtualMachines/temurin-21.jdk/Contents/Home
run-server.sh 가 자동 처리.
4.6 로그 파일
| 모듈 | 경로 |
|---|---|
| makitt-api | /tmp/makitt-api.log |
| makitt-shop-api | /tmp/makitt-shop-api.log |
| makitt-consumer | /tmp/makitt-consumer.log |
| makitt-ai-agent | /tmp/makitt-ai.log |
| Argo JWT cache | /tmp/argo-test-token-{loginId} |
서버 로그는 tail -100 /tmp/makitt-shop-api.log | grep ERROR 처럼 직접 Read.
5. 테스트 유저 / 샵 카탈로그
10개 고정 test seller (/reset 가 인식)
| userId | |
|---|---|
| test-aaron | aaron@makitt.io |
| test-tom | tom@makitt.io |
| test-luna | luna@makitt.io |
| test-jake | jake@makitt.io |
| test-mia | mia@makitt.io |
| test-rex | rex@makitt.io |
| test-nora | nora@makitt.io |
| test-kai | kai@makitt.io |
| test-ivy | ivy@makitt.io |
| test-finn | finn@makitt.io |
/reset aaron 처럼 bare name 만 줘도 자동 test- prefix.
자주 쓰는 테스트 샵
| 샵 | 용도 | shopId / slug |
|---|---|---|
| Henriette | i18n / fragment library 검증 기본 testbed | 71261f65-ee4e-4238-9b4f-d56cee9be523 (셀러 chance@makitt.io) |
| Henriette PDP | 위 샵 표준 상품 | productId 386d4ca2-6dbf-43bb-9d5e-47e215589ebc, slug cephira-shot-jellyfish-pore-serum |
| First Chance | Toss 결제 승인용 | 22654209-ec91-4988-9057-b2c1618f8ef9, userId 3d0d3bae-bcb3-4c48-b35c-53a8a6cb8fd7, slug firstchance |
하드코딩 dev 유저
userId = 00000000-0000-0000-0000-000000000001, email = developer@makitt.com — POST /api/v1/admin/developer/token 첫 호출 시 자동 생성.
Argo test seller (2026-05-01 검증)
loginId = makitttest1, argoUserId = 3840, vendorId = 900983, vendorCode = SHIPPER-3039, default password Xpzmxkzk2021!.
기본 agency vendor: 900761 (테크타카 로지스틱스, AGENCY-687).
기본 workplaces: 376 (FC_NEWNEW, KR), 1280 (FC_US_OUTBOUND, US).
6. 셸 헬퍼 (scripts/shell/makitt.zsh)
~/.zshrc 에서 sourced.
makitt api # low-level makitt-api bootRun (port 8080) makitt shop-api # low-level makitt-shop-api bootRun (port 10000) makitt consumer # low-level makitt-consumer bootRun makitt web # apps/web (3002) makitt builder # apps/builder (3001) makitt shop # apps/shop (3003) makitt compiler # apps/compiler (3004) makitt docs # apps/docs (3007) makitt ai # makitt-ai-agent makitt chrome # Chrome Canary remote debugging port 9222 makitt happen <args> # dev.sh forward makitt devctl <args> # shared tmux service console for humans + agents makitt ready # 의존성 설치 argo vms-api / cms-api / sugar-api / oms-api / wms-{inbound,outbound,inventory}-api ...
중요: Claude/Codex 는 로컬 MAKITT 서비스를 자기 세션에서 직접 기동하지 않는다. makitt api, makitt shop-api, makitt consumer, pnpm dev|start, gradlew bootRun 대신 makitt devctl restart <service> / makitt devctl logs <service> 를 사용한다. 사람이 볼 전체 콘솔은 makitt devctl console이다. Argo 쪽 (sugar/cms/vms) 은 OK.
4.4 공유 서비스 콘솔 (makitt devctl)
여러 agent 세션이 동시에 작업할 때는 사용자가 한 번만 공용 콘솔을 띄운다. console은 알려진 로컬 포트/프로세스와 기존 makitt-dev tmux 세션을 정리한 뒤 전체 서비스를 새로 띄우고, 새 iTerm2 창에 4x2 split pane으로 서비스별 로그를 띄운다.
makitt devctl console
agent는 같은 세션을 제어한다.
makitt devctl restart api makitt devctl restart builder --mode start makitt devctl logs shop-api 200 makitt devctl status
web/builder/shop의 기본 mode는 기존 makitt helper와 같은 prod다. 빠른 재시작은 --mode start를 사용한다. 공유 devctl 환경에서 --mode dev는 금지한다.
console은 기본적으로 web builder shop api shop-api consumer compiler ai 전체를 대상으로 한다. 로그 pane은 .agents/devctl-logs/{service}.log를 tail -F한다. 서비스 프로세스 자체는 tmux 세션에서 관리되므로 agent는 계속 restart/logs를 사용할 수 있다.
7. dev.sh / setup.sh
초기 셋업 (최초 1회)
git clone <this-repo> makitt-root cd makitt-root ./setup.sh # 전체 하위 프로젝트 clone ./dev.sh --ready # 의존성 설치
부분 셋업
./setup.sh makitt # client + server + ai-agent + operator ./setup.sh argo # argo 만 ./setup.sh infra # infra-dev 만
개발 서버 실행
./dev.sh # 전체 ./dev.sh frontend # client 만 ./dev.sh backend # api + shop-api ./dev.sh payments # payment-api + payment-web ./dev.sh agent # agent-api + agent-worker ./dev.sh full # client + api + shop-api + docs + agent ./dev.sh -l # 사용 가능한 서비스/프리셋 목록
8. 트러블슈팅
"ProductDto 구조가 admin API 와 다른데?"
shop-api 의 ProductDto 는 admin 의 ProductResponse 와 구조 다름 (CLAUDE 메모리 참조):
- variant group 에
productVariantGroupId,combinations없음 - options → values 의
valueSelection토큰 +/resolve엔드포인트로 해석 - SEO 필드 flat (
seoSlug,seoTitle,seoDescription) - 타임스탬프 Long (epoch ms)
서버 DTO 소스: makitt-server/makitt-shop-api/src/main/java/com/makitt/shop/dto/
"DDB 에 데이터가 안 보여요"
KeyPrefix.java + 해당 {Entity}Key.java 를 먼저 확인. SK 가 엔티티마다 다름 (METADATA vs META 등). 단순 키 오타가 가장 흔한 원인.
aws dynamodb get-item --table-name makitt-dev-table --profile makitt-dev --region ap-northeast-2 \ --key '{"PK":{"S":"SHOP#xxx"},"SK":{"S":"METADATA"}}'
"API 호출이 401/403"
- admin token 인지 dev-bypass 인지 surface 확인 (§1)
- admin token: 만료 (1h) → 재발급
- dev-bypass: profile 확인 (
spring.profiles.active가local|dev포함?) - path:
/api/...prefix 인지 (admin),/shop/...prefix 인지 (shop-api)
"compiler /save 가 422"
3단 검증 (expansion + token + validator) 중 errors. response 의 validations.errors[].phase 보고 사용자에게 보고. 절대 우회 금지 (CLAUDE.md HCS 섹션 금지 항목).
"compileJava 통과했는데 런타임 실패"
DynamoDB TableSchema / Spring context 는 런타임에서만 검증됨. Entity/DTO 수정 시 실제 bootRun 또는 ./gradlew build (테스트 포함) 까지 돌릴 것 (CLAUDE 메모리 feedback_server_build_verification.md).
"OpenSearch 에 직접 접속이 안돼요"
Dev OpenSearch 는 VPC-internal. Proxy API 사용:
GET http://localhost:8080/api/v1/admin/opensearch/indices
POST http://localhost:8080/api/v1/admin/opensearch/{index}/_search
admin JWT (Bearer) 필요.
"Argo psql 이 timeout"
VPN 먼저 확인. Cluster 와 password 매칭 확인 (Cluster A xpzmxkzk2021! 소문자, Cluster B Xpzmxkzk2020! 대문자).
"git/ls 가 'Operation not permitted'"
샌드박스에 의해 cwd 가 막힌 경우. cd /tmp 후 git -C <abs-path> ... 또는 절대경로 사용.
"MCP fill() 로 폼 입력했는데 submit 시 빈값"
Chrome DevTools MCP 의 fill() 은 native value setter + dispatchEvent('input') 으로 동작. RHF (react-hook-form) 의 ref-based register 는 React synthetic onChange 만 신뢰하기 때문에 MCP 우회 입력이 값 반영 안 됨 — submit 시 빈값으로 전송.
→ MCP 자동화로 RHF form submit E2E 까지 가는 건 한계. 실제 키보드 입력 (DevTools 외 수동 또는 다른 자동화) 은 정상.
권장: 백엔드 API 직접 호출로 submit 검증 (§1.5 customer order actions 패턴) + MCP 는 모달 노출 / 라벨 / 옵션 list 렌더링까지만 검증. 이번 007 cycle 의 표준 패턴.
9. 사용자가 직접 해야 하는 것 (Claude 가 하면 안 되는 것)
- makitt-api 기동/재기동:
makitt devctl restart api사용. 전체 fresh start가 필요하면makitt devctl console. Argo 쪽은 OK. - 모든 git 명령 (commit/push/checkout/reset/stash/restore/clean) — 사용자 명시 지시 시에만.
- prod 환경 접근 — argo prod / makitt prod 는 모든 skill 에서 hard-block.
- 코드 수정 — CLAUDE.md "코드 수정 원칙" 4-step 준수: 원인 파악 → 해결 논의 → 사용자 명시 지시 → 작업.
- MCP 사용 — 사용자가 명시적으로 요청할 때만 (CLAUDE 메모리
feedback_no_mcp_unless_asked.md).
10. 다음 단계 / 더 깊이 들어갈 때
- Spec 작성 워크플로우:
feedback_spec_workflow.md— E2E 시나리오로 사용자와 합의 → spec 기록 → 구현 - HCS authoring 관례:
feedback_hcs_authoring_patterns.md— CSS 바인딩 금지, emit-on, contract binding, sticky grid 등 10개 룰 - Self-Declarative Fragments 완료 SSOT:
docs/spec/finished/self-declarative-fragments/ - HCS Save Pipeline 상세:
docs/spec/finished/hcs-save-pipeline-unification/save-endpoint.md - Shop Archive 상세:
docs/spec/finished/shop-archive/archive-on-demand.md - Argo 연동 가이드:
docs/spec/argo/README.md(9개 문서 인덱스 + 테스트 계정 + dev URL + 기술 교훈 8가지)