optbrew 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/.claude-plugin/marketplace.json +4 -4
  2. package/.claude-plugin/plugin.json +2 -1
  3. package/.codex-plugin/plugin.json +2 -2
  4. package/AGENT_INSTALL.md +94 -0
  5. package/CHANGELOG.md +74 -1
  6. package/README.ko.md +129 -105
  7. package/README.md +129 -105
  8. package/THIRD_PARTY_NOTICES.md +88 -0
  9. package/docs/platforms.md +45 -8
  10. package/docs/release.md +37 -4
  11. package/lib/cli.mjs +5 -2
  12. package/lib/install.mjs +2 -1
  13. package/lib/records.mjs +2 -1
  14. package/lib/transaction.mjs +12 -5
  15. package/lib/update.mjs +3 -2
  16. package/package.json +29 -9
  17. package/plugin.json +2 -2
  18. package/skills/.claude-plugin/icon.png +0 -0
  19. package/skills/.claude-plugin/plugin.json +23 -0
  20. package/skills/README.md +27 -0
  21. package/skills/optbrew/SKILL.md +223 -8
  22. package/skills/optbrew/THIRD_PARTY_NOTICES.md +88 -0
  23. package/skills/optbrew/bundle-manifest.json +409 -49
  24. package/skills/optbrew/modules/naver-searchadvisor/scripts/audit.mjs +4 -4
  25. package/skills/optbrew/references/ai-crawlers.json +1 -1
  26. package/skills/optbrew/references/ai-crawlers.md +1 -1
  27. package/skills/optbrew/references/api.md +6 -4
  28. package/skills/optbrew/references/check-registry.json +16 -16
  29. package/skills/optbrew/references/chrome-paths.json +31 -0
  30. package/skills/optbrew/references/endpoints.json +10 -0
  31. package/skills/optbrew/references/harness-signals.json +5 -0
  32. package/skills/optbrew/references/intake.md +76 -5
  33. package/skills/optbrew/references/judging.md +233 -0
  34. package/skills/optbrew/references/limits.json +26 -0
  35. package/skills/optbrew/references/measurement.md +1 -1
  36. package/skills/optbrew/references/messages.ko.json +170 -0
  37. package/skills/optbrew/references/report.md +18 -6
  38. package/skills/optbrew/references/routing.md +2 -2
  39. package/skills/optbrew/references/runbooks/anthropic-api-key.md +34 -0
  40. package/skills/optbrew/references/runbooks/chrome-install.md +56 -0
  41. package/skills/optbrew/references/runbooks/cwv-site-collection.md +38 -0
  42. package/skills/optbrew/references/runbooks/google-api-key.md +51 -0
  43. package/skills/optbrew/references/runbooks/gsc-export.md +32 -0
  44. package/skills/optbrew/references/runbooks/index.json +18 -0
  45. package/skills/optbrew/references/runbooks/key-set-claude-cloud.md +32 -0
  46. package/skills/optbrew/references/runbooks/key-set-codex.md +33 -0
  47. package/skills/optbrew/references/runbooks/key-set-local.md +40 -0
  48. package/skills/optbrew/references/runbooks/naver-searchadvisor-export.md +32 -0
  49. package/skills/optbrew/references/runbooks/network-claude-cloud-custom.md +33 -0
  50. package/skills/optbrew/references/runbooks/network-codex-approval.md +26 -0
  51. package/skills/optbrew/references/runbooks/network-codex-config.md +36 -0
  52. package/skills/optbrew/references/runbooks/seranking.md +38 -0
  53. package/skills/optbrew/references/schemas/contract.schema.json +42 -15
  54. package/skills/optbrew/references/scoring.md +40 -5
  55. package/skills/optbrew/references/source-analysis.md +40 -0
  56. package/skills/optbrew/references/versions.json +9 -0
  57. package/skills/optbrew/scripts/adapters/anthropic.mjs +5 -5
  58. package/skills/optbrew/scripts/adapters/imports.mjs +138 -1
  59. package/skills/optbrew/scripts/audit.mjs +10 -96
  60. package/skills/optbrew/scripts/cli.mjs +5 -2
  61. package/skills/optbrew/scripts/core/bundle.mjs +30 -13
  62. package/skills/optbrew/scripts/core/capabilities.mjs +8 -4
  63. package/skills/optbrew/scripts/core/cdp.mjs +105 -0
  64. package/skills/optbrew/scripts/core/checks/canonical-consistency.mjs +56 -0
  65. package/skills/optbrew/scripts/core/checks/cwv-field.mjs +77 -0
  66. package/skills/optbrew/scripts/core/checks/discovery.mjs +43 -0
  67. package/skills/optbrew/scripts/core/checks/document-title.mjs +62 -0
  68. package/skills/optbrew/scripts/core/checks/entity-identity.mjs +31 -0
  69. package/skills/optbrew/scripts/core/checks/fact-consistency.mjs +31 -0
  70. package/skills/optbrew/scripts/core/checks/fetch-response.mjs +18 -0
  71. package/skills/optbrew/scripts/core/checks/index-policy.mjs +16 -0
  72. package/skills/optbrew/scripts/core/checks/index.mjs +18 -0
  73. package/skills/optbrew/scripts/core/checks/internal-navigation.mjs +67 -0
  74. package/skills/optbrew/scripts/core/checks/locale-links.mjs +55 -0
  75. package/skills/optbrew/scripts/core/checks/naver-markup.mjs +21 -0
  76. package/skills/optbrew/scripts/core/checks/observation.mjs +37 -0
  77. package/skills/optbrew/scripts/core/checks/prepare.mjs +103 -0
  78. package/skills/optbrew/scripts/core/checks/render-content.mjs +104 -0
  79. package/skills/optbrew/scripts/core/checks/search-crawl-policy.mjs +29 -0
  80. package/skills/optbrew/scripts/core/checks/structured-data-truth.mjs +46 -0
  81. package/skills/optbrew/scripts/core/chrome-temp.mjs +68 -0
  82. package/skills/optbrew/scripts/core/chrome.mjs +546 -0
  83. package/skills/optbrew/scripts/core/collector.mjs +65 -59
  84. package/skills/optbrew/scripts/core/compare.mjs +18 -1
  85. package/skills/optbrew/scripts/core/config.mjs +31 -0
  86. package/skills/optbrew/scripts/core/contracts.mjs +31 -2
  87. package/skills/optbrew/scripts/core/cwv.mjs +100 -0
  88. package/skills/optbrew/scripts/core/derive.mjs +10 -161
  89. package/skills/optbrew/scripts/core/evidence-store.mjs +112 -0
  90. package/skills/optbrew/scripts/core/facts.mjs +203 -19
  91. package/skills/optbrew/scripts/core/findings.mjs +19 -1
  92. package/skills/optbrew/scripts/core/html.mjs +41 -3
  93. package/skills/optbrew/scripts/core/index-targets.mjs +118 -0
  94. package/skills/optbrew/scripts/core/judgments.mjs +675 -0
  95. package/skills/optbrew/scripts/core/keys.mjs +86 -0
  96. package/skills/optbrew/scripts/core/link-graph.mjs +93 -0
  97. package/skills/optbrew/scripts/core/messages.mjs +23 -0
  98. package/skills/optbrew/scripts/core/metrics.mjs +4 -2
  99. package/skills/optbrew/scripts/core/migration.mjs +37 -0
  100. package/skills/optbrew/scripts/core/page-select.mjs +136 -0
  101. package/skills/optbrew/scripts/core/probe.mjs +83 -0
  102. package/skills/optbrew/scripts/core/protocol.mjs +38 -14
  103. package/skills/optbrew/scripts/core/render.mjs +160 -0
  104. package/skills/optbrew/scripts/core/report-compose.mjs +377 -0
  105. package/skills/optbrew/scripts/core/report-html.mjs +198 -0
  106. package/skills/optbrew/scripts/core/report-page.mjs +14 -0
  107. package/skills/optbrew/scripts/core/report.mjs +357 -131
  108. package/skills/optbrew/scripts/core/runbooks.mjs +63 -0
  109. package/skills/optbrew/scripts/core/scope.mjs +23 -5
  110. package/skills/optbrew/scripts/core/score.mjs +12 -4
  111. package/skills/optbrew/scripts/core/secrets.mjs +2 -0
  112. package/skills/optbrew/scripts/core/stages.mjs +694 -0
  113. package/skills/optbrew/scripts/core/transport.mjs +157 -0
  114. package/skills/optbrew/scripts/core/weights.mjs +2 -1
  115. package/skills/optbrew/scripts/key.mjs +90 -0
  116. package/skills/optbrew/scripts/preflight.mjs +176 -0
  117. package/skills/optbrew/scripts/run.mjs +81 -0
  118. package/skills/optbrew/source-lock.json +1 -1
  119. package/source-lock.json +1 -1
@@ -1,4 +1,4 @@
1
- # optbrew outcome measurement contract (0.1.0)
1
+ # optbrew outcome measurement contract (evaluation 0.4.0)
2
2
 
3
3
  Readiness scores and actual outcomes stay separate. `ScoreInput` contains the 22-family readiness observations. `MetricObservation[]` contains owner-provided native search, analytics, and AI panel observations. Neither indexing, search traffic, citations, conversions, nor revenue is inferred from readiness. The official source IDs and URLs are in `docs/superpowers/specs/2026-10-13-optbrew-sources.md` in the source repository. The rules below follow approved design section 7.
4
4
 
