optbrew 0.1.3 → 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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +2 -2
- package/AGENT_INSTALL.md +13 -11
- package/CHANGELOG.md +52 -0
- package/README.ko.md +40 -15
- package/README.md +40 -15
- package/THIRD_PARTY_NOTICES.md +88 -0
- package/docs/platforms.md +10 -10
- package/docs/release.md +37 -5
- package/lib/cli.mjs +5 -2
- package/lib/install.mjs +2 -1
- package/lib/records.mjs +2 -1
- package/lib/transaction.mjs +12 -5
- package/lib/update.mjs +3 -2
- package/package.json +6 -4
- package/plugin.json +2 -2
- package/skills/.claude-plugin/icon.png +0 -0
- package/skills/.claude-plugin/plugin.json +23 -0
- package/skills/README.md +27 -0
- package/skills/optbrew/SKILL.md +223 -8
- package/skills/optbrew/THIRD_PARTY_NOTICES.md +88 -0
- package/skills/optbrew/bundle-manifest.json +407 -47
- package/skills/optbrew/modules/naver-searchadvisor/scripts/audit.mjs +4 -4
- package/skills/optbrew/references/api.md +5 -3
- package/skills/optbrew/references/check-registry.json +16 -16
- package/skills/optbrew/references/chrome-paths.json +31 -0
- package/skills/optbrew/references/endpoints.json +10 -0
- package/skills/optbrew/references/harness-signals.json +5 -0
- package/skills/optbrew/references/intake.md +76 -5
- package/skills/optbrew/references/judging.md +233 -0
- package/skills/optbrew/references/limits.json +26 -0
- package/skills/optbrew/references/measurement.md +1 -1
- package/skills/optbrew/references/messages.ko.json +170 -0
- package/skills/optbrew/references/report.md +18 -6
- package/skills/optbrew/references/routing.md +2 -2
- package/skills/optbrew/references/runbooks/anthropic-api-key.md +34 -0
- package/skills/optbrew/references/runbooks/chrome-install.md +56 -0
- package/skills/optbrew/references/runbooks/cwv-site-collection.md +38 -0
- package/skills/optbrew/references/runbooks/google-api-key.md +51 -0
- package/skills/optbrew/references/runbooks/gsc-export.md +32 -0
- package/skills/optbrew/references/runbooks/index.json +18 -0
- package/skills/optbrew/references/runbooks/key-set-claude-cloud.md +32 -0
- package/skills/optbrew/references/runbooks/key-set-codex.md +33 -0
- package/skills/optbrew/references/runbooks/key-set-local.md +40 -0
- package/skills/optbrew/references/runbooks/naver-searchadvisor-export.md +32 -0
- package/skills/optbrew/references/runbooks/network-claude-cloud-custom.md +33 -0
- package/skills/optbrew/references/runbooks/network-codex-approval.md +26 -0
- package/skills/optbrew/references/runbooks/network-codex-config.md +36 -0
- package/skills/optbrew/references/runbooks/seranking.md +38 -0
- package/skills/optbrew/references/schemas/contract.schema.json +42 -15
- package/skills/optbrew/references/scoring.md +40 -5
- package/skills/optbrew/references/source-analysis.md +40 -0
- package/skills/optbrew/references/versions.json +9 -0
- package/skills/optbrew/scripts/adapters/anthropic.mjs +5 -5
- package/skills/optbrew/scripts/adapters/imports.mjs +138 -1
- package/skills/optbrew/scripts/audit.mjs +10 -112
- package/skills/optbrew/scripts/cli.mjs +5 -2
- package/skills/optbrew/scripts/core/bundle.mjs +30 -13
- package/skills/optbrew/scripts/core/capabilities.mjs +8 -4
- package/skills/optbrew/scripts/core/cdp.mjs +105 -0
- package/skills/optbrew/scripts/core/checks/canonical-consistency.mjs +56 -0
- package/skills/optbrew/scripts/core/checks/cwv-field.mjs +77 -0
- package/skills/optbrew/scripts/core/checks/discovery.mjs +43 -0
- package/skills/optbrew/scripts/core/checks/document-title.mjs +62 -0
- package/skills/optbrew/scripts/core/checks/entity-identity.mjs +31 -0
- package/skills/optbrew/scripts/core/checks/fact-consistency.mjs +31 -0
- package/skills/optbrew/scripts/core/checks/fetch-response.mjs +18 -0
- package/skills/optbrew/scripts/core/checks/index-policy.mjs +16 -0
- package/skills/optbrew/scripts/core/checks/index.mjs +18 -0
- package/skills/optbrew/scripts/core/checks/internal-navigation.mjs +67 -0
- package/skills/optbrew/scripts/core/checks/locale-links.mjs +55 -0
- package/skills/optbrew/scripts/core/checks/naver-markup.mjs +21 -0
- package/skills/optbrew/scripts/core/checks/observation.mjs +37 -0
- package/skills/optbrew/scripts/core/checks/prepare.mjs +103 -0
- package/skills/optbrew/scripts/core/checks/render-content.mjs +104 -0
- package/skills/optbrew/scripts/core/checks/search-crawl-policy.mjs +29 -0
- package/skills/optbrew/scripts/core/checks/structured-data-truth.mjs +46 -0
- package/skills/optbrew/scripts/core/chrome-temp.mjs +68 -0
- package/skills/optbrew/scripts/core/chrome.mjs +546 -0
- package/skills/optbrew/scripts/core/collector.mjs +45 -49
- package/skills/optbrew/scripts/core/compare.mjs +18 -1
- package/skills/optbrew/scripts/core/config.mjs +31 -0
- package/skills/optbrew/scripts/core/contracts.mjs +31 -2
- package/skills/optbrew/scripts/core/cwv.mjs +100 -0
- package/skills/optbrew/scripts/core/derive.mjs +10 -186
- package/skills/optbrew/scripts/core/evidence-store.mjs +112 -0
- package/skills/optbrew/scripts/core/facts.mjs +175 -17
- package/skills/optbrew/scripts/core/findings.mjs +19 -1
- package/skills/optbrew/scripts/core/html.mjs +41 -3
- package/skills/optbrew/scripts/core/index-targets.mjs +118 -0
- package/skills/optbrew/scripts/core/judgments.mjs +675 -0
- package/skills/optbrew/scripts/core/keys.mjs +86 -0
- package/skills/optbrew/scripts/core/link-graph.mjs +93 -0
- package/skills/optbrew/scripts/core/messages.mjs +23 -0
- package/skills/optbrew/scripts/core/metrics.mjs +4 -2
- package/skills/optbrew/scripts/core/migration.mjs +37 -0
- package/skills/optbrew/scripts/core/page-select.mjs +136 -0
- package/skills/optbrew/scripts/core/probe.mjs +83 -0
- package/skills/optbrew/scripts/core/protocol.mjs +38 -14
- package/skills/optbrew/scripts/core/render.mjs +160 -0
- package/skills/optbrew/scripts/core/report-compose.mjs +377 -0
- package/skills/optbrew/scripts/core/report-html.mjs +198 -0
- package/skills/optbrew/scripts/core/report-page.mjs +14 -0
- package/skills/optbrew/scripts/core/report.mjs +327 -122
- package/skills/optbrew/scripts/core/runbooks.mjs +63 -0
- package/skills/optbrew/scripts/core/scope.mjs +23 -5
- package/skills/optbrew/scripts/core/score.mjs +12 -4
- package/skills/optbrew/scripts/core/secrets.mjs +2 -0
- package/skills/optbrew/scripts/core/stages.mjs +694 -0
- package/skills/optbrew/scripts/core/transport.mjs +157 -0
- package/skills/optbrew/scripts/core/weights.mjs +2 -1
- package/skills/optbrew/scripts/key.mjs +90 -0
- package/skills/optbrew/scripts/preflight.mjs +176 -0
- package/skills/optbrew/scripts/run.mjs +81 -0
- package/skills/optbrew/source-lock.json +1 -1
- package/source-lock.json +1 -1
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
checked: 2026-10-16
|
|
3
|
+
source: https://learn.chatgpt.com/docs/config-file/config-basic https://learn.chatgpt.com/docs/agent-approvals-security
|
|
4
|
+
harness: codex
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Codex에서 Google 키 쓰기
|
|
8
|
+
|
|
9
|
+
## beginner
|
|
10
|
+
|
|
11
|
+
왜 필요한가: Codex는 명령을 sandbox 안에서 실행하고, 환경 변수를 명령에 어떻게 넘기는지는 설정에 따라 다릅니다. 가장 확실한 방법은 키를 내 컴퓨터의 키 파일에 저장해 두는 것입니다. 파일은 환경 변수 설정과 관계없이 optbrew가 읽습니다. 키는 내 터미널에서만 넣고 Codex 대화창에는 붙이지 않습니다.
|
|
12
|
+
|
|
13
|
+
1. Codex 창이 아니라 별도의 터미널 창을 엽니다. Windows는 시작(Start) 메뉴에서 `Windows PowerShell`을 입력해 열고, macOS는 Spotlight(⌘+Space)에서 `Terminal`을 입력해 엽니다. (화면 확인 필요: macOS)
|
|
14
|
+
2. 다음 명령 한 줄을 붙여 넣고 Enter를 누릅니다.
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
node "{{skill_root}}/scripts/key.mjs" set google
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
3. 키를 입력하라는 안내가 나오면 복사해 둔 키를 붙여 넣고 Enter를 누릅니다. 입력한 글자가 화면에 보이지 않는 것이 정상입니다.
|
|
21
|
+
4. 터미널 창을 닫고 Codex로 돌아가 감사를 요청합니다. 저장 확인 명령은 `key-set-local` 런북 7번에 있습니다.
|
|
22
|
+
5. 에이전트가 "Google 키 있음"이라고 알려 주면 끝입니다.
|
|
23
|
+
6. "Google 키 없음"이라고 나오면 에이전트에게 키 파일을 읽으려다 실패했는지 확인해 달라고 합니다. 실패가 확인된 경우에만 7번 이후를 합니다. 확인되지 않으면 3번의 저장을 다시 합니다. (화면 확인 필요: sandbox가 사용자 폴더의 키 파일을 읽는지)
|
|
24
|
+
7. 최후 수단(화면 확인 필요): 6번에서 실패가 확인됐을 때만, 운영체제에 `OPTBREW_GOOGLE_API_KEY` 환경 변수를 만듭니다. 키가 사용자 계정의 모든 프로그램에 노출되므로 마지막에 씁니다. Windows는 시작 메뉴에서 `환경 변수`를 검색해 **계정에 대한 환경 변수 편집**을 열고, **새로 만들기**를 눌러 변수 이름에 `OPTBREW_GOOGLE_API_KEY`, 변수 값에 키를 붙여 넣고 **확인**을 누릅니다. (화면 확인 필요: 한글 이름)
|
|
25
|
+
8. 7번의 환경 변수를 만든 뒤에도 Codex 명령에 변수가 안 보이면 Codex 설정 파일 `config.toml`(위치는 `network-codex-config` 런북 2번)에 `[shell_environment_policy]` 표를 만들고 `ignore_default_excludes = true`를 둡니다. 이 값이면 이름에 KEY가 든 변수도 명령에 넘어갑니다. 설정 문서의 기본값은 true이지만 Codex 버전에 따라 다를 수 있습니다. (화면 확인 필요)
|
|
26
|
+
9. 설정 파일에는 키 값을 적지 않습니다.
|
|
27
|
+
10. Codex를 다시 켜고 감사를 요청합니다.
|
|
28
|
+
|
|
29
|
+
## brief
|
|
30
|
+
|
|
31
|
+
- 기본: 내 터미널에서 `key-set-local`대로 `node "{{skill_root}}/scripts/key.mjs" set google`을 실행합니다. 키 파일은 sandbox 설정과 관계없이 읽힙니다. (sandbox의 파일 읽기 허용은 화면 확인 필요)
|
|
32
|
+
- 파일이 안 읽히면 OS 환경 변수 `OPTBREW_GOOGLE_API_KEY`를 만들고 `config.toml`의 `[shell_environment_policy]`에 `ignore_default_excludes = true`를 둡니다. 키 값을 설정 파일에 적지 않습니다.
|
|
33
|
+
- 키를 Codex 대화창에 붙이지 않습니다.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
checked: 2026-10-17
|
|
3
|
+
source: https://learn.microsoft.com/en-us/powershell/scripting/windows-powershell/starting-windows-powershell
|
|
4
|
+
harness: local
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 내 컴퓨터에서 Google 키 저장하기 (optbrew key set google)
|
|
8
|
+
|
|
9
|
+
## beginner
|
|
10
|
+
|
|
11
|
+
왜 필요한가: 복사해 둔 Google API 키를 optbrew가 쓸 수 있게 내 컴퓨터에만 저장합니다. 키는 내 사용자 폴더의 `.optbrew` 폴더에 저장되고 화면·보고서·로그에는 남지 않습니다. 이 단계는 반드시 내가 직접 내 터미널에서 합니다. 키를 에이전트 대화창에 붙여 넣지 않습니다.
|
|
12
|
+
|
|
13
|
+
1. 터미널을 엽니다. Windows는 시작(Start) 메뉴를 열고 `Windows PowerShell`을 입력한 뒤 **Windows PowerShell**을 골라 **열기**(Open)를 누릅니다. macOS는 Spotlight(⌘+Space)에서 `Terminal`을 입력해 엽니다. (화면 확인 필요: macOS)
|
|
14
|
+
2. 다음 명령 한 줄을 그대로 붙여 넣습니다. 붙여 넣기는 Windows PowerShell에서 Ctrl+V 또는 마우스 오른쪽 버튼, macOS에서 ⌘+V입니다. 이 명령은 optbrew 스킬 폴더의 키 스크립트를 실행합니다.
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
node "{{skill_root}}/scripts/key.mjs" set google
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
3. Enter를 누릅니다.
|
|
21
|
+
4. `Google API key (input is hidden):`라는 안내가 나옵니다. 키를 붙여 넣습니다. 입력한 글자는 화면에 보이지 않는 것이 정상입니다.
|
|
22
|
+
5. Enter를 누릅니다.
|
|
23
|
+
6. `google key saved`가 나오면 끝납니다.
|
|
24
|
+
7. 저장됐는지 확인하려면 다음 명령을 붙여 넣고 Enter를 누릅니다. 결과 한 줄에 `"present":true`가 보이면 저장된 것입니다. 키 전체는 보이지 않고 해시 앞 8자(`sha256_8`)만 보입니다.
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
node "{{skill_root}}/scripts/key.mjs" status
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
8. 터미널 창을 닫습니다.
|
|
31
|
+
9. 에이전트에게 "키 넣었어"라고만 말합니다. 키 값은 말하지 않습니다.
|
|
32
|
+
|
|
33
|
+
npm으로 optbrew를 설치했다면 같은 일을 `optbrew key set google`, `optbrew key status`로 할 수 있습니다. 저장한 키를 지울 때는 같은 방법으로 `key remove google` 명령을 씁니다. 지우면 `google key removed`가 나옵니다.
|
|
34
|
+
|
|
35
|
+
## brief
|
|
36
|
+
|
|
37
|
+
- 내 터미널에서 `node "{{skill_root}}/scripts/key.mjs" set google`(npm 설치는 `optbrew key set google`)을 실행하고, 숨겨진 입력 창에 키를 붙여 넣습니다.
|
|
38
|
+
- 키는 `~/.optbrew/credentials.json`에 저장됩니다. `OPTBREW_GOOGLE_API_KEY` 환경 변수가 있으면 그것이 먼저입니다.
|
|
39
|
+
- `key status`로 존재와 해시 앞 8자만 확인하고, 지우려면 `key remove`입니다.
|
|
40
|
+
- 키를 대화창에 붙이지 않습니다.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
checked: 2026-10-18
|
|
3
|
+
source: https://searchadvisor.naver.com/guide/report-expose-ctr https://searchadvisor.naver.com/guide/faq-start-register
|
|
4
|
+
harness: any
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 네이버 서치어드바이저 리포트 내려받기
|
|
8
|
+
|
|
9
|
+
## beginner
|
|
10
|
+
|
|
11
|
+
이 절차는 네이버 서치어드바이저용입니다. 이 서비스를 쓰지 않으면 이 절차는 건너뛰세요.
|
|
12
|
+
|
|
13
|
+
왜 필요한가: 네이버에서 사이트가 얼마나 노출되고 클릭됐는지는 사이트 소유자만 서치어드바이저에서 볼 수 있습니다. optbrew는 밖에서 읽을 수 없는 이 숫자를 리포트 파일로 받아서 감사 결과와 함께 봅니다. 네이버 검색이 목표가 아니거나 사이트를 서치어드바이저에 등록하지 않았다면 건너뜁니다. 등록과 소유확인은 이 런북의 범위가 아닙니다.
|
|
14
|
+
|
|
15
|
+
1. 브라우저에서 searchadvisor.naver.com을 엽니다.
|
|
16
|
+
2. 오른쪽 위 **로그인**을 눌러 사이트를 등록한 네이버 계정으로 로그인합니다.
|
|
17
|
+
3. 위쪽 메뉴의 **웹마스터 도구**를 누릅니다.
|
|
18
|
+
4. 사이트 목록에서 감사할 사이트를 누릅니다. 목록에 없으면 이 계정에 등록되지 않았거나 소유확인이 안 된 것이므로 여기서 멈춥니다.
|
|
19
|
+
5. 왼쪽 메뉴의 **SEO 리포트** 아래에서 **콘텐츠 노출 및 클릭**을 누릅니다.
|
|
20
|
+
6. 조회 기간을 **90일**로 고릅니다. 이 리포트는 최근 90일까지만 보관하고, 최신 날짜는 약 1주 전 검색 기준입니다.
|
|
21
|
+
7. 조회 장비는 **PC + Mobile**로 둡니다. 필요하면 PC와 Mobile을 각각 한 번씩 조회합니다.
|
|
22
|
+
8. 공식 안내에는 이 리포트의 내려받기 버튼이 없습니다. 화면에 내려받기(엑셀 또는 CSV) 버튼이 보이면 눌러 파일을 저장하고 10번으로 갑니다. 없으면 9번으로 갑니다.
|
|
23
|
+
9. **검색 키워드 및 웹문서 TOP 30** 표를 드래그해 복사하고, 메모장에 붙여 넣어 `naver-report.txt`로 저장합니다.
|
|
24
|
+
10. 저장한 파일의 폴더와 이름을 에이전트에게 알려 줍니다. 파일 내용은 대화에 붙이지 않습니다.
|
|
25
|
+
|
|
26
|
+
## brief
|
|
27
|
+
|
|
28
|
+
- 네이버 서치어드바이저용입니다. 이 서비스를 쓰지 않으면 이 절차는 건너뛰세요.
|
|
29
|
+
- searchadvisor.naver.com 로그인 → **웹마스터 도구** → 사이트 선택 → **SEO 리포트 → 콘텐츠 노출 및 클릭**에서 기간 90일로 조회합니다.
|
|
30
|
+
- 클릭수·노출수·CTR과 **검색 키워드 및 웹문서 TOP 30**을 담습니다. 최신 값은 약 1주 전 기준입니다.
|
|
31
|
+
- 내려받기 버튼이 있는지는 확인 안 됨입니다. 없으면 TOP 30 표를 복사해 텍스트 파일로 저장합니다.
|
|
32
|
+
- 파일 경로만 에이전트에 알립니다.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
checked: 2026-10-18
|
|
3
|
+
source: https://code.claude.com/docs/en/cloud-environments https://code.claude.com/docs/en/claude-code-on-the-web
|
|
4
|
+
harness: claude-code-cloud
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Claude Code 클라우드에서 감사할 사이트 허용하기 (Network access: Custom)
|
|
8
|
+
|
|
9
|
+
## beginner
|
|
10
|
+
|
|
11
|
+
왜 필요한가: Claude Code 클라우드 세션은 허용 목록에 있는 사이트로만 나갈 수 있습니다. 감사할 사이트가 목록에 없어서 막혔습니다. 환경의 네트워크 수준을 Custom으로 바꾸고 감사할 도메인을 추가합니다. Full(모든 사이트 허용)보다 범위가 좁아 안전합니다.
|
|
12
|
+
|
|
13
|
+
1. 브라우저에서 claude.ai/code를 엽니다.
|
|
14
|
+
2. 세션을 시작하는 화면(새 세션 화면)으로 갑니다. 이미 실행 중인 세션이면 위쪽 세션 제목 옆 화살표(caret)를 누르고 **Edit cloud environment**를 고른 뒤 7번으로 갑니다.
|
|
15
|
+
3. 메시지 입력 상자 바로 아래 줄에서 첫 번째 컨트롤(환경 이름이 적힌 버튼)을 누릅니다.
|
|
16
|
+
4. 열린 목록에서 **클라우드**(Cloud)를 누르면 옆에 클라우드 환경 목록이 열립니다. 쓰려는 환경 줄에 마우스를 올립니다.
|
|
17
|
+
5. 줄 오른쪽에 나타나는 톱니바퀴(설정) 아이콘을 누릅니다. 별 아이콘은 즐겨찾기이니 누르지 않습니다. 환경 설정 창이 열립니다.
|
|
18
|
+
6. 창에서 **Network access** 항목을 찾습니다.
|
|
19
|
+
7. **Network access**의 선택 상자를 눌러 **Custom**을 고릅니다.
|
|
20
|
+
8. **Allowed domains** 칸에 감사할 사이트의 도메인을 한 줄에 하나씩 적습니다. 예: `example.com` 한 줄, `*.example.com` 한 줄. 맨 앞 `*.`는 하위 주소 전부를 뜻합니다.
|
|
21
|
+
9. 패키지 설치도 계속 쓰려면, **Custom** 아래에 있는 기본 허용 목록(패키지 저장소 등)을 함께 쓰는 선택 상자를 체크합니다. 필요 없으면 비워 둡니다.
|
|
22
|
+
10. 창의 **Save changes** 버튼을 누릅니다.
|
|
23
|
+
11. 약 1분을 기다립니다. 이미 열린 세션에도 새 설정이 적용됩니다.
|
|
24
|
+
12. 에이전트에게 "다시 시도해 줘"라고 말합니다. 사이트를 읽기 시작하면 끝난 것입니다.
|
|
25
|
+
|
|
26
|
+
한글 화면에서는 항목 이름이 번역되어 있을 수 있습니다. 위 영문 이름과 같은 자리의 항목을 고릅니다.
|
|
27
|
+
|
|
28
|
+
## brief
|
|
29
|
+
|
|
30
|
+
- claude.ai/code 새 세션 화면에서 메시지 상자 아래 첫 컨트롤 → 환경 행의 설정 아이콘 → **Network access**를 **Custom**으로.
|
|
31
|
+
- **Allowed domains**에 감사할 도메인을 한 줄에 하나씩(`example.com`, `*.example.com`). 저장하면 약 1분 안에 기존 세션에도 적용됩니다.
|
|
32
|
+
- 실행 중인 세션은 제목 옆 caret → **Edit cloud environment**.
|
|
33
|
+
- Full 대신 Custom을 권합니다. 환경마다 목록이 따로이고 조직 전체 목록은 없습니다.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
checked: 2026-10-17
|
|
3
|
+
source: https://learn.chatgpt.com/docs/agent-approvals-security
|
|
4
|
+
harness: codex
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Codex가 묻는 네트워크 승인에 답하기
|
|
8
|
+
|
|
9
|
+
## beginner
|
|
10
|
+
|
|
11
|
+
왜 필요한가: Codex는 기본으로 네트워크를 끈 채 명령을 실행합니다. 감사는 사이트에서 페이지를 받아 와야 하므로, Codex가 승인을 물으면 허용해야 감사가 돌아갑니다.
|
|
12
|
+
|
|
13
|
+
1. 감사를 시작하면 Codex 화면에 승인을 묻는 창(또는 메시지)이 뜹니다. 뜰 때까지 기다립니다. 이 창은 파일이나 네트워크를 쓰는 명령을 실행하기 전에 Codex가 사용자에게 허락을 구하는 것입니다.
|
|
14
|
+
2. 창에 적힌 명령을 읽습니다. 명령에 `node`와 optbrew의 `preflight.mjs` 또는 `run.mjs`가 보이면 정상입니다.
|
|
15
|
+
3. 명령이 낯선 곳(다른 사이트 주소, 설치 명령 등)으로 가는 내용이면 **No, and tell Codex what to do differently**(거절하고 이유 말하기)를 고릅니다. 에이전트에게 이유를 묻습니다.
|
|
16
|
+
4. 정상이면 **Yes, just this once**(이번 한 번만 허용)를 고릅니다. 한 번만 허용하면 실행마다 다시 묻습니다.
|
|
17
|
+
5. 같은 승인이 계속 반복되어 번거로우면 **Yes, and don't ask again for this command in this session**(이 세션 동안 이 명령은 다시 묻지 않음)을 고릅니다. 사이트 주소를 허용할지 물으면 **Yes, and allow this host for this conversation**(이 대화 동안 이 주소 허용)을 고릅니다. 세션을 닫으면 허용은 사라집니다.
|
|
18
|
+
6. 승인 후 에이전트가 "사이트를 읽는 중"이라는 취지로 진행하면 끝난 것입니다.
|
|
19
|
+
7. 승인 창이 아예 뜨지 않고 "네트워크가 막혔다"는 오류만 나오면, `network-codex-config` 런북(설정 파일로 허용)으로 넘어갑니다.
|
|
20
|
+
|
|
21
|
+
## brief
|
|
22
|
+
|
|
23
|
+
- Codex는 sandbox 기본값(workspace-write)에서 네트워크를 끄고, 필요한 명령 앞에서 승인을 묻습니다. 승인 창에서 optbrew의 `node` 명령인지 확인하고 허용합니다. 선택지 글자는 Codex CLI 0.162.0 기준(`Yes, just this once` 등)입니다.
|
|
24
|
+
- 한 번만 허용하면 실행마다 묻고, 세션 동안 허용하면 세션 종료까지 묻지 않습니다.
|
|
25
|
+
- 한 번의 실행에만 쓸 때는 `-c 'sandbox_workspace_write.network_access=true'`를 Codex 실행 인자로 줄 수 있습니다.
|
|
26
|
+
- 승인 창이 없이 막히면 `network-codex-config`를 봅니다.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
checked: 2026-10-16
|
|
3
|
+
source: https://learn.chatgpt.com/docs/agent-approvals-security https://learn.chatgpt.com/docs/config-file/config-basic
|
|
4
|
+
harness: codex
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Codex 설정 파일로 네트워크 미리 허용하기 (선택)
|
|
8
|
+
|
|
9
|
+
## beginner
|
|
10
|
+
|
|
11
|
+
왜 필요한가: 승인 창을 매번 누르기 싫을 때, Codex 설정 파일에 한 줄을 넣어 sandbox 안에서 네트워크를 미리 허용합니다. 선택 사항이며, 하지 않아도 승인 창으로 감사를 할 수 있습니다.
|
|
12
|
+
|
|
13
|
+
1. Codex를 모두 닫습니다.
|
|
14
|
+
2. 설정 파일 `config.toml`을 엽니다. 위치는 사용자 폴더 안의 `.codex` 폴더입니다. Windows는 `C:\Users\<내 계정 이름>\.codex\config.toml`, macOS와 Linux는 `~/.codex/config.toml`입니다.
|
|
15
|
+
3. 파일이 없으면 메모장(Windows) 또는 텍스트 편집기(macOS)에서 새 파일을 만들어 같은 이름으로 같은 폴더에 저장합니다. 확장자가 `.txt`로 붙지 않게 주의합니다.
|
|
16
|
+
4. 파일 안에 `[sandbox_workspace_write]`라는 줄이 이미 있는지 찾습니다.
|
|
17
|
+
5. 이미 있으면 그 줄 바로 아래에 `network_access = true` 한 줄만 추가합니다.
|
|
18
|
+
6. 없으면 파일 맨 아래에 다음 두 줄을 붙여 넣습니다.
|
|
19
|
+
|
|
20
|
+
```toml
|
|
21
|
+
[sandbox_workspace_write]
|
|
22
|
+
network_access = true
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
7. 파일을 저장합니다.
|
|
26
|
+
8. Codex를 다시 켭니다.
|
|
27
|
+
9. 감사를 다시 요청합니다. 이제 네트워크 승인 창이 뜨지 않으면 성공입니다.
|
|
28
|
+
10. 그래도 막히면 설정이 적용되지 않은 것입니다. 설정에 `sandbox_mode = "workspace-write"`가 있는지 확인하고, macOS에서는 이 설정이 적용되지 않는다는 사용자 보고가 있으므로 `network-codex-approval` 런북(승인 창)으로 돌아갑니다. (화면 확인 필요)
|
|
29
|
+
|
|
30
|
+
설정을 되돌리려면 위에서 추가한 줄을 지웁니다. 이 설정은 Codex가 실행하는 모든 작업에 적용되므로, 신뢰하지 않는 프로젝트가 있는 컴퓨터에서는 승인 창 방식을 권합니다.
|
|
31
|
+
|
|
32
|
+
## brief
|
|
33
|
+
|
|
34
|
+
- 사용자 설정 `~/.codex/config.toml`(Windows는 사용자 폴더의 `.codex\config.toml`)에 `[sandbox_workspace_write]` 아래 `network_access = true`를 둡니다.
|
|
35
|
+
- 한 번의 실행에만 필요하면 `-c 'sandbox_workspace_write.network_access=true'`를 인자로 줍니다.
|
|
36
|
+
- Codex를 다시 켠 뒤 적용됩니다. macOS에서는 적용되지 않는다는 보고가 있어 안 되면 승인 창(`network-codex-approval`)으로 갑니다. (화면 확인 필요)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
checked: 2026-10-16
|
|
3
|
+
source: https://seranking.com/api.html https://seranking.com/api/integrations/mcp
|
|
4
|
+
harness: any
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# SE Ranking 연결하기 (선택, 유료 계정 필요)
|
|
8
|
+
|
|
9
|
+
## beginner
|
|
10
|
+
|
|
11
|
+
왜 필요한가: SE Ranking은 AI 검색 인용 같은 실측 수치를 줍니다. 기본 감사는 이 연결 없이 끝까지 갑니다. SE Ranking은 별도의 유료 계정이고 사용량(크레딧)이 내 계정에서 차감됩니다. Anthropic 키로는 대신할 수 없습니다. 비용이 부담되면 이 런북 전체를 건너뜁니다.
|
|
12
|
+
|
|
13
|
+
1. 브라우저에서 seranking.com에 로그인합니다. 계정이 없으면 만들 수 있지만, 가입과 결제는 내가 직접 결정합니다.
|
|
14
|
+
2. API 사용 권한이 있는지 확인합니다. SE Ranking 문서는 API가 구독에 포함되며 무료 체험 계정에서도 시험할 수 있다고 안내합니다. 요금 조건은 서비스 화면에서 직접 확인합니다. (화면 확인 필요)
|
|
15
|
+
3. OAuth로 연결하는 경우에는 키를 복사할 필요가 없습니다. 아래 4번으로 갑니다. 키가 필요하다면 로그인 후 **Settings**의 **API** 탭을 열어 키를 확인합니다. (화면 확인 필요: 한글 이름과 위치)
|
|
16
|
+
4. Claude Code를 쓰는 경우, 터미널에서 다음 명령 한 줄을 실행해 MCP 서버를 등록합니다.
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
claude mcp add --transport http --scope local se-ranking https://api.seranking.com/mcp
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
5. Codex를 쓰는 경우, 터미널에서 다음 명령 한 줄을 실행합니다.
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
codex mcp add se-ranking --url https://api.seranking.com/mcp
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
6. 등록 뒤 인증 창이 브라우저에서 열리면 내 SE Ranking 계정으로 허용합니다. 허용 화면의 내용을 읽고 직접 결정합니다. (화면 확인 필요)
|
|
29
|
+
7. 에이전트를 다시 시작하고 SE Ranking 도구가 보이는지 확인합니다.
|
|
30
|
+
8. 키(`X-Api-Key`)를 설정 파일, 저장소, 대화창, 보고서에 적지 않습니다.
|
|
31
|
+
|
|
32
|
+
이 명령은 내 하네스 설정을 바꾸고 인증 절차를 시작할 수 있습니다. 실행 전에 하네스의 현재 도움말(`--help`)로 문법을 확인합니다.
|
|
33
|
+
|
|
34
|
+
## brief
|
|
35
|
+
|
|
36
|
+
- SE Ranking은 별도 유료 계정이며 크레딧이 내 계정에서 차감됩니다. 필수가 아닙니다.
|
|
37
|
+
- Claude Code: `claude mcp add --transport http --scope local se-ranking https://api.seranking.com/mcp`. Codex: `codex mcp add se-ranking --url https://api.seranking.com/mcp`. 등록 뒤 OAuth 인증을 브라우저에서 허용합니다.
|
|
38
|
+
- 키를 쓰는 경우는 **Settings → API** 탭에서 확인하고(화면 확인 필요) 설정 파일·대화에 적지 않습니다.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://optbrew.local/schemas/contract.schema.json",
|
|
4
|
-
"title": "optbrew persisted entities
|
|
4
|
+
"title": "optbrew persisted entities v2.0.0",
|
|
5
5
|
"$defs": {
|
|
6
6
|
"ApplicabilityDecision": { "type": "object", "additionalProperties": false, "required": ["condition", "reason", "evidence_ref"], "properties": { "condition": { "type": "string" }, "reason": { "type": "string" }, "evidence_ref": { "type": "string" } } },
|
|
7
7
|
"UrlRef": { "type": "object", "additionalProperties": false, "required": ["original", "normalized"], "properties": { "original": { "type": "string", "format": "uri" }, "normalized": { "type": "string", "format": "uri" }, "public_indexing": { "type": "boolean" }, "indexing_decision": { "$ref": "#/$defs/ApplicabilityDecision" } } },
|
|
@@ -9,40 +9,67 @@
|
|
|
9
9
|
"PageType": { "type": "object", "required": ["id", "urls"], "properties": { "id": { "type": "string" }, "urls": { "type": "array", "items": { "$ref": "#/$defs/UrlRef" } } } },
|
|
10
10
|
"FamilyPlan": { "type": "object", "additionalProperties": false, "required": ["family_id", "applicable"], "properties": { "family_id": { "type": "string" }, "applicable": { "type": "boolean" }, "urls": { "type": "array", "items": { "type": "string" } }, "engines": { "type": "array", "items": { "type": "string" } }, "applicability_decision": { "$ref": "#/$defs/ApplicabilityDecision" } } },
|
|
11
11
|
"Question": { "type": "object", "required": ["id", "question", "category"], "properties": { "id": { "type": "string" }, "question": { "type": "string" }, "category": { "enum": ["brand", "non_brand"] }, "goal": { "type": "string" } } },
|
|
12
|
-
"
|
|
12
|
+
"RenderPlan": { "type": "object", "additionalProperties": false, "required": ["enabled", "pages", "max_render_requests"], "properties": { "enabled": { "type": "boolean" }, "pages": { "type": "array", "items": { "$ref": "#/$defs/UrlRef" } }, "max_render_requests": { "type": "integer", "minimum": 1, "maximum": 300 } } },
|
|
13
|
+
"Protocol": { "type": "object", "additionalProperties": false, "required": ["page_types", "engines", "families", "locale", "region", "browser", "device", "question_panel", "surfaces", "models", "search_enabled", "render"], "properties": {
|
|
13
14
|
"page_types": { "type": "array", "items": { "$ref": "#/$defs/PageType" } }, "engines": { "type": "array", "items": { "type": "string" } }, "families": { "type": "array", "items": { "$ref": "#/$defs/FamilyPlan" } },
|
|
14
15
|
"auxiliary_resources": { "type": "array", "items": { "$ref": "#/$defs/AuxiliaryResource" } },
|
|
15
16
|
"locale": { "type": "string" }, "region": { "type": "string" }, "browser": { "type": "string" }, "device": { "type": "string" }, "question_panel": { "type": "array", "items": { "$ref": "#/$defs/Question" } }, "surfaces": { "type": "array" }, "models": { "type": "array" }, "search_enabled": { "type": ["boolean", "null"] },
|
|
16
|
-
"target": { "type": ["string", "object"] }, "environment": { "type": "string" }, "evaluation_version": { "type": "string" }, "protocol_version": { "type": "string" }, "target_identity": { "type": "object" }
|
|
17
|
+
"target": { "type": ["string", "object"] }, "environment": { "type": "string" }, "evaluation_version": { "type": "string" }, "protocol_version": { "type": "string" }, "target_identity": { "type": "object" },
|
|
18
|
+
"render": { "$ref": "#/$defs/RenderPlan" }
|
|
17
19
|
} },
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
+
"EnvironmentProbe": { "type": "object", "additionalProperties": false, "required": ["harness", "transport", "address_vetting", "block_reason", "chrome", "google_key", "source_tools", "sitemap_pages"], "properties": {
|
|
21
|
+
"harness": { "enum": ["claude-code", "codex", "claude-code-cloud", "other"] }, "transport": { "enum": ["direct", "proxy", null] }, "address_vetting": { "enum": ["direct", "proxy", null] },
|
|
22
|
+
"proxy": { "type": ["object", "null"], "additionalProperties": false, "required": ["host", "port"], "properties": { "host": { "type": "string" }, "port": { "type": "integer" } } },
|
|
23
|
+
"block_reason": { "enum": ["codex_sandbox", "cloud_network_level", "site_down", null] },
|
|
24
|
+
"chrome": { "type": "object", "additionalProperties": false, "required": ["found", "kind"], "properties": { "found": { "type": "boolean" }, "kind": { "type": ["string", "null"] }, "reason": { "type": ["string", "null"] } } },
|
|
25
|
+
"google_key": { "type": "boolean" }, "source_tools": { "type": "array", "items": { "type": "string" } }, "sitemap_pages": { "type": ["integer", "null"], "minimum": 0 }
|
|
20
26
|
} },
|
|
21
|
-
"
|
|
22
|
-
|
|
27
|
+
"Fact": { "type": "object", "additionalProperties": false, "required": ["key", "value", "source_url"], "properties": { "key": { "type": "string" }, "value": { "type": "string" }, "source_url": { "type": "string" } } },
|
|
28
|
+
"Goal": { "type": "string", "minLength": 1 },
|
|
29
|
+
"OwnerExport": { "type": "object", "additionalProperties": false, "required": ["kind", "path"], "properties": { "kind": { "enum": ["gsc", "naver"] }, "path": { "type": "string", "minLength": 1 } } },
|
|
30
|
+
"IntakeAnswers": { "type": "object", "additionalProperties": false, "required": ["schema_version", "guidance_level", "url", "goals", "questions", "facts", "sample_mode", "max_urls", "source_tools", "probe"], "properties": {
|
|
31
|
+
"schema_version": { "const": "2.0.0" }, "guidance_level": { "enum": ["beginner", "brief"] }, "url": { "type": "string" }, "goals": { "type": "array", "items": { "$ref": "#/$defs/Goal" } },
|
|
32
|
+
"owner_exports": { "type": "array", "items": { "$ref": "#/$defs/OwnerExport" } },
|
|
33
|
+
"questions": { "type": "array", "items": { "$ref": "#/$defs/Question" } }, "facts": { "type": "array", "items": { "$ref": "#/$defs/Fact" } },
|
|
34
|
+
"sample_mode": { "enum": ["sample", "raise", null] }, "max_urls": { "type": "integer", "minimum": 1 }, "source_tools": { "type": "array", "items": { "type": "string" } }, "probe": { "$ref": "#/$defs/EnvironmentProbe" }
|
|
35
|
+
} },
|
|
36
|
+
"RunContext": { "type": "object", "required": ["schema_version", "run_id", "target", "environment", "goals", "allowed_changes", "scope", "protocol", "evaluation_version", "protocol_version", "data_sources", "cost_decision", "evidence_ref", "environment_probe"], "properties": {
|
|
37
|
+
"environment_probe": { "$ref": "#/$defs/EnvironmentProbe" },
|
|
38
|
+
"schema_version": { "const": "2.0.0" }, "run_id": { "type": "string" }, "target": { "type": ["string", "object"] }, "environment": { "type": "string" }, "goals": { "type": "array" }, "allowed_changes": { "type": "array" }, "scope": { "type": "object" }, "protocol": { "$ref": "#/$defs/Protocol" }, "evaluation_version": { "type": "string" }, "protocol_version": { "type": "string" }, "data_sources": { "type": "array" }, "cost_decision": { "type": "object" }, "evidence_ref": { "type": "string" }, "audited": { "type": "boolean" }
|
|
23
39
|
} },
|
|
40
|
+
"CheckObservation": { "type": "object", "required": ["schema_version", "observation_id", "check_id", "family_id", "unit_key", "state", "q", "partial_basis", "reason", "evaluator_type", "source_ref", "evidence_ref"], "properties": {
|
|
41
|
+
"schema_version": { "const": "2.0.0" }, "observation_id": { "type": "string" }, "check_id": { "type": "string" }, "family_id": { "type": "string" }, "unit_key": { "type": "string" }, "state": { "enum": ["pass", "partial", "fail", "unknown", "not_applicable"] }, "q": { "type": ["number", "null"], "minimum": 0, "maximum": 1 }, "partial_basis": { "type": ["object", "null"] }, "reason": { "type": "string" }, "evaluator_type": { "enum": ["human", "host_model", "external_model", "code"] }, "source_ref": { "type": "string" }, "evidence_ref": { "type": "string" }, "applicability_decision": { "$ref": "#/$defs/ApplicabilityDecision" }, "blocker": { "type": "boolean" }, "measurement": { "type": "object" }, "problem_messages": { "type": "array", "items": { "$ref": "#/$defs/MessageRef" } }, "evidence_span": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/EvidenceSpan" } }
|
|
42
|
+
},
|
|
43
|
+
"if": { "required": ["evaluator_type", "state"], "properties": { "evaluator_type": { "const": "host_model" }, "state": { "not": { "const": "unknown" } } } },
|
|
44
|
+
"then": { "required": ["evidence_span"] } },
|
|
45
|
+
"EvidenceSpan": { "type": "object", "additionalProperties": false, "required": ["evidence_sha256", "start", "end"], "properties": { "evidence_sha256": { "type": "string" }, "start": { "type": "integer", "minimum": 0 }, "end": { "type": "integer", "minimum": 1 } } },
|
|
24
46
|
"MetricObservation": { "type": "object", "required": ["schema_version", "metric_id", "value_state", "value", "numerator", "denominator", "definition", "filters", "window", "panel_hash", "provider_definition", "sample_selection", "source_ref", "evidence_ref"], "properties": {
|
|
25
|
-
"schema_version": { "const": "
|
|
47
|
+
"schema_version": { "const": "2.0.0" }, "metric_id": { "type": "string" }, "value_state": { "enum": ["measured", "unknown", "zero_or_missing", "not_applicable"] }, "value": { "type": ["number", "null"] }, "numerator": { "type": ["number", "null"] }, "denominator": { "type": ["number", "null"] }, "definition": { "type": "string" }, "filters": { "type": "object" }, "window": { "$ref": "#/$defs/MetricWindow" }, "panel_hash": { "type": ["string", "null"] }, "provider_definition": { "type": "string" }, "sample_selection": { "type": "object" }, "source_ref": { "type": "string" }, "evidence_ref": { "type": "string" }
|
|
26
48
|
} },
|
|
27
49
|
"MetricWindow": { "type": "object", "required": ["start", "end", "timezone"], "properties": { "start": { "type": "string" }, "end": { "type": "string" }, "timezone": { "type": "string" } } },
|
|
28
50
|
"ComparisonResult": { "type": "object", "required": ["status", "score_deltas", "reasons"], "properties": { "status": { "enum": ["comparable", "not_comparable", "no_after"] }, "score_deltas": { "type": "array" }, "reasons": { "type": "array", "items": { "type": "string" } } } },
|
|
29
51
|
"Finding": { "type": "object", "required": ["schema_version", "finding_id", "check_id", "unit_key", "priority", "blocker", "message", "source_ref", "evidence_ref", "remediation_ids", "status"], "properties": {
|
|
30
|
-
"schema_version": { "const": "
|
|
52
|
+
"schema_version": { "const": "2.0.0" }, "finding_id": { "type": "string" }, "check_id": { "type": "string" }, "unit_key": { "type": "string" }, "priority": { "type": "string" }, "blocker": { "type": "boolean" }, "message": { "type": "string" }, "source_ref": { "type": "string" }, "evidence_ref": { "type": "string" }, "remediation_ids": { "type": "array" }, "status": { "type": "string" }, "message_key": { "type": ["string", "null"] }, "message_values": { "type": "object" }
|
|
31
53
|
} },
|
|
32
|
-
"
|
|
33
|
-
|
|
54
|
+
"MessageRef": { "type": "object", "required": ["message_key", "message_values"], "properties": { "message_key": { "type": ["string", "null"] }, "message_values": { "type": "object" } } },
|
|
55
|
+
"ChangeRecord": { "type": "object", "required": ["schema_version", "change_id", "finding_ids", "environment", "path", "before_hash", "after_hash", "backup_ref", "verification", "evidence_ref", "analysis"], "properties": {
|
|
56
|
+
"schema_version": { "const": "2.0.0" }, "change_id": { "type": "string" }, "analysis": { "$ref": "#/$defs/ChangeAnalysis" }, "finding_ids": { "type": "array" }, "environment": { "type": "string" }, "path": { "type": "string" }, "before_hash": { "type": "string" }, "after_hash": { "type": "string" }, "backup_ref": { "type": "string" }, "verification": { "type": "object" }, "evidence_ref": { "type": "string" }
|
|
34
57
|
} },
|
|
58
|
+
"ChangeAnalysis": { "type": "object", "additionalProperties": false, "required": ["tools", "queries", "self_analysis"], "properties": { "tools": { "type": "array", "items": { "type": "string" } }, "queries": { "type": "array", "items": { "type": "string" } }, "self_analysis": { "type": "boolean" } } },
|
|
35
59
|
"CostLedger": { "type": "object", "required": ["schema_version", "run_id", "budgets", "reservations", "settlements", "approval_ref", "source_ref"], "properties": {
|
|
36
|
-
"schema_version": { "const": "
|
|
60
|
+
"schema_version": { "const": "2.0.0" }, "run_id": { "type": "string" }, "budgets": { "type": "array" }, "reservations": { "type": "array" }, "settlements": { "type": "array" }, "approval_ref": { "type": ["string", "null"] }, "source_ref": { "type": "string" }
|
|
37
61
|
} },
|
|
38
62
|
"ScoreResult": { "type": "object", "required": ["schema_version", "evaluation_version", "profile", "A", "M", "N", "score", "coverage", "missing_bounds", "band", "readiness_status", "blockers", "unknown_core", "source_ref"], "properties": {
|
|
39
|
-
"schema_version": { "const": "
|
|
63
|
+
"schema_version": { "const": "2.0.0" }, "evaluation_version": { "type": "string" }, "profile": { "type": "string" }, "A": { "type": "number" }, "M": { "type": "number" }, "N": { "type": "number" }, "score": { "type": ["number", "null"] }, "coverage": { "type": ["number", "null"] }, "missing_bounds": { "type": ["array", "null"] }, "band": { "type": ["string", "null"] }, "readiness_status": { "enum": ["blocked", "unverified", "provisional", "measured", "not_applicable"] }, "blockers": { "type": "array" }, "unknown_core": { "type": "array" }, "source_ref": { "type": "string" }
|
|
40
64
|
} },
|
|
41
65
|
"ScoreInput": { "type": "object", "required": ["schema_version", "run_context", "observations"], "properties": {
|
|
42
|
-
"schema_version": { "const": "
|
|
66
|
+
"schema_version": { "const": "2.0.0" }, "run_context": { "$ref": "#/$defs/RunContext" }, "observations": { "type": "array", "items": { "$ref": "#/$defs/CheckObservation" } }
|
|
43
67
|
} },
|
|
44
68
|
"ReportPacket": { "type": "object", "required": ["schema_version", "run_context", "baseline", "after", "comparison", "metrics", "findings", "changes", "costs", "remaining", "evidence_root", "evidence_index", "source_ref"], "properties": {
|
|
45
|
-
"schema_version": { "const": "
|
|
69
|
+
"schema_version": { "const": "2.0.0" }, "run_context": { "$ref": "#/$defs/RunContext" }, "baseline": { "type": "object", "required": ["input", "scores"], "properties": { "input": { "$ref": "#/$defs/ScoreInput" }, "scores": { "type": "array", "items": { "$ref": "#/$defs/ScoreResult" } } } }, "after": { "type": ["object", "null"] }, "comparison": { "$ref": "#/$defs/ComparisonResult" }, "metrics": { "type": "array", "items": { "$ref": "#/$defs/MetricObservation" } }, "findings": { "type": "array", "items": { "$ref": "#/$defs/Finding" } }, "changes": { "type": "array", "items": { "$ref": "#/$defs/ChangeRecord" } }, "costs": { "$ref": "#/$defs/CostLedger" }, "remaining": { "type": "array" }, "evidence_root": { "type": "string" }, "evidence_index": { "type": "array", "items": { "type": "object", "required": ["ref", "sha256", "kind", "run_id"], "properties": { "ref": { "type": "string" }, "sha256": { "type": "string" }, "kind": { "type": "string" }, "run_id": { "type": "string" }, "source_ref": { "type": "string" }, "source_url": { "type": "string" } } } }, "audit_ref": { "type": "string" }, "audit_snapshots": { "type": "array" }, "source_ref": { "type": "string" }, "data_class": { "enum": ["synthetic", "real", "unknown"] }, "capabilities": { "type": "object" },
|
|
70
|
+
"lab_cwv": { "type": ["object", "null"], "additionalProperties": false, "required": ["lcp", "cls"], "properties": { "lcp": { "type": ["number", "null"] }, "cls": { "type": ["number", "null"] } } },
|
|
71
|
+
"answered_runbooks": { "type": "array", "items": { "type": "string" } },
|
|
72
|
+
"start_redirect": { "type": ["object", "null"], "additionalProperties": false, "required": ["from", "to"], "properties": { "from": { "type": "string" }, "to": { "type": "string" } } }
|
|
46
73
|
} },
|
|
47
74
|
"InstallRecord": { "type": "object", "required": ["schema_version", "scope_id", "release_version", "bundle_hash", "source", "managed_root", "targets", "file_hashes", "previous_releases", "transaction", "source_ref"], "properties": {
|
|
48
75
|
"schema_version": { "const": "1.0.0" }, "scope_id": { "type": "string" }, "release_version": { "type": "string" }, "bundle_hash": { "type": "string" }, "source": { "type": "string" }, "managed_root": { "type": "string" }, "targets": { "type": "array" }, "file_hashes": { "type": "object" }, "previous_releases": { "type": "array" }, "transaction": { "type": "object" }, "source_ref": { "type": "string" }
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
# optbrew readiness scoring contract (evaluation 0.
|
|
1
|
+
# optbrew readiness scoring contract (evaluation 0.4.0)
|
|
2
2
|
|
|
3
3
|
This is a readiness audit. It does not predict indexing, rank, AI citations, accuracy, or revenue. `check-registry.json` holds the 22 unique families. `profiles.json` holds the five optbrew axis weight sets. Those weights, the middle-band CWV `q=0.5`, the score bands and the 80% coverage threshold are **optbrew rules**, not search engine weights or official sufficiency tests. `source_refs` names match the official references in `docs/superpowers/specs/2026-10-13-optbrew-sources.md` in the source repository.
|
|
4
4
|
|
|
5
5
|
## Fixed input
|
|
6
6
|
|
|
7
|
-
`ScoreInput` is `{schema_version:"
|
|
7
|
+
`ScoreInput` is `{schema_version:"2.0.0",run_context:RunContext,observations:CheckObservation[]}`. The JSON Schema in `schemas/contract.schema.json` names this and the nine persisted entities. `validateEntity(typeName,value)` returns the value or throws `ContractError`. `freezeProtocol(run_context)` returns `{protocol,protocol_hash}`. Its hash covers the selected URLs and page types, indexing intent, family condition codes, engines, location/language, panel and surface/model/search settings, browser/device, target, environment and separate evaluation/protocol versions. It excludes `run_id`, evidence paths, collection times and explanatory decision text. Changing a comparison condition requires re-evaluation from preserved raw data or `not_comparable`.
|
|
8
|
+
|
|
9
|
+
Contract 2.0.0, evaluation 0.4.0 and protocol 0.2.0 start with optbrew 0.2.0. Evidence an older release made (any of those labels older than these) is refused with `migration_required` and one sentence telling the user to run the audit again in a new output folder. `compare` reports such an input as `not_comparable` (`no_after` when only the baseline is given) instead of failing the schema. `InstallRecord` and the bundle manifest keep schema 1.0.0.
|
|
8
10
|
|
|
9
11
|
Every `run_context.protocol.families` entry is `{family_id,applicable,urls?,engines?,applicability_decision?}`. A `ScoreInput` must explicitly decide all 22 entries. False requires `{condition,reason,evidence_ref}` with a condition allowed for that family in `check-registry.json`; true forbids a decision. The reason and evidence reference must be nonempty. Unknown facts or missing collection data leave applicable units `unknown`; they do not remove denominator weight. `urls` and `engines`, when supplied, must equal the entire applicable selected set. To exclude one optional unit, submit an evidence-backed `not_applicable` observation for that unit. A subset in a family plan is rejected. NEO is reported only for a Naver target.
|
|
10
12
|
|
|
11
|
-
Every selected URL is a public audit target and always gets `fetch-response`. Its optional `public_indexing` defaults to true. An intentional non-index target sets `public_indexing:false` and supplies `indexing_decision:{condition:"not_public_index_target",reason,evidence_ref}`. Public-index targets automatically get `index-policy` and `discovery`; target engines also get `search-crawl-policy`. Naver as a target automatically activates `naver-markup`. Bare flags cannot turn these families off. A selected URL still gets `fetch-response` if intentionally non-indexed. An unreachable, uncollected or unclear check is `unknown`, never N/A.
|
|
13
|
+
Every selected URL is a public audit target and always gets `fetch-response`. Its optional `public_indexing` defaults to true. An intentional non-index target sets `public_indexing:false` and supplies `indexing_decision:{condition:"not_public_index_target",reason,evidence_ref}`. The audit sets it itself, after collection, for a selected page that carries noindex in its raw response, is not the home page and is listed in no sitemap the run read, while the sitemaps it read list other pages of the same site and every declared sitemap was read in full. The rule is: when the evidence is incomplete or ambiguous, the noindex stays a failure and a blocker. R96: sitemap entries and page addresses are compared with the product's own normalizer (scope check, fragment, `index.html`) after `www.`, http/https and a trailing slash of the run's host are folded, so a sitemap that lists the page as `http://www.host/page/` lists it; only same-site entries that are not sitemap files (`.xml`, `.xml.gz`) count as listed pages. R97 (and R89a): a sitemap is read only on a 2xx whose body is a sitemap (`urlset` or `sitemapindex`); 404 and 410 are read and list nothing; a 5xx, timeout, connection error, 403 or a body that is no sitemap is missing evidence; a declared sitemap that was never fetched is missing evidence; a sitemap index counts only when every child it names is a declared sitemap that was read (a child beyond the preflight request limit is not), and the entries of an index are never pages. The decision's reason is then exactly `noindex and not listed in the sitemap`, its `evidence_ref` is the page's stored raw HTML, and report replay derives it again from the stored bytes (no approval record). With no sitemap that lists pages, a noindex stays a confirmed failure and a blocker. Public-index targets automatically get `index-policy` and `discovery`; target engines also get `search-crawl-policy`. Naver as a target automatically activates `naver-markup`. Bare flags cannot turn these families off. A selected URL still gets `fetch-response` if intentionally non-indexed. R94: the families that only matter for indexed pages are marked `public_only` in `check-registry.json` (`index-policy`, `search-crawl-policy`, `discovery`, `naver-markup`, `document-title`, `canonical-consistency`); a page with `public_indexing:false` has no unit, verdict or fix in them, and a title it shares with an indexed page is no duplicate. Its `fetch-response`, render and model-judged units stay. An applicable `public_only` family needs at least one public index target. An unreachable, uncollected or unclear check is `unknown`, never N/A.
|
|
12
14
|
|
|
13
15
|
`check-registry.json` fixes every family's `verdict_mode` and `na_conditions`. The family-level exclusion and unit N/A condition codes are:
|
|
14
16
|
|
|
@@ -36,7 +38,21 @@ Every selected URL is a public audit target and always gets `fetch-response`. It
|
|
|
36
38
|
|
|
37
39
|
These codes identify a condition and point to evidence; the reference alone does not verify the evidence's truth. The collecting agent must preserve the actual evidence. A missing approved fact list for `fact-consistency`, or missing field performance data, is `unknown`, not an exclusion.
|
|
38
40
|
|
|
39
|
-
`
|
|
41
|
+
A host model may declare a unit `not_applicable` only for a per-page content family (`claim-support`, `answer-clarity`, `original-value`, `freshness-truth`, `source-transparency`), only with that family's code from the table, and only citing at least one text span the judge queue offered for the unit. The decision is `{condition,reason,evidence_ref}` with the unit's page evidence as `evidence_ref`, and replay checks the condition and the spans like any model verdict. The report counts these units for each family, apart from the coverage they leave out. The question coverage is derived, not judged: `question-coverage|site` is the number of panel questions that an audited page answers (an answer-clarity `pass` or `partial` naming the question as `measurement.question_id`, counted once per question) over the panel length, `fail` when none is answered, and it exists once the model has judged at least one page against a question. When the home page has no Organization or LocalBusiness JSON-LD and code gave no verdict for `entity-identity|site`, the model judges that unit from the home page's visible text; no JSON-LD alone is never a fail. The model's identity verdict is `pass` only when the cited text shows the target name and at least one other identifying fact that agrees, `partial` (`threshold_half`, q=0.5) when it shows the name only, `fail` when it shows no matching name, and `unknown` when nothing there can be compared. Code's own identity verdicts stay pass or fail.
|
|
42
|
+
|
|
43
|
+
A model verdict is accepted only if the code can re-derive it from the stored page. A page that shows no text (empty body) gets no model verdict: every verdict on it, `not_applicable` included, becomes `unknown` ("page shows no text"). A verdict that rests on absence (an absence `not_applicable`, a `fail` with `basis: "absence"`, a freshness `fail` labelled `shown` on a page with no date) becomes `unknown` when the queue's spans cover less than `judge_absence_min_coverage` of the page text. An absence `fail` also becomes `unknown` when the page's own text (lines that most audited pages do not repeat) shows a date or names its operator. A date that only site chrome shows (lines on more than half of at least five audited pages) is not the page showing a date: it neither blocks a freshness absence `fail` nor satisfies freshness. A `fail` of an absence family, and a freshness `pass`, that cites only site chrome is `unknown`. Every such `unknown` stays in the denominator as a blank and is counted in the report by reason.
|
|
44
|
+
|
|
45
|
+
Where the collector's own checks give a verdict instead of leaving a unit blank (each re-derived on replay, and each names its basis in `reason` or `measurement`):
|
|
46
|
+
|
|
47
|
+
- `render-content`: a page outside the render sample takes the verdict of the rendered page of its page type, only when every rendered page of the type gives the same pass or `not_render_dependent` and that page loads the same external scripts and head assets (stylesheets, preloads) as this one. The page needs body text in its own raw HTML. The reason names the rendered page and the evidence is that page's render record. Otherwise the page stays unknown, including inside the catch-all `pages` type and for list versus detail pages.
|
|
48
|
+
- `structured-data-truth`: no JSON-LD, microdata or RDFa is `markup_not_used_or_selected`. For a page's own top-level and `@graph` nodes (never an organization nested in another node): an Organization or LocalBusiness states a name, phone and street address; an Article-family node states a headline (the h1 or title must hold it or be held by it, the shorter being at least four fifths of the longer; only a page with an h1 can contradict it) and a publication date (matched only when year, month and day all match: a full date in the visible text, or the full `datetime` of a `<time>` element that has visible text outside script, template and hidden elements; the matched text and its source, `text` or `time`, are in the measurement, and a `time` element is close to circular because the site writes it and the JSON-LD from one source). The page passes when every stated fact is shown and none is contradicted, fails when a labelled value (or the h1) differs, and is unknown when a stated fact is neither shown nor contradicted or when the JSON-LD states nothing of these kinds. A publisher or author name alone never passes.
|
|
49
|
+
- `document-title`: a title that is present, shared with no other audited page, and not only the site's name (host, its first label, `og:site_name`, or the name of a JSON-LD organization) or a word of `generic_titles` (registry thresholds) passes, when at least two pages were audited. One audited page proves no uniqueness, so its title stays unknown. Whether the title describes the page is not established, and the pass says so.
|
|
50
|
+
- `fact-consistency`: a page with no labelled, marked-up (JSON-LD) or `tel:` name, phone, address or founding year is `no_evaluable_facts`. A page that states a fact but meets no reference value stays unknown (that is the missing fact list).
|
|
51
|
+
- `canonical-consistency`: no canonical and no redirect is `no_representative_url_policy`; no canonical and exactly one redirect hop passes; both only when no other audited page shares the page's title or body text, otherwise unknown.
|
|
52
|
+
- `discovery`: a page neither listed in a read sitemap nor linked, and not provable as an orphan (crawl cut short, unread sitemap index, script-built links, one page), is unknown; its measurement says which of these applies. Being selected for the audit is not discoverability.
|
|
53
|
+
- `entity-identity`: a page is a separate branch when every Organization node on it is a sub-organization (`parentOrganization`, `branchOf` or a branch word in its name), or when a sub-organization's own `url` (or an `@id` that is the page address) is this page, or the page names it as `about` or `mainEntity` and names no other sub-organization there. A sub-organization whose `url` or `@id` is the site home (with or without a fragment) never makes the home page a branch page. Otherwise, on a page that also has a node that is not a sub-organization, only that node speaks for the page.
|
|
54
|
+
|
|
55
|
+
`question_panel` contains fixed `{id,question,category,goal?}` entries. Category is `brand` or `non_brand`; IDs are unique. `question-coverage` and `answer-clarity` require a nonempty panel. A partial `question-coverage` ratio uses the fixed panel length as its denominator. A question counts as answered once when some audited page's pass or partial `answer-clarity` verdict names it as `measurement.question_id` or in `measurement.also_question_ids` (a page may answer several questions); text of site chrome that answers a question counts through the page whose verdict cites it. A question no page answers is reported as not found within the pages read, never as missing from the site.
|
|
40
56
|
|
|
41
57
|
Each page type has a stable ID and an array of `{original,normalized}` HTTP(S) URLs. Normalized URLs must be unique across types. Unit keys are deterministic:
|
|
42
58
|
|
|
@@ -49,7 +65,7 @@ Each page type has a stable ID and an array of `{original,normalized}` HTTP(S) U
|
|
|
49
65
|
|
|
50
66
|
`CheckObservation.check_id` equals its registered `family_id` in this evaluation version. Multiple modules can submit the same unit and verdict; it counts once. An unregistered unit or conflicting verdict is a contract error. For `pass` and `fail`, `q` is exactly 1 and 0. For `unknown` and `not_applicable`, `q` and `partial_basis` are null. Unit N/A also requires `applicability_decision:{condition,reason,evidence_ref}`; core policy units and fixed site/public units cannot use N/A. A confirmed noindex or target crawler block is `fail`, `q=0`, and a blocker. Missing policy evidence is `unknown`.
|
|
51
67
|
|
|
52
|
-
The
|
|
68
|
+
The 13 binary families do not accept partial; their applicable units use pass, fail or unknown. Optional units may use N/A only under the allowed condition decision above. `lcp-field`, `inp-field`, `cls-field`, `answer-clarity`, `original-value` and `entity-identity` accept `partial_basis:{kind:"threshold_half"}` with `q=0.5`. `question-coverage`, `claim-support` and `fact-consistency` accept `partial_basis:{kind:"ratio",numerator:int,denominator:positive_int}` with a matching interior ratio. `question-coverage` also requires denominator equal to the fixed panel length. A CWV verdict requires `measurement:{value,percentile:75,source_type:"field",unit}`; use `ms` for LCP/INP and `unitless` for CLS. An absent field measurement is `unknown`, not N/A.
|
|
53
69
|
|
|
54
70
|
## Weight and status calculation
|
|
55
71
|
|
|
@@ -60,3 +76,22 @@ The 14 binary families do not accept partial; their applicable units use pass, f
|
|
|
60
76
|
`score(input)` returns ordered `ScoreResult[]` for SEO, optional NEO, AEO, GEO and LLMO. SEO is the default aggregate and is calculated directly, never from the mean of profile scores. Each result includes `{schema_version,evaluation_version,profile,A,M,N,score,coverage,missing_bounds,band,readiness_status,blockers,unknown_core,source_ref}`. Bands are `focus_improvement` (0–59), `major_improvement` (60–79), `additional_improvement` (80–94), and `remaining_review` (95–100). Status precedence is `not_applicable` for `A=0`, then `blocked` for confirmed access/crawl/noindex failures or an explicit severe-fact blocker, then `unverified` for unknown applicable access/index core units, then `provisional` below 80% measured coverage, else `measured`. `measured` means only that this optbrew readiness audit has enough measured coverage by its own rule.
|
|
61
77
|
|
|
62
78
|
Run a stored input without service access: `node skills/optbrew/scripts/cli.mjs score --input input.json --output score.json`. The output is the same JSON `ScoreResult[]` as the library call. The CLI validates before writing.
|
|
79
|
+
|
|
80
|
+
## Field Core Web Vitals: thresholds and sources
|
|
81
|
+
|
|
82
|
+
The `good` and `poor` bounds of `lcp-field`, `inp-field` and `cls-field` sit in `check-registry.json` (`thresholds`), so `evaluation_hash` covers them. Each is judged at the 75th percentile of real page loads: at or below `good` is pass, above `poor` is fail, between them is partial (`q=0.5`, an optbrew rule). Confirmed against the primary pages on 2026-10-17:
|
|
83
|
+
|
|
84
|
+
| Family | Good | Poor | Unit | Source |
|
|
85
|
+
|---|---|---|---|---|
|
|
86
|
+
| lcp-field | 2500 | above 4000 | ms | https://web.dev/articles/lcp |
|
|
87
|
+
| inp-field | 200 | above 500 | ms | https://web.dev/articles/inp |
|
|
88
|
+
| cls-field | 0.1 | above 0.25 | unitless | https://web.dev/articles/cls |
|
|
89
|
+
|
|
90
|
+
The 75th percentile as the measuring point: https://web.dev/articles/vitals. PageSpeed Insights states the same CLS bands: https://developers.google.com/speed/docs/insights/v5/about.
|
|
91
|
+
|
|
92
|
+
Field data comes from the user's own Google key and nowhere else. `endpoints.json` holds the two endpoints without a key, and the key is added to a request only at request time, in the `X-Goog-Api-Key` header (Google documents the header as the primary way to send an API key, and warns that a `key` query parameter exposes it in URLs: https://docs.cloud.google.com/docs/authentication/api-keys-use). A value that is not shaped like a Google key (letters, digits, `.`, `_`, `-`, at least 20; AI Studio's AQ. keys have a dot) is not used: the run goes keyless and the stored record says `key_ignored_malformed`. Stored answers and error messages have the key removed. Requests go through the run's transport (direct, or the CONNECT proxy when DNS fails), limited by `cwv_timeout_ms` and `cwv_max_bytes` in `limits.json`: a PSI Lighthouse run can take longer than a page fetch, and its answer is far larger than the page limit allows for.
|
|
93
|
+
|
|
94
|
+
- CrUX API `records:queryRecord` (POST, `origin` and `formFactor` in the body, p75 at `record.metrics.<name>.percentiles.p75`; a 404 with `chrome ux report data not found` means the origin has no data): https://developer.chrome.com/docs/crux/api and https://developer.chrome.com/docs/crux/guides/crux-api. optbrew asks CrUX for `formFactor: "PHONE"` (valid values `DESKTOP`, `PHONE`, `TABLET`) so that it matches PSI `strategy=mobile`; an origin with no phone data is a CrUX 404 and falls back to PSI.
|
|
95
|
+
- PageSpeed Insights API `runPagespeed` (GET, `url`, `strategy`, `category`, `key`): https://developers.google.com/speed/docs/insights/v5/reference/pagespeedapi/runpagespeed. optbrew reads `originLoadingExperience`. Google does not document how PSI scales the CLS percentile, so optbrew divides it by 100 (`PSI_CLS_PERCENTILE_SCALE`) and trusts that only while the first distribution bucket ends at 10; any other first bucket, or none, makes the CLS unknown.
|
|
96
|
+
|
|
97
|
+
Lab Core Web Vitals (measured by `labCwv` with PerformanceObserver in the page optbrew rendered) are reference only: they are stored in `RunState.lab_cwv`, shown under the run conditions as "reference (not scored)", and never become an observation.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Source analysis tools
|
|
2
|
+
|
|
3
|
+
사이트의 소스 폴더가 있을 때, 고칠 곳을 찾기 전에 소스 분석 도구를 먼저 쓴다. 이 문서는 SKILL.md 2단계(도구 찾기)와 10단계(고치기 전 원인 찾기)가 쓴다.
|
|
4
|
+
|
|
5
|
+
## 도구 찾는 법
|
|
6
|
+
|
|
7
|
+
에이전트가 지금 쓸 수 있는 스킬, MCP, 플러그인 이름 목록에서 아래 이름을 찾는다. 하네스의 설정 파일은 열지 않는다. 설정 파일에는 사용자 비밀값이 있을 수 있고, 위치도 하네스마다 다르다. 이름이 맞는 도구가 하나도 없으면 "없음"이다.
|
|
8
|
+
|
|
9
|
+
| 이름 | 어디서 보이는가 | 쓰임 |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| `graft` | MCP 도구 `graft_find_code`, `graft_find_all`, `graft_trace_calls`, `graft_file_api`, `graft_repo_map` | 기호가 정의된 파일·줄 찾기, 누가 부르는지 따라가기, 파일 하나의 전체 목록 보기 |
|
|
12
|
+
| `understand-anything` | 스킬 `understand`, `understand-chat`, `understand-diff` | 소스 폴더를 지식 그래프로 만들고 질문으로 구조를 묻기 |
|
|
13
|
+
| `graphify` | 스킬 또는 명령 이름 | 소스 폴더를 그래프로 만들어 위치와 연결을 묻는 도구로 알려진 이름. 이 저장소는 동작을 확인하지 않았다. 도구의 자체 안내를 먼저 읽는다 |
|
|
14
|
+
| `rea` | MCP 도구 `inspect_web_page`, `analyze_web_bundle`, `recover_javascript_sources`, `trace_web_source_location` | 소스 폴더가 없고 배포된 쪽·번들만 있을 때, 번들에서 원래 소스 위치를 되짚기 |
|
|
15
|
+
|
|
16
|
+
목록에 없어도 "소스를 색인해 위치와 호출 관계를 묻게 해 주는 도구"라면 같은 방식으로 쓸 수 있다. 이름을 `source_tools`에 그대로 적는다.
|
|
17
|
+
|
|
18
|
+
## 쓰는 법
|
|
19
|
+
|
|
20
|
+
1. 고칠 문제마다 질의를 한 문장으로 정한다. 예: "`<head>` 밖에서 `<meta name=description>`을 만드는 템플릿은 어디인가", "제목을 만드는 코드는 어디인가".
|
|
21
|
+
2. 도구로 질의를 보낸다. 읽기 질의만 한다. 도구가 소스를 바꾸거나 소스를 밖으로 보내는 기능은 쓰지 않는다.
|
|
22
|
+
3. 도구가 가리킨 파일과 줄을 직접 열어 맞는지 확인한다. 도구의 답을 확인 없이 믿지 않는다.
|
|
23
|
+
4. 도구가 설치돼 있지 않으면 설치하지 않는다. 설치는 환경을 바꾸는 일이므로 사용자의 몫이다. 없는 채로 5번으로 간다.
|
|
24
|
+
5. 맞는 도구가 없으면 직접 분석한다(파일 검색, 열어 읽기). 그리고 사용자에게 한 줄 알린다: "소스 분석 도구가 없어 직접 읽어 찾았습니다. 놓친 곳이 있을 수 있습니다."
|
|
25
|
+
|
|
26
|
+
## 기록
|
|
27
|
+
|
|
28
|
+
고친 파일의 `ChangeRecord.analysis`에 이렇게 쓴다.
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"tools": ["graft"],
|
|
33
|
+
"queries": ["<head> 밖에서 meta description을 만드는 템플릿은 어디인가"],
|
|
34
|
+
"self_analysis": false
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
- `tools`: 실제로 쓴 도구 이름. 쓰지 않았으면 빈 배열이다.
|
|
39
|
+
- `queries`: 도구에 보낸 질의 문장. 도구 없이 분석했으면 빈 배열이다.
|
|
40
|
+
- `self_analysis`: 도구 없이 직접 읽어서 원인을 찾았으면 `true`다. 도구로 위치를 찾고 직접 확인만 했으면 `false`다.
|