Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 175 additions & 0 deletions .claude/skills/catalog-intake/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
---
name: catalog-intake
description: >-
Google Forms(식탁보 사이트 추가/수정/삭제 요청) 응답 CSV를 읽어 docs/Catalog.xml 등록을
돕는다. 헤더를 정규화하고, 브라우저(Edge)로 위장해 보안 플러그인 URL 생존/파일명을 확인하고,
공식 로고를 투명 정사각 PNG로 만들고, catalogutil.cs 검증을 거쳐 사람이 검토할 PR 후보를
만든다. "제보 CSV 처리", "폼 응답 등록", "catalog intake" 요청 시 사용.
---

# 카탈로그 제보 인테이크 (catalog-intake)

Google Forms 제보(비-GitHub 사용자용)를 `docs/Catalog.xml` 변경으로 옮기는 반자동 절차.
`docs/SITE_HEALTH_WORKFLOW.md`와 같은 철학을 따른다: **기계는 정규화·검증·후보 생성까지,
최종 판정과 반영(merge)은 사람.**

## ⚠️ 안전 경계선 (먼저 읽기)

- 이 카탈로그의 `<Package>`는 **사일런트 스위치로 무인 설치되는 보안 프로그램**이다.
제보는 익명은 아니지만(폼 로그인 필수) **미검증 외부 입력**이다.
- 따라서 이 스킬은 **PR 후보까지만** 만든다. **자동 commit/merge 금지.** 사람이 검토 후 반영.
- `check_urls.py`·`fetch_logo.py`는 URL을 **읽기만** 한다. 설치 파일을 실행하지 않는다.

## 입력

- 필수: Google Forms 응답을 내보낸 **CSV 경로** (Sheets → 파일 → 다운로드 → CSV).
- 참고 문서: [references/field-mapping.md](references/field-mapping.md) — 필드 매핑·Category·
gap 정의·설치기/스위치 시드표. 처리 규칙의 근거는 전부 여기에 있다.

## 파이프라인

```text
[0] 최신화 (필수) main pull → 작업 브랜치 생성 ← 스킬 시작 전 반드시
[1] normalize (오프라인) CSV → 정규화 JSON + gap 플래그
[2] 게이트 확인 consent/dedup 누락 건은 보류 표시
[3] 항목별 처리 빈칸 4종 채우기(아래 규칙) — URL확인/로고/스위치/분류
[4] 편집 Catalog.xml + 이미지 배치 (변경 유형별 commit 분리)
[5] 검증 catalogutil.cs 스키마 검증 + (선택) checksites.cs probe
[6] PR + 미완 이슈 완전반영은 PR diff / 미완은 추적 이슈 생성 후 PR에 링크. merge는 사람.
```

### 0. 최신화 — 스킬 실행 전 반드시

제보를 처리하기 전에 **로컬 main을 원격 최신으로 맞추고 새 작업 브랜치에서 시작**한다.
stale한 베이스 위에서 편집하면 이미 반영된 항목을 중복 추가하거나 충돌 검사가 어긋난다.

```bash
git switch main
git pull --ff-only origin main # 최신 카탈로그 확보 (fast-forward만)
git switch -c catalog-intake/<날짜-또는-배치명> # 예: catalog-intake/2026-07-forms
```

- `--ff-only`가 실패(로컬에 이탈 커밋 존재)하면 사람에게 알리고 멈춘다 — 임의 merge/rebase 금지.
- 이 최신 main 기준으로 [1] normalize의 `--catalog docs/Catalog.xml` 충돌 검사가 정확해진다.

### 1. normalize — CSV 정규화 (오프라인)

```bash
uv run .claude/skills/catalog-intake/scripts/normalize_csv.py \
<responses.csv> --catalog docs/Catalog.xml --out intake.json --pretty
```

각 레코드에 `change_type`, `display_name_ko/en`, `url`, `category`, `id_suggested`,
`id_collision`, `package_urls[]`, `gaps[]`가 채워진다. **요약의 "매칭 안 된 CSV 열"과
`gaps`를 먼저 확인**하고 처리 계획을 세운다.

### 2. 게이트 확인

`consent-missing` 또는 `dedup-missing`이 있으면 스팸/오제출 의심 → 그 건은 **처리 보류**로
표시하고 사람에게 확인을 요청한다. 임의로 진행하지 않는다.

### 3. 항목별 처리 — 빈칸 4종 채우기

