MAKITTDocs

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-save skill 참조)
  • 코드 수정 전 사용자 명시 승인 필수 (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):

MethodPath동작
POST/api/v1/admin/developer/token하드코딩 dev 유저 (00000000-0000-0000-0000-000000000001, developer@makitt.com) 의 JWT 발급
POST/api/v1/admin/developer/token/emailbody {"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 노출 위험 없음.

헤더:

헤더형식효과
AuthorizationBearer dev-bypass(필수) 우회 활성화
X-Dev-Customer-Idcust_xxxDB 의 진짜 고객 로딩 → 인증된 컨텍스트
X-Dev-Emailtest@example.comcustomer-id 없을 때 simulated 컨텍스트의 식별자
X-Dev-Languageko | en | jai18n 추론 override (translation overlay 적용)
X-Dev-CurrencyKRW | 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-test skill 내부에서만 사용.
  • Argo dev PG: Cluster A argo / xpzmxkzk2021! (소문자 x), Cluster B <dbname> / Xpzmxkzk2020! (대문자 X, 2020). /argo-dev-db skill 통해서만.
  • 고객 진짜 로그인 (cookie): POST /shop/{shopId}/auth/{login,signup,guest}/email-password → HttpOnly cookie accessToken 설정. 프론트 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.

동작MethodPath노트
체크아웃 강제 결제완료 + 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}/processPAID → PROCESSING
배송 출발POST/{orderId}/shipbody { trackingNumber, trackingCarrier, trackingUrl }. PROCESSING → SHIPPED
배송 완료POST/{orderId}/deliverSHIPPED → DELIVERED
셀러 취소POST/{orderId}/cancel?reason= query. (셀러측 강제 취소 — 고객 cancel 과 분리)
환불POST/{orderId}/refundbody { amount, reason }
통합 이벤트 dispatchPOST/{orderId}/eventevent 필드 + 파라미터로 모든 전이 처리 (가장 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).

액션MethodPathbody 핵심가용 status
본인 cancelPOST/{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-saveHCS 라이브 변경 저장 (단일 통로)POST {compiler}/save atomic: 3단 검증 + multi-lang 컴파일 + S3 PUT + makitt-api PUT /save. 422 면 절대 우회 금지.
/hcs-editingHCS shop.json 안전 편집 헬퍼JSON parse + object mutate (text Edit 금지). 로컬 template = 시드/디버그 only, 라이브 SSOT 아님.
/hcs-coveragesales_channel × fragment 커버리지 SQLitedata/hcs-coverage.db, UI http://localhost:3007/coverage. 2,639채널 추적.
/archive-shoppublished 샵 git 시점 스냅샷 (v2 누적 모드)makitt-shop-archive repo 에 매번 archives/{ts}__{name}/ 신규 디렉토리 + latest 심볼릭. 사용자 명시 호출시만. v1 in-place 폐기 (2026-05-06).

데이터 직접 접근

Skill용도핵심
/makitt-dbDDB + OpenSearch--profile makitt-dev, table makitt-dev-table, region ap-northeast-2. OpenSearch 는 VPC-only → Proxy API /api/v1/admin/opensearch.
/argo-dev-dbArgo dev PostgreSQLservice-per-DB. VPN 필수. write 는 사용자 확인 필수. prod hard-block.

테스트 환경

Skill용도핵심
/test테스트 환경 scaffold--epic <id> / --pr <ref>... / --branch <name> <repo>... / --run <services> / --stop. iTerm2 split panes.
/resettest-user 리셋<userId> [full | account | shop <id>]. makitt-api :8080 의 /api/v1/admin/developer/test-users/... 사용.
/argo-testArgo dev fixture E2Eshipper/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-cdnimage → S3 → CDN URLbucket makitt-dev-assets, CDN https://cdn.dev.makitt.io, key {path}/{yyyy}/{mm}/{uuid}.{ext}.
/generate-imageGemini/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).

파이프라인:

  1. getDeveloperToken(email) → admin JWT
  2. getProductDetail(productId) (Bearer JWT) → 현재 상태 확인
  3. submitProductUpdate(productId, payload) → PUT /api/v1/products/{productId} 으로 ProductFormV2 와 동일한 payload 전송
  4. loadTranslationMap(shopId, productId) → 기존 번역 로드
  5. upsertTranslations(shopId, productId, lang, body) → 언어별 PUT /api/v1/shops/{shopId}/products/{productId}/translations/{lang}
  6. 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 절차를 그대로 따른다. 요약:

  1. GET /api/v1/shops/{shopId}/versions/published → 메타 + source URL 4종
  2. 4개 source 다운로드 → 메모리에서 JSON parse + 객체 변경
  3. POST {compiler}/save 한 번에 atomic 처리
  4. 422 → validations.errors 그대로 보고, 사용자와 baseline 청소 합의 후 재시도. 절대 우회 금지.
  5. 200 → dev 즉시 반영, 추가 publish 호출 불필요.