@@ -0,0 +1,170 @@
1
+ {
2
+ "word.unknown": "알 수 없음",
3
+ "fetch-response.http-error": "이 페이지가 {status} 오류를 돌려줍니다. 방문자와 크롤러가 열 수 있게 고치세요.",
4
+ "search-crawl-policy.disallowed": "robots.txt가 {agent:crawler}에게 이 페이지를 막고 있습니다. 검색에 나와야 하는 페이지라면 robots.txt의 이 규칙을 고치세요.",
5
+ "index-policy.noindex": "이 페이지가 noindex로 색인을 막고 있습니다. 검색에 나와야 하는 페이지라면 noindex를 지우세요.",
6
+ "render-content.no-body-text": "원본 HTML에도 크롬으로 그린 화면에도 본문 글자가 없습니다. 본문이 보이게 고치세요.",
7
+ "discovery.orphan": "이번에 확인한 페이지 중 어디에서도 이 페이지로 가는 링크가 없고, 사이트맵에도 없습니다. 사이트맵에 넣거나 다른 페이지에서 링크하세요.",
8
+ "discovery.orphan-sitemap-unusable": "이번에 확인한 페이지 중 어디에서도 이 페이지로 가는 링크가 없고, 선언한 사이트맵도 읽을 수 없습니다: {broken}. 사이트맵부터 고친 뒤 이 페이지를 넣으세요.",
9
+ "document-title.missing": "원본 HTML에 <title>이 없습니다. 페이지 내용을 설명하는 <title>을 넣으세요.",
10
+ "document-title.duplicate": "다른 페이지와 <title>이 같습니다. 페이지마다 서로 다른 <title>을 쓰세요.",
11
+ "document-title.same-document": "이 페이지의 <title>은 같은 페이지의 주소만 다른 변형({pages})하고만 같고, 다른 페이지의 <title>과는 다릅니다.",
12
+ "canonical-consistency.redirect-chain": "이 주소는 리다이렉트를 두 번 이상 거쳐 도착합니다. 한 번에 최종 주소로 가게 줄이세요.",
13
+ "canonical-consistency.target-status": "canonical이 가리키는 주소가 정상으로 열리지 않습니다. canonical을 열리는 주소로 바꾸세요.",
14
+ "canonical-consistency.invalid-url": "canonical 값이 올바른 주소가 아닙니다. canonical을 전체 주소로 고치세요.",
15
+ "internal-navigation.too-deep": "홈 페이지에서 이 페이지까지 클릭이 너무 많이 필요합니다. 홈 페이지나 메뉴에서 더 가까운 링크를 만드세요.",
16
+ "internal-navigation.weak-anchors": "링크 글자가 비어 있거나 '더보기'처럼 뜻이 없는 링크가 많습니다. 링크 글자에 가는 곳을 쓰세요.",
17
+ "structured-data-truth.parse-error": "JSON-LD가 문법 오류로 읽히지 않습니다. 쉼표와 따옴표를 확인해 올바른 JSON으로 고치세요.",
18
+ "structured-data-truth.fact-differs": "JSON-LD의 {type:fact-type} 값과 화면 글이 다릅니다. JSON-LD는 \"{linked}\", 화면은 \"{visible}\"입니다. 두 값을 맞추세요.",
19
+ "locale-links.no-x-default": "hreflang에 x-default가 없습니다. 기본으로 보여 줄 언어 페이지를 x-default로 추가하세요.",
20
+ "locale-links.no-return": "이 페이지가 가리킨 언어별 페이지에 이 페이지로 돌아오는 hreflang이 없습니다. 두 페이지가 서로를 가리키게 하세요.",
21
+ "locale-links.not-listed-back": "다른 페이지가 이 페이지를 언어별 페이지로 가리키는데, 이 페이지는 그 페이지를 가리키지 않습니다. 서로 가리키게 하세요.",
22
+ "naver-markup.no-og": "og:{name} 태그가 없습니다. 공유 미리보기를 위해 og:{name} 태그를 넣으세요.",
23
+ "naver-markup.no-description": "meta description이 없습니다. 페이지를 한두 문장으로 설명하는 meta description을 넣으세요.",
24
+ "naver-markup.no-canonical": "canonical 링크가 없습니다. 이 페이지의 대표 주소를 canonical로 넣으세요.",
25
+ "naver-markup.canonical-relative": "canonical이 상대 주소입니다: {canonical}. 전체 주소로 고치세요.",
26
+ "naver-markup.title-keyword-repeat": "<title>에서 같은 단어가 세 번 이상 반복됩니다: {words}. 반복을 줄여 제목을 짧게 쓰세요.",
27
+ "naver-markup.no-viewport": "meta viewport가 없습니다. 휴대폰 화면에 맞도록 meta viewport를 넣으세요.",
28
+ "naver-markup.resource-blocked": "robots.txt가 네이버 크롤러(Yeti)에게 이 페이지가 쓰는 파일 {path}을(를) 막고 있습니다. robots.txt에서 이 파일을 막는 Disallow 규칙을 지우세요.",
29
+ "entity-identity.name-differs": "페이지마다 사업체 이름이 다릅니다. 모든 페이지에서 같은 이름을 쓰세요.",
30
+ "entity-identity.no-same-as": "Organization JSON-LD에 sameAs가 없는 페이지가 있습니다. 공식 SNS나 지도 주소를 sameAs에 넣으세요.",
31
+ "entity-identity.no-home-organization": "홈 페이지에 Organization이나 LocalBusiness JSON-LD가 없습니다. 홈 페이지에 사업체 정보를 JSON-LD로 넣으세요.",
32
+ "fact-consistency.differs": "{type:fact-type} 값이 페이지마다 다릅니다. 이 페이지는 \"{found}\"이고, 다른 {count}개 페이지는 \"{label}\"입니다. 사실 확인용 목록이 없어 페이지끼리만 비교한 결과입니다. 어느 쪽이 맞는지 확인한 뒤 모든 페이지를 같은 값으로 고치세요.",
33
+ "fact-consistency.jsonld-differs": "JSON-LD의 {type:fact-type} 값과 화면 글이 다릅니다. JSON-LD는 \"{linked}\", 화면은 \"{visible}\"입니다. 두 값을 맞추세요.",
34
+ "lcp-field.slow": "방문자 실측 LCP(가장 큰 내용이 뜨는 시간)가 {value} ms입니다. {poor} ms를 넘으면 느린 것으로 봅니다(좋음은 {good} ms 이하). 자료 출처는 {source:cwv-source}입니다. 큰 이미지나 글이 더 빨리 뜨게 고치세요.",
35
+ "inp-field.slow": "방문자 실측 INP(누른 뒤 화면이 반응하는 시간)가 {value} ms입니다. {poor} ms를 넘으면 느린 것으로 봅니다(좋음은 {good} ms 이하). 자료 출처는 {source:cwv-source}입니다. 누를 때 오래 걸리는 자바스크립트를 줄이세요.",
36
+ "cls-field.slow": "방문자 실측 CLS(화면이 갑자기 밀리는 정도)가 {value}입니다. {poor}를 넘으면 나쁜 것으로 봅니다(좋음은 {good} 이하). 자료 출처는 {source:cwv-source}입니다. 이미지와 광고가 들어갈 자리를 미리 잡아 두세요.",
37
+ "fact-type.name": "이름",
38
+ "fact-type.phone": "전화번호",
39
+ "fact-type.year": "연도",
40
+ "fact-type.address": "주소",
41
+ "fact-type.headline": "글 제목(headline)",
42
+ "cwv-source.crux": "Google의 Chrome UX Report(CrUX)",
43
+ "cwv-source.psi": "PageSpeed Insights",
44
+ "remediation.restore-http-access.mechanism": "오류를 내는 응답이나 잘못된 리다이렉트를 고쳐서, 이 주소가 원래 보여 줄 페이지를 돌려주게 합니다.",
45
+ "remediation.restore-http-access.validation": "같은 주소를 다시 받아서 상태 코드, 리다이렉트 경로, 원본 응답을 확인합니다.",
46
+ "remediation.restore-http-access.effort": "사이트마다 다릅니다",
47
+ "remediation.restore-http-access.cost": "알 수 없음",
48
+ "remediation.restore-http-access.location_hint": "이 주소를 맡은 웹 서버 경로, 리다이렉트 규칙, 또는 페이지 배포",
49
+ "remediation.allow-target-crawler.mechanism": "검색에 나와야 하는 경로와 대상 크롤러에 해당하는 robots.txt 규칙을 고칩니다.",
50
+ "remediation.allow-target-crawler.validation": "robots.txt를 받아서 같은 크롤러 이름과 같은 경로로 다시 확인합니다.",
51
+ "remediation.allow-target-crawler.effort": "사이트마다 다릅니다",
52
+ "remediation.allow-target-crawler.cost": "알 수 없음",
53
+ "remediation.allow-target-crawler.location_hint": "사이트 주소 바로 아래의 robots.txt(예: https://example.com/robots.txt)에서 해당 크롤러와 경로의 규칙",
54
+ "remediation.remove-unintended-noindex.mechanism": "원본 응답이나 X-Robots-Tag 헤더에서 의도하지 않은 noindex를 지웁니다.",
55
+ "remediation.remove-unintended-noindex.validation": "원본 HTML과 응답 헤더를 받아서 robots 지시어를 확인합니다.",
56
+ "remediation.remove-unintended-noindex.effort": "사이트마다 다릅니다",
57
+ "remediation.remove-unintended-noindex.cost": "알 수 없음",
58
+ "remediation.remove-unintended-noindex.location_hint": "해당 페이지 템플릿의 robots meta, 또는 서버의 X-Robots-Tag 헤더",
59
+ "remediation.repair-document-title.mechanism": "해당 페이지 템플릿에 페이지 내용을 설명하는 <title>을 넣습니다.",
60
+ "remediation.repair-document-title.validation": "원본 HTML을 받아서 해당 주소의 <title>을 확인합니다.",
61
+ "remediation.repair-document-title.effort": "사이트마다 다릅니다",
62
+ "remediation.repair-document-title.cost": "알 수 없음",
63
+ "remediation.repair-document-title.location_hint": "해당 페이지 템플릿의 <title> 요소",
64
+ "remediation.repair-structured-data.mechanism": "화면에 보이는 내용과 필수 항목에 맞게 구조화 데이터(JSON-LD)를 고칩니다.",
65
+ "remediation.repair-structured-data.validation": "원본 HTML의 구조화 데이터를 화면 내용과 맞춰 보고, 해당 구조화 데이터 종류의 필수 항목이 빠지지 않았는지 확인합니다.",
66
+ "remediation.repair-structured-data.effort": "사이트마다 다릅니다",
67
+ "remediation.repair-structured-data.cost": "알 수 없음",
68
+ "remediation.repair-structured-data.location_hint": "해당 페이지 원본 HTML의 구조화 데이터 블록",
69
+ "authority.site_owner_edit": "사이트 소유자의 수정 권한",
70
+ "headline.not-indexed": "noindex가 붙어 있고 사이트맵에도 없는 페이지 {count}개는 사이트가 일부러 검색에서 뺀 페이지로 보고 색인 검사에서 뺐습니다({pages}). 이 가운데 검색에 나와야 하는 페이지가 있으면 그 페이지의 noindex를 지우세요.",
71
+ "follow-up.finding.input": "원본 증거와 사이트 소유자의 검토",
72
+ "follow-up.finding.next": "위 문제 문장의 방법대로 고친 뒤, 감사를 다시 해서 확인하세요.",
73
+ "follow-up.finding.when": "새 증거를 모은 뒤",
74
+ "follow-up.preflight-unreadable.problem": "시작 질문 전에 사이트를 미리 읽은 결과를 쓸 수 없었습니다. 그래서 시작 페이지와 사실이 확인된 페이지만 감사했습니다.",
75
+ "follow-up.preflight-unreadable.reason": "사이트 미리 읽기를 쓸 수 없음",
76
+ "follow-up.preflight-unreadable.input": "사이트를 미리 읽은 새 결과",
77
+ "follow-up.preflight-unreadable.next": "에이전트에게 감사를 처음부터 다시 해 달라고 하세요. 감사는 새 폴더에서 시작합니다.",
78
+ "follow-up.preflight-unreadable.when": "사이트를 미리 읽은 결과를 쓸 수 있는 다음 감사에서",
79
+ "follow-up.export-unusable.problem": "{kind:export-kind} 내보내기 파일을 쓸 수 없습니다. {reason:export-problem}",
80
+ "follow-up.export-unusable.reason": "내보내기 파일을 쓸 수 없음",
81
+ "follow-up.export-unusable.input": "사이트 소유자가 새로 내려받은 내보내기 파일",
82
+ "follow-up.export-unusable.next": "런북에 나온 대로 파일을 다시 내려받고, 다음 감사를 시작할 때 에이전트에게 그 파일의 경로를 알려 주세요.",
83
+ "follow-up.export-unusable.when": "새 파일을 넣고 하는 다음 감사에서",
84
+ "follow-up.production-pending.problem": "이 고침은 내 컴퓨터에서만 확인했습니다: {fix} {pages}. 실제 사이트에 적용하고 확인하는 일이 남았습니다.",
85
+ "follow-up.production-pending.reason": "실제 사이트에 적용하기 전",
86
+ "follow-up.production-pending.input": "실제 사이트에 배포할 권한과 실제 사이트 주소",
87
+ "follow-up.production-pending.next": "내 컴퓨터에서 확인한 변경을 배포한 뒤, 실제 사이트의 응답을 받으세요.",
88
+ "follow-up.production-pending.when": "허가받아 배포한 뒤 새 원본 응답을 받을 때",
89
+ "export-problem.export_file_missing": "받은 경로에서 파일을 찾지 못했습니다.",
90
+ "export-problem.export_file_unreadable": "파일을 열 수 없었습니다.",
91
+ "export-problem.export_file_too_large": "파일이 이 감사가 읽는 크기보다 큽니다.",
92
+ "export-problem.export_not_a_zip": "Search Console이 주는 zip 파일이 아닙니다.",
93
+ "export-problem.export_zip_damaged": "zip 파일이 깨져 있습니다.",
94
+ "export-problem.export_zip_method_unsupported": "이 감사가 읽지 못하는 방식으로 압축된 zip 파일입니다.",
95
+ "export-problem.export_table_not_found": "클릭 수와 노출 수가 있는 표를 파일에서 찾지 못했습니다.",
96
+ "export-problem.export_numbers_unreadable": "표의 숫자를 읽을 수 없었습니다.",
97
+ "export-problem.export_period_missing": "붙여 넣은 글에 어느 기간의 자료인지 적혀 있지 않습니다.",
98
+ "export-problem.export_not_accepted": "숫자가 가져오기 검사를 통과하지 못했습니다. 내보내기 파일을 다시 내려받아 보세요.",
99
+ "cost-status.unknown": "알 수 없음",
100
+ "cost-status.overrun": "한도 초과",
101
+ "cost.reservation": "{provider}: 비용 상태가 '{status:cost-status}'입니다 (예약 {reserved}, 실제 {actual}).",
102
+ "cost.host-policy": "{provider}: 유료 비용 상한을 코드가 지키지 못합니다. 지금 쓰는 에이전트 프로그램의 설정이 비용 상한을 정합니다.",
103
+ "cost.bulk-crawl-unverified": "대량 수집: 범위와 비용 상한을 코드가 지키는지 확인되지 않았습니다. 지금 쓰는 에이전트 프로그램의 설정이 상한을 정합니다.",
104
+ "cost.anthropic": "Anthropic 직접 호출 v{version}: 글자만 처리합니다. 쓸 수 있는 모델은 claude-fable-5-1, claude-opus-5-5, claude-sonnet-5-5, claude-haiku-5-5입니다. 최대 출력은 128000입니다. 시도마다 토큰 1000000+max_tokens 이상을 예약합니다. 도구와 검색 기능은 쓸 수 없습니다. 금액만으로 정한 한도나 금액과 토큰을 섞은 한도도 쓸 수 없습니다. 예약한 양은 쓸 수 있는 용량이며 실제 청구액이 아닙니다.",
105
+ "crawler.Googlebot": "구글 크롤러(Googlebot)",
106
+ "crawler.Yeti": "네이버 크롤러(Yeti)",
107
+ "export-kind.gsc": "Search Console",
108
+ "export-kind.naver": "네이버 서치어드바이저",
109
+ "metric-origin.owner-export": "사이트 소유자가 준 내보내기 파일의 출처와 제공처의 완전성은 따로 확인하지 못했습니다.",
110
+ "metric-origin.panel": "검색 서비스가 돌려준 답의 출처와 의미 판단은 기록된 영수증에 근거하며, 따로 확인하지 못했습니다.",
111
+ "outcome-word.comparable_windows": "비교할 수 있는 기간",
112
+ "outcome-word.not_comparable_or_missing_window": "비교할 수 없거나 기간이 빠짐",
113
+ "outcome-word.measured": "측정됨",
114
+ "outcome-word.unknown": "알 수 없음",
115
+ "outcome-word.zero_or_missing": "0이거나 빠짐",
116
+ "outcome-word.not_applicable": "해당 없음",
117
+ "state.pass": "통과",
118
+ "state.fail": "실패",
119
+ "state.partial": "일부 통과",
120
+ "state.unknown": "알 수 없음",
121
+ "state.not_applicable": "해당 없음",
122
+ "identity-basis.name_and_fact": "이름과 식별 사실이 맞음",
123
+ "identity-basis.name_only": "이름만 맞음",
124
+ "identity-basis.no_matching_name": "맞는 이름이 없음",
125
+ "question-coverage.none-answered": "이번에 읽은 페이지 중에서는 시작 질문 목록의 질문에 답하는 페이지를 찾지 못했습니다.",
126
+ "question-coverage.none-answered.mechanism": "사이트에 답이 이미 있다면 그 질문의 말을 해당 페이지의 제목이나 첫 문장에 쓰고, 없다면 질문마다 답이 바로 보이는 페이지를 하나씩 만드세요.",
127
+ "question-coverage.none-answered.effort": "사이트마다 다름 — 이미 있는 페이지의 제목을 고치면 작고, 새 페이지를 쓰면 큼",
128
+ "question-coverage.none-answered.validation": "다시 검사하면 질문에 답하는 페이지를 찾은 질문의 수가 늘어납니다.",
129
+ "question-coverage.not-found": "이번에 읽은 페이지에서는 이 질문에 답하는 페이지를 찾지 못했습니다.",
130
+ "question-coverage.note": "「찾지 못했습니다」는 이번 감사의 판정에서 그 질문의 답으로 꼽힌 페이지가 없다는 뜻일 뿐 사이트에 답이 없다는 뜻이 아니며, 머리말·바닥글처럼 모든 페이지에 되풀이되는 글은 판정이 그 글을 인용한 질문만 답한 것으로 셉니다.",
131
+ "model-fix.cited": "해당 글: 「{quote}」",
132
+ "model-fix.where": "{pages}의 본문",
133
+ "model-fix.claim-support": "이 페이지는 숫자·효과·사실을 말하는데, 근거나 출처가 하나도 붙어 있지 않습니다.",
134
+ "model-fix.claim-support.mechanism": "주장 옆에 출처(기관이나 자료 이름)와 기준 날짜를 적고, 숫자에는 어떻게 셌는지 한 줄을 덧붙이세요.",
135
+ "model-fix.claim-support.effort": "작음 — 주장마다 문장 한 줄",
136
+ "model-fix.claim-support.validation": "다시 검사하면 이 항목이 통과로 바뀝니다.",
137
+ "model-fix.answer-clarity": "이 페이지는 「{question}」에 답하려는 페이지인데, 그 답이 틀렸거나 빠져 있습니다.",
138
+ "model-fix.answer-clarity.mechanism": "「{question}」의 답(금액·시간·장소 등)을 페이지 첫머리에 한 문장으로 직접 적으세요.",
139
+ "model-fix.answer-clarity.effort": "보통 — 문단 하나 고치기",
140
+ "model-fix.answer-clarity.validation": "다시 검사하면 이 항목이 통과로 바뀝니다.",
141
+ "model-fix.original-value": "이 페이지는 직접 겪은 일이나 직접 모은 자료가 보이지 않고, 같은 말의 반복이나 검색어 채우기처럼 읽힙니다.",
142
+ "model-fix.original-value.mechanism": "우리 기관이 직접 한 일, 직접 센 숫자, 실제 진행 절차 중 하나를 문단으로 넣으세요.",
143
+ "model-fix.original-value.effort": "보통 — 문단 하나 쓰기",
144
+ "model-fix.original-value.validation": "다시 검사하면 이 항목이 통과로 바뀝니다.",
145
+ "model-fix.freshness-truth.absence": "이 페이지에 바뀔 수 있는 내용(운영 시간·정원·가격 등)이 있는데 언제 기준인지 날짜가 없습니다.",
146
+ "model-fix.freshness-truth.absence.mechanism": "그 내용 옆에 「2026년 10월 17일 기준」처럼 확인한 날짜를 적으세요.",
147
+ "model-fix.freshness-truth.absence.effort": "작음 — 문장 한 줄",
148
+ "model-fix.freshness-truth.absence.validation": "다시 검사하면 이 항목이 통과로 바뀝니다.",
149
+ "model-fix.freshness-truth.shown": "이 페이지의 날짜가 내용과 맞지 않거나, 중요한 내용이 오래된 것으로 보입니다.",
150
+ "model-fix.freshness-truth.shown.mechanism": "지난 일은 지난 일로 고치고, 바뀐 내용은 최신으로 고친 뒤 확인한 날짜를 옆에 적으세요.",
151
+ "model-fix.freshness-truth.shown.effort": "작음 — 문장 한두 줄",
152
+ "model-fix.freshness-truth.shown.validation": "다시 검사하면 이 항목이 통과로 바뀝니다.",
153
+ "model-fix.source-transparency.absence": "이 페이지의 본문에는 누가 운영하거나 쓴 글인지(기관·글쓴이)가 적혀 있지 않습니다.",
154
+ "model-fix.source-transparency.absence.mechanism": "페이지 본문 안에, 제목 아래나 글 끝에 운영 기관이나 글쓴이의 이름과 연락 방법을 한 줄로 적으세요(대부분의 페이지에 똑같이 되풀이되는 줄은 검사가 그 페이지의 글로 세지 않습니다).",
155
+ "model-fix.source-transparency.absence.effort": "작음 — 문장 한 줄",
156
+ "model-fix.source-transparency.absence.validation": "다시 검사하면 이 페이지의 본문에 적은 운영 기관이 확인되어 이 항목이 통과로 바뀝니다.",
157
+ "model-fix.source-transparency.shown": "이 페이지는 출처나 책임자가 꼭 필요한 내용인데, 페이지에서 그것이 보이지 않습니다.",
158
+ "model-fix.source-transparency.shown.mechanism": "내용의 출처(기관이나 자료 이름), 책임 기관, 연락 방법을 페이지 본문에 적으세요.",
159
+ "model-fix.source-transparency.shown.effort": "작음 — 문장 한두 줄",
160
+ "model-fix.source-transparency.shown.validation": "다시 검사하면 이 항목이 통과로 바뀝니다.",
161
+ "model-fix.entity-identity": "홈 페이지가 사업체의 이름을 말하지 않거나, 다른 이름을 말합니다.",
162
+ "model-fix.entity-identity.mechanism": "홈 페이지 첫 화면에 사업체의 정식 이름과 전화번호·주소를 적으세요.",
163
+ "model-fix.entity-identity.effort": "작음 — 문장 한 줄",
164
+ "model-fix.entity-identity.validation": "다시 검사하면 이 항목이 통과로 바뀝니다.",
165
+ "readiness.blocked": "막힘",
166
+ "readiness.unverified": "확인 전",
167
+ "readiness.provisional": "임시",
168
+ "readiness.measured": "측정됨",
169
+ "readiness.not_applicable": "해당 없음"
170
+ }
@@ -1,6 +1,6 @@
1
- # Report packet and evidence contract (v0.1.0)
1
+ # Report packet and evidence contract (contract 2.0.0)
2
2
 