[references/field-mapping.md](references/field-mapping.md) 3~5절을 근거로:

- **`arguments-missing` + `package-name-derive`** — 각 `package_urls`를 확인:
```bash
uv run .claude/skills/catalog-intake/scripts/check_urls.py <url> [<url> ...] --json
```
살아있는지·최종 URL·파일명을 얻는다. 파일명을 시드표(4절)에 대조해 **Package Name**과
**Arguments**를 정한다. 시드표에 없으면 `src/ussf.cs` 또는 설치기 `/?`로 스위치를 조사하고,
넘겨짚지 않는다(특히 TouchEn=`/silence`, IPInside=`/nodlg`).
- `check_urls`가 403/차단이면 JS 게이트일 수 있음 → 무거운 케이스이므로
`dotnet run --file src/checksites.cs -- probe ...`(Playwright Edge)로 폴백.

- **로고 → 투명 정사각 PNG** — CSV의 `icon_ref`는 인증이 필요한 Drive 링크이므로 보통
사이트에서 직접 만든다:
```bash
uv run .claude/skills/catalog-intake/scripts/fetch_logo.py \
<대표 URL> docs/images/<Category>/<Id>.png --size 256 --bg auto
# 이미 로컬로 받은 아이콘이 있으면:
uv run .claude/skills/catalog-intake/scripts/fetch_logo.py \
--from-image <path> docs/images/<Category>/<Id>.png --bg auto
```
플랫 로고가 아니거나 배경이 복잡하면 `--bg rembg`(무거움: `uv run --with rembg …`).
결과가 어색하면 사람에게 공식 로고 교체를 권한다.

- **`category-needs-decision`(증권 등)** — 자동 결정 금지. Other 잠정값 그대로 두고
사람에게 "Other로 둘지 / XSD에 새 enum 추가할지" 결정을 요청.

- **`id-collision`** — 이름을 조정하거나, 사실상 기존 항목 수정이면 `change_type`을
modify로 재해석. 새 Id를 임의로 만들지 말고 사람과 합의.

### 4. 편집 (변경 유형별 commit 분리)

`references/field-mapping.md` 1절 매핑대로 `docs/Catalog.xml`(또는 `docs/sites.xml`)을
편집하고 이미지를 배치한다. commit은 의미 단위로 분리:
`(1) 신규 추가 (2) 수정 (3) 삭제`.

### 5. 검증

```bash
dotnet run --file src/catalogutil.cs -- ./docs/ ./outputs/ # 스키마/리소스 검증
dotnet run --file src/checkimages.cs -- ./docs/Catalog.xml ./docs/images # 이미지 존재 검증
# 새로 추가/수정한 Id만 실제 접속 재검증(선택):
dotnet run --file src/checksites.cs -- probe ./docs/ ./health-report/ --only <Id1,Id2>
```
Error/Warning이 남지 않을 때까지 정리한다.

### 6. PR 후보 제출

**모든 제보 항목은 아래 3가지 중 하나로만 분류한다. "수동 확인 필요"를 이유로 조용히
드롭하지 않는다 — 드롭하면 그 제보는 유실된다.**

| 결과 | 처리 | PR에서 |
|---|---|---|
| **완전 반영** | 검증까지 끝나 Catalog.xml에 반영 | diff에 포함 |
| **미완(수동 검토 필요)** | 자동으로 확정 못 한 항목 | **제외 금지.** PR 본문에 남기고 + **추적 이슈를 반드시 생성해 링크** |
| **중복/무효로 제외** | 기존 항목과 중복이거나 정크인 것 | 본문에 사유만 기록(진짜 제외) |

- 미완과 제외는 **다르다**: "확정 못 함"(미완)을 "제외"로 처리하면 안 된다. 제외는 중복·정크에만.

**미완 항목마다 추적 이슈를 생성한다**(유실 방지의 핵심 — PR 본문만으론 merge 후 사라진다):

```bash
gh issue create \
--title "[제보 미완] <이름> (<도메인>) — <막힌 지점 요약>" \
--body-file <이슈본문.md> \
--label "help wanted" --label "enhancement"
# 이슈 본문에 담을 것: 제보 요약 / 자동으로 확인한 것 / 막힌 지점 / 사람이 해야 할 일 / 관련 PR #<n>
```

