문서 목차
이 페이지에서목차

값 시트 — `.claude/store-handoff.json`

[SKILL.md](../SKILL.md) §4 의 형식과 조회 명령이다. 앱마다 스토어에 넣을 값을 **그 앱 기준으로** 적고, 값마다 출처와 확정 여부를 붙인다. 스토어 작업(사람이 하든 봇 브리프로 하든)은 `confirmed` 값만 쓴다.

SKILL.md §4 의 형식과 조회 명령이다. 앱마다 스토어에 넣을 값을 그 앱 기준으로 적고, 값마다 출처와 확정 여부를 붙인다. 스토어 작업(사람이 하든 봇 브리프로 하든)은 confirmed 값만 쓴다.

  • 위치: 앱 저장소 루트의 .claude/store-handoff.json.
  • co-bricks(cob create·cob generate)로 만든 프로젝트는 생성기가 초안을 남긴다. 생성기는 이미 있는 파일을 덮어쓰지 않는다.
  • 비밀값을 넣지 않는다. 비밀값이 필요한 칸은 secretRef 에 시크릿의 이름 만 적는다.

1. 모양#

아래 값은 전부 가상의 예시다. 생성기가 만드는 모양을 따랐다.

{
  "$comment": "비밀값 금지. 스토어 작업은 status 가 confirmed 인 값만 쓴다.",
  "botAccount": { "value": null, "status": "pending", "note": "최소 권한 계정을 정한다" },
  "play":  { "developerName": { "value": "Example Corp", "source": "project", "status": "proposal" },
             "developerId":   { "value": null, "status": "pending" } },
  "apple": { "teamName":  { "value": "Example Corp", "source": "project", "status": "proposal" },
             "teamId":    { "value": "<team-id>", "source": "project", "status": "confirmed" },
             "itcTeamId": { "value": null, "status": "pending" } },
  "allowedDomains": ["play.google.com", "accounts.google.com",
                     "appstoreconnect.apple.com", "appleid.apple.com", "idmsa.apple.com"],
  "rules": {
    "doNotCopyDeclarations": ["dataSafety", "appPrivacy", "advertisingId",
                              "targetAudience", "contentRating", "appAccess"],
    "status": { "confirmed": "…", "proposal": "…", "update-needed": "…", "replace": "…", "pending": "…" },
    "source": "project | copy | console | manual",
    "resultFormat": "엔티티 보고서 — id, type, store, field, value, source, status"
  },
  "developerAccount": { "legalName": { "value": "Example Corp", "source": "project", "status": "proposal" },
                        "address":   { "value": null, "status": "pending" } },
  "apps": {
    "app": {
      "directory": "app/sample",
      "distribution": { "value": null, "status": "pending" },
      "android": { "package": { "value": "com.example.sample", "source": "project", "status": "confirmed" } },
      "ios": { "bundleIds": { "prod": { "value": "com.example.sample",     "source": "project", "status": "confirmed" },
                              "stg":  { "value": "com.example.sample.stg", "source": "project", "status": "confirmed" } },
               "sku": { "value": "com.example.sample", "source": "project", "status": "proposal" },
               "primaryLocale": { "value": "ko", "source": "manual", "status": "confirmed" } },
      "store": {
        "name":               { "value": "Sample", "source": "project", "status": "confirmed" },
        "defaultLanguage":    { "value": "ko-KR", "source": "project", "status": "confirmed" },
        "pricing":            { "value": null, "status": "pending" },
        "contactEmail":       { "value": "support@source-app.example", "source": "copy", "status": "replace" },
        "privacyPolicyUrl":   { "value": "https://sample.example/privacy", "source": "project", "status": "update-needed",
                                "note": "이 앱이 실제로 수집하는 항목을 다루는지 확인" },
        "accountDeletionUrl": { "value": null, "status": "pending" },
        "reviewDemoAccount":  { "secretRef": "REVIEW_ACCOUNT_*", "status": "pending" },
        "dataSafety":         { "value": null, "status": "pending", "note": "바이너리의 실제 동작으로 답한다" },
        "appPrivacy":         { "value": null, "status": "pending", "note": "바이너리의 실제 동작으로 답한다" }
      }
    }
  }
}
  • 필드 하나 = { "value", "source", "status", "note"? }. value 는 문자열·null·객체 모두 된다(예: 스토어별 카테고리).
  • 앱 키(app 등)는 저장소의 앱 디렉터리마다 하나씩이다.
  • 생성기가 넣는 상태: 패키지·Bundle ID 는 빌드 파일에서 읽어 confirmed, Apple 팀 id 는 프로젝트 변수에서 confirmed, 이름·기본 언어·웹사이트·방침 URL·SKU 는 proposal, 나머지는 null + pending. 자리표시자 값은 넣지 않는다.
  • 런북이 쓰는 칸 이름: 선언 답은 store.<선언 이름> — 생성기가 만드는 dataSafety·appPrivacy 와, 필요할 때 더하는 advertisingId·targetAudience·contentRating·appAccess(rules.doNotCopyDeclarations 와 같은 이름), containsAds·newsApp·governmentApp·financialFeatures·healthApp. Play 스토어 설정은 store.category(생성기가 만든다)·store.tags(더한다).
  • 봇 계정(botAccount)에는 저장소 공개 범위에 맞는 값을 둔다. 공개 저장소면 역할 별칭을 두고, 브리프에 넣을 실제 주소는 사람이 채운다.
  • 언어 코드는 스토어마다 형식이 다르다. store.defaultLanguage 는 Play 형식(예: ko-KR)이고, App Store 기본 언어는 ios.primaryLocale(예: ko)을 더해 둔다(생성기는 만들지 않는다) — fastlane produce 도 이 형식만 받는다.
  • 콘솔에서 새로 생긴 값은 확인한 뒤 source console 로 더한다. 예: android.playAppId, ios.ascAppleId. 봇 보고서만 보고 넣지 않는다.
  • 첫 AAB 의 서명 키를 무엇으로 할지 기록하려면 android.uploadKey 를 더하고 결정만 적는다(생성기는 만들지 않는다). 키 파일·비밀번호는 넣지 않는다.