3
- `node skills/optbrew/scripts/cli.mjs report --input <packet.json> --output-dir <new-directory>` writes `report.json` and `report.md`. It makes no network or paid calls. It refuses to replace an existing report. A report is a local audit artifact; a local passing verification does not establish deployment, indexing, traffic, ranking, citations, or business impact.
3
+ `node skills/optbrew/scripts/cli.mjs report --input <packet.json> --output-dir <new-directory>` writes `report.json`, `report.md` and `report.html` (the page shows the full-steps runbooks, because a packet does not record the guidance level). It makes no network or paid calls. It refuses to replace an existing report. A report is a local audit artifact; a local passing verification does not establish deployment, indexing, traffic, ranking, citations, or business impact.
4
4
 
5
5
  `ReportPacket` retains the approved `run_context`, raw `baseline.input`, optional `after.input`, their `score()` results, `compare()` result, metrics, findings, changes, costs, remaining work, and source identity. `evidence_root` points to the run directory. The CLI resolves a relative root against the packet's directory. Each `evidence_index` item has `{ref,sha256,kind,run_id,source_ref?,source_url?}`. Every indexed file is reopened, checked against its SHA-256, and contained by the resolved run directory. The context file at each phase's `run_context.evidence_ref` must contain exactly that run context.
6
6
 