- 이슈 본문은 **자기완결**로(site-health `issue` 단계와 동일 철학) — 후속 처리에 필요한 컨텍스트를 모두 담아 PR/대화 없이도 처리 가능하게.
- 생성한 이슈 번호를 PR 본문의 해당 미완 항목 옆에 `추적: #<n>`으로 링크한다.
- PR 본문 구성: 변경 요약 · 완전 반영 목록 · **미완(수동 검토 필요) + 추적 이슈 링크** · 중복 제외 목록 · 검증 결과.
- 완전 반영 항목이 기존 제보 이슈를 닫아야 하면 `Closes #<n>`로 연결.
- **여기서 멈춘다.** merge는 사람이 한다.

## 자동 vs 사람 승인 경계 (site-health와 동일)

| 작업 | 자동 | 사람 승인 |
|---|:---:|:---:|
| CSV 정규화 / URL 생존 확인 / 로고 생성 | ✓ | |
| 같은 도메인 내 URL 한 줄 갱신 | ✓ | |
| 새 `Service` 추가 · Id 신설 | | ✓ |
| `Service` 삭제 | | ✓ |
| 증권 등 Category 결정 · XSD 변경 | | ✓ |
| 사일런트 스위치가 시드표에 없어 조사한 값 | | ✓ |
| 게이트(consent/dedup) 누락 건 반영 | | ✓ |
| commit / merge | | ✓ |

## 스크립트 요약

| 스크립트 | 런타임 | 역할 | 네트워크 |
|---|---|---|:---:|
| `normalize_csv.py` | uv(무의존) | CSV→정규화 JSON·gap 플래그·Id 파생·충돌검사 | ✗ |
| `check_urls.py` | uv(httpx) | Edge 위장 URL 생존/파일명 확인 | ✓ |
| `fetch_logo.py` | uv(pillow/numpy/bs4) | 로고 수집→배경 투명화→정사각 PNG | ✓* |

`*` `--from-image`는 네트워크 불필요. UA 프로파일은 `src/checksites.cs`의 Edge 131과 동일.
87 changes: 87 additions & 0 deletions .claude/skills/catalog-intake/references/field-mapping.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# 제보 폼 → 카탈로그 스키마 매핑 참조

이 문서는 Google Forms **"식탁보 사이트 추가/수정/삭제 요청"** 응답을 `docs/Catalog.xml`
스키마로 옮길 때 쓰는 매핑·규칙·시드 데이터를 담는다. `catalog-intake` 스킬과
`scripts/normalize_csv.py` 가 이 표를 기준으로 동작한다.

## 1. 필드 매핑

| 폼 질문(키워드) | 논리 필드 | 카탈로그 대상 | 비고 |
|---|---|---|---|
| 타임스탬프 | `timestamp` | (이슈 메타) | Google 자동 |
| 이메일 주소 | `reporter_email` | (이슈 메타) | 로그인 필수 → verified |
| 어떤 유형의 변경 사항 | `change_type` | 처리 분기 | 추가/수정/삭제 → add/modify/delete |
| 웹 사이트의 한국어 이름 | `display_name_ko` | `Service@DisplayName` | |
| 웹 사이트의 영어 이름 | `display_name_en` | `Service@en-US-DisplayName` | Id 파생 소스 |
| 웹 사이트의 대표 주소 | `url` | `Service@Url` | |
| 웹 사이트의 아이콘 | `icon_ref` | `docs/images/<Category>/<Id>.png` | CSV엔 Drive 링크(인증 필요) |
| 웹 사이트의 종류 | `category_ko` → `category` | `Service@Category` | 아래 2절 |
| 보안 플러그인 …URL(한 줄씩) | `package_urls[]` | `<Package Url=…>` | Name·Arguments는 미수집 |
| …Windows Sandbox 창이 닫히나요 | `compat_notes` | `CompatNotes` | AST 관련은 생략 가능 |
| 플러그인 사용 메뉴/위치 | `usage_notes` | (판단용 메타) | 개별 사이트 vs 다른 형태 판단 |
| 개인 정보 수집 동의 | `consent` | 게이트 | 비면 `consent-missing` |
| 없거나 반영되지…먼저 확인 | `dedup_ack` | 게이트 | 비면 `dedup-missing` |

> 헤더는 **부분 문자열 키워드**로 매칭하므로 폼 문구가 조금 바뀌어도 견딘다.
> 매칭 안 된 열은 normalize 요약의 "매칭 안 된 CSV 열"에 출력된다 — 무시하지 말 것.