2. status · source#

status뜻브리프에
confirmed이 앱 기준으로 확정✅
proposal 이 앱 기준 제안값 — 사람이 확인하면 confirmed ❌
update-needed 이 앱 값이지만 내용을 고쳐야 한다(예: 방침이 새 앱을 다루지 않음) ❌
replace다른 앱 값이 들어가 있다 — 이 앱 값으로 바꿔야 한다❌
pending아직 없다. 만들어 넣지 않는다❌
source뜻
project이 앱 기준 값
copy다른 앱에서 옮긴 값 — status 가 confirmed 가 되기 전에는 초안이다
console콘솔이 새로 만든 값(앱 id 등)
manual사람이 직접 정한 값

3. 조회 명령#

SHEET=.claude/store-handoff.json
APP=app   # apps 아래 앱 키

모든 명령은 앱 키가 틀리면 값 시트에 앱 키가 없다 오류로 멈춘다(종료 코드 5). 오류가 난 점검은 통과가 아니다.

앱 한 개의 모든 값과 상태:

jq -r --arg app "$APP" '
  (.apps[$app] // error("값 시트에 앱 키가 없다: \($app)"))
  | paths(type == "object" and has("status")) as $p | getpath($p) as $f
  | [($p | map(tostring) | join(".")), $f.status,
     (if ($f | has("secretRef")) then "secretRef:" + $f.secretRef else ($f.value | tostring) end)]
  | @tsv' "$SHEET"

전달 전 점검 — 칸 기준. 런북의 "전제" 에 적힌 칸이 전부 confirmed 인가. 출력되는 줄(칸과 그 상태, 칸이 없으면 없음)이 하나라도 있으면 전달하지 않는다. 계정 단위 칸(botAccount, play.developerName 등)도 같은 목록에 넣을 수 있다.

REQUIRED="store.name store.defaultLanguage store.pricing store.contactEmail"   # 런북의 전제 칸
jq -r --arg app "$APP" --arg keys "$REQUIRED" '
  . as $root
  | ($root.apps[$app] // error("값 시트에 앱 키가 없다: \($app)")) as $a
  | ($keys | split(" ") | map(select(length > 0)))[] as $k
  | ($k | split(".")) as $p
  | (($a | getpath($p)) // ($root | getpath($p))) as $f
  | select(($f | type) != "object" or $f.status != "confirmed")
  | "\($k)\t\(if ($f | type) == "object" then $f.status else "없음" end)"' "$SHEET" \
  || echo "⛔ 값 시트를 읽지 못했다 — 전달하지 않는다"

브리프 입력값 표(confirmed 만, secretRef 칸은 빠진다):

jq -r --arg app "$APP" '
  (.apps[$app] // error("값 시트에 앱 키가 없다: \($app)"))
  | paths(type == "object" and has("status")) as $p | getpath($p) as $f
  | select($f.status == "confirmed" and ($f | has("value")) and $f.value != null)
  | "| \($p | map(tostring) | join(".")) | \($f.value | if type == "string" then . else tojson end) |"' "$SHEET"

브리프에서 비워 두고 pending 으로 보고하라고 쓸 칸:

jq -r --arg app "$APP" '
  (.apps[$app] // error("값 시트에 앱 키가 없다: \($app)"))
  | paths(type == "object" and has("status")) as $p | getpath($p) as $f
  | select($f.status != "confirmed") | "\($p | map(tostring) | join("."))\t\($f.status)"' "$SHEET"

전달 전 점검 — 값 기준. 확정되지 않은 값(이 앱과 계정 단위)이 브리프 본문에 섞였는가. 출력이 있으면 전달하지 않는다. 같은 값이 다른 칸에서 confirmed 이면 목록에서 빠진다(오탐 방지). 그래서 SKU 처럼 확정된 식별자와 값이 같은 제안값은 이 점검이 못 잡는다 — 칸 기준 점검이 먼저다.

NOT_CONFIRMED=$(mktemp)
if jq -r --arg app "$APP" '
  (.apps[$app] // error("값 시트에 앱 키가 없다: \($app)")) as $a
  | [ ($a | paths(type == "object" and has("status")) as $p | $a | getpath($p)),
      (del(.apps, .rules) | paths(type == "object" and has("status")) as $p | getpath($p)) ] as $fs
  | if ($fs | length) == 0 then error("상태를 가진 칸이 없다 — 시트 모양을 확인한다") else . end
  | ([$fs[] | select(.status == "confirmed") | .value | tostring]) as $ok
  | [$fs[] | select(.status != "confirmed" and has("value") and .value != null)
     | (.value | tostring) | select(IN($ok[]) | not)] | unique[]' "$SHEET" > "$NOT_CONFIRMED"; then
  [ -s "$NOT_CONFIRMED" ] && command grep -nF -f "$NOT_CONFIRMED" <브리프 파일>
else
  echo "⛔ 값 시트를 읽지 못했다 — 전달하지 않는다"
fi

계정 단위 값(개발자 계정·팀·봇 계정)의 상태:

jq -r '
  paths(type == "object" and has("status")) as $p | select(($p[0] | IN("apps", "rules")) | not)
  | getpath($p) as $f | "\($p | map(tostring) | join("."))\t\($f.status)\t\($f.value // "-" | tostring)"' "$SHEET"

4. 규칙#

  • 다른 앱 값은 source 를 남기고 replace 로 둔다. 초안일 뿐 제출하지 않는다.
  • 선언 항목(rules.doNotCopyDeclarations)은 초안으로도 복사하지 않는다. 답은 SKILL.md §5 방식으로 만든다.
  • 모르는 값은 null + pending. 계정 삭제 URL 처럼 아직 없는 페이지는 페이지가 생길 때까지 pending 이다.
  • 봇 보고서에서 얻은 값은 확인 전까지 후보다 — 권한 있는 계정의 API·화면으로 확인한 뒤에 넣는다.
  • 한 번 넣으면 바꿀 수 없는 값(SKU·식별자)은 사람이 confirmed 로 바꾼다. 생성기의 SKU 는 proposal 이다.

검색어를 입력하면 문서를 찾습니다.

↑ ↓ 이동 · Enter 열기Esc 닫기