@@ -20,10 +20,22 @@ For `verification.status:"passed"`, the indexed receipt has `{run_id,change_id,t
20
20
 
21
21
  Collector verdicts are replayed, not trusted. For each collector-sourced `index-policy`, `document-title` and `search-crawl-policy` observation the report reopens the preserved raw bytes of the same phase and rebuilds the verdict with the code the audit used. Index directives come from the HTML `<head>` meta tags (comments and script, style, textarea and title text are ignored) and the collected `X-Robots-Tag` header. The generic `robots` directives combine with the target engine's own (`googlebot` for Google, `yeti` for Naver); a header prefixed with another crawler name does not apply, and a crawler prefix scopes only the header line that carries it. Meta directives may be separated by commas or semicolons. A robots meta tag inside `<template>` or `<noscript>` is conservatively counted as a noindex directive. A present title replays as `unknown` because uniqueness and descriptiveness need separate evidence; a missing title replays as `fail`; reviewed duplicate-title or semantic verdicts from other evaluators are not rejected for having a title. A crawl-policy verdict needs the declared `robots` auxiliary artifact, its content hash, the page's final URL and a target engine, then recomputes `pass` or `fail` from the robots bytes. Every collected response is replayed from its hashed `*.response.json` record: the collection entry must equal the record, the final URL must follow the recorded redirect chain (no redirect means the final URL is the normalized request URL), and header lines come from the record, so index every `responses[].evidence_ref` from the audit index with `source_ref:"optbrew:collector"`. A selected page must be either collected or listed in `skipped`, and an indexed collector artifact requires its baseline audit snapshot. Every observation the collector plans for a selected page (fetch, title, index and, for Naver with a disallowing declared robots file, crawl policy) must be present in the packet and in the saved observations; a missing one is an error, never a silent pass. A redirected URL binds its requested unit to the artifact first; the module output must describe that artifact's final URL (`requested_url` and `redirect_chain` stay with it). A header-only `noindex` finding uses the same HTML plus header replay as the observation and must belong to the finding's own unit.
22
22
 
23
- `verified_local` uses the production binding: the change and its receipt name an `observation_id` and `observation_evidence_ref` for a passing after observation that the collector produced (so the audit replay covers it); that raw evidence entry is labelled `phase:"after"`, `environment:"local"` and the run target, and the after context is `local` for the same target. A byte-hash receipt or an agent-written observation alone leaves the finding `modified_unverified`. For `verified_production` the finding carries `resolution` (observation ID, evaluator type, source) and the Status cell reads `resolved by <evaluator_type> observation from <source_ref>`, so a model-attested resolution is visibly distinct from a collector-verified one; the production gating rules are unchanged. Native path mappings match whole path segments: the exported path starts with a single `/` (protocol-relative paths and absolute URLs on another host are refused) and equals the approved `path_prefix` or continues after a `/`.
23
+ `verified_local` uses the production binding: the change and its receipt name an `observation_id` and `observation_evidence_ref` for a passing after observation that the collector produced (so the audit replay covers it); that raw evidence entry is labelled `phase:"after"`, `environment:"local"` and the run target, and the after context is `local` for the same target. A byte-hash receipt or an agent-written observation alone leaves the finding `modified_unverified`. For `verified_production` the finding carries `resolution` (observation ID, evaluator type, source) and the Status cell reads `resolved by <evaluator_type> observation from <source_ref>`, which names the observation that resolved the finding (only a `code` observation from `optbrew:collector` can, because a model judgment is bound to spans of the stored judge queue and never stands for a code-judged check); the production gating rules are unchanged. Native path mappings match whole path segments: the exported path starts with a single `/` (protocol-relative paths and absolute URLs on another host are refused) and equals the approved `path_prefix` or continues after a `/`.
24
24
 
25
- Collector verdicts for the families below come from one derivation, `scripts/core/derive.mjs`, which the audit runs on the live responses and the report runs again on the hashed raw evidence. Every derived observation must be in the packet and in the saved observations, and each packet observation must match its derivation in state, credit and evidence reference; an extra or missing collector observation is an error. The families: `fetch-response`; `index-policy`; `document-title` (missing or duplicated across the selected pages fails; a unique title stays `unknown`); `search-crawl-policy` for every engine with a token in `ai-crawlers.json`, judged on the declared robots file; `canonical-consistency` (a redirect chain of two or more hops, or a canonical target that returns an error, fails; a self-referencing or reachable canonical within one hop passes; the collector fetches in-scope canonical targets while the request count is under the limit); `discovery` (a selected public page that no collected page links to and no reachable declared sitemap lists fails when the crawl was complete and at least two pages were collected; a sitemap index or an incomplete crawl leaves it `unknown`; while any collected page has scripts and no anchors, an unlinked page is `unknown` unless a fetched sitemap that lists other pages leaves it out; entity-encoded hrefs are decoded and `index.html` equals its directory); `structured-data-truth` (a live JSON-LD block that does not parse fails; blocks inside HTML comments and empty placeholder blocks are ignored; parsing alone never passes); `naver-markup` (Open Graph, description, canonical, viewport, title repetition, CSS or JS blocked for Yeti); and, for runs that enable them, `fact-consistency` and `entity-identity`. Fact consistency compares name, address, phone and founding year across the selected pages. Values come only from Organization or LocalBusiness JSON-LD, `tel:` links (counted only on a page with no labelled or JSON-LD phone, so a mobile inquiry link never contradicts the landline) and explicitly labelled fields (`Phone:`, `Tel`, `Address:`, `Founded:` and Korean equivalents); prose, dates and numbers elsewhere are never read as facts, and a fax or registration number is not a phone. Addresses are compared after unifying street abbreviations, dropping postal codes and a trailing country, and treating a street address with or without city detail as the same. A page that presents itself as a branch (the word branch, 지점 or 지사 in its title or first heading, or a branch JSON-LD entity or a different `@id`) is a separate entity and is not compared. A reference value needs at least two pages and a unique majority; a page that disagrees, or whose JSON-LD disagrees with its own visible text, fails; a tie or a single page stays `unknown`. This is consistency between pages, not accuracy against an approved fact list. `entity-identity` (site unit) fails on conflicting names, Organization JSON-LD without `sameAs`, or no Organization JSON-LD on the home page while other pages have it. The `robots.txt` Sitemap line, og:image size, hreflang and JavaScript-only navigation are not scripted. Audit evidence from 0.1.1 or earlier has no request ledger or module list and lacks the families added in 0.1.2, so report replay refuses it with an "older optbrew release" error; run the audit again with 0.1.2 and build a new packet. Every audit also writes `ledger.json`: one entry per queued or rejected URL with its request count. Index it with `source_ref:"optbrew:collector"`. Replay checks that the skipped list, `limit_reached`, `request_count` and `evaluated_count` equal the ledger, that each response entry matches a hashed response record (requests = redirect hops + 1), that a request limit is claimed only when the requests recorded reach the limit, and that a selected page is skipped only for the request limit. The audit index also lists the modules that ran; every listed module must have its output in `modules.json`. All derived families above are computed from the collector evidence whether or not a module ran.
25
+ Collector verdicts for the families below come from one derivation, `scripts/core/derive.mjs`, which the audit runs on the live responses and the report runs again on the hashed raw evidence. Every derived observation must be in the packet and in the saved observations, and each packet observation must match its derivation in state, credit and evidence reference; an extra or missing collector observation is an error. The families: `fetch-response`; `index-policy`; `document-title` (missing or duplicated across the selected pages fails; with at least two audited pages a unique title that is not only the site name or a generic word passes, and otherwise it stays `unknown`; only pages meant to be indexed count. A page whose canonical link names the same page without the query string or fragment (same site with `www.` and http/https folded, same path; a relative canonical is resolved against `<base href>` when the head has one) is a variant of that page, and the variants and the page are one document that never duplicates itself; its pass reason says the title is shared only with query variants of the same page. A canonical to the home page, to another path or to another site leaves each page its own document, so those pages still duplicate each other); `search-crawl-policy` for every engine with a token in `ai-crawlers.json`, judged on the declared robots file; `canonical-consistency` (a redirect chain of two or more hops, or a canonical target that returns an error, fails; a self-referencing or reachable canonical within one hop passes; the collector fetches in-scope canonical targets within the request limit); `discovery` (a selected public page that no collected page links to and no reachable declared sitemap lists fails when the crawl was complete and at least two pages were collected; a sitemap index or an incomplete crawl leaves it `unknown`; an unlinked page is `unknown` when another collected page's own bytes (inline scripts included) name its path as a whole path, or when a script-only page loads a same-origin script file that was not collected, unless a fetched sitemap that lists other pages leaves it out (an inline script that builds the URL from pieces can still give a false orphan); entity-encoded hrefs are decoded and `index.html` equals its directory); `structured-data-truth` (a live JSON-LD block that does not parse fails; blocks inside HTML comments and empty placeholder blocks are ignored; parsing alone never passes); `naver-markup` (Open Graph, description, canonical, viewport, title repetition, CSS or JS blocked for Yeti); and, for runs that enable them, `fact-consistency` and `entity-identity`. Fact consistency compares name, address, phone and founding year across the selected pages. Values come only from Organization or LocalBusiness JSON-LD, `tel:` links (counted only on a page with no labelled or JSON-LD phone, so a mobile inquiry link never contradicts the landline) and explicitly labelled fields (`Phone:`, `Tel`, `Address:`, `Founded:` and Korean equivalents); prose, dates and numbers elsewhere are never read as facts, and a fax or registration number is not a phone. A national number written with spaces only (02 555 0142) is read in a label, and so are Korean nationwide representative numbers (15xx, 16xx or 18xx plus four digits). Addresses are compared after unifying street abbreviations, dropping postal codes and a trailing country, and treating a street address with or without city detail as the same. A page is a separate entity and is not compared only on structural evidence: JSON-LD `branchOf`, `parentOrganization` or a different `@id`; a branch-list URL path (`/branch/`, `/branches/`, `/locations/`, `/stores/`, `/지점/`); or the word branch, 지점 or 지사 in its title or first heading together with an address of its own that differs from the main entity's. A title word alone does not hide a wrong phone. A reference value needs at least two pages and a unique majority; a page that disagrees, or whose JSON-LD disagrees with its own visible text, fails; a tie or a single page stays `unknown`. This is consistency between pages, not accuracy against an approved fact list. `entity-identity` (site unit) fails on conflicting names, Organization JSON-LD without `sameAs`, or no Organization JSON-LD on the home page while other pages have it. The `robots.txt` Sitemap line, og:image size, hreflang and JavaScript-only navigation are not scripted. Audit evidence from 0.1.2 or earlier has no hashed failure records and lacks the 0.1.3 discovery and fact observations, so report replay refuses it with an "older optbrew release" error; run the audit again with the current release and build a new packet. Every audit also writes `ledger.json`: one entry per queued or rejected URL with its request count. Index it with `source_ref:"optbrew:collector"`. Replay checks that the skipped list, `limit_reached`, `request_count` and `evaluated_count` equal the ledger, that each response entry matches a hashed response record (requests = redirect hops + 1), that each failed fetch matches a hashed failure record (`*.failure.json`, listed in `failures`; requests = answered redirect hops + at most one attempt that got no response), that a URL appears in only one entry, that a failed fetch or a request-limit skip belongs to a URL with provenance (selected, auxiliary, or linked from a collected page), that the total never exceeds the request limit and a limit is claimed only when the recorded requests use all of it, and that a selected page is skipped only for the request limit. Index every `*.failure.json` file with `source_ref:"optbrew:collector"`, as for `*.response.json`. The audit index also lists the modules that ran; every listed module must have its output in `modules.json`. All derived families above are computed from the collector evidence whether or not a module ran.
26
26
 
27
- The report opens with a Headline per profile. At least 80% measured coverage and a verdict on every core access and indexing check print the score with its coverage; below 80% the headline is `insufficient coverage`, and with no verdict on a core check it is `insufficient core checks`, with the measured-subset score and the missing-data bounds, and `json.headline[]` carries `status` (`sufficient`, `insufficient_coverage`, `insufficient_core`), `coverage_exact`, `headline_score` (null when withheld), `measured_subset_score`, `coverage` and `missing_bounds`.
27
+ **Composed once.** `core/report-compose.mjs` builds one `Composed` object from the checked report data, and `report.json`, `report.md` and `report.html` only render it: `json.composed = { headline, fixes, completeness, remaining, conditions, follow_ups }` comes before the evidence it was built from, and no score field appears before `fixes`. `headline` is the one-line conclusion `{ open, blocking, already_fixed, text, not_indexed }`; `not_indexed` is `{ count, pages, message_key, message_values, text }` when the run treated pages as deliberately not indexed (noindex and absent from the sitemaps it read), else null, and `report.md` prints it under the result line (`색인 제외:`) and `report.html` in the top box, so an owner sees a noindex that was hidden by mistake without opening the run conditions; with no open fix and any latest-phase headline other than `sufficient` it says nothing failed in what was checked but the check is incomplete and points to the completeness section (얼마나 확인했나), where the cause is shown, never that nothing is left to fix. `fixes` (each with its `change_paths`, `where` (the place to fix), `priority_label` and `status_label`) are the findings sorted open before resolved, then blocker first, then priority (P0 first, an unknown priority last), then the problem seen on the most pages. `completeness` has, per profile, `pass_rate_in_measured` (the score: the pass rate within what was measured), `coverage` and `coverage_without_cwv` (coverage if the three field-data checks were left out), all from the latest phase (the after phase when there is one), plus `before` and `after` (`headline`, `coverage`, `missing_bounds`, `readiness_status` of each phase), `difference` and `between_measured_subsets`. `remaining` lists what is still blank in the latest phase: `{ item, fills, runbook_ids }` for each unknown reason a runbook can fix and each start item the user did not answer (`answered_runbooks` in the packet lists those answered). `item` says what is missing and what it adds; the export file adds actual outcomes, not a score. A user who has a Google key (or Chrome) is not offered the runbook for getting it; the Google key item names the key-saving runbook for the probe's harness. `follow_ups` is the remaining-work list (the checked `follow_ups` rows, composed here so the HTML only formats them). `conditions.lines` is the one list of label and value pairs both formats print under the run conditions. `conditions` holds the start probe (of the latest phase's run context, the same phase as the blanks), the blocker counts `blockers: { before, after }` (`{ count, items, core_unknown }`, `after` null without an after phase), the checked model judgments, the lab Core Web Vitals (`lab_cwv` in the packet, reference only, never a score input) and unscored `notes`: the JavaScript note (Naver and AI crawlers may not run JavaScript) appears only when a page passed `render-content` after rendering. Report text is Korean (R64): every label, headline sentence and note in `report.md` and `report.html`; JSON keys and ids stay English. In the Markdown the order is: result line, 먼저 고칠 것 (fixes), 얼마나 확인했나 (completeness), 아직 비어 있는 곳 (still blank), 실행 조건 (run conditions), 남은 일 (remaining work), 실제 결과 (actual outcomes), 모델 판단 (model judgments). Terms are fixed: 크롬 and 자바스크립트, never Chrome or JavaScript spelled in Korean text. Finding messages, remediation text, remaining-work rows and cost notes are Korean too (R65): a finding keeps its English `message` (the text findings are grouped by, and the text in the golden files and replay) and carries `message_key` and `message_values`; the Korean sentence for a key is in `references/messages.ko.json`, and `composed.fixes[].text_ko` (`message`, and the chosen remediation's `mechanism`, `validation`, `effort`, `cost`, `location_hint`) and `composed.follow_ups[].text_ko` hold it for the renderers. A key with no entry, such as a model's finding, shows its own words. A remaining-work row that a host writes itself (a caller of `buildReport` outside the run path) has no key, so it must be written in the user's language: the report prints it as it is. Replay binds the key and values of a finding to the failed verdict that states the problem, as it does the English message.
28
28
 
29
- Both formats show four tables: score, remediation, actual outcomes, and remaining work. Score arithmetic comes from `score()` and `compare()`. Actual outcome pairs require the same native metric definition, filters, sample selection and panel, plus two adjacent complete 28-day windows. An absent or ambiguous value stays unknown. Markdown escapes pipes, HTML and line breaks in evidence and titles. `data_class:synthetic` labels fixture reports and cannot be presented as observed production performance.
29
+ **HTML report.** `core/report-html.mjs` formats the composed report as one self-contained page (inline CSS and SVG, no script, no outside file, a policy line that refuses any outside request, light and dark by `prefers-color-scheme`, readable at phone width). The top three sections are the fixes, the completeness and the blanks, in few words; every value from the site or the user is escaped. Each blank item opens its runbooks in `<details>` at the guidance level of the start answers as plain lists. A step has a small schematic drawing only where the runbook names the zone with a trailing tag such as `{화면: 주소창}` (zones: 주소창, 화면 위쪽, 왼쪽 메뉴, 화면 가운데, 새 창, 터미널); a zone is never guessed from the words of a step (R66), and no shipped runbook carries a tag today. Every coverage the report shows is the floor to a tenth that the 80% threshold is judged on. Each profile card shows one verdict word, a meter and one plain sentence, with the long line folded; 남은 일 (`follow_ups`) and the run conditions are folded sections. The audit writes the page as `report.html` beside the other two and prints its path in `reports.html`.
30
+
31
+ Other `report.json` fields: `findings` repeats `composed.fixes` for readers of earlier reports, `model_judgments` stays the checked judgment list, and `follow_ups` (called `remaining` before 0.2.0) holds the per-finding follow-up rows shown under Remaining work. `composed.remaining` is a different list: the blanks.
32
+
33
+ The Completeness section opens with a Headline per profile. At least 80% measured coverage and a verdict on every core access and indexing check print the score with its coverage; below 80% the headline is `insufficient coverage`, and with no verdict on a core check it is `insufficient core checks`, with the measured-subset score and the missing-data bounds, and a phase that was not audited (its `RunContext.audited` is not true) shows `not audited` instead of a score when the score would otherwise print, and says so beside any other status (`audited: false`); each phase headline in `composed.completeness[]` carries `status` (`sufficient`, `unaudited`, `insufficient_coverage`, `insufficient_core`), `coverage_exact`, `headline_score` (null when withheld), `measured_subset_score`, `coverage` and `missing_bounds`. One rule shows every coverage in the three formats (`baseline.scores[]` and `after.scores[]` are the replayed ScoreResult objects with their own one-decimal rounding; the coverage the report shows is `composed.completeness[].coverage`): the floor to a tenth of the exact value, the value the 80% threshold reads. `coverage_exact` keeps the exact value, and `completeness[].coverage_without_cwv` follows the same rule. The conditions list and the Model judgments section say how many units the model declared not applicable, for each family (`composed.conditions.model_not_applicable`), apart from the coverage those units leave out, and each profile card of the HTML says how many units that is. Under Model judgments, `composed.conditions.question_coverage` lists each question of the fixed panel with the pages that answer it.
34
+
35
+ Both formats show the same tables (`json.tables`): fixes (`remediation`), completeness (`score`), actual outcomes, remaining work and model judgments. Score arithmetic comes from `score()` and `compare()`. Actual outcome pairs require the same native metric definition, filters, sample selection and panel, plus two adjacent complete 28-day windows. An absent or ambiguous value stays unknown. Markdown escapes pipes, HTML and line breaks in evidence and titles. `data_class:synthetic` labels fixture reports and cannot be presented as observed production performance.
36
+
37
+ **Model judgments.** The audit stores the model's work list as a `judge-queue` evidence file, listed with its hash in the audit index section `judge_queue` (candidate text spans per page and per content family; index the file as `kind:"judge-queue"`). A `host_model` observation has `source_ref:"optbrew:model-judgment"`, cites the raw evidence of the page its unit names, and, with a state other than `unknown`, carries `evidence_span: [{ evidence_sha256, start, end }]`: one UTF-8 byte range (end excluded) per cited span, each starting and ending on character boundaries. Report replay checks that the evidence is the audit raw entry of that page, that each range has that entry's hash, lies inside the bytes and is exactly a candidate of the stored queue, and rejects a tampered span or queue. Replay also recomputes the candidate text runs from the raw bytes, so a forged queue cannot widen them, and reads the queue only from evidence of kind `judge-queue`. A model's `not_applicable` carries one of the family's registry conditions in `applicability_decision` (its evidence is the verdict's own page evidence) and cites spans like any other verdict; replay refuses a condition the family does not have and a span the queue did not offer. An answer-clarity verdict other than unknown records the panel question it answers in `measurement.question_id`, and a pass or partial one may list the other questions the page answers in `measurement.also_question_ids` (other, distinct panel questions; replay checks them). A failed model verdict records one `problem_messages` entry, the catalog key `model-fix.<family>[.<basis>]` with the cited text as `quote`; replay recomputes it from the family, the basis and the cited spans, and the report words the problem, the fix method, the effort and the check from the catalog (the model's own reason stays in the verdict as evidence). Allowed pairs: `code` with `optbrew:collector`, `host_model` with `optbrew:model-judgment`, and `code` with `optbrew:judgment-derived`. A content family (per-page, no code check) accepts only the model pair; a code-judged family refuses any host_model observation, and beside an audit snapshot accepts only the code pair, which is re-derived. Two units are exceptions. `entity-identity|site` may carry the model pair when code gave no verdict for it (the home page has no Organization JSON-LD); replay then reads the home page's raw entry and refuses a model verdict next to a code verdict. `question-coverage|site` is the derived pair: the number of fixed-panel questions that an audited page answers (a pass or partial answer-clarity verdict naming the question in `measurement.question_id` or `measurement.also_question_ids`, which replay checks against the panel, counted once per question) over the panel length, with the list in `measurement`; replay computes it again from the answer-clarity verdicts and the panel and refuses any other figure. Observations from other sources stay what they were: unverified inputs of a packet without an audit. A checked judgment is listed in `model_judgments` and in a "Model judgments" section labelled 근거 확인됨, 판정은 모델: the evidence is verified, the verdict is the model's.
38
+
39
+ **Audited phases.** Each phase's `RunContext` carries `audited: true` when the phase was audited (written with the context at run start, so it is hashed run evidence). Replay refuses a packet whose audit snapshot for a phase exists exactly when its context does not say `audited`. A phase without an audit accepts only outside or synthetic inputs: the collector and model sources are refused there, no finding can reach `verified_local` or `verified_production`, and its score readiness is never `measured` (it reads `unverified`). `verified_*` needs an after observation that is `code` with `optbrew:collector`, re-derived against the after snapshot.
40
+
41
+ Every `ChangeRecord` carries `analysis: { tools, queries, self_analysis }`: the tools and queries used to reach the change, and whether the agent analysed the page itself.
@@ -1,6 +1,6 @@
1
1
  # Situation routing
2
2
 
3
- 필요한 분기의 GUIDE만 연다. 모든 모듈 출력은 관찰 후보이며 한 URL·문제·검사는 한 번만 채점한다. 무료 경로는 허용된 공개 HTTP/HTML과 사용 가능한 호스트 도구를 사용한다. 원본 HTML, 렌더, 소유자 패널과 AI 제공자 응답의 출처를 각각 기록한다.
3
+ 이 표는 SKILL.md 10단계에서 수정안의 근거 가이드를 고를 때 쓴다. 수집, 판정, 채점은 `run.mjs`가 한 번에 하므로, 표의 모듈을 감사 단계에서 따로 돌리지 않는다. 필요한 분기의 GUIDE만 연다. 모든 모듈 출력은 관찰 후보이며 한 URL·문제·검사는 한 번만 채점한다. 무료 경로는 허용된 공개 HTTP/HTML과 사용 가능한 호스트 도구를 사용한다. 원본 HTML, 렌더, 소유자 패널과 AI 제공자 응답의 출처를 각각 기록한다.
4
4
 
5
5
  | 상황 | 주력 | 보완·입력 | 산출물 |
6
6
  |---|---|---|---|
@@ -12,4 +12,4 @@
12
12
  | AI 인용·경쟁 비교 | [SE Ranking](../modules/seranking-seo/skills/seo-ai-search-share-of-voice/GUIDE.md) | 승인된 API 또는 날짜가 있는 내보내기, Magnus 질문 프로토콜 | 엔진별 인용 실측; 무료 감사의 필수 조건 아님 |
13
13
  | 유입·전환 비교 | [mblode monitoring](../modules/mblode-seo/references/monitoring.md), [analytics](../modules/marketing-search/skills/analytics/GUIDE.md) | 같은 소유 속성·필터·완결된 28일 창 | 실제 성과와 비교 가능성 |
14
14
 
15
- 호스트 도구의 경계·비용 강제가 확인되지 않으면 사용하지 않는다. 직접 Anthropic v0.1.0은 [api.md](api.md)의 텍스트 전용 Messages 형태와 정확한 모델 ID 네 개만 지원한다. 다른 API와 SE Ranking 실행은 각자의 승인·상한·능력 근거가 있어야 한다. 호스트 정책만 비용을 강제하면 그 한계를 보고한다.
15
+ 호스트 도구의 경계·비용 강제가 확인되지 않으면 사용하지 않는다. 직접 Anthropic은 [api.md](api.md)의 텍스트 전용 Messages 형태와 정확한 모델 ID 네 개만 지원한다. 다른 API와 SE Ranking 실행은 각자의 승인·상한·능력 근거가 있어야 한다. 호스트 정책만 비용을 강제하면 그 한계를 보고한다.
@@ -0,0 +1,34 @@
1
+ ---
2
+ checked: 2026-10-16
3
+ source: https://platform.claude.com/docs/en/get-api-key
4
+ harness: any
5
+ ---
6
+
7
+ # Anthropic API 키 만들기 (선택, 내 계정에 비용 발생)
8
+
9
+ ## beginner
10
+
11
+ 왜 필요한가: 기본 감사는 이 키 없이 끝까지 갑니다. 이 키는 optbrew가 Claude API를 직접 불러 쓰는 선택 기능용입니다. 호출 비용은 내 Anthropic 계정에 청구됩니다. 구독(Claude Pro·Max, Claude Code 요금제)과는 별개의 결제입니다. 비용이 부담되면 이 런북 전체를 건너뜁니다.
12
+
13
+ 1. 브라우저에서 platform.claude.com을 열고 로그인합니다. 계정이 없으면 만들 수 있지만, 계정 생성과 결제 설정은 내가 직접 결정합니다.
14
+ 2. 주소창에 `platform.claude.com/settings/keys`를 입력하고 Enter를 누릅니다. **API keys** 페이지가 열립니다.
15
+ 3. **Create key** 버튼을 누릅니다. 버튼이 비활성이면 내 역할로는 키를 만들 수 없는 것이므로 조직 관리자에게 요청합니다.
16
+ 4. 키 이름(예: `optbrew`)을 적습니다.
17
+ 5. 만료 시기(expiration)를 고릅니다. 짧게 쓸 키면 가까운 날짜를 고릅니다. (화면 확인 필요: 선택지)
18
+ 6. **Linked account**가 나 자신으로 되어 있는지 확인합니다.
19
+ 7. 만들기를 확정하는 버튼을 누릅니다. (화면 확인 필요: 버튼 이름)
20
+ 8. 키 전체가 한 번만 표시됩니다. 복사 아이콘으로 복사합니다. 창을 닫으면 다시 볼 수 없습니다.
21
+ 9. 키를 대화창, 메모, 문서에 붙여 넣지 않습니다.
22
+ 10. Windows에서 컴퓨터에 넣으려면 시작 메뉴에서 `환경 변수`를 검색해 **계정에 대한 환경 변수 편집**을 엽니다. (화면 확인 필요: 한글 이름)
23
+ 11. **새로 만들기**(New)를 누릅니다.
24
+ 12. 변수 이름에 `ANTHROPIC_API_KEY`를 적습니다.
25
+ 13. 변수 값 칸에 복사한 키를 붙여 넣고 **확인**을 누릅니다. 열려 있는 터미널과 에이전트를 모두 닫았다 다시 켭니다.
26
+ 14. macOS·Linux는 터미널에서 쓰는 셸 설정 파일에 `export ANTHROPIC_API_KEY=키` 한 줄을 넣는 방식이 일반적이지만, 파일 위치가 셸마다 다르므로 에이전트에게 내 셸에 맞는 방법을 묻습니다. 키 값은 말하지 않고 "붙여 넣을 칸을 알려 달라"고만 합니다.
27
+
28
+ 키 사용 한도와 요금은 Anthropic Console의 결제 화면에서 확인합니다. 키가 새어 나갔다고 의심되면 **API keys** 목록에서 그 키를 삭제합니다. (화면 확인 필요: 삭제 메뉴 위치)
29
+
30
+ ## brief
31
+
32
+ - platform.claude.com/settings/keys → **Create key** → 이름·만료·Linked account를 정하고 만듭니다. 키는 생성 때 한 번만 보입니다.
33
+ - 환경 변수 `ANTHROPIC_API_KEY`에 넣습니다. 키를 대화창에 붙이지 않습니다.
34
+ - 호출 비용은 내 Anthropic 계정에 청구되며 Claude 구독과는 별개입니다. 필수가 아닙니다.
@@ -0,0 +1,56 @@
1
+ ---
2
+ checked: 2026-10-17
3
+ source: https://support.google.com/chrome/answer/95346
4
+ harness: local
5
+ ---
6
+
7
+ # 크롬 설치하기
8
+
9
+ ## beginner
10
+
11
+ 왜 필요한가: optbrew는 자바스크립트가 그린 뒤의 화면을 확인하려고 크롬을 잠깐 띄웁니다. 크롬이 없으면 이 확인만 빈칸으로 남고 나머지 감사는 그대로 됩니다. Microsoft Edge가 이미 있으면 그대로 쓰이므로 크롬을 새로 설치하지 않아도 됩니다. 설치 파일은 Google 공식 주소에서만 받습니다.
12
+
13
+ 1. 브라우저 주소창에 `google.com/chrome`을 입력하고 Enter를 누릅니다.
14
+ 2. 열린 사이트의 주소창에 `google.com`이 보이는지 확인합니다. 다른 이름이면 닫고 1번부터 다시 합니다.
15
+ 3. **Chrome 다운로드**(Download Chrome) 버튼을 누릅니다.
16
+ 4. 내 운영체제에 맞는 아래 목록 하나만 따라 합니다.
17
+
18
+ Windows:
19
+
20
+ 1. 받은 설치 파일을 두 번 누릅니다.
21
+ 2. "이 파일을 실행하시겠습니까?"라고 물으면 **실행**(Run)을 누릅니다.
22
+ 3. 사용자 계정 컨트롤 창이 "…변경할 수 있도록 허용하시겠…?"라고 물으면, 창에 보이는 이름이 Google Chrome일 때만 **예**(Yes)를 누릅니다.
23
+ 4. 설치가 끝나면 크롬 창이 저절로 열립니다. Windows 10 이상 기준입니다.
24
+
25
+ macOS:
26
+
27
+ 1. 받은 `googlechrome.dmg`를 엽니다.
28
+ 2. 나타난 창에서 Chrome 아이콘을 **Applications** 폴더로 끌어다 놓습니다.
29
+ 3. 관리자 암호를 물으면 직접 입력합니다.
30
+
31
+ Linux:
32
+
33
+ 1. 내 배포판과 CPU에 맞는 `.deb`(Debian·Ubuntu) 또는 `.rpm`(Fedora·openSUSE) 파일을 받습니다.
34
+ 2. 받은 파일을 열고 **설치**(Install)를 누릅니다.
35
+ 3. 관리자 암호를 입력합니다.
36
+
37
+ 설치를 마친 뒤 이어서 합니다.
38
+
39
+ 5. 에이전트에게 "크롬 설치했어"라고 말합니다. 에이전트가 표준 설치 위치에서 크롬을 찾습니다.
40
+ 6. 에이전트가 크롬을 찾았다고 하면 끝입니다.
41
+ 7. 에이전트가 크롬을 못 찾았다고 하면, 크롬을 표준 위치가 아닌 곳에 설치한 것입니다. 이때 크롬 실행 파일의 전체 경로를 에이전트에게 알려 줍니다. Windows의 표준 경로는 `C:\Program Files\Google\Chrome\Application\chrome.exe`, macOS는 `/Applications/Google Chrome.app/Contents/MacOS/Google Chrome`, Linux는 `/opt/google/chrome/chrome`입니다.
42
+ 8. 에이전트가 그 경로를 환경 변수 `OPTBREW_CHROME`에 넣고 감사를 이어 갑니다. 에이전트가 하지 못하면 아래 9번부터 직접 합니다.
43
+ 9. Windows에서 직접 하려면 키보드에서 Windows 키와 R을 함께 누릅니다. 열린 칸에 `rundll32 sysdm.cpl,EditEnvironmentVariables`를 적고 Enter를 누릅니다. 환경 변수 창이 열립니다.
44
+ 10. 위쪽 사용자 변수 칸 아래의 **새로 만들기**(New)를 누릅니다.
45
+ 11. 변수 이름에 `OPTBREW_CHROME`을 적습니다.
46
+ 12. 변수 값에 크롬 실행 파일의 전체 경로를 적고 **확인**을 누릅니다. 열려 있는 터미널과 에이전트를 닫았다 다시 켭니다.
47
+ 13. macOS·Linux에서 직접 하려면 터미널에서 쓰는 셸 설정 파일에 `export OPTBREW_CHROME="크롬 실행 파일의 전체 경로"` 한 줄을 넣습니다. 파일 위치가 셸마다 다르므로 에이전트에게 내 셸에 맞는 파일을 묻습니다.
48
+
49
+ 설치 파일을 받을 수 없거나 회사 정책으로 설치가 막힌 컴퓨터에서는 이 런북을 건너뜁니다. 감사는 렌더 확인 항목만 빈칸으로 두고 끝까지 갑니다.
50
+
51
+ ## brief
52
+
53
+ - `google.com/chrome`에서 설치합니다. Windows는 설치 파일 실행, macOS는 `googlechrome.dmg`를 Applications로 끌기, Linux는 `.deb`/`.rpm` 설치입니다.
54
+ - 표준 설치 위치와 `PATH`의 `chrome`·`google-chrome`·`chromium`·`msedge`를 자동으로 찾습니다. Edge도 됩니다.
55
+ - 다른 위치에 설치했다면 환경 변수 `OPTBREW_CHROME`에 실행 파일의 전체 경로를 둡니다.
56
+ - 클라우드 하네스에서 설치하는 방법은 확인 안 됨입니다. 이 런북은 내 컴퓨터용입니다.
@@ -0,0 +1,38 @@
1
+ ---
2
+ checked: 2026-10-16
3
+ source: https://github.com/GoogleChrome/web-vitals https://web.dev/articles/vitals-measurement-getting-started
4
+ harness: any
5
+ ---
6
+
7
+ # 사이트 안에서 방문자 측정값 모으기 (web-vitals)
8
+
9
+ ## beginner
10
+
11
+ 왜 필요한가: 방문자 수가 적은 사이트는 Google CrUX에 데이터가 없어 LCP·INP·CLS 실측값이 빈칸입니다. 이때 사이트 안에 작은 스크립트(web-vitals)를 넣어 방문자의 실제 측정값을 직접 모읍니다. 이 런북은 사이트 소스를 직접 고칠 수 있는 사람용입니다. 사이트를 고칠 수 없으면 건너뜁니다. 값이 쌓이는 데 시간이 걸리므로 이번 감사의 빈칸은 이 작업으로 바로 채워지지 않습니다.
12
+
13
+ 1. 사이트의 모든 페이지에 공통으로 들어가는 파일(레이아웃·템플릿·푸터)을 엽니다. 어떤 파일인지 모르면 에이전트에게 로컬 소스에서 찾아 달라고 합니다.
14
+ 2. `</body>` 닫는 태그를 찾습니다.
15
+ 3. `</body>` 바로 위에 다음 코드를 붙여 넣습니다. 이 코드는 방문자의 측정값이 나올 때마다 브라우저 콘솔에 보여 줍니다.
16
+
17
+ ```html
18
+ <script type="module">
19
+ import {onCLS, onINP, onLCP} from 'https://unpkg.com/web-vitals@5?module';
20
+ onCLS(console.log);
21
+ onINP(console.log);
22
+ onLCP(console.log);
23
+ </script>
24
+ ```
25
+
26
+ 4. 파일을 저장하고 사이트에 반영합니다.
27
+ 5. 사이트를 브라우저로 열고 F12를 눌러 개발자 도구를 엽니다. **Console**(콘솔) 탭을 누릅니다.
28
+ 6. 페이지을 한 번 클릭하거나 다른 탭으로 갔다 돌아오면 측정값 줄이 찍힙니다. LCP·INP·CLS 이름이 보이면 코드가 동작하는 것입니다. 일부 값은 클릭, 탭 전환, 페이지 이탈 때 처음 나옵니다.
29
+ 7. 측정값을 모으려면 받는 곳이 필요합니다. 분석 도구가 이미 있으면 web-vitals 설명서(GitHub 저장소의 README)에 있는 분석 도구 전송 예시대로 `console.log` 자리에 전송 코드를 넣습니다. 분석 도구가 없으면 Google Analytics가 무료입니다.
30
+ 8. 안정성을 원하면 코드가 가리키는 CDN 파일을 그대로 쓰지 말고 내 사이트 서버에 파일을 내려받아 올립니다. README도 같은 권고를 합니다.
31
+ 9. 이 코드를 넣은 사실은 개인정보 처리방침에 맞는지 사이트 운영자와 확인합니다.
32
+ 10. 며칠 뒤 분석 도구에서 방문자 측정값이 쌓이는지 확인합니다.
33
+
34
+ ## brief
35
+
36
+ - 모든 페이지에 `<script type="module">import {onCLS, onINP, onLCP} from 'https://unpkg.com/web-vitals@5?module';`와 `onCLS(...)`·`onINP(...)`·`onLCP(...)` 호출을 넣습니다.
37
+ - 콜백에서 `navigator.sendBeacon`으로 내 수집 주소나 분석 도구(예: Google Analytics)에 보냅니다. 값은 클릭·탭 전환·페이지 이탈 때 나옵니다.
38
+ - 가능하면 CDN 대신 파일을 내 서버에서 제공합니다. 방문자 수가 적은 사이트는 값이 쌓이는 데 시간이 걸립니다.
@@ -0,0 +1,51 @@
1
+ ---
2
+ checked: 2026-10-18
3
+ source: https://docs.cloud.google.com/docs/authentication/api-keys https://docs.cloud.google.com/apis/docs/getting-started https://docs.cloud.google.com/resource-manager/docs/creating-managing-projects https://developer.chrome.com/docs/crux/api https://developers.google.com/speed/docs/insights/v5/get-started
4
+ harness: any
5
+ ---
6
+
7
+ # Google API 키 발급하고 CrUX·PageSpeed API 켜기
8
+
9
+ ## beginner
10
+
11
+ 왜 필요한가: 방문자가 실제로 겪은 속도 점수(LCP·INP·CLS)는 Google의 Chrome UX Report(CrUX)와 PageSpeed Insights(PSI)에서 가져옵니다. 키가 있으면 이 세 항목이 채워지고, 없으면 빈칸으로 남습니다. 키는 본인 Google 계정에서 직접 만듭니다. 두 API는 무료이며 CrUX는 프로젝트당 분당 150회 한도가 있습니다. 결제 정보를 입력하라는 화면이 나오면 멈추세요. 키가 없어도 감사는 끝까지 갑니다.
12
+
13
+ 1. 브라우저에서 console.cloud.google.com을 엽니다.
14
+ 2. Google 계정으로 로그인합니다.
15
+ 3. 처음이면 약관 동의 창이 뜰 수 있습니다. 내용을 읽고 직접 결정합니다.
16
+ 4. 화면 맨 위 프로젝트 이름 상자(Project picker)를 누릅니다.
17
+ 5. 열린 목록에 쓸 프로젝트가 있으면 그 이름을 누르고 10번으로 갑니다.
18
+ 6. 쓸 프로젝트가 없으면 **새 프로젝트**(New project)를 누릅니다.
19
+ 7. **프로젝트 이름**(Project name) 칸에 `optbrew`처럼 알아볼 이름을 적습니다.
20
+ 8. **만들기**(Create)를 누릅니다.
21
+ 9. 몇 초 기다린 뒤 위쪽 프로젝트 이름 상자(프로젝트 선택기)를 눌러 방금 만든 프로젝트를 고릅니다.
22
+ 10. 위쪽 프로젝트 이름 상자에 쓸 프로젝트 이름이 보이는지 확인합니다.
23
+ 11. 왼쪽 위의 메뉴 아이콘(☰)을 누릅니다.
24
+ 12. **API 및 서비스**(APIs & Services) → **라이브러리**(Library)를 누릅니다. 공식 문서는 이 화면을 **API 라이브러리**라고 부릅니다.
25
+ 13. 검색 상자에 `Chrome UX Report API`를 입력합니다.
26
+ 14. 검색 결과에서 **Chrome UX Report API**를 누릅니다.
27
+ 15. **사용 설정**(Enable) 버튼을 누릅니다.
28
+ 16. 12번처럼 **라이브러리**로 돌아갑니다.
29
+ 17. 검색 상자에 `PageSpeed Insights API`를 입력합니다.
30
+ 18. 검색 결과에서 **PageSpeed Insights API**를 누릅니다.
31
+ 19. **사용 설정**(Enable) 버튼을 누릅니다.
32
+ 20. 왼쪽 메뉴에서 **사용자 인증 정보**(Credentials)를 누릅니다.
33
+ 21. 위쪽 **+ 사용자 인증 정보 만들기**(+ Create credentials)를 누릅니다. 열린 메뉴에 **API 키**(API key), OAuth 클라이언트 ID, 서비스 계정이 나옵니다.
34
+ 22. 열린 메뉴에서 **API 키**(API key)를 누릅니다.
35
+ 23. 키 만들기 화면에 **API 제한사항**(API restrictions) 영역이 보이면 **키 제한**(Restrict key)을 누릅니다. 보이지 않으면 27번으로 갑니다.
36
+ 24. API 목록을 엽니다.
37
+ 25. 목록에서 **Chrome UX Report API**를 체크합니다.
38
+ 26. 목록에서 **PageSpeed Insights API**를 체크합니다.
39
+ 27. **만들기**(Create)를 누릅니다.
40
+ 28. **API 키 생성 완료**(API key created) 창에 키 문자열이 나옵니다. 키 옆 복사 아이콘을 눌러 복사합니다. 창을 닫기 전에 복사를 마칩니다.
41
+ 29. 키를 채팅창, 메모, 문서, 화면 공유에 붙여 넣지 않습니다.
42
+ 30. 키를 optbrew에 넣는 방법은 사용 중인 환경에 맞는 런북을 따릅니다. 내 컴퓨터의 터미널은 `key-set-local`, Claude Code 클라우드는 `key-set-claude-cloud`, Codex는 `key-set-codex`입니다.
43
+
44
+ 키가 유출됐다고 의심되면 **사용자 인증 정보** 목록에서 그 키를 삭제하고 새로 만듭니다.
45
+
46
+ ## brief
47
+
48
+ - console.cloud.google.com에서 프로젝트를 고르거나 만들고, API 라이브러리에서 `Chrome UX Report API`와 `PageSpeed Insights API`를 각각 **ENABLE** 합니다.
49
+ - **API 및 서비스 → 사용자 인증 정보 → 사용자 인증 정보 만들기 → API 키**. 문서는 키를 제한하라고 권하므로 위 두 API로 제한하고 **만들기**를 누릅니다.
50
+ - 키 문자열은 한 번 복사해 `key-set-local`·`key-set-claude-cloud`·`key-set-codex` 중 맞는 런북으로만 입력합니다. 대화창에 붙이지 않습니다.
51
+ - CrUX는 무료, 프로젝트당 분당 150회. 결제 정보를 요구하면 멈춥니다.
@@ -0,0 +1,32 @@
1
+ ---
2
+ checked: 2026-10-18
3
+ source: https://support.google.com/webmasters/answer/12919797 https://developers.google.com/search/blog/2020/02/data-export
4
+ harness: any
5
+ ---
6
+
7
+ # Search Console 실적 데이터 내보내기
8
+
9
+ ## beginner
10
+
11
+ 이 절차는 Google Search Console용입니다. 이 서비스를 쓰지 않으면 이 절차는 건너뛰세요.
12
+
13
+ 왜 필요한가: optbrew는 사이트를 밖에서 읽는 것만 할 수 있어서, 사이트가 Google 검색에서 실제로 몇 번 보이고 클릭됐는지는 모릅니다. 사이트 소유자만 보는 Search Console 데이터를 파일로 내려받아 주면 그 숫자를 감사 결과와 함께 볼 수 있습니다. 사이트가 Search Console에 등록되어 있고 내가 소유자(또는 권한이 있는 사용자)일 때만 합니다. 내 사이트가 아니면 건너뜁니다.
14
+
15
+ 1. 브라우저에서 search.google.com/search-console을 엽니다.
16
+ 2. 사이트 소유 권한이 있는 Google 계정으로 로그인합니다.
17
+ 3. 왼쪽 위의 속성 선택 상자를 눌러 감사할 사이트를 고릅니다. 사이트가 목록에 없으면 이 계정에 권한이 없는 것이므로 여기서 멈춥니다.
18
+ 4. 왼쪽 메뉴에서 **실적**(Performance)을 누릅니다. 그 아래에 **검색결과**(Search results)가 따로 보이면 그것을 누릅니다.
19
+ 5. 위쪽 기간 단추(24시간·7일·28일·3개월) 끝의 **더보기**를 누릅니다. **지난 16개월**을 고르고 **적용**(Apply)을 누릅니다.
20
+ 6. 화면 오른쪽 위의 **내보내기**(Export) 버튼을 찾습니다. 아래쪽 화살표가 그려진 다운로드 아이콘이 붙어 있습니다.
21
+ 7. 버튼을 누르면 **Google Sheets**, **Excel 다운로드**, **CSV 다운로드**가 나옵니다. 엑셀 없이 쓰려면 **CSV 다운로드**(Download CSV)가 가장 단순합니다.
22
+ 8. 파일이 내려받아집니다. CSV는 여러 시트가 든 압축(.zip) 파일로 받아집니다. 압축을 풀지 않고 그대로 둡니다. 압축 안에는 쿼리, 페이지, 국가, 기기, 날짜 등이 따로 들어 있고, 적용한 필터를 적은 파일도 있습니다.
23
+ 9. 파일은 보통 브라우저의 기본 저장 폴더인 **다운로드**(Downloads) 폴더에 저장됩니다. 그 폴더에서 방금 받은 파일의 이름을 확인해, 폴더 이름과 파일 이름을 에이전트에게 알려 줍니다. 파일 내용은 대화에 붙이지 않습니다.
24
+ 10. 알아둘 점: 화면에서 내려받는 데이터는 최대 1,000행입니다. 방문 많은 사이트는 일부만 담깁니다.
25
+
26
+ ## brief
27
+
28
+ - Google Search Console용입니다. 이 서비스를 쓰지 않으면 이 절차는 건너뛰세요.
29
+ - search.google.com/search-console → 속성 선택 → **실적**(아래에 **검색결과**가 있으면 그것) → **더보기**에서 **지난 16개월** → 오른쪽 위 **내보내기**로 Google Sheets·Excel·CSV 중 하나를 받습니다.
30
+ - CSV는 시트별 파일이 든 zip으로 받아집니다. 쿼리·페이지·국가·기기·날짜와 적용 필터가 들어 있습니다.
31
+ - 화면 내보내기는 1,000행까지입니다. 더 필요하면 API나 BigQuery 내보내기가 있습니다.
32
+ - 파일 경로만 에이전트에 알리고 내용은 붙이지 않습니다.
@@ -0,0 +1,18 @@
1
+ [
2
+ { "id": "network-codex-approval", "kind": "blocking", "when": { "block_reason": "codex_sandbox" }, "fills": [], "item": "Codex 네트워크 허용 — 사이트에 접속하기 위해" },
3
+ { "id": "network-claude-cloud-custom", "kind": "blocking", "when": { "block_reason": "cloud_network_level" }, "fills": [], "item": "Claude Code 클라우드 네트워크 허용 — 사이트에 접속하기 위해" },
4
+ { "id": "network-codex-config", "kind": "improving", "when": {}, "fills": [], "item": "Codex 네트워크 허용 설정 저장 — 다음 감사에서 다시 허용하지 않기 위해" },
5
+ { "id": "google-api-key", "kind": "improving", "when": { "missing": "google_key", "unknown_reason": "field_data_unavailable" }, "fills": ["lcp-field", "inp-field", "cls-field"], "item": "Google 키 — 방문자 실측 점수(LCP·INP·CLS)를 채우기 위해",
6
+ "line": "Google 키가 있으면 방문자 실측 점수(LCP·INP·CLS)가 채워집니다. '키'라고 답하면 발급 절차를 보여 드립니다." },
7
+ { "id": "cwv-site-collection", "kind": "improving", "when": { "unknown_reason": "field_data_unavailable" }, "fills": ["lcp-field", "inp-field", "cls-field"], "item": "사이트 안에서 방문자 측정값 모으기 — 방문자가 적어 Google에 데이터가 없는 실측 점수(LCP·INP·CLS)를 채우기 위해" },
8
+ { "id": "key-set-local", "kind": "improving", "when": {}, "fills": ["lcp-field", "inp-field", "cls-field"], "item": "내 컴퓨터에 Google 키 저장 — 키를 다음 감사에서도 쓰기 위해" },
9
+ { "id": "key-set-claude-cloud", "kind": "improving", "when": {}, "fills": ["lcp-field", "inp-field", "cls-field"], "item": "Claude Code 클라우드에 Google 키 저장 — 키를 다음 감사에서도 쓰기 위해" },
10
+ { "id": "key-set-codex", "kind": "improving", "when": {}, "fills": ["lcp-field", "inp-field", "cls-field"], "item": "Codex에 Google 키 저장 — 키를 다음 감사에서도 쓰기 위해" },
11
+ { "id": "chrome-install", "kind": "improving", "when": { "missing": "chrome", "unknown_reason": "chrome_missing" }, "fills": ["render-content"], "item": "크롬 — 자바스크립트로 그려지는 내용을 확인하기 위해",
12
+ "line": "크롬이 있으면 자바스크립트로 그려지는 내용까지 확인합니다. '크롬'이라고 답하면 설치 절차를 보여 드립니다." },
13
+ { "id": "gsc-export", "kind": "improving", "when": { "missing": "owner_export" }, "fills": [], "expands_to": ["gsc-export", "naver-searchadvisor-export"], "item": "Search Console·네이버 서치어드바이저 내보내기 파일 — 실제 검색 반응(점수에는 들어가지 않음)을 함께 보기 위해",
14
+ "line": "Search Console이나 네이버 서치어드바이저에서 내려받은 파일이 있으면 실제 검색 반응을 함께 봅니다. '내보내기'라고 답하면 두 서비스의 절차를 모두 보여 드립니다." },
15
+ { "id": "naver-searchadvisor-export", "kind": "improving", "when": {}, "fills": [], "item": "네이버 서치어드바이저 내보내기 파일 — 네이버의 실제 검색 반응(점수에는 들어가지 않음)을 함께 보기 위해" },
16
+ { "id": "anthropic-api-key", "kind": "improving", "when": {}, "fills": [], "item": "Anthropic 키(선택) — optbrew가 Claude API를 직접 불러 쓰는 기능을 켜기 위해" },
17
+ { "id": "seranking", "kind": "improving", "when": {}, "fills": [], "item": "SE Ranking 연결(선택) — AI 검색 인용 같은 실측 수치를 가져오기 위해" }
18
+ ]
@@ -0,0 +1,32 @@
1
+ ---
2
+ checked: 2026-10-18
3
+ source: https://code.claude.com/docs/en/cloud-environments
4
+ harness: claude-code-cloud
5
+ ---
6
+
7
+ # Claude Code 클라우드에서 Google 키 넣기 (환경 변수)
8
+
9
+ ## beginner
10
+
11
+ 왜 필요한가: 클라우드 세션에는 내 컴퓨터의 키 파일이 없습니다. 대신 클라우드 환경 설정의 환경 변수 칸에 키를 넣어 두면, 그 환경에서 시작한 세션이 `OPTBREW_GOOGLE_API_KEY`라는 이름으로 읽습니다. 키는 이 설정 창에만 붙여 넣고 에이전트 대화창에는 붙이지 않습니다. 주의: 그 환경을 쓰는 사람은 누구나 환경 변수를 읽을 수 있습니다. 나만 쓰는 개인 환경에만 넣고, 조직이 공유하는 환경에는 넣지 않습니다.
12
+
13
+ 1. 브라우저에서 claude.ai/code를 엽니다.
14
+ 2. 새 세션 화면으로 갑니다. 이미 실행 중인 세션을 고치려면 위쪽 세션 제목 옆 화살표(caret)를 누르고 **Edit cloud environment**를 고른 뒤 5번으로 갑니다.
15
+ 3. 메시지 입력 상자 바로 아래 줄에서 첫 번째 컨트롤(환경 이름이 적힌 버튼)을 누릅니다.
16
+ 4. 열린 목록에서 **클라우드**(Cloud)를 누르면 옆에 클라우드 환경 목록이 열립니다. 내 개인 환경 줄에 마우스를 올리면 줄 오른쪽에 톱니바퀴와 별 아이콘이 나타납니다. 톱니바퀴(설정)를 누릅니다. 환경이 하나도 없으면 그 목록 맨 아래 **클라우드 환경 추가…**(Add cloud environment)를 눌러 환경을 만든 뒤, 만든 환경의 톱니바퀴를 누릅니다.
17
+ 5. 설정 창에서 **Environment variables** 칸을 찾습니다.
18
+ 6. 칸의 맨 아래 새 줄에 `OPTBREW_GOOGLE_API_KEY=`를 입력합니다. 등호 앞뒤에 공백을 넣지 않습니다.
19
+ 7. 등호 바로 뒤에 복사해 둔 Google 키를 붙여 넣습니다. 따옴표는 붙이지 않습니다.
20
+ 8. 줄 하나에 변수 하나만 있는지 확인합니다. 다른 변수가 이미 있으면 그 줄은 그대로 둡니다.
21
+ 9. 창의 **Save changes** 버튼을 누릅니다.
22
+ 10. 새 세션을 시작하고 감사를 요청합니다. 설정은 새 세션에서 확실히 적용됩니다.
23
+ 11. 에이전트가 "Google 키 있음"이라고 알려 주면 끝입니다. 키 값은 보이지 않는 것이 정상입니다.
24
+
25
+ 키를 바꾸거나 지우려면 같은 창에서 그 줄을 고치거나 지우고 저장합니다. 한글 화면에서는 항목 이름이 번역되어 있을 수 있습니다. 위 영문 이름과 같은 자리의 항목을 고릅니다.
26
+
27
+ ## brief
28
+
29
+ - claude.ai/code 새 세션 화면에서 메시지 상자 아래 첫 컨트롤 → 환경 행의 설정 아이콘 → **Environment variables**.
30
+ - `OPTBREW_GOOGLE_API_KEY=키` 한 줄(공백·따옴표 없음)을 넣고 저장합니다. `.env` 형식이며 `#`가 든 값은 따옴표로 감쌉니다.
31
+ - 환경 변수는 그 환경을 쓰는 모두가 읽을 수 있으므로 개인 환경에만 넣습니다. 새 세션에서 적용됩니다.
32
+ - 키를 대화창에 붙이지 않습니다.