금지 (CLAUDE.md 에 박혀있음):

  • 로컬 apps/builder/data/templates/{name}/shop.json 직접 편집해서 dev 자동 반영 가정
  • aws s3 cp / presigned URL 직접 PUT
  • upload-template.ts (검증 게이트 없음)
  • PUT /versions/{vid} 분리 호출
  • /compile 직접 호출 (검증 단계 빠짐)

흔한 silent typo — system namespace 키 카탈로그:

namespace알려진 키
formvalues, errors, touched, dirty, isDirty, isSubmitting, isValid, result, schema
modalid, payload, isOpen, dismissDays
urlparams, 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 동작 흐름 (한 호출 안에서)

  1. compiler 가 makitt-api /api/v1/public/shops/url/{shopUrl} 로 shopId 해석
  2. customerId 없으면 → shop-api /auth/guest 호출 → guest accessToken/refreshToken 쿠키
  3. customerId 있으면 → 쿠키 accessToken=dev-bypass:{customerId} (sentinel) 주입
  4. Playwright context 에 x-shop-url + auth cookie 세팅
  5. shop /snapshot?... URL navigate (JS-on, networkidle + fonts.ready 대기)
  6. shop 의 /snapshot page route:
    • root layout 이 cookie 의 shopUrl 로 AppShell 마운트 (modals/drawers/toast 항상 마운트)
    • search params 파싱 (urlParams, customerId 흐름은 cookie 로)
    • getShopData(shopUrl, locale) → published shop 메타 + compiled HCS
    • findInShop(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"> 마커 박음
  7. Playwright capture:
    • data-snapshot-mode === "modal"|"drawer" → fullPage screenshot
    • cropSelector 명시 → 그 selector 의 첫 매칭 element screenshot (scrollIntoViewIfNeeded)
    • fullPage: true → fullPage
    • 그 외 → [data-snapshot-root] locator screenshot (실패 시 fullPage fallback)
  8. 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_root
  • commerce_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
주문 상세 / 주문 목록 / mypagecust_xxxdev-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 scoperepeat 노드 단독 격리 시 source = undefined → 0 iterbindingOverrides: {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. §1.4 backdoor 로 7 status 의 fixture 주문 시드 (PAYMENT_PENDING 은 신설 manual-payment-pending, 나머지는 manual-payment + 전이 chain)
  2. 각 슬롯에서 §1.5 액션 6종 호출 → 응답 status code 가 기대 매트릭스와 일치하는지 검증
  3. OrderClaim DDB row + outbox OrderClaimRequested event 도 같이 검사

신규 액션 추가 시 매트릭스에 행/열 추가하고 fixture 시드 chain 만 늘리면 됨.


4. 포트 / URL / 프로필 cheat sheet

4.1 로컬 포트

서비스포트비고
web3002--filter=@makitt/web
builder3001--filter=@makitt/builder
shop3003--filter=@makitt/shop
compiler3004--filter=@makitt/compiler
docs3007apps/docs, /handoff, /coverage 등
makitt-api8080makitt devctl restart api
makitt-shop-api10000makitt devctl restart shop-api
makitt-ai-agent8000local; dev cluster 는 4000
Local OpenSearch9200docker-compose
Chrome DevTools9222makitt chrome, --user-data-dir=/tmp/chrome-debug-profile

4.2 Dev URLs

SurfaceURL
makitt-apihttps://api.dev.makitt.io
compilerhttps://compiler.dev.makitt.io
CDNhttps://cdn.dev.makitt.io
docs viewerhttps://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 가 인식)

userIdemail
test-aaronaaron@makitt.io
test-tomtom@makitt.io
test-lunaluna@makitt.io
test-jakejake@makitt.io
test-miamia@makitt.io
test-rexrex@makitt.io
test-noranora@makitt.io
test-kaikai@makitt.io
test-ivyivy@makitt.io
test-finnfinn@makitt.io

/reset aaron 처럼 bare name 만 줘도 자동 test- prefix.

자주 쓰는 테스트 샵

샵용도shopId / slug
Henriettei18n / fragment library 검증 기본 testbed71261f65-ee4e-4238-9b4f-d56cee9be523 (셀러 chance@makitt.io)
Henriette PDP위 샵 표준 상품productId 386d4ca2-6dbf-43bb-9d5e-47e215589ebc, slug cephira-shot-jellyfish-pore-serum
First ChanceToss 결제 승인용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"

  1. admin token 인지 dev-bypass 인지 surface 확인 (§1)
  2. admin token: 만료 (1h) → 재발급
  3. dev-bypass: profile 확인 (spring.profiles.active 가 local|dev 포함?)
  4. 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가지)