## 2. Category 매핑 (한국어 종류 → XSD enum)

XSD `CatalogInternetServiceCategory` enum:
`Other, Banking, Financing, Security, Insurance, CreditCard, Government, Education`

| 폼 선택지 | enum | 상태 |
|---|---|---|
| 은행 | `Banking` | 1:1 |
| 카드 | `CreditCard` | 1:1 |
| 공공기관 | `Government` | 1:1 |
| 금융 | `Financing` | 1:1 |
| 보험 | `Insurance` | 1:1 |
| 교육 | `Education` | 1:1 |
| **증권** | `Financing`(잠정) | ⚠️ 대응 enum 없음. **운영자 결정(2026-07): 잠정적으로 `Financing`**. 향후 XSD에 `Securities` 추가 시 재검토 |
| 기타 | `Other` | 1:1 |

> enum에는 있지만 폼엔 없는 `Security`(보안)는 제보로 들어올 일이 거의 없다.
> 증권은 이제 결정된 매핑이므로 `category-needs-decision` 플래그를 남기지 않는다.

## 3. 기계가 못 채우는 빈칸(gaps) — 스킬이 반드시 개입

| gap 플래그 | 의미 | 해결 방법 |
|---|---|---|
| `arguments-missing` | 폼이 사일런트 스위치를 안 받음 | 4절 시드표 → 없으면 `src/ussf.cs` 또는 설치기 `/?`·`/help` |
| `package-name-derive` | Package Name 미수집(URL만) | 4절 시드표 + `check_urls.py` 파일명 |
| `category-needs-decision` | 증권 등 enum 없는 종류 | 사람 결정 |
| `id-collision` | 기존 Service/Companion Id와 충돌 | 이름 조정 or 기존 항목 수정으로 처리 |
| `consent-missing` / `dedup-missing` | 게이트 미동의 | 스팸/오제출 의심 → 처리 보류 후 확인 |

## 4. 설치 프로그램 시드표 (Catalog.xml 실측 빈도 기반)

URL 파일명 → **Package Name** 및 **Arguments(사일런트 스위치)** 추론용. 신규 URL이
아래 파일명 패턴과 맞으면 그대로 쓰고, 아니면 조사한다. (스위치는 벤더마다 다르니
`/silent`로 넘겨짚지 말 것 — 특히 TouchEn 계열은 `/silence`, IPInside는 `/nodlg`.)

| 파일명 패턴(예) | Package Name | Arguments |
|---|---|---|
| `veraport-g3*`, `veraport*` | `Veraport` | `/silent` |
| `nos_setup*` | `nProtect Online Security` | `/silent` |
| `astx_setup*`, `astxdn*` | `AhnLabSafeTx` | `/silent` |
| `delfino-g3*`, `delfino*` | `Delfino G3` | `/silent` |
| `AnySign_Installer*`, `AnySign*` | `AnySign` | `/S` |
| `TouchEn_nxKey*32*` | `TouchEnKey32` | `/silence` |
| `TouchEn_nxKey*64*` | `TouchEnKey64` | `/silence` |
| `INIS_EX*`, `INISAFE*CrossWeb*` | `INISAFECrossWeb` | `/S` |
| `I3GSvcManager*`, `IPinside*` | `IPInside` | `/nodlg` |
| `ePageSafer*`, `ePageSAFER*` | `ePageSafer` | `/silent` |
| `UniSign*` | `UniSign` | `/silent` |
| `iSASService*` | `iSASService` | `/silent` |
| `MagicLine4NX*` | `MagicLine4NX` | `/silent` |
| `SecuKit*` | `SecuKitNXS` | `/silent` |

> 이 표는 힌트다. 최종 확인은 실제 설치기 동작(무인 설치 테스트) 기준.
> 새 벤더/버전이면 `src/ussf.cs`로 스위치를 탐색하고 표를 갱신하는 것을 권장.

## 5. Id 파생 규칙

영어 이름 → PascalCase. 이미 대문자 2자 이상인 토큰(약어: KB, KEB, NH)은 원형 보존.
예: `Woori Bank`→`WooriBank`, `KB Kookmin Bank`→`KBKookminBank`.
파생 후 **반드시** 기존 `Service/Companion Id`와 충돌 검사(`normalize_csv.py --catalog`).
Loading