Compare commits

..

519 commits

Author SHA1 Message Date
Jeffrey (Dongkyu) Kim
08533bd9eb
Merge pull request #325 from NomaDamas/dev
Merge dev into main
2026-06-21 17:39:44 +09:00
Jeffrey (Dongkyu) Kim
eacdfb882a
Merge pull request #323 from jwb0501/feature/talent-search-skills
잡코리아·사람인 인재검색 스킬 추가
2026-06-19 14:38:47 +09:00
Jeffrey (Dongkyu) Kim
b14f65361f Wire JobKorea talent helper into CI 2026-06-18 10:37:08 +09:00
wbjung
caa1f0fd0d Fix JobKorea fallback row parsing 2026-06-18 00:04:07 +09:00
wbjung
c619d3b7c7 Add recruiting talent search skills 2026-06-17 23:14:54 +09:00
Jeffrey (Dongkyu) Kim
e735abe8a4
Merge PR #322: replace KakaoTalk skill with katok
Feature/#320
2026-06-17 16:52:39 +09:00
Jeffrey (Dongkyu) Kim
c3f44eef14 feat(kakaotalk-mac): replace kakaocli workflow with katok
Closes #320

Plan: .omo/plans/issue-320-katok-skill.md
2026-06-16 13:44:56 +09:00
Jeffrey (Dongkyu) Kim
1f186af480
Merge pull request #319 from NomaDamas/dev
Release dev to main
2026-06-15 13:12:02 +09:00
Jeffrey (Dongkyu) Kim
5fd58facf4
Merge pull request #318 from NomaDamas/changeset-release/main
chore: version packages
2026-06-15 13:11:49 +09:00
Jeffrey (Dongkyu) Kim
e0d842435b Merge main into dev for PR 319 2026-06-14 18:11:45 +09:00
Jeffrey (Dongkyu) Kim
ece355b807 fix(korean-humanizer): quote frontmatter description
Quote the long Korean description so the `예:` segment is parsed as a scalar
instead of an invalid YAML mapping key. Also ignore local `.gjc/` runtime state.

Verified with scripts/validate-skills.sh and downstream benchmark eligibility
classification.
2026-06-13 00:21:13 +09:00
Jeffrey (Dongkyu) Kim
a633b001be
feat: add business due-diligence skills
Reviewed, resolved conflicts with latest dev, applied follow-up fixes, and verified with npm run ci.
2026-06-12 19:35:14 +09:00
Jeffrey (Dongkyu) Kim
c8bb7f9f35 Merge dev and address PR review fixes 2026-06-12 19:34:20 +09:00
github-actions[bot]
7586c0dea8 chore: version packages 2026-06-12 10:07:08 +00:00
Jeffrey (Dongkyu) Kim
66f12cb43d
dev → main: srt-booking 좌석 탐색, korean-humanizer 신규 스킬, toss-securities 공식 OpenAPI 클라이언트, korean-law k-skill-proxy 편입 (#314)
* feat(srt-booking): SRT 좌석 확인과 탐색 우선순위 개선 (#305)

* feat(srt): 좌석 조회와 탐색 우선순위 추가

SRT search 결과의 stable train_id로 객차별 좌석을 조회하고, 특정 호차/좌석 확인과 탐색 우선순위 옵션을 제공한다.

Constraint: SRT와 KTX는 별도 upstream 표면이므로 SRT HTML 파서와 테스트를 분리함
Rejected: KTX 좌석 helper 공유 | Korail API와 SRT 웹 좌석선택 HTML 계약이 달라 혼용하면 파서 안정성이 낮아짐
Confidence: medium
Scope-risk: moderate
Directive: SRT 좌석선택 HTML에서 노출되지 않는 속성은 추정하지 말고 명시적으로 처리할 것
Tested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_srt_booking scripts.test_ktx_booking; python3 -m py_compile scripts/srt_booking.py scripts/srt_seats.py scripts/test_srt_booking.py
Not-tested: 실제 예약 API에 우선순위 좌석 선택을 연결하는 흐름

* fix(srt): 좌석 조회 JSON 출력 안정화

SRT 대기열 메시지가 stdout에 섞여 seats JSON을 깨는 실제 표면 문제를 막고, 누락된 좌석 방향/위치 속성을 unknown으로 정규화한다.

Constraint: issue #303 범위는 예약 부작용이 없는 좌석 조회 보조 흐름으로 제한됨
Rejected: 실제 예약 subcommand 추가 | 좌석 선점/예약은 외부 부작용이라 이번 acceptance criteria에 포함되지 않음
Confidence: high
Scope-risk: narrow
Directive: SRTrain upstream 출력이 추가되더라도 helper stdout은 JSON 전용으로 유지할 것
Tested: RED→GREEN in .omo/ulw-loop/evidence/srt-c002-red-green-tests.txt; live SRT tmux QA in .omo/ulw-loop/evidence/srt-c001-live-search-seats.txt; npm run ci in .omo/ulw-loop/evidence/srt-c003-regression-ci.txt
Not-tested: 실제 예약/결제/취소 부작용 흐름

* test(srt): split seat helper regression coverage

---------

Co-authored-by: Jeffrey (Dongkyu) Kim <vkehfdl1@gmail.com>

* feat: add korean-humanizer skill

AI가 쓴 티가 나는 한국어 글을 자연스러운 사람 글로 고치는 프롬프트 기반 스킬.
blader/humanizer의 구조·방법론(패턴 카탈로그 + draft→audit→final 루프 +
false positive 가이드)을 한국어에 맞게 재구성했다.

- 한국어 특화 33개 패턴: 번역체(직역 조사·무생물 주어·"~들"·"가지다"·이중피동·
  명사화), AI 상투어, 3의 법칙, 과장된 의의 부여, 마무리 상투구, 챗봇 잔재,
  줄표·가운뎃점·곡선따옴표 등
- Triage(최소 개입) 원칙: 서식만 문제면 산문은 그대로 두어 과교정 방지
- Length control: 목표 글자수 지정 시 ±5% 내로 맞추고 공백 포함/제외 수치 보고,
  korean-character-count 스킬과 연동

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(korean-humanizer): rebuild v2 on im-not-ai framework

Build on happy-nut's PR #311 korean-humanizer skill (cherry-picked,
authorship preserved) by re-centering it on the epoko77-ai/im-not-ai
(Humanize KR, MIT) methodology:

- 4대 철칙 (의미 불변 · 근거 기반 · 장르 유지 · 과윤문 금지 30%/50% 가드)
- S1/S2/S3 severity tiers and A~D quality grades
- A~J taxonomy with Korean-specific patterns (A-16 그/그녀 강박,
  A-18 관계절 좌향 수식, A-19 이중 조사, C-11 연결어미 뒤 쉼표, E-7 경어법)
- detect -> rewrite -> audit -> grade loop with self-check checklist
- references/ai-tell-taxonomy.md full A~J table
- docs/features/korean-humanizer.md crediting im-not-ai and happy-nut
- README row + link, regenerated plugin.json, docs regression test

Co-authored-by: happy-nut <happynut.dev@gmail.com>

* docs(korean-law-search): document official precedent API evidence (#313)

Enhance the existing korean-law-search skill and feature doc with the
official 법제처 Open API precedent endpoints and detail retrieval, without
adding a new skill, package, workspace, or changeset.

- Document 판례 목록 조회 (lawSearch.do?target=prec) and 판례 본문 조회
  (lawService.do?target=prec&ID=...) as official evidence behind the
  korean-law-mcp search_precedents/get_precedent_text path.
- Add supported precedent filters (query, court, case number, source
  name, date, sort) and precedent-specific failure modes (missing LAW_OC,
  upstream unavailable/rate-limit/timeout, empty results, body
  unavailable for some sources) plus the legal-advice boundary.
- Keep korean-law-mcp first and Beopmang as the only post-failure
  fallback; lawService.do?target=prec is official detail retrieval, not a
  Beopmang-style fallback.
- Extend the skill-docs regression test with stable endpoint/tool
  literals and concept-level filter/failure-mode/legal-boundary checks.

Closes #308

* feat(toss-securities): add official read-only OpenAPI client (#312)

Add an official Toss Securities Open API client alongside the existing
unofficial tossctl wrapper. The package ships read-only helpers backed by
the official REST API (https://openapi.tossinvest.com): OAuth2
client_credentials token issuance with an in-memory token cache, bearer +
X-Tossinvest-Account header handling, TossApiError/TossCredentialsError
with secret/token redaction, and 429 Retry-After/backoff retry.

Credentials are read from TOSSINVEST_CLIENT_ID/TOSSINVEST_CLIENT_SECRET
(optional TOSSINVEST_ACCOUNT/TOSSINVEST_API_BASE_URL) and sent directly to
Toss, never through a shared proxy. Order mutation remains out of scope;
the tossctl path is retained as a documented fallback.

Closes #306

* Revert "docs(korean-law-search): document official precedent API evidence (#313)"

This reverts commit 5faec8bb2a.

* feat(k-skill-proxy): fold Korean law lookups into k-skill-proxy, drop Beopmang (#315)

Add hosted korean-law proxy routes and make the korean-law-search skill
proxy-first, removing the unstable Beopmang fallback from the support list.

- proxy: new src/korean-law.js wrapping official 법제처 DRF lawSearch.do /
  lawService.do, injecting LAW_OC + browser User-Agent/Referer (the real
  cause of "사용자 정보 검증 실패") and retrying empty/HTML responses.
- proxy: /v1/korean-law/search and /v1/korean-law/detail routes + lawOc
  config + koreanLawConfigured health flag; 17 module + 6 route tests.
- skill/docs: korean-law-search becomes proxy-first (no per-user LAW_OC,
  no local CLI). Drop Beopmang everywhere; credit chrisryugj/korean-law-mcp
  as design reference and 법제처 open.law.go.kr as official source.
- ops: LAW_OC added to deploy doc KEYS, secret accessor loop, and the
  Cloud Run deploy workflow set-secrets.
- changeset: k-skill-proxy minor.

---------

Co-authored-by: iamiks <rmstjr1030@naver.com>
Co-authored-by: happy-nut <happynut.dev@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 19:06:18 +09:00
Jeffrey (Dongkyu) Kim
f485591ac2
feat(k-skill-proxy): fold Korean law lookups into k-skill-proxy, drop Beopmang (#315)
Add hosted korean-law proxy routes and make the korean-law-search skill
proxy-first, removing the unstable Beopmang fallback from the support list.

- proxy: new src/korean-law.js wrapping official 법제처 DRF lawSearch.do /
  lawService.do, injecting LAW_OC + browser User-Agent/Referer (the real
  cause of "사용자 정보 검증 실패") and retrying empty/HTML responses.
- proxy: /v1/korean-law/search and /v1/korean-law/detail routes + lawOc
  config + koreanLawConfigured health flag; 17 module + 6 route tests.
- skill/docs: korean-law-search becomes proxy-first (no per-user LAW_OC,
  no local CLI). Drop Beopmang everywhere; credit chrisryugj/korean-law-mcp
  as design reference and 법제처 open.law.go.kr as official source.
- ops: LAW_OC added to deploy doc KEYS, secret accessor loop, and the
  Cloud Run deploy workflow set-secrets.
- changeset: k-skill-proxy minor.
2026-06-12 18:07:03 +09:00
John
440cd697a7 feat: 사업자 실사 스킬군 — 단품 5종 + 복합 1종, proxy route 3개 추가 (#316)
사업자등록번호로 "이 사업자 실제 문제 없나"를 무료 공공 데이터로 교차 조회하는
스킬군을 기여한다. 점수·등급·"위험" 라벨 없이 사실+출처+조회시각만 병렬한다.

단품 스킬:
- national-pension-workplace  국민연금 가입 사업장 (proxy, 3046071)
- nts-tax-delinquency         국세 체납 명단공개 (무인증 직접)
- fsc-corporate-info          금융위 기업기본정보 (proxy, 15043184)
- g2b-sanctioned-supplier     조달청 부정당제재 (proxy, 15129466)
- localdata-business-status   지방행정 인허가 영업상태 208업종 (무인증 직접)

복합 스킬:
- biz-health-check  위 5종 + 기존 nts-business-registration을 한 번에 호출

proxy(packages/k-skill-proxy):
- keyed route 3개 추가 — 키는 서버의 DATA_GO_KR_API_KEY로만 주입(사용자 시크릿 없음)
- 연금 route는 basic+detail+monthly 3콜 오케스트레이션 + 월별중복 dedup
- server.test.js에 route 테스트 10건 추가 (정상/503 미설정/400/403 forbidden)

무인증 스킬은 stdlib(urllib)만 사용해 의존성 없이 직접 호출한다.
문서: docs/features ×6, README 표·링크, docs/sources.md 갱신, plugin.json 재생성.

활용신청(프록시 운영 서버 등록 필요): 3046071·15043184·15129466
(15081808 국세청 상태조회는 nts-business-registration용으로 이미 등록, 키 공유).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 16:46:30 +09:00
Jeffrey (Dongkyu) Kim
b6200892e3 Revert "docs(korean-law-search): document official precedent API evidence (#313)"
This reverts commit 5faec8bb2a.
2026-06-11 11:45:04 +09:00
Jeffrey (Dongkyu) Kim
79f6038328
feat(toss-securities): add official read-only OpenAPI client (#312)
Add an official Toss Securities Open API client alongside the existing
unofficial tossctl wrapper. The package ships read-only helpers backed by
the official REST API (https://openapi.tossinvest.com): OAuth2
client_credentials token issuance with an in-memory token cache, bearer +
X-Tossinvest-Account header handling, TossApiError/TossCredentialsError
with secret/token redaction, and 429 Retry-After/backoff retry.

Credentials are read from TOSSINVEST_CLIENT_ID/TOSSINVEST_CLIENT_SECRET
(optional TOSSINVEST_ACCOUNT/TOSSINVEST_API_BASE_URL) and sent directly to
Toss, never through a shared proxy. Order mutation remains out of scope;
the tossctl path is retained as a documented fallback.

Closes #306
2026-06-10 22:50:47 +09:00
Jeffrey (Dongkyu) Kim
5faec8bb2a
docs(korean-law-search): document official precedent API evidence (#313)
Enhance the existing korean-law-search skill and feature doc with the
official 법제처 Open API precedent endpoints and detail retrieval, without
adding a new skill, package, workspace, or changeset.

- Document 판례 목록 조회 (lawSearch.do?target=prec) and 판례 본문 조회
  (lawService.do?target=prec&ID=...) as official evidence behind the
  korean-law-mcp search_precedents/get_precedent_text path.
- Add supported precedent filters (query, court, case number, source
  name, date, sort) and precedent-specific failure modes (missing LAW_OC,
  upstream unavailable/rate-limit/timeout, empty results, body
  unavailable for some sources) plus the legal-advice boundary.
- Keep korean-law-mcp first and Beopmang as the only post-failure
  fallback; lawService.do?target=prec is official detail retrieval, not a
  Beopmang-style fallback.
- Extend the skill-docs regression test with stable endpoint/tool
  literals and concept-level filter/failure-mode/legal-boundary checks.

Closes #308
2026-06-10 22:50:34 +09:00
Jeffrey (Dongkyu) Kim
e25f8dd9ab feat(korean-humanizer): rebuild v2 on im-not-ai framework
Build on happy-nut's PR #311 korean-humanizer skill (cherry-picked,
authorship preserved) by re-centering it on the epoko77-ai/im-not-ai
(Humanize KR, MIT) methodology:

- 4대 철칙 (의미 불변 · 근거 기반 · 장르 유지 · 과윤문 금지 30%/50% 가드)
- S1/S2/S3 severity tiers and A~D quality grades
- A~J taxonomy with Korean-specific patterns (A-16 그/그녀 강박,
  A-18 관계절 좌향 수식, A-19 이중 조사, C-11 연결어미 뒤 쉼표, E-7 경어법)
- detect -> rewrite -> audit -> grade loop with self-check checklist
- references/ai-tell-taxonomy.md full A~J table
- docs/features/korean-humanizer.md crediting im-not-ai and happy-nut
- README row + link, regenerated plugin.json, docs regression test

Co-authored-by: happy-nut <happynut.dev@gmail.com>
2026-06-10 14:32:44 +09:00
happy-nut
a8a71eed11 feat: add korean-humanizer skill
AI가 쓴 티가 나는 한국어 글을 자연스러운 사람 글로 고치는 프롬프트 기반 스킬.
blader/humanizer의 구조·방법론(패턴 카탈로그 + draft→audit→final 루프 +
false positive 가이드)을 한국어에 맞게 재구성했다.

- 한국어 특화 33개 패턴: 번역체(직역 조사·무생물 주어·"~들"·"가지다"·이중피동·
  명사화), AI 상투어, 3의 법칙, 과장된 의의 부여, 마무리 상투구, 챗봇 잔재,
  줄표·가운뎃점·곡선따옴표 등
- Triage(최소 개입) 원칙: 서식만 문제면 산문은 그대로 두어 과교정 방지
- Length control: 목표 글자수 지정 시 ±5% 내로 맞추고 공백 포함/제외 수치 보고,
  korean-character-count 스킬과 연동

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 14:22:54 +09:00
iamiks
52dbfee064
feat(srt-booking): SRT 좌석 확인과 탐색 우선순위 개선 (#305)
* feat(srt): 좌석 조회와 탐색 우선순위 추가

SRT search 결과의 stable train_id로 객차별 좌석을 조회하고, 특정 호차/좌석 확인과 탐색 우선순위 옵션을 제공한다.

Constraint: SRT와 KTX는 별도 upstream 표면이므로 SRT HTML 파서와 테스트를 분리함
Rejected: KTX 좌석 helper 공유 | Korail API와 SRT 웹 좌석선택 HTML 계약이 달라 혼용하면 파서 안정성이 낮아짐
Confidence: medium
Scope-risk: moderate
Directive: SRT 좌석선택 HTML에서 노출되지 않는 속성은 추정하지 말고 명시적으로 처리할 것
Tested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_srt_booking scripts.test_ktx_booking; python3 -m py_compile scripts/srt_booking.py scripts/srt_seats.py scripts/test_srt_booking.py
Not-tested: 실제 예약 API에 우선순위 좌석 선택을 연결하는 흐름

* fix(srt): 좌석 조회 JSON 출력 안정화

SRT 대기열 메시지가 stdout에 섞여 seats JSON을 깨는 실제 표면 문제를 막고, 누락된 좌석 방향/위치 속성을 unknown으로 정규화한다.

Constraint: issue #303 범위는 예약 부작용이 없는 좌석 조회 보조 흐름으로 제한됨
Rejected: 실제 예약 subcommand 추가 | 좌석 선점/예약은 외부 부작용이라 이번 acceptance criteria에 포함되지 않음
Confidence: high
Scope-risk: narrow
Directive: SRTrain upstream 출력이 추가되더라도 helper stdout은 JSON 전용으로 유지할 것
Tested: RED→GREEN in .omo/ulw-loop/evidence/srt-c002-red-green-tests.txt; live SRT tmux QA in .omo/ulw-loop/evidence/srt-c001-live-search-seats.txt; npm run ci in .omo/ulw-loop/evidence/srt-c003-regression-ci.txt
Not-tested: 실제 예약/결제/취소 부작용 흐름

* test(srt): split seat helper regression coverage

---------

Co-authored-by: Jeffrey (Dongkyu) Kim <vkehfdl1@gmail.com>
2026-06-09 10:57:54 +09:00
Jeffrey (Dongkyu) Kim
f5d37ddbee
Merge pull request #309 from NomaDamas/changeset-release/main
chore: version packages
2026-06-09 10:55:22 +09:00
github-actions[bot]
819be4897a chore: version packages 2026-06-06 03:08:25 +00:00
Jeffrey (Dongkyu) Kim
1efef285ba
Merge pull request #302 from NomaDamas/dev
Release: dev → main
2026-06-06 12:07:27 +09:00
Jeffrey (Dongkyu) Kim
acc66861ea Merge remote-tracking branch 'origin/main' into dev
# Conflicts:
#	.changeset/issue-268-naver-map-route.md
2026-06-05 22:49:47 +09:00
Jeffrey (Dongkyu) Kim
bbba283151 Archive unsupported map skills 2026-06-05 22:24:15 +09:00
Jeffrey (Dongkyu) Kim
46f44ed724 chore: remove startup-support skill 2026-06-05 17:21:58 +09:00
Jeffrey (Dongkyu) Kim
cc64d66d56 test: isolate python test dependencies 2026-06-05 17:03:27 +09:00
Jeffrey (Dongkyu) Kim
7c1a4530cf test: remove runtime pip install from root ci 2026-06-05 17:00:02 +09:00
Jeffrey (Dongkyu) Kim
346ce7f516 fix(startup-support): use K-Startup proxy surface 2026-06-05 13:03:28 +09:00
Jeffrey (Dongkyu) Kim
e336f7898c chore: resolve integration diagnostics 2026-05-31 17:28:59 +09:00
Jeffrey (Dongkyu) Kim
352876f915 chore(plugin): include startup-support in manifest 2026-05-31 17:26:08 +09:00
Jeffrey (Dongkyu) Kim
581b0344ce Merge commit 'c27a3e9' into ulw-merge-seven-prs
# Conflicts:
#	package.json
2026-05-31 17:25:22 +09:00
Jeffrey (Dongkyu) Kim
cc768f4031 Merge commit 'cff6b29' into ulw-merge-seven-prs 2026-05-31 17:24:25 +09:00
Jeffrey (Dongkyu) Kim
51388a539e Merge commit 'b6b0c70' into ulw-merge-seven-prs
# Conflicts:
#	scripts/validate-skills.sh
2026-05-31 17:24:25 +09:00
Jeffrey (Dongkyu) Kim
fe08b8e068 Merge remote-tracking branch 'origin/pr/293' into ulw-merge-seven-prs 2026-05-31 17:24:05 +09:00
Jeffrey (Dongkyu) Kim
e76c125014 Merge remote-tracking branch 'origin/pr/295' into ulw-merge-seven-prs 2026-05-31 17:24:04 +09:00
Jeffrey (Dongkyu) Kim
a490fcb0c0 Merge remote-tracking branch 'origin/pr/296' into ulw-merge-seven-prs 2026-05-31 17:24:04 +09:00
Jeffrey (Dongkyu) Kim
9dc3577742 Merge remote-tracking branch 'origin/pr/298' into ulw-merge-seven-prs 2026-05-31 17:24:04 +09:00
Jeffrey (Dongkyu) Kim
c27a3e9151 fix(foresttrip-vacancy): preserve category-specific vacancy rows 2026-05-31 17:23:57 +09:00
Jeffrey (Dongkyu) Kim
cff6b29ff9 fix(startup-support): remove fabricated detail data 2026-05-31 17:23:57 +09:00
Jeffrey (Dongkyu) Kim
b6b0c70091 fix(plugin): satisfy Claude marketplace manifest schema 2026-05-31 17:23:57 +09:00
Donghun Seol
edb2892dda test(foresttrip-vacancy): harden availability output coverage 2026-05-29 21:05:31 +09:00
Donghun Seol
b2404e99be test(foresttrip-vacancy): add print_text regression coverage
라이브 불변식 검증(예비 제외·dedup·useDt 범위)이 통과한 뒤,
사용자 대면 출력 함수인 print_text가 테스트 0개였던 갭을 메운다.

- PrintTextTest 2개: 결과 렌더링 / 빈 결과 메시지
- stub_fetch는 fetch_one의 미사용 인자를 **_ 로 흡수해 lint 경고 제거

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 20:57:25 +09:00
Donghun Seol
00a6d9feae fix(foresttrip-vacancy): exclude 예비 rooms, gate useDt strictly, dedup duplicate rooms
월별예약조회 API가 srchDate 단일 일자 요청에도 5일 윈도우를 반환하고,
"예비"로 표기된 운영자 보유분이 raw 응답에 포함되며, 같은 객실이 다른
goodsId로 중복 표시되는 세 가지 문제를 한꺼번에 fix한다.

수정:
- collect_results 안에 strict useDt gate 추가 (today~last_day 범위 밖 행 차단)
- is_reserve_room() helper로 goodsNm에 "예비" 포함 객실 제외
- (forest_id, use_dt, name) 단위 dedup으로 중복 행 제거
- is_available()는 시그니처/로직 변경 없이 booking-state predicate 유지

추가:
- foresttrip-vacancy/tests/ 18개 단위 테스트 (mock + fixture 기반)
- IsReserveRoomTest, IsAvailableTest, CollectResultsFilterTest,
  StrictUseDtGateTest, GroundTruthTest 다섯 클래스
- 거제·구재봉 fixture로 사용자 라이브 검증 결과 회귀 보호
- package.json lint·test 스크립트에 등록

문서:
- SKILL.md: API 5일 윈도우/예비 객실/중복 dedup 자동 처리 명시 + 회복 시나리오 보강
- docs/features/foresttrip-vacancy.md: 기본 흐름 6단계와 주의할 점 보강

사용자 라이브 검증 ground truth (2026-05-12 기준):
- 거제자연휴양림 5/13 ~9개, 5/16 0개, 5/17 19개, 5/23 0개, 5/24 0개
- 구재봉자연휴양림 5/16 1개 (206호 쑥부쟁이방)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 20:57:25 +09:00
Jeffrey (Dongkyu) Kim
3d8008f2f2 Harden KTX seat detail trust boundaries
Fail closed when Korail reports remaining seats but returns malformed, sentinel-only, or schema-incomplete detail rows, and keep the seat research endpoints inside the same Dynapath/Sid request boundary as other protected mobile calls.\n\nConstraint: PR #298 review required TDD regressions for false empty-seat success and raw external payload failures.\nRejected: Preserve lenient normalization of partial seat rows | it can mislead automation with authoritative empty results.\nConfidence: high\nScope-risk: narrow\nDirective: Keep KTX seat detail parsing fail-closed unless live Korail evidence proves a specific empty or partial shape is valid.\nTested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_ktx_booking; node --test scripts/skill-docs.test.js; npm run typecheck; python3 -m compileall -q scripts/ktx_booking.py scripts/test_ktx_booking.py; ruff check scripts/ktx_booking.py scripts/test_ktx_booking.py; shellcheck scripts/validate-skills.sh; bash scripts/validate-skills.sh; PYENV_VERSION=3.11.9 npm run ci\nNot-tested: live Korail seat-detail network flow with production credentials
2026-05-29 01:55:52 +09:00
Jeffrey (Dongkyu) Kim
f8401ef405 Fail clearly at KTX seat payload boundaries
Boundary validation now distinguishes unavailable/malformed Korail car and seat-detail payloads from valid zero-seat results, so automation does not consume false successful empty-seat JSON.\n\nConstraint: PR #298 follow-up for issue #293 required TDD, clear errors for malformed details/metadata, and branch feature/#293.\nRejected: Per-car partial error payloads | broader interface change than the existing fail-fast command contract.\nRejected: Missing Korail car counts defaulting to zero | masks unknown external summary data as authoritative no-seat state.\nConfidence: high\nScope-risk: narrow\nDirective: Keep seat-detail and car-summary schema checks at the command boundary before normalizing rows.\nTested: Targeted malformed payload probe; PYTHONPATH=.:scripts python3 -m unittest scripts.test_ktx_booking; node --test scripts/skill-docs.test.js; npm run typecheck; python3 -m compileall -q scripts/ktx_booking.py scripts/test_ktx_booking.py; ruff check scripts/ktx_booking.py scripts/test_ktx_booking.py; shellcheck scripts/validate-skills.sh; bash scripts/validate-skills.sh; PYENV_VERSION=3.11.9 npm run ci.\nNot-tested: Live Korail seat-detail endpoints requiring user credentials.
2026-05-29 01:41:33 +09:00
Jeffrey (Dongkyu) Kim
1d4b2eb723 Keep KTX seat lookup typing from obscuring review signals
The seat inspection follow-up already fixed the runtime schema issues, but review still had changed-area pyright noise around the dynamic Korail boundaries. Route those calls through explicit narrow dynamic seams so the remaining pyright output stays confined to the existing baseline.\n\nConstraint: PR #296 follow-up must preserve the existing korail2 dynamic runtime behavior while cleaning the reviewed changed area.\nRejected: Broad pyright baseline cleanup | outside issue #292 scope and higher risk for this follow-up.\nConfidence: high\nScope-risk: narrow\nDirective: Keep future Korail mobile endpoint additions behind explicit typed/dynamic boundaries so changed-area diagnostics remain actionable.\nTested: PYTHONPATH=.:scripts PYTHONNOUSERSITE=1 python3 -m unittest discover -s scripts -p 'test_ktx_booking.py'; PYTHONPATH=.:scripts python3 -m unittest scripts.test_ktx_booking; python3 -m py_compile scripts/ktx_booking.py scripts/test_ktx_booking.py; node --check scripts/skill-docs.test.js; node --test scripts/skill-docs.test.js; git diff --check origin/dev...HEAD; ruff check scripts/ktx_booking.py scripts/test_ktx_booking.py; PYTHONPATH=.:scripts python3 scripts/ktx_booking.py seats --help; pyright changed-area grep against search/seat lines\nNot-tested: live authenticated Korail seat lookup; full pyright remains on existing dynamic Korail/test-helper baseline
2026-05-29 01:30:33 +09:00
Jeffrey (Dongkyu) Kim
002c6c13bc Make KTX seat summaries match active filters
Constraint: PR #296 follow-up must address reproducible review findings without live Korail credentials.
Rejected: Keeping available_seats unfiltered | It conflicted with --power-only user intent and review feedback.
Confidence: high
Scope-risk: narrow
Directive: Keep filtered availability and all_available_* semantics documented together when changing seats output.
Tested: PYTHONPATH=.:scripts PYTHONNOUSERSITE=1 python3 -m unittest discover -s scripts -p 'test_ktx_booking.py'; PYTHONPATH=.:scripts python3 -m unittest scripts.test_ktx_booking; python3 -m py_compile scripts/ktx_booking.py scripts/test_ktx_booking.py; node --check scripts/skill-docs.test.js; node --test scripts/skill-docs.test.js; git diff --check origin/dev...HEAD; ruff check scripts/ktx_booking.py scripts/test_ktx_booking.py; PYTHONPATH=.:scripts python3 scripts/ktx_booking.py seats --help
Not-tested: live authenticated Korail seat lookup
2026-05-29 01:00:27 +09:00
Jeffrey (Dongkyu) Kim
20e8f3f8f0 Expose missing KTX seat detail payloads
Constraint: PR #298 review blocker required missing seat_infos.seat_info to fail clearly instead of reporting zero seats.
Rejected: Treating absent seat_info as an empty list | this masks Korail detail endpoint failures as authoritative no-seat results.
Confidence: high
Scope-risk: narrow
Directive: Preserve explicit empty seat_info lists as valid; only absent or malformed detail schema should fail.
Tested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_ktx_booking; node --test scripts/skill-docs.test.js; npm run typecheck; python3 -m compileall -q scripts/ktx_booking.py scripts/test_ktx_booking.py; ruff check scripts/ktx_booking.py scripts/test_ktx_booking.py; shellcheck scripts/validate-skills.sh; bash scripts/validate-skills.sh; PYENV_VERSION=3.11.9 npm run ci
Not-tested: plain npm run ci with local default Python 3.14 remains blocked by pyexpat/libexpat linkage before project tests
2026-05-29 00:27:02 +09:00
Jeffrey (Dongkyu) Kim
eb08ef6134 Keep malformed KTX seat payloads actionable
Convert malformed Korail seat-detail payloads into the existing CLI failure path so advisory lookup callers get a clear retryable error instead of an AttributeError.

Constraint: PR #295 review watch item identified successful-but-malformed seat_infos payloads as an unhelpful crash surface.

Rejected: Letting raw adapter fallbacks leak AttributeError | CLI users need actionable SystemExit diagnostics at the command boundary.

Confidence: high

Scope-risk: narrow

Directive: Keep detailed seat lookup advisory; validate raw Korail shapes before exposing fields to JSON callers.

Tested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_ktx_booking; node --test scripts/skill-docs.test.js; npm run typecheck; python3 -m compileall -q scripts/ktx_booking.py scripts/test_ktx_booking.py; ruff check scripts/ktx_booking.py scripts/test_ktx_booking.py; shellcheck scripts/validate-skills.sh; bash scripts/validate-skills.sh; PATH=<pyenv 3.11.9 shim> npm run ci

Not-tested: Plain npm run ci with /opt/homebrew Python 3.14 due local pyexpat/libexpat linkage error reproduced before project tests.
2026-05-29 00:11:29 +09:00
iamiks
1d8fd333d8 Expose KTX malformed seat detail evidence
Keep seat-priority output from reporting upstream seat-detail schema failures as authoritative zero-seat availability. Preserve car-level remaining_seats and surface seat_lookup_error when detail payloads are malformed while seats remain.

Constraint: PR #295 review follow-up keeps advisory priority/filter behavior while avoiding false zero-seat results.

Rejected: Treat malformed or empty detail payloads as [] | masks Korail endpoint/schema failures as no availability.

Confidence: high

Scope-risk: narrow

Directive: Keep detailed seat lookup advisory unless reserve gains explicit car/seat selection support.

Tested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_ktx_booking; python3 -m py_compile scripts/ktx_booking.py scripts/test_ktx_booking.py; node --check scripts/skill-docs.test.js; node --test scripts/skill-docs.test.js; git diff --check origin/dev...HEAD; npm run typecheck

Not-tested: live authenticated Korail seat-detail lookup
2026-05-29 00:09:29 +09:00
Jeffrey (Dongkyu) Kim
95c6222a65 Fail early on incomplete KTX seat lookup context
Constraint: PR #296 review watchlist noted raw Korail mobile context can otherwise send empty residual-seat payload fields upstream.
Rejected: Keep empty-string defaults in residual-seat payloads | That hides stale or malformed search detail context until the anti-bot-sensitive endpoint is called.
Confidence: high
Scope-risk: narrow
Directive: Keep residual-seat payload requirements explicit if Korail changes the mobile h_* field contract.
Tested: PYTHONPATH=.:scripts PYTHONNOUSERSITE=1 python3 -m unittest discover -s scripts -p 'test_ktx_booking.py'; PYTHONPATH=.:scripts python3 -m unittest scripts.test_ktx_booking; python3 -m py_compile scripts/ktx_booking.py scripts/test_ktx_booking.py; node --check scripts/skill-docs.test.js; node --test scripts/skill-docs.test.js; PYTHONPATH=.:scripts python3 scripts/ktx_booking.py seats --help
Not-tested: Live Korail authenticated seat lookup; credentials are not available in automation.
2026-05-29 00:06:55 +09:00
Jeffrey (Dongkyu) Kim
48a0420752 Expose KTX malformed seat detail evidence
Preserve car-level remaining_seats when Korail detail payloads drift, and mark the affected car with an explicit lookup error instead of reporting a false zero-seat result.\n\nConstraint: PR #293 review required malformed detail responses to be visible when car summary still reports remaining seats.\nRejected: silently normalizing malformed seat_infos to [] | it hides upstream schema drift as legitimate zero availability.\nConfidence: high\nScope-risk: narrow\nDirective: Keep seat-detail schema drift observable; do not collapse positive remaining seats and malformed detail payloads into successful empty results.\nTested: PYTHONPATH=.:scripts PYTHONNOUSERSITE=1 python3 -m unittest discover -s scripts -p 'test_ktx_booking.py'\nTested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_ktx_booking\nTested: python3 -m py_compile scripts/ktx_booking.py scripts/test_ktx_booking.py\nTested: node --check scripts/skill-docs.test.js\nTested: node --test scripts/skill-docs.test.js\nNot-tested: live Korail seat-detail endpoint with production credentials
2026-05-29 00:06:55 +09:00
Jeffrey (Dongkyu) Kim
6a26a194c6 Fail early on incomplete KTX seat lookup context
Constraint: PR #296 review watchlist noted raw Korail mobile context can otherwise send empty residual-seat payload fields upstream.
Rejected: Keep empty-string defaults in residual-seat payloads | That hides stale or malformed search detail context until the anti-bot-sensitive endpoint is called.
Confidence: high
Scope-risk: narrow
Directive: Keep residual-seat payload requirements explicit if Korail changes the mobile h_* field contract.
Tested: PYTHONPATH=.:scripts PYTHONNOUSERSITE=1 python3 -m unittest discover -s scripts -p 'test_ktx_booking.py'; PYTHONPATH=.:scripts python3 -m unittest scripts.test_ktx_booking; python3 -m py_compile scripts/ktx_booking.py scripts/test_ktx_booking.py; node --check scripts/skill-docs.test.js; node --test scripts/skill-docs.test.js; PYTHONPATH=.:scripts python3 scripts/ktx_booking.py seats --help
Not-tested: Live Korail authenticated seat lookup; credentials are not available in automation.
2026-05-28 23:59:49 +09:00
Jeffrey (Dongkyu) Kim
6aff1e7301 Expose KTX malformed seat detail evidence
Preserve car-level remaining_seats when Korail detail payloads drift, and mark the affected car with an explicit lookup error instead of reporting a false zero-seat result.\n\nConstraint: PR #293 review required malformed detail responses to be visible when car summary still reports remaining seats.\nRejected: silently normalizing malformed seat_infos to [] | it hides upstream schema drift as legitimate zero availability.\nConfidence: high\nScope-risk: narrow\nDirective: Keep seat-detail schema drift observable; do not collapse positive remaining seats and malformed detail payloads into successful empty results.\nTested: PYTHONPATH=.:scripts PYTHONNOUSERSITE=1 python3 -m unittest discover -s scripts -p 'test_ktx_booking.py'\nTested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_ktx_booking\nTested: python3 -m py_compile scripts/ktx_booking.py scripts/test_ktx_booking.py\nTested: node --check scripts/skill-docs.test.js\nTested: node --test scripts/skill-docs.test.js\nNot-tested: live Korail seat-detail endpoint with production credentials
2026-05-28 23:42:10 +09:00
iamiks
70b92d6b03 Clarify KTX seat filter failures
Constraint: PR #295 review found power-only summaries included non-power seats and empty car-list payloads looked successful.
Rejected: Document available_seats as unfiltered | would preserve a surprising JSON contract for power-only callers.
Rejected: Ignore local hidden directories one by one | hidden runtime/cache directories can keep reappearing outside git.
Confidence: high
Scope-risk: narrow
Directive: Keep seats output summaries aligned with active display filters, while --limit remains a detailed seats display cap.
Tested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_ktx_booking; python3 -m compileall -q scripts/ktx_booking.py scripts/test_ktx_booking.py; git diff --check; node --test scripts/skill-docs.test.js; npm run typecheck; PIP_BREAK_SYSTEM_PACKAGES=1 npm run ci
Not-tested: live Korail production seat endpoint behavior
2026-05-28 23:07:09 +09:00
iamiks
9b9f5bc7a2 Stabilize KTX seat lookup review fixes
Constraint: PR #293 review found fallback korail2 imports failed under PYTHONNOUSERSITE and seat lookup needed clearer Dynapath/power-seat contracts.
Rejected: Leave research endpoints outside DYNAPATH_PATHS | would keep the auth-header behavior ambiguous for live seat lookup.
Confidence: high
Scope-risk: narrow
Directive: Keep fallback-only tests independent of installed korail2 so CI catches missing dependency behavior.
Tested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_ktx_booking; PYTHONPATH=.:scripts PYTHONNOUSERSITE=1 python3 -m unittest discover -s scripts -p 'test_ktx_booking.py'; python3 -m py_compile scripts/ktx_booking.py scripts/test_ktx_booking.py; PYTHONPATH=.:scripts python3 scripts/ktx_booking.py seats --help; node --test scripts/skill-docs.test.js
Not-tested: live Korail seat lookup against production endpoints
2026-05-28 22:59:24 +09:00
iamiks
ee51dbc2f1 chore(ci): 로컬 런타임 디렉터리 검증 제외
로컬 개발 환경에서 생성되는 .agents와 .venv를 최상위 스킬로
검사하지 않도록 제외해 skill layout 검증을 실제 스킬 디렉터리에 한정합니다.

Constraint: npm run ci가 validate-skills.sh를 필수로 실행함
Rejected: 로컬 .agents/.venv 삭제 | 사용자 환경 파일을 제거하는 방식은 부적절함
Confidence: high
Scope-risk: narrow
Directive: 새 로컬 런타임 디렉터리가 생기면 skill-docs.test.js 제외 목록과 함께 검토할 것
Tested: npm run ci
2026-05-28 22:49:31 +09:00
iamiks
24feb3edca feat(ktx-booking): 좌석 탐색 우선순위 개선
KTX 상세 좌석 조회에서 가운데 호차를 먼저 탐색하고,
같은 호차 안에서는 콘센트 좌석과 순방향 좌석을 우선 노출합니다.
#294 요구사항을 우선순위 함수와 command_seats 회귀 테스트로 고정합니다.

Constraint: #294는 기존 seats 공개 인터페이스 유지를 요구함
Rejected: 예약 API에 객차/좌석 번호를 직접 주입 | 현재 helper 예약 경로가 해당 선택 인자를 노출하지 않음
Confidence: high
Scope-risk: narrow
Directive: 좌석 우선순위 변경 시 command_seats 출력 순서 테스트를 함께 갱신할 것
Tested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_ktx_booking
Tested: node --test scripts/skill-docs.test.js
Tested: npm run typecheck
Tested: npm run ci
2026-05-28 22:49:31 +09:00
iamiks
8420792a82 Expose KTX seat-level lookup before reservation
Add a seats command to inspect residual seats by car, normalize seat metadata, and surface power-outlet hints while preserving the existing search and reserve selector flow. Update skill docs so seat-number and good-seat requests route through the detailed lookup before reservation.

Constraint: Korail reservation remains separate from seat inspection; the new flow must not select, hold, or pay for seats.

Rejected: Reusing train-level has_general_seat flags for good-seat requests | those flags cannot answer car number, seat label, or outlet availability.

Confidence: high

Scope-risk: moderate

Directive: Keep search, seats, and reserve on the same train_type and stable train_id path so stale results fail explicitly.

Tested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_ktx_booking; python3 -m py_compile scripts/ktx_booking.py scripts/test_ktx_booking.py; node --check scripts/skill-docs.test.js

Not-tested: npm run ci is blocked locally because gitignored .agents/ and .venv directories are treated as missing SKILL.md by scripts/validate-skills.sh.
2026-05-28 22:49:13 +09:00
seungwonme
3ba8899f63 feat(plugin): Claude Code 플러그인 + 마켓플레이스 매니페스트 추가
디렉토리 구조와 npm/changesets 릴리스 흐름을 건드리지 않고 루트의
한국 특화 Agent Skill 모음을 Claude Code 플러그인으로 설치 가능하게 한다.

- .claude-plugin/plugin.json: skills 배열로 루트 89개 skill을 노출하는 단일 번들
  (지원 중단된 blue-ribbon-nearby 제외)
- .claude-plugin/marketplace.json: repo 자체를 마켓플레이스로, 번들 플러그인 1개(source ./)
- scripts/generate-plugin-manifest.js: SKILL.md 스캔으로 skills 배열 생성, --check drift 게이트
- scripts/test_generate_plugin_manifest.js: discovery/제외/drift node --test 6케이스
- scripts/validate-skills.sh: dot 디렉토리 개별 나열을 '! -name .*'로 일반화 (.claude-plugin 오인 방지)
- package.json: generate:plugin-manifest 스크립트 + lint/test 등록, lint 끝에 drift 게이트
- README.md: 플러그인 설치 안내 추가

설치: /plugin marketplace add NomaDamas/k-skill -> /plugin install k-skill@k-skill
2026-05-27 23:27:56 +09:00
TaeyoungPark
9b2e0957f2 chore: merge upstream/dev into feat/myrealtrip-mcp-search 2026-05-27 16:33:36 +09:00
TaeyoungPark
0e30b79e83 fix: stabilize startup-support deadline filtering and tests 2026-05-27 16:30:29 +09:00
TaeyoungPark
807fa0c900 feat: Add startup-support API routes to k-skill-proxy
- Add startup-support API routes for Korean government startup programs
- Implement /v1/startup-support/list, /detail, /region, /deadline endpoints
- Integrate with existing k-skill-proxy infrastructure

Closes #startup-support
2026-05-27 15:41:11 +09:00
TaeyoungPark
d12bfa1fab feat: Add startup-support skill for Korean government startup programs
- Add startup-support skill to search Korean government startup support programs
- Implement Python script with multiple data sources (public data, local governments)
- Add k-skill-proxy routes for API endpoints
- Update documentation (README.md, docs/features/, docs/sources.md, etc.)
- Add comprehensive test suite

Closes #startup-support
2026-05-27 15:40:12 +09:00
Jeffrey (Dongkyu) Kim
0ea646a03d
Merge pull request #287 from NomaDamas/dev
ci(deploy): naverMapConfigured smoke test 예외 처리
2026-05-26 10:17:00 +09:00
Jeffrey (Dongkyu) Kim
abe26e411d ci(deploy): skip naverMapConfigured in smoke test until NCP keys are provisioned 2026-05-26 09:58:37 +09:00
Jeffrey (Dongkyu) Kim
234790a7ea
Merge pull request #286 from NomaDamas/changeset-release/main
chore: version packages
2026-05-26 09:53:29 +09:00
github-actions[bot]
d2db629640 chore: version packages 2026-05-26 00:52:47 +00:00
Jeffrey (Dongkyu) Kim
0300e9b91b
Merge pull request #285 from NomaDamas/dev
release: dev → main 통합 릴리스 (2026-05-25)
2026-05-26 09:51:49 +09:00
Jeffrey (Dongkyu) Kim
19af47399d merge: resolve conflicts with main (keep dev action versions + lint entries) 2026-05-26 09:33:04 +09:00
Jeffrey (Dongkyu) Kim
99340393de docs: 네이버맵 스킬 미작동 안내 추가 (NCP 키 미설정) 2026-05-25 18:31:03 +09:00
Jeffrey (Dongkyu) Kim
e90897a684
feat(#267): 카카오맵 스킬 (Kakao Local 장소검색 + Kakao Mobility 자동차 길찾기)
feat(#267): 카카오맵 스킬 (Kakao Local 장소검색 + Kakao Mobility 자동차 길찾기)
2026-05-25 17:18:27 +09:00
Jeffrey (Dongkyu) Kim
72a3fd7ca6 merge: resolve conflicts with dev after PR #282 merge 2026-05-25 17:16:53 +09:00
Jeffrey (Dongkyu) Kim
440f33b52f
feat(#268): 네이버맵 길찾기 MVP 스킬 + NCP Maps proxy 라우트 3종
feat(#268): 네이버맵 길찾기 MVP 스킬 + NCP Maps proxy 라우트 3종
2026-05-25 16:39:51 +09:00
Jeffrey (Dongkyu) Kim
ed30f22f86
fix(ci): add npm auth preflight to release workflow
fix(ci): add npm auth preflight to catch publish permission failures early
2026-05-25 15:19:04 +09:00
Jeffrey (Dongkyu) Kim
5e58d1fe86 fix(ci): add npm auth preflight to release workflow
The latest npm release (changeset publish) failed with E404 on 4 packages:
daiso-product-search, sh-notice-search, emergency-room-beds,
local-election-candidate-search.

Root cause: NPM_TOKEN lost publish access to these packages
(previously worked on May 19; failed on May 22).

This commit adds two preflight steps before changeset publish:
1. npm whoami — fail fast if the token is invalid or expired
2. npm access check — verify publish rights for every package
   whose local version differs from npm registry, before the
   changeset action runs

The actual publish fix requires rotating or re-authorizing the
NPM_TOKEN secret in GitHub → Settings → Secrets. Once the token
is valid, re-trigger the workflow to publish the 4 stuck packages.
2026-05-25 13:44:08 +09:00
Jeffrey (Dongkyu) Kim
51ea778a2d Keep Kakao waypoint validation at the proxy boundary
Constraint: Kakao Mobility waypoint coordinates share the same x,y shape as origin and destination.\nRejected: Letting out-of-range waypoints reach upstream | it spends quota on a deterministic bad request.\nConfidence: high\nScope-risk: narrow\nDirective: Keep Kakao Mobility coordinate validation local before cache lookup or upstream fetch.\nTested: node --test packages/k-skill-proxy/test/server.test.js; npm test --workspace k-skill-proxy; npm run lint --workspace k-skill-proxy; node --test scripts/skill-docs.test.js; bash scripts/validate-skills.sh; manual Fastify inject invalid waypoint 400/0 upstream calls and valid waypoint 200/1 upstream call.\nNot-tested: npm run ci full root pipeline, because prior PR validation documented a local Python 3.14 pyexpat/pip install environment blocker.
2026-05-23 21:56:35 +09:00
Jeffrey (Dongkyu) Kim
2dbad40078 Keep Kakao radius filters local
Reject keyword radius without a coordinate center before Kakao Local calls so predictable client errors do not spend upstream quota.\n\nConstraint: PR #283 review round 3 requested local radius validation for issue #267.\nRejected: Letting Kakao Local reject radius-only keyword searches | wastes quota and weakens proxy determinism.\nConfidence: high\nScope-risk: narrow\nDirective: Keep coordinate-centered Kakao filters validated before cache lookup or upstream fetch.\nTested: node --test packages/k-skill-proxy/test/server.test.js; npm test --workspace k-skill-proxy; npm run lint --workspace k-skill-proxy; node --test scripts/skill-docs.test.js; bash scripts/validate-skills.sh; manual Fastify inject smoke.\nNot-tested: npm run ci remains blocked in local Python 3.14 pyexpat during pip install beautifulsoup4 after lint/typecheck.
2026-05-23 21:34:38 +09:00
Jeffrey (Dongkyu) Kim
68bd64ebd4 Preserve route proxy rate-limit semantics
Narrow the Naver Maps proxy contract to JSON reverse geocode responses and preserve upstream quota signals so client fallback can make accurate decisions.

Constraint: PR #282 review requested TDD fixes for XML contract mismatch, upstream 429 mapping, lint coverage, and route option documentation.

Rejected: XML passthrough in this follow-up | It would require a separate response-shaping contract and tests beyond the JSON proxy boundary.

Confidence: high

Scope-risk: narrow

Directive: Keep Naver Maps auth failures sanitized as 503 without upstream body snippets while preserving non-auth diagnostic snippets.

Tested: node --test packages/k-skill-proxy/test/server.test.js; node --test scripts/skill-docs.test.js; bash scripts/validate-skills.sh; PYENV_VERSION=3.12.0 npm run ci; architect verification CLEAR

Not-tested: Live NCP Maps calls with production credentials

Co-authored-by: OmX <omx@oh-my-codex.dev>
2026-05-23 19:57:21 +09:00
Jeffrey (Dongkyu) Kim
366d346f03 Keep Kakao route contracts local and explicit
Constraint: PR #283 review requested TDD fixes for Kakao Local distance sorting, Mobility toll avoidance, lint coverage, and coord2region routing coverage.
Rejected: Relying on upstream Kakao validation for sort=distance | it spends quota and returns a proxy/upstream error instead of local bad_request.
Rejected: Document-only toll avoidance correction | the skill already promises the behavior and Kakao Mobility exposes an explicit avoid option.
Confidence: high
Scope-risk: narrow
Directive: Preserve server-side KAKAO_REST_API_KEY injection only; never accept or forward caller apiKey query values.
Tested: node --test packages/k-skill-proxy/test/server.test.js; npm test --workspace k-skill-proxy; npm run lint --workspace k-skill-proxy; node --test scripts/skill-docs.test.js; bash scripts/validate-skills.sh; manual Fastify inject smoke for sort=distance and avoid forwarding; npm run ci through lint/typecheck until local Python pyexpat failure.
Not-tested: Full npm run ci completion due local Python 3.14 pyexpat ImportError during pip install.
2026-05-23 19:25:10 +09:00
Jeffrey (Dongkyu) Kim
73c3611e8a Protect Naver Maps credential boundary
Sanitize auth-failure upstream bodies while retaining non-auth diagnostics for operator debugging.

Constraint: PR #282 review requires Naver Maps 401/403 bodies to be hidden from public callers
Rejected: Blanket removal of all upstream snippets | non-auth 5xx diagnostics are still useful and covered by regression
Confidence: high
Scope-risk: narrow
Directive: Keep 401/403 response bodies out of public Naver Maps proxy payloads
Tested: node --test packages/k-skill-proxy/test/server.test.js; PYENV_VERSION=3.12.0 npm run ci; mocked app injection for 401 response
Not-tested: Live NCP Maps auth failure against production credentials
2026-05-23 18:07:19 +09:00
Jeffrey (Dongkyu) Kim
45084293f0
Feature/#270 (#281)
* Enable deterministic Middle Korean-style rewriting

Constraint: Issue #270 requested a new skill that converts incoming Korean text into 한국 중세 국어 style under non-interactive TDD automation.
Rejected: LLM-only prompt guidance | It would not provide deterministic CLI behavior or regression-testable output.
Confidence: high
Scope-risk: narrow
Directive: Keep this as creative style conversion, not an academically exact Middle Korean translator.
Tested: node --test scripts/test_korean_middle_korean.js; npm run lint; npm run typecheck; root node/python/workspace tests without pip bootstrap; npm run pack:dry-run; installed-skill smoke.
Not-tested: npm run test bootstrap step because python3 -m pip fails in this local Homebrew Python 3.14 environment due pyexpat/libexpat symbol mismatch before tests start.

* Align Middle Korean profile contract with implementation

Preserve the existing date-before-lexicon transform order and document it as the v1 contract instead of reordering an already-reviewed helper.

Constraint: PR #281 review requested the docs/contract mismatch be resolved with TDD evidence.

Rejected: Reordering the converter | would alter current output behavior beyond the approved follow-up.

Confidence: high

Scope-risk: narrow

Directive: Treat middle-korean-style-v1 output-changing rule order edits as contract changes that need regression tests and docs updates.

Tested: node --test scripts/test_korean_middle_korean.js; npm run lint; npm run typecheck; npm run pack:dry-run; npm run test without pip bootstrap commands; installed-skill smoke.

Not-tested: npm run test direct bootstrap remains blocked locally by Homebrew Python 3.14 pyexpat/libexpat symbol mismatch.

* Clarify middle Korean profile stability

Align the documented v1 contract with the intentionally broad deterministic replacer so future readers do not infer exact proper-noun preservation.

Constraint: PR #281 round 2 architect WATCH asked to weaken preservation guarantees or add stronger rule boundaries.

Rejected: Changing replacement behavior | The PR already verified the creative v1 output and only the contract wording was mismatched.

Confidence: high

Scope-risk: narrow

Directive: Treat output-changing edits to middle-korean-style-v1 as compatibility-affecting and update docs plus regression tests together.

Tested: node --test scripts/test_korean_middle_korean.js; node --check korean-middle-korean/scripts/korean_middle_korean.js; node --check scripts/korean_middle_korean.js; npm run lint; npm run typecheck; root/workspace post-bootstrap test chain; npm run pack:dry-run; installed skill smoke.

Not-tested: Direct npm run test still blocked before tests by local Homebrew Python 3.14 pyexpat/libexpat symbol mismatch.

* Protect structural spans during style conversion

Keep arbitrary text links, email addresses, and code spans usable while preserving the deterministic middle-korean-style-v1 prose transform.\n\nConstraint: PR #281 round 3 requested URL/email/code-like span protection after broad global replacement probes rewrote structural tokens.\nRejected: Narrowing all lexicon and particle rules with word-boundary heuristics | would change established v1 creative broad-replacement behavior beyond the reviewed issue.\nConfidence: high\nScope-risk: narrow\nDirective: Protect new structural span classes before broad replacements and add regression tests before extending the protected surface.\nTested: node --test scripts/test_korean_middle_korean.js; node --check korean-middle-korean/scripts/korean_middle_korean.js; node --check scripts/korean_middle_korean.js; CLI URL/email/code/Markdown probes; installed-skill smoke via ~/.agents/skills/korean-middle-korean/scripts/korean_middle_korean.js; npm run lint; npm run typecheck; root/workspace test chain without pip bootstrap; npm run pack:dry-run; post-deslop npm run lint && npm run typecheck && node --test scripts/test_korean_middle_korean.js && npm run pack:dry-run\nNot-tested: direct npm run test remains blocked before repo tests by local Homebrew Python 3.14 pyexpat/libexpat import error.
2026-05-23 17:54:56 +09:00
Jeffrey (Dongkyu) Kim
6d49a28d87 feat(kakao-map): Kakao Local/Mobility 프록시 라우트 + 장소·자동차 길찾기 스킬 (#267)
- packages/k-skill-proxy:
  - /v1/kakao-map/search/keyword (좌표 중심·반경·카테고리 필터)
  - /v1/kakao-map/search/category (좌표 중심 필수, FD6/CE7 등 공식 코드 화이트리스트)
  - /v1/kakao-map/coord2address (좌표→도로명/지번)
  - /v1/kakao-map/coord2region (좌표→법정동/행정동)
  - /v1/kakao-mobility/directions (자동차 길찾기, priority/car_fuel/waypoints/alternatives 옵션)
  - 모두 운영자 KAKAO_REST_API_KEY 서버측 주입, caller apiKey 무시
- kakao-map 스킬 + docs/features/kakao-map.md 신규
- proxy 테스트 10건 신규 (헤더 주입·캐시, 좌표·반경·정렬·카테고리·priority 검증, 503 missing-key 매트릭스, semantic failure non-cache, health 플래그)
- README/포함된 기능, packages/k-skill-proxy/README, docs/sources, changeset 동시 갱신

Closes #267
2026-05-23 17:51:06 +09:00
Jeffrey (Dongkyu) Kim
ff2aa91f83 feat(naver-map-route): NCP Maps Directions/Geocode/Reverse-Geocode 프록시 라우트 + MVP 길찾기 스킬 (#268)
- packages/k-skill-proxy: NAVER_MAP_CLIENT_ID/SECRET 서버측 보관, /v1/naver-map/{directions,geocode,reverse-geocode} 라우트 3종 추가
- naver-map-route: instruction-level MVP 스킬 (mock 기본, ROUTE_PLANNER_ENABLE_LIVE_PROVIDER=true + ROUTE_PLANNER_PROVIDER=naver 에서만 live)
- /route, /이동루트 수동 입력 처리, graceful fallback 정책 문서화
- proxy 테스트 8건 신규 (missing-key 503, 캐시, 좌표 검증, semantic failure non-cache, auth error sanitize, geocode 헤더 주입, reverse-geocode orders 검증, health 플래그)
- README 표/포함된 기능, packages/k-skill-proxy/README, docs/features/naver-map-route, docs/sources, changeset 동시 갱신

Closes #268
2026-05-23 17:36:55 +09:00
Jeffrey (Dongkyu) Kim
876077c7c9
Feature/#26269601403 (#280)
* ci: bump GHA actions to Node.js 24 runtime majors

GitHub Actions runner는 2026-06-02부터 Node.js 20 기반 action들을 강제로
Node.js 24로 돌리고, 2026-09-16에는 Node.js 20 자체를 runner에서 제거한다.
2026-05-22 Deploy k-skill-proxy run #26269601403에서 deprecation annotation
관측: 'actions/checkout@v4, google-github-actions/setup-gcloud@v2 ...running
on Node.js 20'.

이번 핫픽스는 우리 모든 워크플로의 action pin을 명시적으로 Node 24 major로
올려둔다. node24 runtime 확인은 action repo의 action.yml runs.using 값을
직접 조회해 검증했다.

변경 (10 replacements across 5 workflows):

- actions/checkout: v4 -> v5  (v5/v6 모두 node24, 안정 stable인 v5 채택)
- actions/setup-node: v4 -> v5  (v4은 node20, v5+가 node24)
- google-github-actions/setup-gcloud: v2 -> v3  (v2은 node20, v3이 node24)

이미 node24인 채로 pin돼 있어 손대지 않은 항목 (sanity):

- google-github-actions/auth@v3       (v3 = node24)
- google-github-actions/deploy-cloudrun@v3 (v3 = node24)
- changesets/action@v1                (v1.8.0 = node24, major pin이 자동 follow)
- googleapis/release-please-action@v4 (v4.4.1 = node24, major pin이 자동 follow)

검증:

- yaml grammar는 ast-grep yaml replace로 보존 (10 surgical replacements only).
- runtime은 'gh api .../action.yml | grep using:' 으로 모든 새 ref가 node24임을
  실제 확인. 추측 없음.
- 머지 후 첫 deploy run에서 deprecation annotation이 사라지는지 최종 검증.

* Keep workflow actions ahead of Node 20 removal

Release-please was still pinned to a Node 20 runtime major in the Python release scaffold, so the workflow set was not fully clean for the runner cutoff. Add a workflow regression test to keep the reviewed action majors on Node 24 refs.

Constraint: GitHub-hosted runners begin forcing Node 20 actions to Node 24 on 2026-06-02 and remove Node 20 on 2026-09-16.

Rejected: Leaving release-please-action@v4 as scaffold-only | it would become a latent release workflow break once Python packages are added.

Confidence: high

Scope-risk: narrow

Directive: Keep workflow action runtime-major claims backed by action.yml metadata checks and regression tests.

Tested: node --test scripts/workflow-actions.test.js; Ruby YAML.load_file for workflows; direct GitHub API action.yml runs.using checks; npm run ci attempted through lint/typecheck before local pip pyexpat blocker; equivalent Node/Python/workspace tests, validate-skills.sh, and npm run pack:dry-run passed.

Not-tested: npm run ci end-to-end due local Homebrew Python 3.14 pyexpat dynamic-link failure during pip install.

* Protect workflow action guardrails from commented uses refs

The Node 24 migration guard should catch reviewed stale action majors even when a valid workflow line carries an inline comment or YAML quotes. Reuse one extractor for fixtures and real workflow scans so the regression covers production behavior.\n\nConstraint: PR #279 review round 3 found inline-commented uses lines could be skipped by the text extractor.\nRejected: Full YAML parser adoption | unnecessary for the bounded guardrail and would add complexity to a no-dependency test.\nConfidence: high\nScope-risk: narrow\nDirective: Keep workflow action runtime guardrails deterministic and dependency-free unless broad runtime metadata validation is explicitly required.\nTested: node --test scripts/workflow-actions.test.js; npm run lint; npm run typecheck; direct root/workspace/Python test segments; validate-skills; pack:dry-run; git diff --check; package.json JSON parse.\nNot-tested: npm run test end-to-end past the initial pip install gate because local Homebrew Python 3.14 pyexpat linkage fails before repo tests run.

* Clarify curated workflow action runtime guard

Document the reviewed scope behind the Node runtime action guard so future maintainers do not mistake the hotfix inventory for exhaustive workflow enforcement.

Constraint: Follow-up to PR #280 review watchlist; keep behavior scoped to the reviewed Node 20 to Node 24 action migration set.

Rejected: Broadening the guard to every external action | outside this hotfix scope and explicitly deferred by review.

Confidence: high

Scope-risk: narrow

Directive: Expand the source URL map and tests if the guard becomes comprehensive runtime enforcement.

Tested: node --test scripts/workflow-actions.test.js; npm run lint; npm run typecheck; git diff --check; python3 -m json.tool package.json; downstream direct test fragments; workspace tests; ./scripts/validate-skills.sh; npm run pack:dry-run

Not-tested: npm run test remains blocked before repo tests by local Homebrew Python 3.14 pyexpat/pip import linker error
2026-05-23 10:25:24 +09:00
Jeffrey (Dongkyu) Kim
e6d7072e93
Feature/#274 (#277)
* Add Seoul Bike live station lookup

Expose narrow Seoul Open Data proxy surfaces for realtime bike availability, station master pages, and coordinate-based nearby lookups while keeping the upstream key server-side. Add a single Python skill entrypoint plus docs so agents can answer last-mile bike and dock availability questions.

Constraint: Issue #274 requires , TDD, three proxy routes, branch feature/#274, and PR to dev.
Rejected: Client-side Seoul OpenAPI key handling | would leak upstream credentials and violate existing proxy patterns.
Confidence: high
Scope-risk: moderate
Directive: Keep these routes read-only; do not add rental/booking mutations or user-key requirements.
Tested: node --test packages/k-skill-proxy/test/server.test.js --test-name-pattern 'seoul bike'; PYTHONPATH=.:scripts python3 -m unittest scripts.test_seoul_bike; local fake-proxy smoke run; PATH="/Users/jeffrey/.pyenv/versions/3.11.9/bin:/Users/jeffrey/.codex/tmp/arg0/codex-arg08RBix6:/opt/homebrew/lib/node_modules/@openai/codex/node_modules/@openai/codex-darwin-arm64/vendor/aarch64-apple-darwin/path:/Users/jeffrey/.cmuxterm/omo-bin:/opt/homebrew/share/android-commandlinetools/platform-tools:/opt/homebrew/share/android-commandlinetools/emulator:/opt/homebrew/share/android-commandlinetools/cmdline-tools/latest/bin:/Users/jeffrey/.local/bin:/Users/jeffrey/.bun/bin:/opt/homebrew/opt/node@22/bin:/opt/homebrew/opt/openjdk@21/bin:/opt/homebrew/opt/postgresql@18/bin:/Users/jeffrey/.jenv/shims:/Users/jeffrey/.jenv/bin:/opt/homebrew/opt/imagemagick/bin:/opt/homebrew/Cellar/pyenv-virtualenv/1.4.0/shims:/Users/jeffrey/.pyenv/shims:/opt/homebrew/opt/openssl@3/bin:/Users/jeffrey/.rbenv/shims:/Users/jeffrey/.rbenv/bin:/Users/jeffrey/google-cloud-sdk/bin:/Applications/cmux.app/Contents/Resources/bin:/Users/jeffrey/Library/pnpm:/Users/jeffrey/.nvm/versions/node/v24.13.0/bin:/Users/jeffrey/.cops/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/opt/pmk/env/global/bin:/Library/Apple/usr/bin:/Library/TeX/texbin:/Users/jeffrey/.cargo/bin:/Users/jeffrey/Library/Application Support/JetBrains/Toolbox/scripts:/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/bin:/Users/jeffrey/xcode-projects/marshroom/cli" npm run ci.
Not-tested: Live hosted Seoul Open Data request with production SEOUL_OPEN_API_KEY.

* Prevent Seoul Bike upstream errors from masquerading as empty availability

Constraint: Seoul Open API can return application-level error JSON with HTTP 200, so proxy routes must inspect RESULT envelopes before caching or normalizing rows.
Rejected: Treating missing rentBikeStatus.row as an empty success | it masks quota/service failures and caches false no-station results.
Confidence: high
Scope-risk: narrow
Directive: Preserve non-cacheable proxy error behavior for Seoul Open API semantic failures across realtime, stations, and nearby routes.
Tested: node --test packages/k-skill-proxy/test/server.test.js --test-name-pattern 'seoul bike'; PYTHONPATH=.:scripts python3 -m unittest scripts.test_seoul_bike; local fake-proxy seoul_bike.py nearby smoke; PATH="/Users/jeffrey/.pyenv/versions/3.11.9/bin:/Users/jeffrey/.codex/tmp/arg0/codex-arg0j0fIum:/opt/homebrew/lib/node_modules/@openai/codex/node_modules/@openai/codex-darwin-arm64/vendor/aarch64-apple-darwin/path:/Users/jeffrey/.cmuxterm/omo-bin:/opt/homebrew/share/android-commandlinetools/platform-tools:/opt/homebrew/share/android-commandlinetools/emulator:/opt/homebrew/share/android-commandlinetools/cmdline-tools/latest/bin:/Users/jeffrey/.local/bin:/Users/jeffrey/.bun/bin:/opt/homebrew/opt/node@22/bin:/opt/homebrew/opt/openjdk@21/bin:/opt/homebrew/opt/postgresql@18/bin:/Users/jeffrey/.jenv/shims:/Users/jeffrey/.jenv/bin:/opt/homebrew/opt/imagemagick/bin:/opt/homebrew/Cellar/pyenv-virtualenv/1.4.0/shims:/Users/jeffrey/.pyenv/shims:/opt/homebrew/opt/openssl@3/bin:/Users/jeffrey/.rbenv/shims:/Users/jeffrey/.rbenv/bin:/Users/jeffrey/google-cloud-sdk/bin:/Applications/cmux.app/Contents/Resources/bin:/Users/jeffrey/Library/pnpm:/Users/jeffrey/.nvm/versions/node/v24.13.0/bin:/Users/jeffrey/.cops/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/opt/pmk/env/global/bin:/Library/Apple/usr/bin:/Library/TeX/texbin:/Users/jeffrey/.cargo/bin:/Users/jeffrey/Library/Application Support/JetBrains/Toolbox/scripts:/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/bin:/Users/jeffrey/xcode-projects/marshroom/cli" npm run ci; architect review APPROVED.
Not-tested: Live Seoul Open API error response from production service.

* Reject ambiguous Seoul Bike integer input

Tighten the public Seoul Bike query boundary so malformed integer strings cannot be partially parsed into valid requests.

Constraint: PR #277 review found parseInt accepted partially numeric query values on Seoul Bike routes.\nRejected: Keep parseInt with bounds checks | bounds still allow misleading values like 10abc and 1.5.\nConfidence: high\nScope-risk: narrow\nDirective: Keep Seoul Bike public query aliases strict; do not reintroduce partial numeric parsing.\nTested: node --test packages/k-skill-proxy/test/server.test.js --test-name-pattern 'seoul bike'; PYTHONPATH=.:scripts python3 -m unittest scripts.test_seoul_bike; explicit app.inject invalid-query smoke; PATH="/Users/jeffrey/.pyenv/versions/3.11.9/bin:/Users/jeffrey/.codex/tmp/arg0/codex-arg0uv50Mt:/opt/homebrew/lib/node_modules/@openai/codex/node_modules/@openai/codex-darwin-arm64/vendor/aarch64-apple-darwin/path:/Users/jeffrey/.cmuxterm/omo-bin:/opt/homebrew/share/android-commandlinetools/platform-tools:/opt/homebrew/share/android-commandlinetools/emulator:/opt/homebrew/share/android-commandlinetools/cmdline-tools/latest/bin:/Users/jeffrey/.local/bin:/Users/jeffrey/.bun/bin:/opt/homebrew/opt/node@22/bin:/opt/homebrew/opt/openjdk@21/bin:/opt/homebrew/opt/postgresql@18/bin:/Users/jeffrey/.jenv/shims:/Users/jeffrey/.jenv/bin:/opt/homebrew/opt/imagemagick/bin:/opt/homebrew/Cellar/pyenv-virtualenv/1.4.0/shims:/Users/jeffrey/.pyenv/shims:/opt/homebrew/opt/openssl@3/bin:/Users/jeffrey/.rbenv/shims:/Users/jeffrey/.rbenv/bin:/Users/jeffrey/google-cloud-sdk/bin:/Applications/cmux.app/Contents/Resources/bin:/Users/jeffrey/Library/pnpm:/Users/jeffrey/.nvm/versions/node/v24.13.0/bin:/Users/jeffrey/.cops/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/opt/pmk/env/global/bin:/Library/Apple/usr/bin:/Library/TeX/texbin:/Users/jeffrey/.cargo/bin:/Users/jeffrey/Library/Application Support/JetBrains/Toolbox/scripts:/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/bin:/Users/jeffrey/xcode-projects/marshroom/cli" npm run ci\nNot-tested: live hosted Seoul Open API traffic

* Protect hosted Seoul Bike proxy secrets

Sanitize Seoul Bike upstream fetch and parse failures before they can reach the global error handler, and reject blank nearby coordinates before JavaScript can coerce them to zero.\n\nConstraint: PR #277 round-3 review found server-side Seoul Open API keys could leak through exception messages containing keyed upstream URLs.\nRejected: Letting the global error handler format Seoul Bike upstream exceptions | it echoes exception messages and can expose the hosted proxy API key.\nConfidence: high\nScope-risk: narrow\nDirective: Keep server-side API-key-bearing upstream URLs out of client-visible error messages and logs for hosted no-user-key routes.\nTested: node --test packages/k-skill-proxy/test/server.test.js --test-name-pattern 'seoul bike'; PYTHONPATH=.:scripts python3 -m unittest scripts.test_seoul_bike; explicit app.inject smoke for sanitized Seoul Bike failures and blank coordinates; local fake-proxy seoul-bike nearby smoke; PATH="/Users/jeffrey/.pyenv/versions/3.11.9/bin:/Users/jeffrey/.codex/tmp/arg0/codex-arg0mxZmWx:/opt/homebrew/lib/node_modules/@openai/codex/node_modules/@openai/codex-darwin-arm64/vendor/aarch64-apple-darwin/path:/Users/jeffrey/.cmuxterm/omo-bin:/opt/homebrew/share/android-commandlinetools/platform-tools:/opt/homebrew/share/android-commandlinetools/emulator:/opt/homebrew/share/android-commandlinetools/cmdline-tools/latest/bin:/Users/jeffrey/.local/bin:/Users/jeffrey/.bun/bin:/opt/homebrew/opt/node@22/bin:/opt/homebrew/opt/openjdk@21/bin:/opt/homebrew/opt/postgresql@18/bin:/Users/jeffrey/.jenv/shims:/Users/jeffrey/.jenv/bin:/opt/homebrew/opt/imagemagick/bin:/opt/homebrew/Cellar/pyenv-virtualenv/1.4.0/shims:/Users/jeffrey/.pyenv/shims:/opt/homebrew/opt/openssl@3/bin:/Users/jeffrey/.rbenv/shims:/Users/jeffrey/.rbenv/bin:/Users/jeffrey/google-cloud-sdk/bin:/Applications/cmux.app/Contents/Resources/bin:/Users/jeffrey/Library/pnpm:/Users/jeffrey/.nvm/versions/node/v24.13.0/bin:/Users/jeffrey/.cops/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/opt/pmk/env/global/bin:/Library/Apple/usr/bin:/Library/TeX/texbin:/Users/jeffrey/.cargo/bin:/Users/jeffrey/Library/Application Support/JetBrains/Toolbox/scripts:/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/bin:/Users/jeffrey/xcode-projects/marshroom/cli" npm run ci.\nNot-tested: Live Seoul Open API network failure from production Cloud Run.
2026-05-22 13:54:36 +09:00
github-actions[bot]
6551004967
chore: version packages (#278)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-05-22 11:42:03 +09:00
Jeffrey (Dongkyu) Kim
01cd887579
release: dev → main — Cloud Run 자동 배포 전환 + 신규 스킬 다수 (#276)
* docs(flight-ticket-search): register skill in README table and add feature guide

PR #224 머지 시 README "어떤 걸 할 수 있나" 표와 "포함된 기능" 리스트, 그리고
docs/features/flight-ticket-search.md 가이드가 등록되지 않아 main에 있는 다른
모든 스킬과 달리 사용자/에이전트가 README만 봐서는 이 스킬을 발견할 수 없는
상태였다. 누락분을 hotfix로 보강한다.

- README 표에 `flight-ticket-search` 행 추가 (마이리얼트립 옆 항공 클러스터)
- README "포함된 기능" 리스트에 가이드 링크 추가
- docs/features/flight-ticket-search.md 신규 작성:
  · 사용 시나리오, 구현 표면(fast-flights==2.2, 사용자 venv 격리)
  · search / compare-month / compare-range / compare-years CLI 예시
  · 응답 필드, IATA 입력 가이드, 예약 링크 정책
  · 검증된 노선 목록, 실패 모드, 비범위, 출처

검증:
- node --test scripts/skill-docs.test.js → 138/138 pass
- ./scripts/validate-skills.sh → skill layout looks valid

코드 변경 없음 → changeset 불필요.

* feat(daiso-product-search): replace blocked-API fallback with Bearer token auth

selStrPkupStck는 더 이상 차단 상태가 아니며, /api/auth/request로 비로그인 JWT를
발급받아 AES-128-CBC(키: PRE_AUTH_ENC_KEY)로 암호화한 Bearer 토큰으로 접근한다.
403 응답 시 토큰을 재발급해 1회 재시도한다. pickupEligibility(selPkupStr) 폴백
로직은 제거했다.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Preserve Daiso pickup answers when Bearer auth degrades

Keep exact stock lookup on the official Bearer-token path while restoring the public selPkupStr fallback for repeated auth blocks.

Constraint: PR #250 review required Bearer auth to remain primary without removing the resilient pickup eligibility API.

Rejected: Throwing after the retry | it collapses callers back to a brittle single upstream-auth dependency.

Confidence: high

Scope-risk: narrow

Directive: Keep pickupStock quantity semantics separate from pickupEligibility yes/no fallback.

Tested: node --test packages/daiso-product-search/test/index.test.js; npm test --workspace daiso-product-search; npm run lint --workspace daiso-product-search; npm run ci; live lookupStoreProductAvailability smoke for 강남역2호점 / VT 리들샷 100.

Not-tested: Live forced 403 from Daiso upstream; covered with injected fetch regression tests.

* Prove Daiso stock retry sends auth headers

Strengthen the retry regression so the Bearer-token contract cannot regress while still returning success from mocked stock responses.\n\nConstraint: PR #250 review requested explicit Authorization, X-DM-UID, and request body assertions on the retry path.\nRejected: Counting requests only | it allowed header/body regressions to pass.\nConfidence: high\nScope-risk: narrow\nDirective: Keep auth-header assertions on both initial and retry stock requests when editing this flow.\nTested: node --test packages/daiso-product-search/test/index.test.js; npm test --workspace daiso-product-search; npm run lint --workspace daiso-product-search; npm run ci; live lookupStoreProductAvailability smoke for 강남역2호점 / VT 리들샷 100; repeated-403 fixture probe.\nNot-tested: Live repeated upstream 403 because forcing Daiso production auth failure is not available without changing upstream state.

* Preserve Daiso caller headers through Bearer stock lookup

Keep advanced caller headers on the authenticated stock endpoint while generated Bearer and X-DM-UID values remain authoritative. Document the degraded selPkupStr fallback order in skill and source docs so the public workflow matches the restored API surface.\n\nConstraint: PR #250 review required resilient Bearer-primary stock lookup plus selPkupStr fallback and header/body contract coverage.\nRejected: Replacing caller headers with only auth headers | It regressed tracing/test-control header pass-through.\nConfidence: high\nScope-risk: narrow\nDirective: Keep Authorization and X-DM-UID generated by the auth flow even when callers provide same-named headers.\nTested: node --test packages/daiso-product-search/test/index.test.js; npm test --workspace daiso-product-search; npm run lint --workspace daiso-product-search; node --test scripts/skill-docs.test.js; npm run ci; live lookupStoreProductAvailability smoke for 강남역2호점 / VT 리들샷 100.\nNot-tested: Forced live upstream repeated 403; covered by injected fixture tests.

* fix(danawa-price-search): capture .ico.* payment-condition badges and surface as row labels

PR #226 row 파서에 결제조건 배지(`.ico.cash`/`.ico.point`/`.ico.coupon`/`.ico.card`) selector가 누락돼, 카드 결제 불가능한 현금/쿠폰/포인트 전용가가 일반 최저가로 노출되는 결함을 고친다.

- `offers()` row 파싱부에 결제조건 배지 화이트리스트 캡처 블록 추가 (클래스 `cash`/`point`/`coupon`/`discount`/`card`/`membership` 또는 텍스트 `현금`/`포인트`/`쿠폰`/`할인`만 인정 — 빠른배송/안내/상품리뷰 노이즈 차단)
- row dict 신규 필드 6개: `payment_badges`, `cash_only`, `point_only`, `coupon_only`, `card_only_badge`, `is_conditional_price`
- 반환 dict에 `normal_count`, `conditional_count` 추가
- `SKILL.md` / `docs/features/danawa-price-search.md` 갱신 (Output shape · Response style · Workflow · Failure modes에 결제조건 정책과 표 예시 명시)

정렬 정책은 그대로 `total_price` 단일 기준이며, 결제조건은 row 단위 플래그/라벨로만 노출해 호출자가 결제수단에 맞춰 직접 판단하도록 한다.

회귀 (pcode=75001853, 갤럭시 S25 256GB 자급제 `offers --limit 5`):
- 1위 킴스클럽 979,000원 / `cash_only=True` / `payment_badges=["현금"]`
- 2위 롯데ON 1,072,080원 / `cash_only=False` / `payment_badges=[]`
- 3~5위 일반가 row 모두 `payment_badges` 빈 리스트 (노이즈 0건)

Closes #252

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Ensure captured Danawa payment badges stay conditional

Classify every whitelisted payment badge into normalized condition types so callers cannot count captured discount, membership, or text-only card rows as normal prices.

Constraint: PR #253 review required TDD follow-up on feature/#252 without changing total_price sorting.\nRejected: Removing discount and membership from the whitelist | would lose Danawa condition labels already captured by the parser.\nConfidence: high\nScope-risk: narrow\nDirective: Keep payment_badge whitelist and payment_condition_types in sync whenever adding new badge classes or text keywords.\nTested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_danawa_price_search; live offers 75001853 --limit 5; npm run lint; npm run typecheck; npm run test; architect verification CLEAR.\nNot-tested: Danawa markup variants not represented by current live page or synthetic badge fixtures.

* Keep icon-only Danawa payment badges visible

Class-only Danawa payment icons can carry eligibility information without visible text, so synthesize display labels from the same normalized condition map used for types and booleans. This keeps raw row labels, condition fields, and returned-window counts aligned for downstream table renderers.\n\nConstraint: PR #253 review follow-up requires TDD coverage before parser changes.\nRejected: Leaving payment_badges text-only | icon-only conditional rows would still render without visible payment labels.\nConfidence: high\nScope-risk: narrow\nDirective: Derive future payment badge labels, types, and booleans from one canonical mapping.\nTested: python3 -m py_compile danawa-price-search/scripts/danawa_search.py scripts/test_danawa_price_search.py; PYTHONPATH=.:scripts python3 -m unittest scripts.test_danawa_price_search; python3 danawa-price-search/scripts/danawa_search.py offers 75001853 --limit 5; npm run lint; npm run typecheck; npm run test\nNot-tested: Danawa icon-only markup was verified with synthetic fixtures rather than a live page snapshot.

* Merge pull request #249 from NomaDamas/feature/#248

Feature/#248

* Restore SH notice lookup without proxy policy drift

Reintroduce SH notice search as a direct public HTML client so the skill complies with the free-API proxy boundary while preserving verifiable keyword, pagination, and attachment behavior.

Constraint: i-sh.co.kr board is public unauthenticated HTML, so k-skill-proxy must not host the scraper.\nRejected: Re-adding /v1/sh-notice proxy routes | public HTML scraping in proxy violates repository policy.\nConfidence: high\nScope-risk: moderate\nDirective: Keep SH public HTML access local/direct unless a key-required official free API is discovered and documented.\nTested: npm run ci; npm run lint --workspace sh-notice-search; npm test --workspace sh-notice-search; live SH smoke for 행복주택, 매입임대, 신혼희망타운, page 1/page 5, 1/6/9/11/0 attachment details.\nNot-tested: authenticated SH flows, 청약 application/submission, direct attachment downloads.

* Preserve public SH helper semantics

Route exported URL builders through the same normalization as the CLI/API so natural category aliases cannot bypass srchTp title narrowing or category mapping.\n\nConstraint: PR #254 review found exported helper callers could pass Korean/English public category inputs and get broken or broadened SH URLs.\nRejected: Keep normalized-only fast paths | exported helpers are public API and must protect natural inputs.\nConfidence: high\nScope-risk: narrow\nDirective: Keep exported helper behavior aligned with normalizeSearchOptions and normalizeDetailOptions when adding new public aliases.\nTested: npm test --workspace sh-notice-search; npm run lint --workspace sh-notice-search; npm run typecheck; npm run ci; node helper smoke for 임대 search/detail URLs.\nNot-tested: Live SH network smoke was not rerun for this helper-only change.

* Preserve SH parser helper aliases

Route exported parser helpers through the same public normalizers used by the SH fetch and URL-builder APIs so natural category aliases stay consistent across the package surface.

Constraint: PR #254 Round 2 review found parser helpers still treated raw category aliases as pre-normalized inputs.
Rejected: Keep parser helpers normalized-only | inconsistent with exported URL builders and public helper ergonomics.
Confidence: high
Scope-risk: narrow
Directive: Keep exported SH helper entry points on canonical normalizeSearchOptions/normalizeDetailOptions unless a separate internal-only API is introduced.
Tested: npm test --workspace sh-notice-search; npm run lint --workspace sh-notice-search; npm run typecheck; npm pack --workspace sh-notice-search --dry-run; npm run ci; parser smoke for Korean 임대 list/detail helpers; Ralph architect verification CLEAR; post-deslop regression npm run ci
Not-tested: Live SH network smoke for this follow-up; fixture and injected-fetch coverage exercised the helper contract.

* Make SH parser failures explicit

Warn when SH returns block or maintenance HTML without the expected public board markup, and constrain exposed preview links to the SH converter origin/path.\n\nConstraint: Round 3 review required TDD coverage for block/maintenance HTML and untrusted preview URLs.\nRejected: Throwing on unexpected HTML | Existing parser helpers return partial fixture-friendly results, so warnings preserve compatibility while exposing failure evidence.\nConfidence: high\nScope-risk: narrow\nDirective: Keep SH public HTML lookup direct; do not add proxy routing unless a key-required official free API is adopted.\nTested: npm run lint --workspace sh-notice-search; npm test --workspace sh-notice-search; npm run typecheck; npm pack --workspace sh-notice-search --dry-run; npm run ci; Node smoke for blocked HTML warnings and external preview filtering.\nNot-tested: Live blocked/NetFunnel SH response, because no live blocked page was available during implementation.

* ci: install beautifulsoup4 so danawa price search tests can import bs4

The new scripts/test_danawa_price_search.py imports danawa_search.py,
which requires beautifulsoup4. CI only runs npm ci, so the bs4 import
fails with 'beautifulsoup4 is required: python -m pip install
beautifulsoup4' and the validate job exits with code 1.

Install beautifulsoup4 via pip before running npm run ci so the
Python test suite can import danawa_search and run the new payment
badge regression tests.

* Revert "ci: install beautifulsoup4 so danawa price search tests can import bs4"

This reverts commit 8330e5adf7.

* test: install beautifulsoup4 inside npm test before Python tests

The new scripts/test_danawa_price_search.py imports danawa_search.py,
which requires beautifulsoup4. CI runs npm ci + npm run ci and does
not install Python packages, so the bs4 import fails at module load.

Install beautifulsoup4 via 'pip install --user' as the first step of
the test script so it is available when Python unittests import the
danawa helper. Local dev environments are unaffected because pip
install is idempotent and quiet.

* feat(qa-bot): add k-skill-qa-bot under tools/

External macOS daemon that clones NomaDamas/k-skill main every 3 days, runs
each skill through codex exec, has an LLM judge grade pass/fail/skip via
codex exec --output-schema, and files dedup'd GitHub issues for true failures.

Layout:
- install.sh copies tools/k-skill-qa-bot/ to ~/.local/share/k-skill-qa-bot/
  and registers a LaunchAgent at ~/Library/LaunchAgents/.
- update-clone.sh has a hard guard: refuses any K_SKILL_CLONE outside
  K_QA_HOME/k-skill-clone unless ALLOW_EXTERNAL_CLONE_TARGET=1.
- Force-skip 10 destructive/login-required skills (ktx-booking, srt-booking,
  catchtable-sniper, kakaotalk-mac, hipass-receipt, toss-securities, etc.)
  so the bot never triggers reservation abuse.
- Deprecated skills (strike-through + 지원 중단 in README) auto-detected
  and skipped, never failed.
- First-run safety: CREATE_ISSUES=false by default.
- mkdir-based concurrency lock with atomic stale reclaim.
- Issue dedup: sha1(skill_name + symptom_class)[:12] body marker.
- Deterministic gates override LLM judge to FAIL on exit_code != 0, missing
  VERDICT line, or near-timeout duration.

* Support nearby ER status checks

Add an E-Gen based emergency-room skill that resolves a user location, queries the public nearby emergency-room list, and reports operation flags while documenting that exact remaining bed counts are not exposed by this surface.

Constraint: Issue #255 requested NEMC emergency bed status using public monitoring/E-Gen surfaces.
Rejected: Scraping private monitoring dashboards or claiming exact bed utilization | public endpoints expose operation flags, not per-hospital remaining bed counts.
Confidence: high
Scope-risk: narrow
Directive: Preserve the public-data limitation text unless a verified official bed-count endpoint is added.
Tested: npm run lint --workspace emergency-room-beds; npm test --workspace emergency-room-beds; node --test scripts/skill-docs.test.js; npm run typecheck; npm pack --workspace emergency-room-beds --dry-run; ./scripts/validate-skills.sh; live E-Gen coordinate smoke.
Not-tested: npm run ci end-to-end due local Python 3.14 pip/pyexpat import error before tests.

* Prevent ER status ambiguity from reaching users

Constraint: Health-adjacent public E-Gen/Kakao data can be absent, delayed, schema-drifted, or partially unknown.

Rejected: Mapping all non-Y operation flags to false | It misrepresents missing upstream data as a negative operating status.

Rejected: Treating unknown E-Gen payloads as empty results | It hides upstream failure behind a false no-results response.

Confidence: high

Scope-risk: narrow

Directive: Keep unknown health availability data explicit and preserve upstream failure evidence.

Tested: npm run lint --workspace emergency-room-beds; npm test --workspace emergency-room-beds; node --test scripts/skill-docs.test.js; npm run typecheck; npm pack --workspace emergency-room-beds --dry-run; ./scripts/validate-skills.sh; direct Node smoke for tri-state/schema/coordinate guards.

Not-tested: npm run ci due pre-existing local Python 3.14 pyexpat/libexpat bootstrap failure noted on PR.

Co-authored-by: OmX <omx@oh-my-codex.dev>

* fix(ci): exclude tools/ from skill validator

The tools/ directory hosts repo tooling (e.g. k-skill-qa-bot), not
skills, so validate-skills.sh should skip it like other non-skill
top-level directories.

* 영화관 검색 스킬 추가 (#260)

* Add korean cinema search skill

* Document playDate for cinema skill

* feat(kstartup-search): 창업진흥원 K-Startup 조회 스킬 + 프록시 라우트 4종 (#259)

* feat(kstartup-search): 창업진흥원 K-Startup 조회 스킬과 프록시 라우트 추가

공공데이터포털 dataset 15125364 (창업진흥원_K-Startup(사업소개,사업공고,콘텐츠 등)_조회서비스) 의
4개 endpoint 를 k-skill-proxy 경유로 조회하는 스킬을 추가한다.

- 신규 라우트: GET /v1/kstartup/{business-info,announcements,contents,statistics}
  - 각각 getBusinessInformation01/getAnnouncementInformation01/getContentInformation01/
    getStatisticalInformation01 으로 중계
  - ServiceKey 는 서버 측 DATA_GO_KR_API_KEY 로 주입, returnType=json 강제
  - 정상 응답만 캐시, data.go.kr 에러 envelope (resultCode != "00", errMsg 등) 은 캐시 우회
- helper: kstartup-search/scripts/run_kstartup.py (stdlib only)
  - 일반 조회는 hosted proxy 사용 → 사용자 키 불필요
  - --direct 옵션은 사용자가 본인 KSKILL_KSTARTUP_API_KEY (혹은 DATA_GO_KR_API_KEY) 로
    upstream 직접 호출 + --dry-run 시 키 redact
- 입력 검증: page/perPage 정수·범위, YYYYMMDD 날짜 + 시작일 ≤ 종료일, Y/N 대문자화,
  텍스트 필드 길이 상한, biz_yr 4자리
- 테스트: k-skill-proxy 서버 테스트 10건 신규 (normalizer, 라우트, 캐시 분리,
  returnType=json 강제, 503/400/502, 키 누수 회귀), Python unittest 13건
- 문서: SKILL.md, docs/features/kstartup-search.md, README 표/리스트,
  docs/sources.md, .changeset/kstartup-search.md (k-skill-proxy minor)

* docs(kstartup-search): docs/setup·security·k-skill-setup·proxy README 에 K-Startup 항목 추가

seoul-density · KOSIS · NTS 선례와 동일한 위치·문구로 다음을 보강한다.

- docs/setup.md: dotenv 예시에 KSKILL_KSTARTUP_API_KEY 추가, credential 표에 K-Startup 행 추가, "다음에 볼 문서" 리스트 추가
- docs/security-and-secrets.md: standard variable names 에 KSKILL_KSTARTUP_API_KEY 추가, hosted proxy 사용 스킬 목록·proxy 운영 prose 에 K-Startup 추가, dotenv 예시 추가
- k-skill-setup/SKILL.md: credential resolution prose 와 시크릿 요약 표에 K-Startup 안내 추가
- packages/k-skill-proxy/README.md: 라우트 목록에 /v1/kstartup/{business-info,announcements,contents,statistics} 추가
- docs/features/k-skill-proxy.md: 라우트 목록에 같은 4개 추가

* fix(kstartup-search): strict calendar-date validation in Python helper

validate_yyyymmdd() previously only checked month in [1,12] and day in [1,31],
which accepted impossible dates like 20240230 or 20240431 in --direct mode.
The proxy-side normalizer in packages/k-skill-proxy/src/kstartup.js already
uses Date.UTC() to reject such inputs, so this aligns the --direct path with
the proxy path and eliminates validator drift.

Uses datetime.date(year, month, day) and raises HelperError on ValueError.

Adds regression test covering impossible calendar dates (Feb 30, Apr 31,
month 13, day 0) and the leap-year boundary (2024-02-29 valid, 2023-02-29
not).

---------

Co-authored-by: Jeffrey (Dongkyu) Kim <vkehfdl1@gmail.com>

* fix(qa-bot): upgrade judge to gpt-5.5 and run codex with sandbox bypass

PR #257 follow-up. Two changes:

1. JUDGE_MODEL default: gpt-5.4-mini -> gpt-5.5

   The cheaper judge was misclassifying every wrong-output verdict because
   the offline matcher fell through to the dumb 'VERDICT: FAIL in transcript'
   check. Re-running the same 10 historical fail cases with gpt-5.5 +
   real LLM judge correctly reclassified 7 of them as pass (the codex agent
   actually accomplished the skill goal) and the remaining 3 as
   network-error / partial-success / skip with accurate reasons.

2. Drop -s read-only, add --dangerously-bypass-approvals-and-sandbox

   The read-only codex sandbox was triggering spurious DNS resolution
   failures inside the test runs (host blocked at the syscall level even
   for legitimate proxy / public-API calls). Live re-test with the bypass
   flag and provider pin produced clean transcripts: cheap-gas-nearby,
   daangn-realty-search, han-river-water-level, naver-news-search,
   naver-shopping-search, seoul-density, seoul-subway-arrival all PASS.
   The QA bot is sandboxed externally by launchd anyway.

3. New CODEX_PROVIDER env (default: openai)

   Lets users pin the codex model_provider explicitly so the bot does not
   accidentally route through a private OpenAI-compatible proxy that may
   not have keys registered for all model names.

* Add Ohou today deal skill

* fix spacing in package.json

* fix(qa-bot): per-skill test_prompt overrides and smarter judge

11 skills that need specific inputs (not just a 'demonstrate' query) now
ship with a hardcoded test_prompt in config/skill-overrides.yml:

  flight-ticket-search           ICN -> NRT, 2026-08-20 one-way
  nts-business-registration      124-81-00998 (Samsung Electronics)
  korean-stock-search            005930 Samsung 5-day quote
  joseon-sillok-search           키워드 훈민정음
  korean-law-search              산업안전보건법 제5조
  library-book-search            코스모스 칼 세이건
  lotto-results                  latest round
  k-schoollunch-menu             서울특별시교육청 초등학교 오늘 식단
  delivery-tracking              CJ dummy invoice (negative case ok)
  ticket-availability            YES24 / 인터파크 sample
  zipcode-search                 서울특별시 강남구 테헤란로 152

These were previously synthesized from the SKILL.md first When-to-use bullet,
which is a one-line teaser without concrete inputs. The agent would then
either ask the user for the missing input (partial-success) or fall back
to a generic demo (often producing a VERDICT: FAIL response). Both got
mis-classified as fail by the judge.

qa_utils.synthesize_test_prompt now honors default_inputs.test_prompt as a
verbatim override (only appending the VERDICT line if the override does not
already include it).

Two additional fixes for negative-case correctness:

1. judge-prompt.md: explicitly tells the judge that the agent's literal
   VERDICT: PASS / VERDICT: FAIL is just a hint, not binding. A skill that
   correctly returns 'no such business number' or 'invoice not found' for
   a deliberately invalid input is PASS, not fail.

2. judge-skill.py: drop the deterministic gate that flipped pass to fail
   when 'VERDICT: PASS' literal was missing from the transcript. That gate
   was producing false fails for negative-case tests where the agent
   correctly responded with VERDICT: FAIL because the skill rejected an
   invalid input. The judge LLM (gpt-5.5) is now trusted to evaluate the
   transcript against the SKILL.md 'Done when' criteria.

Verified live:

- nts-business-registration with valid number  -> pass/success (0.99)
- nts-business-registration with fake number   -> pass/success (0.99)
- flight-ticket-search ICN->NRT 2026-08-20     -> pass/success (0.99)

* fix(ohou-today-deal): address PR #264 review (live UA, explicit feed selection, argv validators)

- HIGH: switch fetch_html() to well-formed bot UA with contact URL
  (k-skill-ohou-today-deal/1.0 (+https://github.com/NomaDamas/k-skill)).
  ohou.se Akamai bot manager 403s anonymous UAs but allows identified
  bot UAs that include a contact URL. Live default workflow now returns
  74 deals end-to-end instead of failing with HTTP 403.
- MEDIUM: extract_deals() now explicitly selects React Query entries with
  queryKey == ['today-deal-feed'] or ['special-today-deal-feed'] and
  reads only state.data.todayDealFeed.slots[type=='DEAL']. Unrelated
  DEAL-shaped nodes from navigation/banner modules are excluded.
  Legacy fixture/JSON-payload fallback path preserved for tests that
  construct simplified payloads.
- LOW: --limit now requires a positive integer; --min-discount is
  constrained to 0..100. Both validated via argparse.ArgumentTypeError
  so users get a clear CLI error instead of silent slicing or nonsensical
  thresholds.
- Tests: add 9 new unit tests covering explicit feed selection,
  navigation/GOODS exclusion, fallback compatibility, and argv validators.
  Strengthen skill-docs.test.js to lock the special-today-deal-feed
  surface and well-formed UA signature.
- Docs: update SKILL.md and feature doc to document the explicit
  today-deal-feed + special-today-deal-feed extraction boundary and the
  Akamai UA policy.

* Merge pull request #263 from NomaDamas/feature/#257

Feature/#257

* Feature/#256 (#266)

* Enable public local-election candidate lookups

Add an NEC integrated-search skill and helper package so agents can answer 지방선거 후보자 lookup requests without credentials or proxy routes.

Constraint: Issue #256 requested TDD, Ralph completion, branch feature/#256, and PR targeting dev.

Rejected: k-skill-proxy route | NEC integrated candidate search is public and requires no API key.

Confidence: high

Scope-risk: moderate

Directive: Keep the helper read-only and do not automate NEC login, CAPTCHA, filing, or privileged election workflows.

Tested: git diff --check; node --test packages/local-election-candidate-search/test/index.test.js; npm run lint --workspace local-election-candidate-search; npm run test --workspace local-election-candidate-search; npm pack --workspace local-election-candidate-search --dry-run; node packages/local-election-candidate-search/src/cli.js 오세훈 --election 시도지사 --region 서울 --limit 1; PATH=/usr/bin:/bin:/usr/sbin:/sbin:/opt/homebrew/bin:/Users/jeffrey/.codex/tmp/arg0/codex-arg0a6JueA:/opt/homebrew/lib/node_modules/@openai/codex/node_modules/@openai/codex-darwin-arm64/vendor/aarch64-apple-darwin/path:/Users/jeffrey/.cmuxterm/omo-bin:/opt/homebrew/share/android-commandlinetools/platform-tools:/opt/homebrew/share/android-commandlinetools/emulator:/opt/homebrew/share/android-commandlinetools/cmdline-tools/latest/bin:/Users/jeffrey/.local/bin:/Users/jeffrey/.bun/bin:/opt/homebrew/opt/node@22/bin:/opt/homebrew/opt/openjdk@21/bin:/opt/homebrew/opt/postgresql@18/bin:/Users/jeffrey/.jenv/shims:/Users/jeffrey/.jenv/bin:/opt/homebrew/opt/imagemagick/bin:/opt/homebrew/Cellar/pyenv-virtualenv/1.4.0/shims:/Users/jeffrey/.pyenv/shims:/opt/homebrew/opt/openssl@3/bin:/Users/jeffrey/.rbenv/shims:/Users/jeffrey/.rbenv/bin:/Users/jeffrey/google-cloud-sdk/bin:/Applications/cmux.app/Contents/Resources/bin:/Users/jeffrey/Library/pnpm:/Users/jeffrey/.nvm/versions/node/v24.13.0/bin:/Users/jeffrey/.cops/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/opt/pmk/env/global/bin:/Library/Apple/usr/bin:/Library/TeX/texbin:/Users/jeffrey/.cargo/bin:/Users/jeffrey/Library/Application Support/JetBrains/Toolbox/scripts:/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/bin:/Users/jeffrey/xcode-projects/marshroom/cli npm run ci

Not-tested: Exhaustive NEC markup variants for every historical election type.

Co-authored-by: OmX <omx@oh-my-codex.dev>

* Enforce fail-closed candidate identity parsing

Constraint: PR #266 review required exact candidate-name matching and CLI help regression coverage.\nRejected: fallback-to-query-name on missing upstream markup | it can mislabel unrelated candidates as exact matches.\nConfidence: high\nScope-risk: narrow\nDirective: Keep NEC parser changes fail-closed when candidate identity cannot be parsed.\nTested: git diff --check; node --test packages/local-election-candidate-search/test/index.test.js; npm run lint --workspace local-election-candidate-search; npm run test --workspace local-election-candidate-search; npm pack --workspace local-election-candidate-search --dry-run; live CLI smoke for 오세훈; CLI --help smoke.\nNot-tested: repo-wide npm run ci remains blocked by pre-existing missing SKILL.md: ohou-today-deal.

* Preserve unique candidate lookup results

Deduplicate parsed NEC candidate/election rows before applying user limits, and make expected CLI validation failures concise by default while keeping an explicit debug stack escape hatch.

Constraint: PR #266 round-2 follow-up requested TDD fixes for duplicate NEC rows and CLI validation UX.\nRejected: Deduplicating after limit | would still allow duplicates to crowd out unique rows.\nRejected: Always printing stack traces | exposes local paths for normal user-input failures.\nConfidence: high\nScope-risk: narrow\nDirective: Keep dedupe keys stable enough to avoid collapsing legitimately distinct historical election rows.\nTested: git diff --check; node --test packages/local-election-candidate-search/test/index.test.js; npm run lint --workspace local-election-candidate-search; npm run test --workspace local-election-candidate-search; npm pack --workspace local-election-candidate-search --dry-run; live 오세훈 smoke; live 김동연 duplicate repro; CLI no-args/help.\nNot-tested: Full npm run ci remains blocked by pre-existing missing SKILL.md: ohou-today-deal.

* Prevent filtered NEC lookup false negatives

Fix the candidate parser so documented education-superintendent and filtered local-election lookups return bounded, evidence-backed results instead of silently dropping valid rows.

Constraint: PR #266 round-3 review required TDD, Ralph verification, and branch update for issue #256.

Rejected: Full NEC pagination in this follow-up | broader than the approved change; bounded 100-row fetch now avoids user-limit false negatives and warns when capped.

Confidence: high

Scope-risk: narrow

Directive: Preserve exact-name fail-closed parsing and count raw parsed upstream rows before cap-warning decisions.

Tested: git diff --check; node --test packages/local-election-candidate-search/test/index.test.js; npm run lint --workspace local-election-candidate-search; npm run test --workspace local-election-candidate-search; npm pack --workspace local-election-candidate-search --dry-run; live CLI smokes for 오세훈, 조희연, 김동연; CLI help/no-args checks; architect verification CLEAR.

Not-tested: Full npm run ci remains blocked by pre-existing repo-wide missing SKILL.md: ohou-today-deal.

---------

Co-authored-by: OmX <omx@oh-my-codex.dev>

* chore(changesets): rename daiso bearer-auth changeset to avoid name collision with consumed main release

PR #245 already consumed .changeset/issue-207-daiso-pickup-eligibility.md
into daiso-product-search v0.3.0 on main. The dev branch later modified that
same changeset file in d7263a5 to describe the newer Bearer-auth fix, which
collides with main's deletion on the next dev→main sync.

Renaming the still-unreleased Bearer-auth note to
issue-207-daiso-bearer-auth.md preserves the release entry for the next
version-packages run and clears the modify/delete conflict on PR #271
without losing the changelog content.

* fix(kstartup-search): implement promised client-side filter to deliver on SKILL.md L121

Live data revealed two unmet contracts in the kstartup-search helper:

1. SKILL.md L121 promised the helper re-applies supt_regin / aply_trgt /
   biz_enyy filters on the client side because K-Startup upstream ignores
   them server-side. The helper had no such logic — calling
   `--supt-regin 서울특별시 --rcrt-prgs-yn Y` returned 경북/충북/충남
   announcements as-is, silently misleading callers.

2. The upstream `supt_regin` field is stored as the short form
   (`서울`, `경기`, `충북`, ...) but every CLI example in the skill used
   the standard 광역지자체 long form (`서울특별시`), which would never
   substring-match even after a client filter was added.

Add `apply_client_filters()` that runs after `urlopen` returns. It honors
the SKILL.md contract literally: substring match per token, AND-joined
across comma-separated user values, with a 17-region (+`전국`) shortname
normalisation table so both `--supt-regin 서울특별시` and
`--supt-regin 서울` resolve to upstream's `서울`. Filtered responses
expose a new `client_filter: {fields, upstream_returned, after_filter}`
metadata block so callers can detect "first page was depleted by filter"
and page through.

Tests: 9 new ClientFilterTests + 2 normalisation tests on top of the
existing 14 (25 total, all passing).

Live smoke (against a dev proxy with DATA_GO_KR_API_KEY activated for
dataset 15125364): `--supt-regin 서울특별시 --rcrt-prgs-yn Y --per-page 10`
now returns 4 actual 서울 announcements (upstream returned 10 mixed-region
rows; client filter narrowed to 4), with detl_pg_url to k-startup.go.kr.

Confidence: high. Scope-risk: narrow — purely additive on the response
path; other endpoints (business-info / contents / statistics) pass
through unchanged.

* ci(k-skill-proxy): replace local pm2+cloudflared with Cloud Run auto-deploy via GitHub Actions

main에 머지되면 GitHub Actions가 자동으로 Workload Identity Federation으로 GCP 인증 후
Artifact Registry에 컨테이너 이미지를 빌드/푸시하고 Cloud Run(asia-northeast1) 서비스
k-skill-proxy를 재배포한다. 시크릿은 GCP Secret Manager에서 런타임에 주입된다.

- add .github/workflows/deploy-k-skill-proxy.yml (WIF, on push to main)
- add packages/k-skill-proxy/Dockerfile (multi-stage node:20-alpine, port bridge)
- add docs/deploy-k-skill-proxy.md (1회성 GCP 셋업 + 운영 점검 절차)
- remove ecosystem.config.cjs (PM2 root config)
- remove scripts/run-k-skill-proxy.sh (local secrets.env source + node launcher)
- remove wrangler devDependency (unused Cloudflare Workers CLI)
- update AGENTS.md, CLAUDE.md, CONTRIBUTING.md, docs/features/k-skill-proxy.md,
  packages/k-skill-proxy/README.md to describe the new Cloud Run + GHA flow
- clean dead k-skill-proxy-cloudrun entries from .gitignore

* docs(AGENTS): proxy 운영 전반(회전·롤백·비상 수동 배포 포함) docs/deploy-k-skill-proxy.md 참고 명시

* test(skill-docs): update stale CONTRIBUTING.md assertion for Cloud Run migration

80e7805(ci(k-skill-proxy): replace local pm2+cloudflared with Cloud Run auto-deploy)
가 CONTRIBUTING.md의 '프록시 서버 개발과 배포' 섹션을 Cloud Run + GCP Secret
Manager 흐름으로 다시 썼는데, 같은 섹션을 검증하는 skill-docs.test.js의 어서션은
구버전(`~/.local/share/k-skill-proxy`) 그대로였다. PR #276 CI에서 이 stale
어서션이 fail하여 머지를 막고 있었다.

기존 한 줄 regex(localhost 시크릿 경로)를 새 사실에 맞춰 두 개의 어서션으로 교체:

1. 프로덕션이 Google Cloud Run(asia-northeast1) + k-skill-proxy.nomadamas.org에서
   운영된다는 문구를 강제한다.
2. 시크릿이 GCP Secret Manager에 있고 운영 점검 절차가
   docs/deploy-k-skill-proxy.md에 있다는 문구를 강제한다.

이렇게 하면 문서가 다시 옛 로컬 흐름으로 돌아가거나 운영 가이드 링크가 빠지는
회귀가 발생할 때 CI가 잡아준다.

---------

Co-authored-by: arnold714 <arnold714@naver.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: chanmin <cmju@cowave.kr>
Co-authored-by: OmX <omx@oh-my-codex.dev>
Co-authored-by: hmmhmmhm/ <hmmhmmhm@naver.com>
Co-authored-by: 배기민 <53887180+BAEM1N@users.noreply.github.com>
Co-authored-by: lee-ji-hong <zhffktkdlekghksxk@naver.com>
2026-05-22 11:40:40 +09:00
Jeffrey (Dongkyu) Kim
6dc9d9d9c6 test(skill-docs): update stale CONTRIBUTING.md assertion for Cloud Run migration
80e7805(ci(k-skill-proxy): replace local pm2+cloudflared with Cloud Run auto-deploy)
가 CONTRIBUTING.md의 '프록시 서버 개발과 배포' 섹션을 Cloud Run + GCP Secret
Manager 흐름으로 다시 썼는데, 같은 섹션을 검증하는 skill-docs.test.js의 어서션은
구버전(`~/.local/share/k-skill-proxy`) 그대로였다. PR #276 CI에서 이 stale
어서션이 fail하여 머지를 막고 있었다.

기존 한 줄 regex(localhost 시크릿 경로)를 새 사실에 맞춰 두 개의 어서션으로 교체:

1. 프로덕션이 Google Cloud Run(asia-northeast1) + k-skill-proxy.nomadamas.org에서
   운영된다는 문구를 강제한다.
2. 시크릿이 GCP Secret Manager에 있고 운영 점검 절차가
   docs/deploy-k-skill-proxy.md에 있다는 문구를 강제한다.

이렇게 하면 문서가 다시 옛 로컬 흐름으로 돌아가거나 운영 가이드 링크가 빠지는
회귀가 발생할 때 CI가 잡아준다.
2026-05-21 16:11:45 +09:00
Jeffrey (Dongkyu) Kim
1d6f97bb8a Merge branch 'main' into dev: resolve release conflicts + drop pm2 leftovers
PR #271 + #272로 main에 신규 스킬 6종 + version bump가 이미 머지되어
같은 .changeset/*.md 와 package.json 이 양쪽에서 충돌. Resolution:

- .changeset/*.md : main 채택(이미 consume된 changeset 삭제 유지)
- packages/*/package.json (emergency-room-beds, local-election-candidate-search,
  sh-notice-search) : main의 bump된 버전(0.2.0) 채택
- packages/*/CHANGELOG.md : main 채택 (release-please/changeset이 생성한 내용 유지)
- root package.json : dev 채택 (8d52850 'fix spacing in package.json' 의 올바른
  4-space 들여쓰기 유지. main은 indentation fix가 lost된 상태였음)

추가 정리:
- 80e7805 'replace local pm2+cloudflared with Cloud Run' 커밋이 메시지엔
  'remove ecosystem.config.cjs / scripts/run-k-skill-proxy.sh' 라 적었으나
  실제 git rm 이 누락돼 있었음. 이번 merge 커밋에서 같이 제거.
2026-05-21 15:46:48 +09:00
Jeffrey (Dongkyu) Kim
9164835e9e docs(AGENTS): proxy 운영 전반(회전·롤백·비상 수동 배포 포함) docs/deploy-k-skill-proxy.md 참고 명시 2026-05-21 14:52:43 +09:00
Jeffrey (Dongkyu) Kim
80e7805681 ci(k-skill-proxy): replace local pm2+cloudflared with Cloud Run auto-deploy via GitHub Actions
main에 머지되면 GitHub Actions가 자동으로 Workload Identity Federation으로 GCP 인증 후
Artifact Registry에 컨테이너 이미지를 빌드/푸시하고 Cloud Run(asia-northeast1) 서비스
k-skill-proxy를 재배포한다. 시크릿은 GCP Secret Manager에서 런타임에 주입된다.

- add .github/workflows/deploy-k-skill-proxy.yml (WIF, on push to main)
- add packages/k-skill-proxy/Dockerfile (multi-stage node:20-alpine, port bridge)
- add docs/deploy-k-skill-proxy.md (1회성 GCP 셋업 + 운영 점검 절차)
- remove ecosystem.config.cjs (PM2 root config)
- remove scripts/run-k-skill-proxy.sh (local secrets.env source + node launcher)
- remove wrangler devDependency (unused Cloudflare Workers CLI)
- update AGENTS.md, CLAUDE.md, CONTRIBUTING.md, docs/features/k-skill-proxy.md,
  packages/k-skill-proxy/README.md to describe the new Cloud Run + GHA flow
- clean dead k-skill-proxy-cloudrun entries from .gitignore
2026-05-21 13:45:06 +09:00
github-actions[bot]
34a0928edd
chore: version packages (#272)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-05-19 11:10:07 +09:00
Jeffrey (Dongkyu) Kim
271ea185c4
Sync dev → main: 신규 스킬 6종 (emergency-room-beds · korean-cinema-search · kstartup-search · local-election-candidate-search · ohou-today-deal · sh-notice-search) + k-skill-qa-bot + daiso/danawa 보강 (#271)
* docs(flight-ticket-search): register skill in README table and add feature guide

PR #224 머지 시 README "어떤 걸 할 수 있나" 표와 "포함된 기능" 리스트, 그리고
docs/features/flight-ticket-search.md 가이드가 등록되지 않아 main에 있는 다른
모든 스킬과 달리 사용자/에이전트가 README만 봐서는 이 스킬을 발견할 수 없는
상태였다. 누락분을 hotfix로 보강한다.

- README 표에 `flight-ticket-search` 행 추가 (마이리얼트립 옆 항공 클러스터)
- README "포함된 기능" 리스트에 가이드 링크 추가
- docs/features/flight-ticket-search.md 신규 작성:
  · 사용 시나리오, 구현 표면(fast-flights==2.2, 사용자 venv 격리)
  · search / compare-month / compare-range / compare-years CLI 예시
  · 응답 필드, IATA 입력 가이드, 예약 링크 정책
  · 검증된 노선 목록, 실패 모드, 비범위, 출처

검증:
- node --test scripts/skill-docs.test.js → 138/138 pass
- ./scripts/validate-skills.sh → skill layout looks valid

코드 변경 없음 → changeset 불필요.

* feat(daiso-product-search): replace blocked-API fallback with Bearer token auth

selStrPkupStck는 더 이상 차단 상태가 아니며, /api/auth/request로 비로그인 JWT를
발급받아 AES-128-CBC(키: PRE_AUTH_ENC_KEY)로 암호화한 Bearer 토큰으로 접근한다.
403 응답 시 토큰을 재발급해 1회 재시도한다. pickupEligibility(selPkupStr) 폴백
로직은 제거했다.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Preserve Daiso pickup answers when Bearer auth degrades

Keep exact stock lookup on the official Bearer-token path while restoring the public selPkupStr fallback for repeated auth blocks.

Constraint: PR #250 review required Bearer auth to remain primary without removing the resilient pickup eligibility API.

Rejected: Throwing after the retry | it collapses callers back to a brittle single upstream-auth dependency.

Confidence: high

Scope-risk: narrow

Directive: Keep pickupStock quantity semantics separate from pickupEligibility yes/no fallback.

Tested: node --test packages/daiso-product-search/test/index.test.js; npm test --workspace daiso-product-search; npm run lint --workspace daiso-product-search; npm run ci; live lookupStoreProductAvailability smoke for 강남역2호점 / VT 리들샷 100.

Not-tested: Live forced 403 from Daiso upstream; covered with injected fetch regression tests.

* Prove Daiso stock retry sends auth headers

Strengthen the retry regression so the Bearer-token contract cannot regress while still returning success from mocked stock responses.\n\nConstraint: PR #250 review requested explicit Authorization, X-DM-UID, and request body assertions on the retry path.\nRejected: Counting requests only | it allowed header/body regressions to pass.\nConfidence: high\nScope-risk: narrow\nDirective: Keep auth-header assertions on both initial and retry stock requests when editing this flow.\nTested: node --test packages/daiso-product-search/test/index.test.js; npm test --workspace daiso-product-search; npm run lint --workspace daiso-product-search; npm run ci; live lookupStoreProductAvailability smoke for 강남역2호점 / VT 리들샷 100; repeated-403 fixture probe.\nNot-tested: Live repeated upstream 403 because forcing Daiso production auth failure is not available without changing upstream state.

* Preserve Daiso caller headers through Bearer stock lookup

Keep advanced caller headers on the authenticated stock endpoint while generated Bearer and X-DM-UID values remain authoritative. Document the degraded selPkupStr fallback order in skill and source docs so the public workflow matches the restored API surface.\n\nConstraint: PR #250 review required resilient Bearer-primary stock lookup plus selPkupStr fallback and header/body contract coverage.\nRejected: Replacing caller headers with only auth headers | It regressed tracing/test-control header pass-through.\nConfidence: high\nScope-risk: narrow\nDirective: Keep Authorization and X-DM-UID generated by the auth flow even when callers provide same-named headers.\nTested: node --test packages/daiso-product-search/test/index.test.js; npm test --workspace daiso-product-search; npm run lint --workspace daiso-product-search; node --test scripts/skill-docs.test.js; npm run ci; live lookupStoreProductAvailability smoke for 강남역2호점 / VT 리들샷 100.\nNot-tested: Forced live upstream repeated 403; covered by injected fixture tests.

* fix(danawa-price-search): capture .ico.* payment-condition badges and surface as row labels

PR #226 row 파서에 결제조건 배지(`.ico.cash`/`.ico.point`/`.ico.coupon`/`.ico.card`) selector가 누락돼, 카드 결제 불가능한 현금/쿠폰/포인트 전용가가 일반 최저가로 노출되는 결함을 고친다.

- `offers()` row 파싱부에 결제조건 배지 화이트리스트 캡처 블록 추가 (클래스 `cash`/`point`/`coupon`/`discount`/`card`/`membership` 또는 텍스트 `현금`/`포인트`/`쿠폰`/`할인`만 인정 — 빠른배송/안내/상품리뷰 노이즈 차단)
- row dict 신규 필드 6개: `payment_badges`, `cash_only`, `point_only`, `coupon_only`, `card_only_badge`, `is_conditional_price`
- 반환 dict에 `normal_count`, `conditional_count` 추가
- `SKILL.md` / `docs/features/danawa-price-search.md` 갱신 (Output shape · Response style · Workflow · Failure modes에 결제조건 정책과 표 예시 명시)

정렬 정책은 그대로 `total_price` 단일 기준이며, 결제조건은 row 단위 플래그/라벨로만 노출해 호출자가 결제수단에 맞춰 직접 판단하도록 한다.

회귀 (pcode=75001853, 갤럭시 S25 256GB 자급제 `offers --limit 5`):
- 1위 킴스클럽 979,000원 / `cash_only=True` / `payment_badges=["현금"]`
- 2위 롯데ON 1,072,080원 / `cash_only=False` / `payment_badges=[]`
- 3~5위 일반가 row 모두 `payment_badges` 빈 리스트 (노이즈 0건)

Closes #252

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Ensure captured Danawa payment badges stay conditional

Classify every whitelisted payment badge into normalized condition types so callers cannot count captured discount, membership, or text-only card rows as normal prices.

Constraint: PR #253 review required TDD follow-up on feature/#252 without changing total_price sorting.\nRejected: Removing discount and membership from the whitelist | would lose Danawa condition labels already captured by the parser.\nConfidence: high\nScope-risk: narrow\nDirective: Keep payment_badge whitelist and payment_condition_types in sync whenever adding new badge classes or text keywords.\nTested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_danawa_price_search; live offers 75001853 --limit 5; npm run lint; npm run typecheck; npm run test; architect verification CLEAR.\nNot-tested: Danawa markup variants not represented by current live page or synthetic badge fixtures.

* Keep icon-only Danawa payment badges visible

Class-only Danawa payment icons can carry eligibility information without visible text, so synthesize display labels from the same normalized condition map used for types and booleans. This keeps raw row labels, condition fields, and returned-window counts aligned for downstream table renderers.\n\nConstraint: PR #253 review follow-up requires TDD coverage before parser changes.\nRejected: Leaving payment_badges text-only | icon-only conditional rows would still render without visible payment labels.\nConfidence: high\nScope-risk: narrow\nDirective: Derive future payment badge labels, types, and booleans from one canonical mapping.\nTested: python3 -m py_compile danawa-price-search/scripts/danawa_search.py scripts/test_danawa_price_search.py; PYTHONPATH=.:scripts python3 -m unittest scripts.test_danawa_price_search; python3 danawa-price-search/scripts/danawa_search.py offers 75001853 --limit 5; npm run lint; npm run typecheck; npm run test\nNot-tested: Danawa icon-only markup was verified with synthetic fixtures rather than a live page snapshot.

* Merge pull request #249 from NomaDamas/feature/#248

Feature/#248

* Restore SH notice lookup without proxy policy drift

Reintroduce SH notice search as a direct public HTML client so the skill complies with the free-API proxy boundary while preserving verifiable keyword, pagination, and attachment behavior.

Constraint: i-sh.co.kr board is public unauthenticated HTML, so k-skill-proxy must not host the scraper.\nRejected: Re-adding /v1/sh-notice proxy routes | public HTML scraping in proxy violates repository policy.\nConfidence: high\nScope-risk: moderate\nDirective: Keep SH public HTML access local/direct unless a key-required official free API is discovered and documented.\nTested: npm run ci; npm run lint --workspace sh-notice-search; npm test --workspace sh-notice-search; live SH smoke for 행복주택, 매입임대, 신혼희망타운, page 1/page 5, 1/6/9/11/0 attachment details.\nNot-tested: authenticated SH flows, 청약 application/submission, direct attachment downloads.

* Preserve public SH helper semantics

Route exported URL builders through the same normalization as the CLI/API so natural category aliases cannot bypass srchTp title narrowing or category mapping.\n\nConstraint: PR #254 review found exported helper callers could pass Korean/English public category inputs and get broken or broadened SH URLs.\nRejected: Keep normalized-only fast paths | exported helpers are public API and must protect natural inputs.\nConfidence: high\nScope-risk: narrow\nDirective: Keep exported helper behavior aligned with normalizeSearchOptions and normalizeDetailOptions when adding new public aliases.\nTested: npm test --workspace sh-notice-search; npm run lint --workspace sh-notice-search; npm run typecheck; npm run ci; node helper smoke for 임대 search/detail URLs.\nNot-tested: Live SH network smoke was not rerun for this helper-only change.

* Preserve SH parser helper aliases

Route exported parser helpers through the same public normalizers used by the SH fetch and URL-builder APIs so natural category aliases stay consistent across the package surface.

Constraint: PR #254 Round 2 review found parser helpers still treated raw category aliases as pre-normalized inputs.
Rejected: Keep parser helpers normalized-only | inconsistent with exported URL builders and public helper ergonomics.
Confidence: high
Scope-risk: narrow
Directive: Keep exported SH helper entry points on canonical normalizeSearchOptions/normalizeDetailOptions unless a separate internal-only API is introduced.
Tested: npm test --workspace sh-notice-search; npm run lint --workspace sh-notice-search; npm run typecheck; npm pack --workspace sh-notice-search --dry-run; npm run ci; parser smoke for Korean 임대 list/detail helpers; Ralph architect verification CLEAR; post-deslop regression npm run ci
Not-tested: Live SH network smoke for this follow-up; fixture and injected-fetch coverage exercised the helper contract.

* Make SH parser failures explicit

Warn when SH returns block or maintenance HTML without the expected public board markup, and constrain exposed preview links to the SH converter origin/path.\n\nConstraint: Round 3 review required TDD coverage for block/maintenance HTML and untrusted preview URLs.\nRejected: Throwing on unexpected HTML | Existing parser helpers return partial fixture-friendly results, so warnings preserve compatibility while exposing failure evidence.\nConfidence: high\nScope-risk: narrow\nDirective: Keep SH public HTML lookup direct; do not add proxy routing unless a key-required official free API is adopted.\nTested: npm run lint --workspace sh-notice-search; npm test --workspace sh-notice-search; npm run typecheck; npm pack --workspace sh-notice-search --dry-run; npm run ci; Node smoke for blocked HTML warnings and external preview filtering.\nNot-tested: Live blocked/NetFunnel SH response, because no live blocked page was available during implementation.

* ci: install beautifulsoup4 so danawa price search tests can import bs4

The new scripts/test_danawa_price_search.py imports danawa_search.py,
which requires beautifulsoup4. CI only runs npm ci, so the bs4 import
fails with 'beautifulsoup4 is required: python -m pip install
beautifulsoup4' and the validate job exits with code 1.

Install beautifulsoup4 via pip before running npm run ci so the
Python test suite can import danawa_search and run the new payment
badge regression tests.

* Revert "ci: install beautifulsoup4 so danawa price search tests can import bs4"

This reverts commit 8330e5adf7.

* test: install beautifulsoup4 inside npm test before Python tests

The new scripts/test_danawa_price_search.py imports danawa_search.py,
which requires beautifulsoup4. CI runs npm ci + npm run ci and does
not install Python packages, so the bs4 import fails at module load.

Install beautifulsoup4 via 'pip install --user' as the first step of
the test script so it is available when Python unittests import the
danawa helper. Local dev environments are unaffected because pip
install is idempotent and quiet.

* feat(qa-bot): add k-skill-qa-bot under tools/

External macOS daemon that clones NomaDamas/k-skill main every 3 days, runs
each skill through codex exec, has an LLM judge grade pass/fail/skip via
codex exec --output-schema, and files dedup'd GitHub issues for true failures.

Layout:
- install.sh copies tools/k-skill-qa-bot/ to ~/.local/share/k-skill-qa-bot/
  and registers a LaunchAgent at ~/Library/LaunchAgents/.
- update-clone.sh has a hard guard: refuses any K_SKILL_CLONE outside
  K_QA_HOME/k-skill-clone unless ALLOW_EXTERNAL_CLONE_TARGET=1.
- Force-skip 10 destructive/login-required skills (ktx-booking, srt-booking,
  catchtable-sniper, kakaotalk-mac, hipass-receipt, toss-securities, etc.)
  so the bot never triggers reservation abuse.
- Deprecated skills (strike-through + 지원 중단 in README) auto-detected
  and skipped, never failed.
- First-run safety: CREATE_ISSUES=false by default.
- mkdir-based concurrency lock with atomic stale reclaim.
- Issue dedup: sha1(skill_name + symptom_class)[:12] body marker.
- Deterministic gates override LLM judge to FAIL on exit_code != 0, missing
  VERDICT line, or near-timeout duration.

* Support nearby ER status checks

Add an E-Gen based emergency-room skill that resolves a user location, queries the public nearby emergency-room list, and reports operation flags while documenting that exact remaining bed counts are not exposed by this surface.

Constraint: Issue #255 requested NEMC emergency bed status using public monitoring/E-Gen surfaces.
Rejected: Scraping private monitoring dashboards or claiming exact bed utilization | public endpoints expose operation flags, not per-hospital remaining bed counts.
Confidence: high
Scope-risk: narrow
Directive: Preserve the public-data limitation text unless a verified official bed-count endpoint is added.
Tested: npm run lint --workspace emergency-room-beds; npm test --workspace emergency-room-beds; node --test scripts/skill-docs.test.js; npm run typecheck; npm pack --workspace emergency-room-beds --dry-run; ./scripts/validate-skills.sh; live E-Gen coordinate smoke.
Not-tested: npm run ci end-to-end due local Python 3.14 pip/pyexpat import error before tests.

* Prevent ER status ambiguity from reaching users

Constraint: Health-adjacent public E-Gen/Kakao data can be absent, delayed, schema-drifted, or partially unknown.

Rejected: Mapping all non-Y operation flags to false | It misrepresents missing upstream data as a negative operating status.

Rejected: Treating unknown E-Gen payloads as empty results | It hides upstream failure behind a false no-results response.

Confidence: high

Scope-risk: narrow

Directive: Keep unknown health availability data explicit and preserve upstream failure evidence.

Tested: npm run lint --workspace emergency-room-beds; npm test --workspace emergency-room-beds; node --test scripts/skill-docs.test.js; npm run typecheck; npm pack --workspace emergency-room-beds --dry-run; ./scripts/validate-skills.sh; direct Node smoke for tri-state/schema/coordinate guards.

Not-tested: npm run ci due pre-existing local Python 3.14 pyexpat/libexpat bootstrap failure noted on PR.

Co-authored-by: OmX <omx@oh-my-codex.dev>

* fix(ci): exclude tools/ from skill validator

The tools/ directory hosts repo tooling (e.g. k-skill-qa-bot), not
skills, so validate-skills.sh should skip it like other non-skill
top-level directories.

* 영화관 검색 스킬 추가 (#260)

* Add korean cinema search skill

* Document playDate for cinema skill

* feat(kstartup-search): 창업진흥원 K-Startup 조회 스킬 + 프록시 라우트 4종 (#259)

* feat(kstartup-search): 창업진흥원 K-Startup 조회 스킬과 프록시 라우트 추가

공공데이터포털 dataset 15125364 (창업진흥원_K-Startup(사업소개,사업공고,콘텐츠 등)_조회서비스) 의
4개 endpoint 를 k-skill-proxy 경유로 조회하는 스킬을 추가한다.

- 신규 라우트: GET /v1/kstartup/{business-info,announcements,contents,statistics}
  - 각각 getBusinessInformation01/getAnnouncementInformation01/getContentInformation01/
    getStatisticalInformation01 으로 중계
  - ServiceKey 는 서버 측 DATA_GO_KR_API_KEY 로 주입, returnType=json 강제
  - 정상 응답만 캐시, data.go.kr 에러 envelope (resultCode != "00", errMsg 등) 은 캐시 우회
- helper: kstartup-search/scripts/run_kstartup.py (stdlib only)
  - 일반 조회는 hosted proxy 사용 → 사용자 키 불필요
  - --direct 옵션은 사용자가 본인 KSKILL_KSTARTUP_API_KEY (혹은 DATA_GO_KR_API_KEY) 로
    upstream 직접 호출 + --dry-run 시 키 redact
- 입력 검증: page/perPage 정수·범위, YYYYMMDD 날짜 + 시작일 ≤ 종료일, Y/N 대문자화,
  텍스트 필드 길이 상한, biz_yr 4자리
- 테스트: k-skill-proxy 서버 테스트 10건 신규 (normalizer, 라우트, 캐시 분리,
  returnType=json 강제, 503/400/502, 키 누수 회귀), Python unittest 13건
- 문서: SKILL.md, docs/features/kstartup-search.md, README 표/리스트,
  docs/sources.md, .changeset/kstartup-search.md (k-skill-proxy minor)

* docs(kstartup-search): docs/setup·security·k-skill-setup·proxy README 에 K-Startup 항목 추가

seoul-density · KOSIS · NTS 선례와 동일한 위치·문구로 다음을 보강한다.

- docs/setup.md: dotenv 예시에 KSKILL_KSTARTUP_API_KEY 추가, credential 표에 K-Startup 행 추가, "다음에 볼 문서" 리스트 추가
- docs/security-and-secrets.md: standard variable names 에 KSKILL_KSTARTUP_API_KEY 추가, hosted proxy 사용 스킬 목록·proxy 운영 prose 에 K-Startup 추가, dotenv 예시 추가
- k-skill-setup/SKILL.md: credential resolution prose 와 시크릿 요약 표에 K-Startup 안내 추가
- packages/k-skill-proxy/README.md: 라우트 목록에 /v1/kstartup/{business-info,announcements,contents,statistics} 추가
- docs/features/k-skill-proxy.md: 라우트 목록에 같은 4개 추가

* fix(kstartup-search): strict calendar-date validation in Python helper

validate_yyyymmdd() previously only checked month in [1,12] and day in [1,31],
which accepted impossible dates like 20240230 or 20240431 in --direct mode.
The proxy-side normalizer in packages/k-skill-proxy/src/kstartup.js already
uses Date.UTC() to reject such inputs, so this aligns the --direct path with
the proxy path and eliminates validator drift.

Uses datetime.date(year, month, day) and raises HelperError on ValueError.

Adds regression test covering impossible calendar dates (Feb 30, Apr 31,
month 13, day 0) and the leap-year boundary (2024-02-29 valid, 2023-02-29
not).

---------

Co-authored-by: Jeffrey (Dongkyu) Kim <vkehfdl1@gmail.com>

* fix(qa-bot): upgrade judge to gpt-5.5 and run codex with sandbox bypass

PR #257 follow-up. Two changes:

1. JUDGE_MODEL default: gpt-5.4-mini -> gpt-5.5

   The cheaper judge was misclassifying every wrong-output verdict because
   the offline matcher fell through to the dumb 'VERDICT: FAIL in transcript'
   check. Re-running the same 10 historical fail cases with gpt-5.5 +
   real LLM judge correctly reclassified 7 of them as pass (the codex agent
   actually accomplished the skill goal) and the remaining 3 as
   network-error / partial-success / skip with accurate reasons.

2. Drop -s read-only, add --dangerously-bypass-approvals-and-sandbox

   The read-only codex sandbox was triggering spurious DNS resolution
   failures inside the test runs (host blocked at the syscall level even
   for legitimate proxy / public-API calls). Live re-test with the bypass
   flag and provider pin produced clean transcripts: cheap-gas-nearby,
   daangn-realty-search, han-river-water-level, naver-news-search,
   naver-shopping-search, seoul-density, seoul-subway-arrival all PASS.
   The QA bot is sandboxed externally by launchd anyway.

3. New CODEX_PROVIDER env (default: openai)

   Lets users pin the codex model_provider explicitly so the bot does not
   accidentally route through a private OpenAI-compatible proxy that may
   not have keys registered for all model names.

* Add Ohou today deal skill

* fix spacing in package.json

* fix(qa-bot): per-skill test_prompt overrides and smarter judge

11 skills that need specific inputs (not just a 'demonstrate' query) now
ship with a hardcoded test_prompt in config/skill-overrides.yml:

  flight-ticket-search           ICN -> NRT, 2026-08-20 one-way
  nts-business-registration      124-81-00998 (Samsung Electronics)
  korean-stock-search            005930 Samsung 5-day quote
  joseon-sillok-search           키워드 훈민정음
  korean-law-search              산업안전보건법 제5조
  library-book-search            코스모스 칼 세이건
  lotto-results                  latest round
  k-schoollunch-menu             서울특별시교육청 초등학교 오늘 식단
  delivery-tracking              CJ dummy invoice (negative case ok)
  ticket-availability            YES24 / 인터파크 sample
  zipcode-search                 서울특별시 강남구 테헤란로 152

These were previously synthesized from the SKILL.md first When-to-use bullet,
which is a one-line teaser without concrete inputs. The agent would then
either ask the user for the missing input (partial-success) or fall back
to a generic demo (often producing a VERDICT: FAIL response). Both got
mis-classified as fail by the judge.

qa_utils.synthesize_test_prompt now honors default_inputs.test_prompt as a
verbatim override (only appending the VERDICT line if the override does not
already include it).

Two additional fixes for negative-case correctness:

1. judge-prompt.md: explicitly tells the judge that the agent's literal
   VERDICT: PASS / VERDICT: FAIL is just a hint, not binding. A skill that
   correctly returns 'no such business number' or 'invoice not found' for
   a deliberately invalid input is PASS, not fail.

2. judge-skill.py: drop the deterministic gate that flipped pass to fail
   when 'VERDICT: PASS' literal was missing from the transcript. That gate
   was producing false fails for negative-case tests where the agent
   correctly responded with VERDICT: FAIL because the skill rejected an
   invalid input. The judge LLM (gpt-5.5) is now trusted to evaluate the
   transcript against the SKILL.md 'Done when' criteria.

Verified live:

- nts-business-registration with valid number  -> pass/success (0.99)
- nts-business-registration with fake number   -> pass/success (0.99)
- flight-ticket-search ICN->NRT 2026-08-20     -> pass/success (0.99)

* fix(ohou-today-deal): address PR #264 review (live UA, explicit feed selection, argv validators)

- HIGH: switch fetch_html() to well-formed bot UA with contact URL
  (k-skill-ohou-today-deal/1.0 (+https://github.com/NomaDamas/k-skill)).
  ohou.se Akamai bot manager 403s anonymous UAs but allows identified
  bot UAs that include a contact URL. Live default workflow now returns
  74 deals end-to-end instead of failing with HTTP 403.
- MEDIUM: extract_deals() now explicitly selects React Query entries with
  queryKey == ['today-deal-feed'] or ['special-today-deal-feed'] and
  reads only state.data.todayDealFeed.slots[type=='DEAL']. Unrelated
  DEAL-shaped nodes from navigation/banner modules are excluded.
  Legacy fixture/JSON-payload fallback path preserved for tests that
  construct simplified payloads.
- LOW: --limit now requires a positive integer; --min-discount is
  constrained to 0..100. Both validated via argparse.ArgumentTypeError
  so users get a clear CLI error instead of silent slicing or nonsensical
  thresholds.
- Tests: add 9 new unit tests covering explicit feed selection,
  navigation/GOODS exclusion, fallback compatibility, and argv validators.
  Strengthen skill-docs.test.js to lock the special-today-deal-feed
  surface and well-formed UA signature.
- Docs: update SKILL.md and feature doc to document the explicit
  today-deal-feed + special-today-deal-feed extraction boundary and the
  Akamai UA policy.

* Merge pull request #263 from NomaDamas/feature/#257

Feature/#257

* Feature/#256 (#266)

* Enable public local-election candidate lookups

Add an NEC integrated-search skill and helper package so agents can answer 지방선거 후보자 lookup requests without credentials or proxy routes.

Constraint: Issue #256 requested TDD, Ralph completion, branch feature/#256, and PR targeting dev.

Rejected: k-skill-proxy route | NEC integrated candidate search is public and requires no API key.

Confidence: high

Scope-risk: moderate

Directive: Keep the helper read-only and do not automate NEC login, CAPTCHA, filing, or privileged election workflows.

Tested: git diff --check; node --test packages/local-election-candidate-search/test/index.test.js; npm run lint --workspace local-election-candidate-search; npm run test --workspace local-election-candidate-search; npm pack --workspace local-election-candidate-search --dry-run; node packages/local-election-candidate-search/src/cli.js 오세훈 --election 시도지사 --region 서울 --limit 1; PATH=/usr/bin:/bin:/usr/sbin:/sbin:/opt/homebrew/bin:/Users/jeffrey/.codex/tmp/arg0/codex-arg0a6JueA:/opt/homebrew/lib/node_modules/@openai/codex/node_modules/@openai/codex-darwin-arm64/vendor/aarch64-apple-darwin/path:/Users/jeffrey/.cmuxterm/omo-bin:/opt/homebrew/share/android-commandlinetools/platform-tools:/opt/homebrew/share/android-commandlinetools/emulator:/opt/homebrew/share/android-commandlinetools/cmdline-tools/latest/bin:/Users/jeffrey/.local/bin:/Users/jeffrey/.bun/bin:/opt/homebrew/opt/node@22/bin:/opt/homebrew/opt/openjdk@21/bin:/opt/homebrew/opt/postgresql@18/bin:/Users/jeffrey/.jenv/shims:/Users/jeffrey/.jenv/bin:/opt/homebrew/opt/imagemagick/bin:/opt/homebrew/Cellar/pyenv-virtualenv/1.4.0/shims:/Users/jeffrey/.pyenv/shims:/opt/homebrew/opt/openssl@3/bin:/Users/jeffrey/.rbenv/shims:/Users/jeffrey/.rbenv/bin:/Users/jeffrey/google-cloud-sdk/bin:/Applications/cmux.app/Contents/Resources/bin:/Users/jeffrey/Library/pnpm:/Users/jeffrey/.nvm/versions/node/v24.13.0/bin:/Users/jeffrey/.cops/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/opt/pmk/env/global/bin:/Library/Apple/usr/bin:/Library/TeX/texbin:/Users/jeffrey/.cargo/bin:/Users/jeffrey/Library/Application Support/JetBrains/Toolbox/scripts:/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/bin:/Users/jeffrey/xcode-projects/marshroom/cli npm run ci

Not-tested: Exhaustive NEC markup variants for every historical election type.

Co-authored-by: OmX <omx@oh-my-codex.dev>

* Enforce fail-closed candidate identity parsing

Constraint: PR #266 review required exact candidate-name matching and CLI help regression coverage.\nRejected: fallback-to-query-name on missing upstream markup | it can mislabel unrelated candidates as exact matches.\nConfidence: high\nScope-risk: narrow\nDirective: Keep NEC parser changes fail-closed when candidate identity cannot be parsed.\nTested: git diff --check; node --test packages/local-election-candidate-search/test/index.test.js; npm run lint --workspace local-election-candidate-search; npm run test --workspace local-election-candidate-search; npm pack --workspace local-election-candidate-search --dry-run; live CLI smoke for 오세훈; CLI --help smoke.\nNot-tested: repo-wide npm run ci remains blocked by pre-existing missing SKILL.md: ohou-today-deal.

* Preserve unique candidate lookup results

Deduplicate parsed NEC candidate/election rows before applying user limits, and make expected CLI validation failures concise by default while keeping an explicit debug stack escape hatch.

Constraint: PR #266 round-2 follow-up requested TDD fixes for duplicate NEC rows and CLI validation UX.\nRejected: Deduplicating after limit | would still allow duplicates to crowd out unique rows.\nRejected: Always printing stack traces | exposes local paths for normal user-input failures.\nConfidence: high\nScope-risk: narrow\nDirective: Keep dedupe keys stable enough to avoid collapsing legitimately distinct historical election rows.\nTested: git diff --check; node --test packages/local-election-candidate-search/test/index.test.js; npm run lint --workspace local-election-candidate-search; npm run test --workspace local-election-candidate-search; npm pack --workspace local-election-candidate-search --dry-run; live 오세훈 smoke; live 김동연 duplicate repro; CLI no-args/help.\nNot-tested: Full npm run ci remains blocked by pre-existing missing SKILL.md: ohou-today-deal.

* Prevent filtered NEC lookup false negatives

Fix the candidate parser so documented education-superintendent and filtered local-election lookups return bounded, evidence-backed results instead of silently dropping valid rows.

Constraint: PR #266 round-3 review required TDD, Ralph verification, and branch update for issue #256.

Rejected: Full NEC pagination in this follow-up | broader than the approved change; bounded 100-row fetch now avoids user-limit false negatives and warns when capped.

Confidence: high

Scope-risk: narrow

Directive: Preserve exact-name fail-closed parsing and count raw parsed upstream rows before cap-warning decisions.

Tested: git diff --check; node --test packages/local-election-candidate-search/test/index.test.js; npm run lint --workspace local-election-candidate-search; npm run test --workspace local-election-candidate-search; npm pack --workspace local-election-candidate-search --dry-run; live CLI smokes for 오세훈, 조희연, 김동연; CLI help/no-args checks; architect verification CLEAR.

Not-tested: Full npm run ci remains blocked by pre-existing repo-wide missing SKILL.md: ohou-today-deal.

---------

Co-authored-by: OmX <omx@oh-my-codex.dev>

* chore(changesets): rename daiso bearer-auth changeset to avoid name collision with consumed main release

PR #245 already consumed .changeset/issue-207-daiso-pickup-eligibility.md
into daiso-product-search v0.3.0 on main. The dev branch later modified that
same changeset file in d7263a5 to describe the newer Bearer-auth fix, which
collides with main's deletion on the next dev→main sync.

Renaming the still-unreleased Bearer-auth note to
issue-207-daiso-bearer-auth.md preserves the release entry for the next
version-packages run and clears the modify/delete conflict on PR #271
without losing the changelog content.

* fix(kstartup-search): implement promised client-side filter to deliver on SKILL.md L121

Live data revealed two unmet contracts in the kstartup-search helper:

1. SKILL.md L121 promised the helper re-applies supt_regin / aply_trgt /
   biz_enyy filters on the client side because K-Startup upstream ignores
   them server-side. The helper had no such logic — calling
   `--supt-regin 서울특별시 --rcrt-prgs-yn Y` returned 경북/충북/충남
   announcements as-is, silently misleading callers.

2. The upstream `supt_regin` field is stored as the short form
   (`서울`, `경기`, `충북`, ...) but every CLI example in the skill used
   the standard 광역지자체 long form (`서울특별시`), which would never
   substring-match even after a client filter was added.

Add `apply_client_filters()` that runs after `urlopen` returns. It honors
the SKILL.md contract literally: substring match per token, AND-joined
across comma-separated user values, with a 17-region (+`전국`) shortname
normalisation table so both `--supt-regin 서울특별시` and
`--supt-regin 서울` resolve to upstream's `서울`. Filtered responses
expose a new `client_filter: {fields, upstream_returned, after_filter}`
metadata block so callers can detect "first page was depleted by filter"
and page through.

Tests: 9 new ClientFilterTests + 2 normalisation tests on top of the
existing 14 (25 total, all passing).

Live smoke (against a dev proxy with DATA_GO_KR_API_KEY activated for
dataset 15125364): `--supt-regin 서울특별시 --rcrt-prgs-yn Y --per-page 10`
now returns 4 actual 서울 announcements (upstream returned 10 mixed-region
rows; client filter narrowed to 4), with detl_pg_url to k-startup.go.kr.

Confidence: high. Scope-risk: narrow — purely additive on the response
path; other endpoints (business-info / contents / statistics) pass
through unchanged.

---------

Co-authored-by: arnold714 <arnold714@naver.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: chanmin <cmju@cowave.kr>
Co-authored-by: OmX <omx@oh-my-codex.dev>
Co-authored-by: hmmhmmhm/ <hmmhmmhm@naver.com>
Co-authored-by: 배기민 <53887180+BAEM1N@users.noreply.github.com>
Co-authored-by: lee-ji-hong <zhffktkdlekghksxk@naver.com>
2026-05-19 11:08:10 +09:00
Jeffrey (Dongkyu) Kim
2f68b1ab4b fix(kstartup-search): implement promised client-side filter to deliver on SKILL.md L121
Live data revealed two unmet contracts in the kstartup-search helper:

1. SKILL.md L121 promised the helper re-applies supt_regin / aply_trgt /
   biz_enyy filters on the client side because K-Startup upstream ignores
   them server-side. The helper had no such logic — calling
   `--supt-regin 서울특별시 --rcrt-prgs-yn Y` returned 경북/충북/충남
   announcements as-is, silently misleading callers.

2. The upstream `supt_regin` field is stored as the short form
   (`서울`, `경기`, `충북`, ...) but every CLI example in the skill used
   the standard 광역지자체 long form (`서울특별시`), which would never
   substring-match even after a client filter was added.

Add `apply_client_filters()` that runs after `urlopen` returns. It honors
the SKILL.md contract literally: substring match per token, AND-joined
across comma-separated user values, with a 17-region (+`전국`) shortname
normalisation table so both `--supt-regin 서울특별시` and
`--supt-regin 서울` resolve to upstream's `서울`. Filtered responses
expose a new `client_filter: {fields, upstream_returned, after_filter}`
metadata block so callers can detect "first page was depleted by filter"
and page through.

Tests: 9 new ClientFilterTests + 2 normalisation tests on top of the
existing 14 (25 total, all passing).

Live smoke (against a dev proxy with DATA_GO_KR_API_KEY activated for
dataset 15125364): `--supt-regin 서울특별시 --rcrt-prgs-yn Y --per-page 10`
now returns 4 actual 서울 announcements (upstream returned 10 mixed-region
rows; client filter narrowed to 4), with detl_pg_url to k-startup.go.kr.

Confidence: high. Scope-risk: narrow — purely additive on the response
path; other endpoints (business-info / contents / statistics) pass
through unchanged.
2026-05-19 00:21:21 +09:00
Jeffrey (Dongkyu) Kim
7c2dc59c6c chore(changesets): rename daiso bearer-auth changeset to avoid name collision with consumed main release
PR #245 already consumed .changeset/issue-207-daiso-pickup-eligibility.md
into daiso-product-search v0.3.0 on main. The dev branch later modified that
same changeset file in d7263a5 to describe the newer Bearer-auth fix, which
collides with main's deletion on the next dev→main sync.

Renaming the still-unreleased Bearer-auth note to
issue-207-daiso-bearer-auth.md preserves the release entry for the next
version-packages run and clears the modify/delete conflict on PR #271
without losing the changelog content.
2026-05-18 23:29:09 +09:00
Jeffrey (Dongkyu) Kim
68abad3de0
Feature/#256 (#266)
* Enable public local-election candidate lookups

Add an NEC integrated-search skill and helper package so agents can answer 지방선거 후보자 lookup requests without credentials or proxy routes.

Constraint: Issue #256 requested TDD, Ralph completion, branch feature/#256, and PR targeting dev.

Rejected: k-skill-proxy route | NEC integrated candidate search is public and requires no API key.

Confidence: high

Scope-risk: moderate

Directive: Keep the helper read-only and do not automate NEC login, CAPTCHA, filing, or privileged election workflows.

Tested: git diff --check; node --test packages/local-election-candidate-search/test/index.test.js; npm run lint --workspace local-election-candidate-search; npm run test --workspace local-election-candidate-search; npm pack --workspace local-election-candidate-search --dry-run; node packages/local-election-candidate-search/src/cli.js 오세훈 --election 시도지사 --region 서울 --limit 1; PATH=/usr/bin:/bin:/usr/sbin:/sbin:/opt/homebrew/bin:/Users/jeffrey/.codex/tmp/arg0/codex-arg0a6JueA:/opt/homebrew/lib/node_modules/@openai/codex/node_modules/@openai/codex-darwin-arm64/vendor/aarch64-apple-darwin/path:/Users/jeffrey/.cmuxterm/omo-bin:/opt/homebrew/share/android-commandlinetools/platform-tools:/opt/homebrew/share/android-commandlinetools/emulator:/opt/homebrew/share/android-commandlinetools/cmdline-tools/latest/bin:/Users/jeffrey/.local/bin:/Users/jeffrey/.bun/bin:/opt/homebrew/opt/node@22/bin:/opt/homebrew/opt/openjdk@21/bin:/opt/homebrew/opt/postgresql@18/bin:/Users/jeffrey/.jenv/shims:/Users/jeffrey/.jenv/bin:/opt/homebrew/opt/imagemagick/bin:/opt/homebrew/Cellar/pyenv-virtualenv/1.4.0/shims:/Users/jeffrey/.pyenv/shims:/opt/homebrew/opt/openssl@3/bin:/Users/jeffrey/.rbenv/shims:/Users/jeffrey/.rbenv/bin:/Users/jeffrey/google-cloud-sdk/bin:/Applications/cmux.app/Contents/Resources/bin:/Users/jeffrey/Library/pnpm:/Users/jeffrey/.nvm/versions/node/v24.13.0/bin:/Users/jeffrey/.cops/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/opt/pmk/env/global/bin:/Library/Apple/usr/bin:/Library/TeX/texbin:/Users/jeffrey/.cargo/bin:/Users/jeffrey/Library/Application Support/JetBrains/Toolbox/scripts:/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/bin:/Users/jeffrey/xcode-projects/marshroom/cli npm run ci

Not-tested: Exhaustive NEC markup variants for every historical election type.

Co-authored-by: OmX <omx@oh-my-codex.dev>

* Enforce fail-closed candidate identity parsing

Constraint: PR #266 review required exact candidate-name matching and CLI help regression coverage.\nRejected: fallback-to-query-name on missing upstream markup | it can mislabel unrelated candidates as exact matches.\nConfidence: high\nScope-risk: narrow\nDirective: Keep NEC parser changes fail-closed when candidate identity cannot be parsed.\nTested: git diff --check; node --test packages/local-election-candidate-search/test/index.test.js; npm run lint --workspace local-election-candidate-search; npm run test --workspace local-election-candidate-search; npm pack --workspace local-election-candidate-search --dry-run; live CLI smoke for 오세훈; CLI --help smoke.\nNot-tested: repo-wide npm run ci remains blocked by pre-existing missing SKILL.md: ohou-today-deal.

* Preserve unique candidate lookup results

Deduplicate parsed NEC candidate/election rows before applying user limits, and make expected CLI validation failures concise by default while keeping an explicit debug stack escape hatch.

Constraint: PR #266 round-2 follow-up requested TDD fixes for duplicate NEC rows and CLI validation UX.\nRejected: Deduplicating after limit | would still allow duplicates to crowd out unique rows.\nRejected: Always printing stack traces | exposes local paths for normal user-input failures.\nConfidence: high\nScope-risk: narrow\nDirective: Keep dedupe keys stable enough to avoid collapsing legitimately distinct historical election rows.\nTested: git diff --check; node --test packages/local-election-candidate-search/test/index.test.js; npm run lint --workspace local-election-candidate-search; npm run test --workspace local-election-candidate-search; npm pack --workspace local-election-candidate-search --dry-run; live 오세훈 smoke; live 김동연 duplicate repro; CLI no-args/help.\nNot-tested: Full npm run ci remains blocked by pre-existing missing SKILL.md: ohou-today-deal.

* Prevent filtered NEC lookup false negatives

Fix the candidate parser so documented education-superintendent and filtered local-election lookups return bounded, evidence-backed results instead of silently dropping valid rows.

Constraint: PR #266 round-3 review required TDD, Ralph verification, and branch update for issue #256.

Rejected: Full NEC pagination in this follow-up | broader than the approved change; bounded 100-row fetch now avoids user-limit false negatives and warns when capped.

Confidence: high

Scope-risk: narrow

Directive: Preserve exact-name fail-closed parsing and count raw parsed upstream rows before cap-warning decisions.

Tested: git diff --check; node --test packages/local-election-candidate-search/test/index.test.js; npm run lint --workspace local-election-candidate-search; npm run test --workspace local-election-candidate-search; npm pack --workspace local-election-candidate-search --dry-run; live CLI smokes for 오세훈, 조희연, 김동연; CLI help/no-args checks; architect verification CLEAR.

Not-tested: Full npm run ci remains blocked by pre-existing repo-wide missing SKILL.md: ohou-today-deal.

---------

Co-authored-by: OmX <omx@oh-my-codex.dev>
2026-05-18 23:11:23 +09:00
Jeffrey (Dongkyu) Kim
5b08b4c86e
Merge pull request #263 from NomaDamas/feature/#257
Feature/#257
2026-05-18 21:18:56 +09:00
Jeffrey (Dongkyu) Kim
6831b3147e
Merge pull request #264 from lee-ji-hong/feature/ohou-today-skill
오늘의딜 특가 상품 조회 스킬 추가
2026-05-18 17:52:13 +09:00
Jeffrey (Dongkyu) Kim
4a78169220 fix(ohou-today-deal): address PR #264 review (live UA, explicit feed selection, argv validators)
- HIGH: switch fetch_html() to well-formed bot UA with contact URL
  (k-skill-ohou-today-deal/1.0 (+https://github.com/NomaDamas/k-skill)).
  ohou.se Akamai bot manager 403s anonymous UAs but allows identified
  bot UAs that include a contact URL. Live default workflow now returns
  74 deals end-to-end instead of failing with HTTP 403.
- MEDIUM: extract_deals() now explicitly selects React Query entries with
  queryKey == ['today-deal-feed'] or ['special-today-deal-feed'] and
  reads only state.data.todayDealFeed.slots[type=='DEAL']. Unrelated
  DEAL-shaped nodes from navigation/banner modules are excluded.
  Legacy fixture/JSON-payload fallback path preserved for tests that
  construct simplified payloads.
- LOW: --limit now requires a positive integer; --min-discount is
  constrained to 0..100. Both validated via argparse.ArgumentTypeError
  so users get a clear CLI error instead of silent slicing or nonsensical
  thresholds.
- Tests: add 9 new unit tests covering explicit feed selection,
  navigation/GOODS exclusion, fallback compatibility, and argv validators.
  Strengthen skill-docs.test.js to lock the special-today-deal-feed
  surface and well-formed UA signature.
- Docs: update SKILL.md and feature doc to document the explicit
  today-deal-feed + special-today-deal-feed extraction boundary and the
  Akamai UA policy.
2026-05-18 16:36:56 +09:00
Jeffrey (Dongkyu) Kim
7e95ac69ab
Merge pull request #265 from NomaDamas/fix/qa-bot-test-prompts
fix(qa-bot): hardcoded test_prompt for input-driven skills and negative-case judge rubric
2026-05-18 15:54:17 +09:00
Jeffrey (Dongkyu) Kim
cf8e96acdc fix(qa-bot): per-skill test_prompt overrides and smarter judge
11 skills that need specific inputs (not just a 'demonstrate' query) now
ship with a hardcoded test_prompt in config/skill-overrides.yml:

  flight-ticket-search           ICN -> NRT, 2026-08-20 one-way
  nts-business-registration      124-81-00998 (Samsung Electronics)
  korean-stock-search            005930 Samsung 5-day quote
  joseon-sillok-search           키워드 훈민정음
  korean-law-search              산업안전보건법 제5조
  library-book-search            코스모스 칼 세이건
  lotto-results                  latest round
  k-schoollunch-menu             서울특별시교육청 초등학교 오늘 식단
  delivery-tracking              CJ dummy invoice (negative case ok)
  ticket-availability            YES24 / 인터파크 sample
  zipcode-search                 서울특별시 강남구 테헤란로 152

These were previously synthesized from the SKILL.md first When-to-use bullet,
which is a one-line teaser without concrete inputs. The agent would then
either ask the user for the missing input (partial-success) or fall back
to a generic demo (often producing a VERDICT: FAIL response). Both got
mis-classified as fail by the judge.

qa_utils.synthesize_test_prompt now honors default_inputs.test_prompt as a
verbatim override (only appending the VERDICT line if the override does not
already include it).

Two additional fixes for negative-case correctness:

1. judge-prompt.md: explicitly tells the judge that the agent's literal
   VERDICT: PASS / VERDICT: FAIL is just a hint, not binding. A skill that
   correctly returns 'no such business number' or 'invoice not found' for
   a deliberately invalid input is PASS, not fail.

2. judge-skill.py: drop the deterministic gate that flipped pass to fail
   when 'VERDICT: PASS' literal was missing from the transcript. That gate
   was producing false fails for negative-case tests where the agent
   correctly responded with VERDICT: FAIL because the skill rejected an
   invalid input. The judge LLM (gpt-5.5) is now trusted to evaluate the
   transcript against the SKILL.md 'Done when' criteria.

Verified live:

- nts-business-registration with valid number  -> pass/success (0.99)
- nts-business-registration with fake number   -> pass/success (0.99)
- flight-ticket-search ICN->NRT 2026-08-20     -> pass/success (0.99)
2026-05-18 15:53:26 +09:00
lee-ji-hong
8d52850fec fix spacing in package.json 2026-05-18 15:49:20 +09:00
lee-ji-hong
ca5aefd990 Add Ohou today deal skill 2026-05-18 15:40:18 +09:00
Jeffrey (Dongkyu) Kim
b46bb6a0d7
Merge pull request #261 from NomaDamas/fix/qa-bot-judge-gpt-5.5
fix(qa-bot): upgrade judge to gpt-5.5 and bypass codex sandbox
2026-05-18 14:31:44 +09:00
Jeffrey (Dongkyu) Kim
136d2afce1 fix(qa-bot): upgrade judge to gpt-5.5 and run codex with sandbox bypass
PR #257 follow-up. Two changes:

1. JUDGE_MODEL default: gpt-5.4-mini -> gpt-5.5

   The cheaper judge was misclassifying every wrong-output verdict because
   the offline matcher fell through to the dumb 'VERDICT: FAIL in transcript'
   check. Re-running the same 10 historical fail cases with gpt-5.5 +
   real LLM judge correctly reclassified 7 of them as pass (the codex agent
   actually accomplished the skill goal) and the remaining 3 as
   network-error / partial-success / skip with accurate reasons.

2. Drop -s read-only, add --dangerously-bypass-approvals-and-sandbox

   The read-only codex sandbox was triggering spurious DNS resolution
   failures inside the test runs (host blocked at the syscall level even
   for legitimate proxy / public-API calls). Live re-test with the bypass
   flag and provider pin produced clean transcripts: cheap-gas-nearby,
   daangn-realty-search, han-river-water-level, naver-news-search,
   naver-shopping-search, seoul-density, seoul-subway-arrival all PASS.
   The QA bot is sandboxed externally by launchd anyway.

3. New CODEX_PROVIDER env (default: openai)

   Lets users pin the codex model_provider explicitly so the bot does not
   accidentally route through a private OpenAI-compatible proxy that may
   not have keys registered for all model names.
2026-05-18 14:26:32 +09:00
배기민
540e80b804
feat(kstartup-search): 창업진흥원 K-Startup 조회 스킬 + 프록시 라우트 4종 (#259)
* feat(kstartup-search): 창업진흥원 K-Startup 조회 스킬과 프록시 라우트 추가

공공데이터포털 dataset 15125364 (창업진흥원_K-Startup(사업소개,사업공고,콘텐츠 등)_조회서비스) 의
4개 endpoint 를 k-skill-proxy 경유로 조회하는 스킬을 추가한다.

- 신규 라우트: GET /v1/kstartup/{business-info,announcements,contents,statistics}
  - 각각 getBusinessInformation01/getAnnouncementInformation01/getContentInformation01/
    getStatisticalInformation01 으로 중계
  - ServiceKey 는 서버 측 DATA_GO_KR_API_KEY 로 주입, returnType=json 강제
  - 정상 응답만 캐시, data.go.kr 에러 envelope (resultCode != "00", errMsg 등) 은 캐시 우회
- helper: kstartup-search/scripts/run_kstartup.py (stdlib only)
  - 일반 조회는 hosted proxy 사용 → 사용자 키 불필요
  - --direct 옵션은 사용자가 본인 KSKILL_KSTARTUP_API_KEY (혹은 DATA_GO_KR_API_KEY) 로
    upstream 직접 호출 + --dry-run 시 키 redact
- 입력 검증: page/perPage 정수·범위, YYYYMMDD 날짜 + 시작일 ≤ 종료일, Y/N 대문자화,
  텍스트 필드 길이 상한, biz_yr 4자리
- 테스트: k-skill-proxy 서버 테스트 10건 신규 (normalizer, 라우트, 캐시 분리,
  returnType=json 강제, 503/400/502, 키 누수 회귀), Python unittest 13건
- 문서: SKILL.md, docs/features/kstartup-search.md, README 표/리스트,
  docs/sources.md, .changeset/kstartup-search.md (k-skill-proxy minor)

* docs(kstartup-search): docs/setup·security·k-skill-setup·proxy README 에 K-Startup 항목 추가

seoul-density · KOSIS · NTS 선례와 동일한 위치·문구로 다음을 보강한다.

- docs/setup.md: dotenv 예시에 KSKILL_KSTARTUP_API_KEY 추가, credential 표에 K-Startup 행 추가, "다음에 볼 문서" 리스트 추가
- docs/security-and-secrets.md: standard variable names 에 KSKILL_KSTARTUP_API_KEY 추가, hosted proxy 사용 스킬 목록·proxy 운영 prose 에 K-Startup 추가, dotenv 예시 추가
- k-skill-setup/SKILL.md: credential resolution prose 와 시크릿 요약 표에 K-Startup 안내 추가
- packages/k-skill-proxy/README.md: 라우트 목록에 /v1/kstartup/{business-info,announcements,contents,statistics} 추가
- docs/features/k-skill-proxy.md: 라우트 목록에 같은 4개 추가

* fix(kstartup-search): strict calendar-date validation in Python helper

validate_yyyymmdd() previously only checked month in [1,12] and day in [1,31],
which accepted impossible dates like 20240230 or 20240431 in --direct mode.
The proxy-side normalizer in packages/k-skill-proxy/src/kstartup.js already
uses Date.UTC() to reject such inputs, so this aligns the --direct path with
the proxy path and eliminates validator drift.

Uses datetime.date(year, month, day) and raises HelperError on ValueError.

Adds regression test covering impossible calendar dates (Feb 30, Apr 31,
month 13, day 0) and the leap-year boundary (2024-02-29 valid, 2023-02-29
not).

---------

Co-authored-by: Jeffrey (Dongkyu) Kim <vkehfdl1@gmail.com>
2026-05-18 11:43:33 +09:00
hmmhmmhm/
e5b4465630
영화관 검색 스킬 추가 (#260)
* Add korean cinema search skill

* Document playDate for cinema skill
2026-05-18 11:42:40 +09:00
Jeffrey (Dongkyu) Kim
0721252fb6
Merge pull request #258 from NomaDamas/feature/#255
Feature/#255
2026-05-17 23:04:10 +09:00
Jeffrey (Dongkyu) Kim
638be3bad9
Merge pull request #257 from NomaDamas/feat/k-skill-qa-bot
feat(qa-bot): add k-skill-qa-bot under tools/
2026-05-17 22:41:46 +09:00
Jeffrey (Dongkyu) Kim
8838131565 fix(ci): exclude tools/ from skill validator
The tools/ directory hosts repo tooling (e.g. k-skill-qa-bot), not
skills, so validate-skills.sh should skip it like other non-skill
top-level directories.
2026-05-17 22:40:26 +09:00
Jeffrey (Dongkyu) Kim
4c7bbc0bd3 Prevent ER status ambiguity from reaching users
Constraint: Health-adjacent public E-Gen/Kakao data can be absent, delayed, schema-drifted, or partially unknown.

Rejected: Mapping all non-Y operation flags to false | It misrepresents missing upstream data as a negative operating status.

Rejected: Treating unknown E-Gen payloads as empty results | It hides upstream failure behind a false no-results response.

Confidence: high

Scope-risk: narrow

Directive: Keep unknown health availability data explicit and preserve upstream failure evidence.

Tested: npm run lint --workspace emergency-room-beds; npm test --workspace emergency-room-beds; node --test scripts/skill-docs.test.js; npm run typecheck; npm pack --workspace emergency-room-beds --dry-run; ./scripts/validate-skills.sh; direct Node smoke for tri-state/schema/coordinate guards.

Not-tested: npm run ci due pre-existing local Python 3.14 pyexpat/libexpat bootstrap failure noted on PR.

Co-authored-by: OmX <omx@oh-my-codex.dev>
2026-05-17 19:00:44 +09:00
Jeffrey (Dongkyu) Kim
4e2d1faf19 Support nearby ER status checks
Add an E-Gen based emergency-room skill that resolves a user location, queries the public nearby emergency-room list, and reports operation flags while documenting that exact remaining bed counts are not exposed by this surface.

Constraint: Issue #255 requested NEMC emergency bed status using public monitoring/E-Gen surfaces.
Rejected: Scraping private monitoring dashboards or claiming exact bed utilization | public endpoints expose operation flags, not per-hospital remaining bed counts.
Confidence: high
Scope-risk: narrow
Directive: Preserve the public-data limitation text unless a verified official bed-count endpoint is added.
Tested: npm run lint --workspace emergency-room-beds; npm test --workspace emergency-room-beds; node --test scripts/skill-docs.test.js; npm run typecheck; npm pack --workspace emergency-room-beds --dry-run; ./scripts/validate-skills.sh; live E-Gen coordinate smoke.
Not-tested: npm run ci end-to-end due local Python 3.14 pip/pyexpat import error before tests.
2026-05-17 18:37:07 +09:00
Jeffrey (Dongkyu) Kim
7f73e55011 feat(qa-bot): add k-skill-qa-bot under tools/
External macOS daemon that clones NomaDamas/k-skill main every 3 days, runs
each skill through codex exec, has an LLM judge grade pass/fail/skip via
codex exec --output-schema, and files dedup'd GitHub issues for true failures.

Layout:
- install.sh copies tools/k-skill-qa-bot/ to ~/.local/share/k-skill-qa-bot/
  and registers a LaunchAgent at ~/Library/LaunchAgents/.
- update-clone.sh has a hard guard: refuses any K_SKILL_CLONE outside
  K_QA_HOME/k-skill-clone unless ALLOW_EXTERNAL_CLONE_TARGET=1.
- Force-skip 10 destructive/login-required skills (ktx-booking, srt-booking,
  catchtable-sniper, kakaotalk-mac, hipass-receipt, toss-securities, etc.)
  so the bot never triggers reservation abuse.
- Deprecated skills (strike-through + 지원 중단 in README) auto-detected
  and skipped, never failed.
- First-run safety: CREATE_ISSUES=false by default.
- mkdir-based concurrency lock with atomic stale reclaim.
- Issue dedup: sha1(skill_name + symptom_class)[:12] body marker.
- Deterministic gates override LLM judge to FAIL on exit_code != 0, missing
  VERDICT line, or near-timeout duration.
2026-05-17 18:24:11 +09:00
Jeffrey (Dongkyu) Kim
5591502f9e
Merge pull request #253 from Combi153/feature/#252
fix(danawa-price-search): .ico.* 결제조건 배지 캡처해 row 단위 라벨로 노출
2026-05-17 16:27:54 +09:00
Jeffrey (Dongkyu) Kim
2227a3bd60 test: install beautifulsoup4 inside npm test before Python tests
The new scripts/test_danawa_price_search.py imports danawa_search.py,
which requires beautifulsoup4. CI runs npm ci + npm run ci and does
not install Python packages, so the bs4 import fails at module load.

Install beautifulsoup4 via 'pip install --user' as the first step of
the test script so it is available when Python unittests import the
danawa helper. Local dev environments are unaffected because pip
install is idempotent and quiet.
2026-05-17 16:25:33 +09:00
Jeffrey (Dongkyu) Kim
fe0f122e84 Revert "ci: install beautifulsoup4 so danawa price search tests can import bs4"
This reverts commit 8330e5adf7.
2026-05-17 16:24:49 +09:00
Jeffrey (Dongkyu) Kim
fadc23c3ff Merge branch 'dev' into feature/#252
Resolves package.json conflict by keeping both:
- test: scripts.test_danawa_price_search (from this PR)
- pack:dry-run: sh-notice-search workspace (from dev #251)
2026-05-17 16:19:47 +09:00
Jeffrey (Dongkyu) Kim
8330e5adf7 ci: install beautifulsoup4 so danawa price search tests can import bs4
The new scripts/test_danawa_price_search.py imports danawa_search.py,
which requires beautifulsoup4. CI only runs npm ci, so the bs4 import
fails with 'beautifulsoup4 is required: python -m pip install
beautifulsoup4' and the validate job exits with code 1.

Install beautifulsoup4 via pip before running npm run ci so the
Python test suite can import danawa_search and run the new payment
badge regression tests.
2026-05-17 16:17:27 +09:00
Jeffrey (Dongkyu) Kim
0895136e51
Merge pull request #251 from NomaDamas/feature/#207
Feature/#207
2026-05-17 16:16:23 +09:00
Jeffrey (Dongkyu) Kim
e066742b47
Merge pull request #254 from NomaDamas/feature/#246
Feature/#246
2026-05-17 16:15:53 +09:00
Jeffrey (Dongkyu) Kim
dc6cf4b879 Make SH parser failures explicit
Warn when SH returns block or maintenance HTML without the expected public board markup, and constrain exposed preview links to the SH converter origin/path.\n\nConstraint: Round 3 review required TDD coverage for block/maintenance HTML and untrusted preview URLs.\nRejected: Throwing on unexpected HTML | Existing parser helpers return partial fixture-friendly results, so warnings preserve compatibility while exposing failure evidence.\nConfidence: high\nScope-risk: narrow\nDirective: Keep SH public HTML lookup direct; do not add proxy routing unless a key-required official free API is adopted.\nTested: npm run lint --workspace sh-notice-search; npm test --workspace sh-notice-search; npm run typecheck; npm pack --workspace sh-notice-search --dry-run; npm run ci; Node smoke for blocked HTML warnings and external preview filtering.\nNot-tested: Live blocked/NetFunnel SH response, because no live blocked page was available during implementation.
2026-05-15 19:57:25 +09:00
Jeffrey (Dongkyu) Kim
f26efea98d Preserve SH parser helper aliases
Route exported parser helpers through the same public normalizers used by the SH fetch and URL-builder APIs so natural category aliases stay consistent across the package surface.

Constraint: PR #254 Round 2 review found parser helpers still treated raw category aliases as pre-normalized inputs.
Rejected: Keep parser helpers normalized-only | inconsistent with exported URL builders and public helper ergonomics.
Confidence: high
Scope-risk: narrow
Directive: Keep exported SH helper entry points on canonical normalizeSearchOptions/normalizeDetailOptions unless a separate internal-only API is introduced.
Tested: npm test --workspace sh-notice-search; npm run lint --workspace sh-notice-search; npm run typecheck; npm pack --workspace sh-notice-search --dry-run; npm run ci; parser smoke for Korean 임대 list/detail helpers; Ralph architect verification CLEAR; post-deslop regression npm run ci
Not-tested: Live SH network smoke for this follow-up; fixture and injected-fetch coverage exercised the helper contract.
2026-05-15 19:46:27 +09:00
Jeffrey (Dongkyu) Kim
f139d604cf Preserve public SH helper semantics
Route exported URL builders through the same normalization as the CLI/API so natural category aliases cannot bypass srchTp title narrowing or category mapping.\n\nConstraint: PR #254 review found exported helper callers could pass Korean/English public category inputs and get broken or broadened SH URLs.\nRejected: Keep normalized-only fast paths | exported helpers are public API and must protect natural inputs.\nConfidence: high\nScope-risk: narrow\nDirective: Keep exported helper behavior aligned with normalizeSearchOptions and normalizeDetailOptions when adding new public aliases.\nTested: npm test --workspace sh-notice-search; npm run lint --workspace sh-notice-search; npm run typecheck; npm run ci; node helper smoke for 임대 search/detail URLs.\nNot-tested: Live SH network smoke was not rerun for this helper-only change.
2026-05-15 19:35:03 +09:00
Jeffrey (Dongkyu) Kim
c83e194a84 Restore SH notice lookup without proxy policy drift
Reintroduce SH notice search as a direct public HTML client so the skill complies with the free-API proxy boundary while preserving verifiable keyword, pagination, and attachment behavior.

Constraint: i-sh.co.kr board is public unauthenticated HTML, so k-skill-proxy must not host the scraper.\nRejected: Re-adding /v1/sh-notice proxy routes | public HTML scraping in proxy violates repository policy.\nConfidence: high\nScope-risk: moderate\nDirective: Keep SH public HTML access local/direct unless a key-required official free API is discovered and documented.\nTested: npm run ci; npm run lint --workspace sh-notice-search; npm test --workspace sh-notice-search; live SH smoke for 행복주택, 매입임대, 신혼희망타운, page 1/page 5, 1/6/9/11/0 attachment details.\nNot-tested: authenticated SH flows, 청약 application/submission, direct attachment downloads.
2026-05-15 19:22:55 +09:00
Jeffrey (Dongkyu) Kim
5a6dcedb99
Merge pull request #249 from NomaDamas/feature/#248
Feature/#248
2026-05-15 18:02:04 +09:00
Jeffrey (Dongkyu) Kim
945bd32296 Keep icon-only Danawa payment badges visible
Class-only Danawa payment icons can carry eligibility information without visible text, so synthesize display labels from the same normalized condition map used for types and booleans. This keeps raw row labels, condition fields, and returned-window counts aligned for downstream table renderers.\n\nConstraint: PR #253 review follow-up requires TDD coverage before parser changes.\nRejected: Leaving payment_badges text-only | icon-only conditional rows would still render without visible payment labels.\nConfidence: high\nScope-risk: narrow\nDirective: Derive future payment badge labels, types, and booleans from one canonical mapping.\nTested: python3 -m py_compile danawa-price-search/scripts/danawa_search.py scripts/test_danawa_price_search.py; PYTHONPATH=.:scripts python3 -m unittest scripts.test_danawa_price_search; python3 danawa-price-search/scripts/danawa_search.py offers 75001853 --limit 5; npm run lint; npm run typecheck; npm run test\nNot-tested: Danawa icon-only markup was verified with synthetic fixtures rather than a live page snapshot.
2026-05-15 17:50:58 +09:00
Jeffrey (Dongkyu) Kim
56a00005ef Ensure captured Danawa payment badges stay conditional
Classify every whitelisted payment badge into normalized condition types so callers cannot count captured discount, membership, or text-only card rows as normal prices.

Constraint: PR #253 review required TDD follow-up on feature/#252 without changing total_price sorting.\nRejected: Removing discount and membership from the whitelist | would lose Danawa condition labels already captured by the parser.\nConfidence: high\nScope-risk: narrow\nDirective: Keep payment_badge whitelist and payment_condition_types in sync whenever adding new badge classes or text keywords.\nTested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_danawa_price_search; live offers 75001853 --limit 5; npm run lint; npm run typecheck; npm run test; architect verification CLEAR.\nNot-tested: Danawa markup variants not represented by current live page or synthetic badge fixtures.
2026-05-15 17:33:48 +09:00
chanmin
5ff8859b2a fix(danawa-price-search): capture .ico.* payment-condition badges and surface as row labels
PR #226 row 파서에 결제조건 배지(`.ico.cash`/`.ico.point`/`.ico.coupon`/`.ico.card`) selector가 누락돼, 카드 결제 불가능한 현금/쿠폰/포인트 전용가가 일반 최저가로 노출되는 결함을 고친다.

- `offers()` row 파싱부에 결제조건 배지 화이트리스트 캡처 블록 추가 (클래스 `cash`/`point`/`coupon`/`discount`/`card`/`membership` 또는 텍스트 `현금`/`포인트`/`쿠폰`/`할인`만 인정 — 빠른배송/안내/상품리뷰 노이즈 차단)
- row dict 신규 필드 6개: `payment_badges`, `cash_only`, `point_only`, `coupon_only`, `card_only_badge`, `is_conditional_price`
- 반환 dict에 `normal_count`, `conditional_count` 추가
- `SKILL.md` / `docs/features/danawa-price-search.md` 갱신 (Output shape · Response style · Workflow · Failure modes에 결제조건 정책과 표 예시 명시)

정렬 정책은 그대로 `total_price` 단일 기준이며, 결제조건은 row 단위 플래그/라벨로만 노출해 호출자가 결제수단에 맞춰 직접 판단하도록 한다.

회귀 (pcode=75001853, 갤럭시 S25 256GB 자급제 `offers --limit 5`):
- 1위 킴스클럽 979,000원 / `cash_only=True` / `payment_badges=["현금"]`
- 2위 롯데ON 1,072,080원 / `cash_only=False` / `payment_badges=[]`
- 3~5위 일반가 row 모두 `payment_badges` 빈 리스트 (노이즈 0건)

Closes #252

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 17:12:23 +09:00
Jeffrey (Dongkyu) Kim
1476011e4f Preserve Daiso caller headers through Bearer stock lookup
Keep advanced caller headers on the authenticated stock endpoint while generated Bearer and X-DM-UID values remain authoritative. Document the degraded selPkupStr fallback order in skill and source docs so the public workflow matches the restored API surface.\n\nConstraint: PR #250 review required resilient Bearer-primary stock lookup plus selPkupStr fallback and header/body contract coverage.\nRejected: Replacing caller headers with only auth headers | It regressed tracing/test-control header pass-through.\nConfidence: high\nScope-risk: narrow\nDirective: Keep Authorization and X-DM-UID generated by the auth flow even when callers provide same-named headers.\nTested: node --test packages/daiso-product-search/test/index.test.js; npm test --workspace daiso-product-search; npm run lint --workspace daiso-product-search; node --test scripts/skill-docs.test.js; npm run ci; live lookupStoreProductAvailability smoke for 강남역2호점 / VT 리들샷 100.\nNot-tested: Forced live upstream repeated 403; covered by injected fixture tests.
2026-05-15 16:35:16 +09:00
Jeffrey (Dongkyu) Kim
eb83296cc6 Prove Daiso stock retry sends auth headers
Strengthen the retry regression so the Bearer-token contract cannot regress while still returning success from mocked stock responses.\n\nConstraint: PR #250 review requested explicit Authorization, X-DM-UID, and request body assertions on the retry path.\nRejected: Counting requests only | it allowed header/body regressions to pass.\nConfidence: high\nScope-risk: narrow\nDirective: Keep auth-header assertions on both initial and retry stock requests when editing this flow.\nTested: node --test packages/daiso-product-search/test/index.test.js; npm test --workspace daiso-product-search; npm run lint --workspace daiso-product-search; npm run ci; live lookupStoreProductAvailability smoke for 강남역2호점 / VT 리들샷 100; repeated-403 fixture probe.\nNot-tested: Live repeated upstream 403 because forcing Daiso production auth failure is not available without changing upstream state.
2026-05-15 16:18:13 +09:00
Jeffrey (Dongkyu) Kim
d7263a54b9 Preserve Daiso pickup answers when Bearer auth degrades
Keep exact stock lookup on the official Bearer-token path while restoring the public selPkupStr fallback for repeated auth blocks.

Constraint: PR #250 review required Bearer auth to remain primary without removing the resilient pickup eligibility API.

Rejected: Throwing after the retry | it collapses callers back to a brittle single upstream-auth dependency.

Confidence: high

Scope-risk: narrow

Directive: Keep pickupStock quantity semantics separate from pickupEligibility yes/no fallback.

Tested: node --test packages/daiso-product-search/test/index.test.js; npm test --workspace daiso-product-search; npm run lint --workspace daiso-product-search; npm run ci; live lookupStoreProductAvailability smoke for 강남역2호점 / VT 리들샷 100.

Not-tested: Live forced 403 from Daiso upstream; covered with injected fetch regression tests.
2026-05-15 16:04:31 +09:00
arnold714
2641f43863 feat(daiso-product-search): replace blocked-API fallback with Bearer token auth
selStrPkupStck는 더 이상 차단 상태가 아니며, /api/auth/request로 비로그인 JWT를
발급받아 AES-128-CBC(키: PRE_AUTH_ENC_KEY)로 암호화한 Bearer 토큰으로 접근한다.
403 응답 시 토큰을 재발급해 1회 재시도한다. pickupEligibility(selPkupStr) 폴백
로직은 제거했다.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 15:33:24 +09:00
Jeffrey (Dongkyu) Kim
47ed22b076
Merge pull request #247 from NomaDamas/fix/readme-flight-ticket-search
docs(flight-ticket-search): register skill in README table and add feature guide
2026-05-15 01:36:50 +09:00
Jeffrey (Dongkyu) Kim
80303f55f4
Merge pull request #245 from NomaDamas/changeset-release/main
chore: version packages
2026-05-15 01:36:35 +09:00
Jeffrey (Dongkyu) Kim
6df974df36 docs(flight-ticket-search): register skill in README table and add feature guide
PR #224 머지 시 README "어떤 걸 할 수 있나" 표와 "포함된 기능" 리스트, 그리고
docs/features/flight-ticket-search.md 가이드가 등록되지 않아 main에 있는 다른
모든 스킬과 달리 사용자/에이전트가 README만 봐서는 이 스킬을 발견할 수 없는
상태였다. 누락분을 hotfix로 보강한다.

- README 표에 `flight-ticket-search` 행 추가 (마이리얼트립 옆 항공 클러스터)
- README "포함된 기능" 리스트에 가이드 링크 추가
- docs/features/flight-ticket-search.md 신규 작성:
  · 사용 시나리오, 구현 표면(fast-flights==2.2, 사용자 venv 격리)
  · search / compare-month / compare-range / compare-years CLI 예시
  · 응답 필드, IATA 입력 가이드, 예약 링크 정책
  · 검증된 노선 목록, 실패 모드, 비범위, 출처

검증:
- node --test scripts/skill-docs.test.js → 138/138 pass
- ./scripts/validate-skills.sh → skill layout looks valid

코드 변경 없음 → changeset 불필요.
2026-05-15 01:35:43 +09:00
github-actions[bot]
94e4d81f0b chore: version packages 2026-05-14 15:43:24 +00:00
Jeffrey (Dongkyu) Kim
9cb2ea037e
Merge pull request #244 from NomaDamas/dev
Sync dev → main: 신규 스킬 8종 + NTS proxy 라우팅 + k-skill-setup 보강
2026-05-15 00:42:35 +09:00
Jeffrey Han
d965c2ab62 Ship seoul-density script next to its SKILL.md
SKILL.md instructs callers to run $SKILL_DIR/scripts/seoul_density.py but
the script only lived under the repo-root scripts/ tree, so the skill
broke as soon as it was synced into ~/.claude/skills/seoul-density or
~/.agents/skills/seoul-density. Mirror the file into the skill directory
to match the pattern used by nts-business-registration, ticket-availability,
and the daangn-* skills, restoring the single-entrypoint flow described
in the SKILL.md.
2026-05-15 00:32:54 +09:00
Jeffrey (Dongkyu) Kim
9287ce1418
Merge pull request #239 from NomaDamas/feature/#228
Feature/#228
2026-05-15 00:22:59 +09:00
Jeffrey (Dongkyu) Kim
8baf3adc23 Merge branch 'dev' into feature/#228 2026-05-15 00:19:51 +09:00
Jeffrey (Dongkyu) Kim
729a94071a
Merge pull request #241 from Romano1994/feat/seoul-density
Add seoul-density skill and proxy route for Seoul realtime hotspot crowd levels
2026-05-15 00:18:08 +09:00
Jeffrey (Dongkyu) Kim
2a3877381b
Merge pull request #243 from NomaDamas/feature/#242
Feature/#242
2026-05-15 00:14:47 +09:00
Jeffrey (Dongkyu) Kim
641d96b8fc Harden NTS validate privacy boundary
Prevent proxy exception messages from exposing upstream URLs, align validate field bounds across proxy and Python helpers, and make the hosted validate privacy path explicit in docs.

Constraint: non-interactive PR #243 follow-up with no production DATA_GO_KR_API_KEY authority.

Rejected: returning raw upstream fetch errors | could leak serviceKey if custom fetch/proxy errors include full URLs.

Rejected: leaving helper-copy drift to manual cmp checks | behavior test now loads the skill-local helper directly.

Confidence: high

Scope-risk: narrow

Directive: keep validate uncached and avoid echoing representative/date/address inputs in proxy responses.

Tested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_nts_business_registration; npm run test --workspace k-skill-proxy -- --test-name-pattern 'NTS business'; mocked fetch-exception smoke; git diff --check origin/dev...HEAD; npm run ci

Not-tested: live data.go.kr calls, no production DATA_GO_KR_API_KEY authority
2026-05-14 22:20:33 +09:00
Jeffrey (Dongkyu) Kim
521524edeb Harden NTS validate privacy boundaries
Keep status lookups cacheable while making authenticity validation non-cacheable and redacting validate-only sensitive fields from proxy-shaped and upstream-echoed responses. Treat semantic NTS non-OK payloads as upstream errors so transient service failures are not cached.

Constraint: Review follow-up required TDD for privacy-sensitive validate behavior and semantic upstream failures.

Rejected: Reusing the status response-shaping path for validate | it retains or echoes representative/date/address inputs beyond the upstream request.

Confidence: high

Scope-risk: narrow

Directive: Do not re-enable validate success caching or echo full normalized validate inputs without a fresh privacy review.

Tested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_nts_business_registration; npm run test --workspace k-skill-proxy -- --test-name-pattern 'NTS business'; mocked validate smoke for no-cache/redaction; npm run ci

Not-tested: Live data.go.kr NTS calls; no production DATA_GO_KR_API_KEY authority in automation.
2026-05-14 21:55:32 +09:00
Jeffrey (Dongkyu) Kim
cd3366a9dc Route NTS business checks through the proxy
Add the NTS business registration skill and proxy endpoints so agents can verify business-number status and authenticity without exposing data.go.kr keys to users.\n\nConstraint: data.go.kr publicDataPk=15081808 requires a server-side API key, so the route belongs behind k-skill-proxy.\nRejected: caller-supplied service keys | would violate the proxy credential boundary and duplicate user setup.\nConfidence: high\nScope-risk: moderate\nDirective: Keep future NTS fields normalized at the proxy boundary and never accept client serviceKey overrides.\nTested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_nts_business_registration; npm run test --workspace k-skill-proxy -- --test-name-pattern 'NTS business'; buildServer smoke inject; npm run ci\nNot-tested: live data.go.kr request, because this session has no production DATA_GO_KR_API_KEY authority.
2026-05-14 21:40:26 +09:00
romano1994
315dbbb66b Add seoul-density skill and proxy route for Seoul realtime hotspot crowd levels 2026-05-14 15:37:54 +09:00
Jeffrey (Dongkyu) Kim
ca9a7df933
Keep dev CI/CD workflows plannable
Fix the Python release workflow and ticket helper import behavior so remote CI can run to completion after recent dev merges.

Constraint: Python release automation remains scaffold-only until python-packages/* contains a real pyproject.toml
Rejected: Installing ad-hoc Python dependencies in CI | the repository does not yet have a Python package dependency contract
Confidence: high
Scope-risk: narrow
Directive: Keep workflow-time package detection in a checked-out job, not job-level hashFiles guards
Tested: PR #240 GitHub Actions validate; local npm run ci
Not-tested: Actual PyPI publication because no Python package release exists yet
2026-05-14 12:31:04 +09:00
Jeffrey (Dongkyu) Kim
b7169efd95 Let ticket tests run without httpx installed
Make httpx a checked runtime dependency instead of an import-time requirement so CI can import and test the mocked ticket availability helpers in a clean Python environment.

Constraint: ticket availability runtime still uses httpx for live read-only endpoint calls
Rejected: Adding a repository-wide Python dependency installer | this repo has no concrete Python package dependency flow yet
Confidence: high
Scope-risk: narrow
Directive: Keep live ticket lookup dependency failures explicit at command execution time
Tested: python3 -m py_compile scripts/ticket_availability.py ticket-availability/scripts/ticket_availability.py; PYTHONPATH=. python3 -m unittest scripts.test_ticket_availability; npm run ci
Not-tested: live YES24/Interpark calls without httpx, expected to fail with dependency guidance
2026-05-14 12:30:05 +09:00
Jeffrey (Dongkyu) Kim
3f9aee6111 Make Python release workflow plan safely
Move Python package detection into an explicit setup job so GitHub Actions can plan the release workflow instead of failing before jobs/logs are created.

Constraint: Python release flow is scaffold-only until a real python-packages/* pyproject exists
Rejected: Keeping job-level hashFiles guards | GitHub reported zero-second workflow-file failures with no jobs or logs
Confidence: high
Scope-risk: narrow
Directive: Keep release-please publish work gated behind detected concrete Python package paths
Tested: ruby YAML parse; npm run ci
Not-tested: actual release-please publication because no Python package exists yet
2026-05-14 12:26:57 +09:00
Jeffrey (Dongkyu) Kim
a22cfdc3dc
Merge pull request #233 from NomaDamas/feature/#220
Feature/#220
2026-05-14 12:24:08 +09:00
Jeffrey (Dongkyu) Kim
1d310eec39 Rebase Gangnam Unni search onto latest dev
Keep the Gangnam Unni package dry-run coverage while incorporating the latest dev validation scripts.\n\nConstraint: PR #233 became conflicting after dev advanced with ticket availability and Daangn skills.\nRejected: Taking either package script side wholesale | would drop either Gangnam Unni pack coverage or current dev test coverage.\nConfidence: high\nScope-risk: narrow\nDirective: Preserve additive root script checks for independently merged skills.\nTested: package.json JSON parse; git diff --check.\nNot-tested: Full npm run ci pending after merge commit.
2026-05-14 12:23:09 +09:00
Jeffrey (Dongkyu) Kim
3ccb44afda Constrain report fetch credentials
Scope caller-owned GitHub credentials to API requests, add exact-file contents fallback for known report fetches, and report actual inspected detail attempts. This tightens the public mirror boundary without adding proxy auth or broadening release metadata.

Constraint: public GitHub mirror remains keyless by default; optional caller tokens must stay least-privilege.

Rejected: forwarding GitHub auth headers to all GitHub-operated hosts | raw.githubusercontent.com does not need API credentials for the verified path.

Confidence: high

Scope-risk: narrow

Directive: Keep optional credentials host-scoped unless a future caller explicitly opts into raw-host forwarding.

Tested: npm run lint --workspace daishin-report-search; npm run test --workspace daishin-report-search; npm pack --workspace daishin-report-search --dry-run; npm run ci; injected raw/API header and contents fallback smoke; live exact-report and latest-list CLI smokes; architect/code-reviewer verification.

Not-tested: authenticated live GitHub token path with a real token.
2026-05-14 09:57:45 +09:00
Jeffrey (Dongkyu) Kim
3e1d3a21a2 Make report discovery failures actionable
Constraint: GitHub tree discovery is public and keyless by default, so latest/search must not require the proxy or a token.\nRejected: Proxying the public GitHub API | violates the repo no-proxy policy for fully public endpoints.\nConfidence: high\nScope-risk: narrow\nDirective: Keep caller-supplied GitHub tokens optional and never persist or echo request headers in source errors.\nTested: npm run lint --workspace daishin-report-search; npm run test --workspace daishin-report-search; npm pack --workspace daishin-report-search --dry-run; npm run ci; live CLI latest structured 403 smoke; live exact-report smoke; injected rate/header/entity smokes; architect verification CLEAR.\nNot-tested: Authenticated live latest search with a real GitHub token.
2026-05-14 09:45:50 +09:00
Jeffrey (Dongkyu) Kim
448d0c8acc Bound report search inputs to protect public fetches
Clamp list sizing in the library, keep CLI values uncoerced until validation, and preserve malformed numeric entities so mirrored HTML cannot crash parsing. Document the limits and provenance caveat surfaced in review.

Constraint: GitHub mirror access is public and unauthenticated, so defensive client-side fetch bounds matter more than adding proxy/token behavior in this follow-up.
Rejected: Coercing CLI numeric flags with Number before library validation | this preserved Infinity/huge values and bypassed the shared bounds.
Confidence: high
Scope-risk: narrow
Directive: Keep future daishin-report-search listing options bounded before they drive raw GitHub fetch loops.
Tested: npm run lint --workspace daishin-report-search; npm run test --workspace daishin-report-search; npm pack --workspace daishin-report-search --dry-run; npm run ci; exact-report live smoke for 20260511082352 with includeExplain; local injected-fetch smoke for clamped CLI/library behavior and malformed entities.
Not-tested: live latest-list smoke remained blocked by upstream unauthenticated GitHub API 403 rate limiting.

Co-authored-by: OmX <omx@oh-my-codex.dev>
2026-05-14 09:32:36 +09:00
Jeffrey (Dongkyu) Kim
d48a962d91 Make Daishin reports discoverable from the public mirror
Constraint: Issue #228 requires a skill that discovers latest report pages from the provided GitHub Pages mirror and makes them agent-readable.\nRejected: Screen-scraping GitHub Pages directory listings | the GitHub recursive tree API is a more stable public index.\nConfidence: high\nScope-risk: narrow\nDirective: Keep this skill on public unauthenticated GitHub/raw endpoints unless upstream starts requiring an API key; do not add a proxy route for public pages.\nTested: npm run lint --workspace daishin-report-search; npm run test --workspace daishin-report-search; npm pack --workspace daishin-report-search --dry-run; npm run ci; live CLI list/detail smoke tests.\nNot-tested: Authenticated GitHub API higher-rate-limit path.
2026-05-14 09:19:28 +09:00
Jeffrey (Dongkyu) Kim
577091aa44
Merge pull request #234 from Hybirdss/feat/ticket-availability
feat(ticket-availability): YES24·인터파크 공연 일정·잔여석 조회 (조회 전용)
2026-05-14 00:58:16 +09:00
Jeffrey (Dongkyu) Kim
a5eb876511 Rebase ticket availability onto latest dev
Keep the ticket availability validation entries while incorporating the latest dev Daangn skill checks.\n\nConstraint: PR #234 became conflicting again after dev advanced.\nRejected: Taking dev package scripts unchanged | would drop ticket availability validation.\nConfidence: high\nScope-risk: narrow\nDirective: Preserve additive root script checks for independently merged skills.\nTested: package.json JSON parse; git diff --check.\nNot-tested: Full npm run ci pending after merge commit.
2026-05-14 00:57:09 +09:00
Jeffrey (Dongkyu) Kim
609f6150b5
Merge pull request #237 from taeyoung1005/feature/daangn-search-skills
feat: add Daangn search skills
2026-05-14 00:55:49 +09:00
Jeffrey (Dongkyu) Kim
3109b6684a Keep Daangn jobs detail resilient
Add an HTML metadata fallback because the public jobs detail _data route currently returns an empty response while the redirected public page still exposes read-only title/meta data.

Constraint: PR 237 must remain read-only and avoid proxy/auth additions for public Daangn surfaces
Rejected: Treating the 204 _data response as acceptable | it breaks the documented detail command
Confidence: high
Scope-risk: narrow
Directive: Keep Daangn jobs detail on public HTML/meta fallback unless a stable JSON detail surface is verified
Tested: npm run ci; live daangn_jobs.py search/detail smoke
Not-tested: authenticated or interactive Daangn actions, intentionally out of scope
2026-05-14 00:40:00 +09:00
Jeffrey (Dongkyu) Kim
45dcfd0897 Unblock ticket availability PR against current dev
Preserve the ticket availability checks while keeping the newer manus bundle CI additions from dev.\n\nConstraint: PR #234 was non-mergeable because root package scripts changed on both head and dev.\nRejected: Taking either side wholesale | would drop either ticket availability validation or manus bundle validation.\nConfidence: high\nScope-risk: narrow\nDirective: Keep root lint/test script additions additive when merging independent skills.\nTested: node JSON parse for package.json; git diff --check.\nNot-tested: Full npm run ci pending after merge commit.
2026-05-14 00:38:28 +09:00
Jeffrey (Dongkyu) Kim
860bf53ed3 Unblock Gangnam Unni PR against current dev
Preserve the PR's workspace release coverage while keeping the newer manus bundle test entry from dev.\n\nConstraint: PR #233 was non-mergeable because package.json changed on both head and dev.\nRejected: Taking either side wholesale | would drop either gangnamunni pack coverage or manus bundle test coverage.\nConfidence: high\nScope-risk: narrow\nDirective: Keep additive package script conflicts merged rather than replacing workspace entries.\nTested: node JSON parse for package.json; git diff --check.\nNot-tested: Full npm run ci pending after merge commit.
2026-05-14 00:36:55 +09:00
Jeffrey (Dongkyu) Kim
0ddb23d2af
Merge pull request #238 from NomaDamas/changeset-release/main
chore: version packages
2026-05-14 00:34:23 +09:00
github-actions[bot]
20522ab43c chore: version packages 2026-05-13 08:02:54 +00:00
Jeffrey (Dongkyu) Kim
3a4e409887
Merge pull request #232 from NomaDamas/dev
Merge dev into main
2026-05-13 17:02:08 +09:00
Jeffrey (Dongkyu) Kim
5da1f0e240 Make KOBUS seat holds reproducible for checkout handoff
Capture the verified KOBUS non-member flow in a reusable helper that searches schedules, creates a temporary seat hold, saves an official payment-page autosubmit helper, and records cancellation fields.

Constraint: KOBUS requires session-backed POST fields and returns pcpyNoAll/satsNoAll from setPcpy.ajax before checkout entry.
Rejected: Opening payment page by URL alone | stplcfmpym.do requires the selected schedule, fare, seat, and hold POST body.
Confidence: high
Scope-risk: narrow
Directive: Never submit card/payment fields automatically; cancel abandoned holds with cancPcpy.ajax.
Tested: python3 -m py_compile express-bus-booking/scripts/kobus_express_booking.py; live 서울 센트럴시티(021)→광주 유스퀘어(500) 20260520 --hold-first-seat returned MSG_CD=S0000 pcpyNoAll and rendered payment-info page; /mrs/cancPcpy.ajax returned MSG_CD=S0000; ./scripts/validate-skills.sh
Not-tested: final payment submission, mobile in-app browser behavior, mixed passenger discounts
Co-authored-by: OpenAI Codex <codex@openai.com>
Co-authored-by: OmX <omx@oh-my-codex.local>
2026-05-13 16:37:10 +09:00
Jeffrey (Dongkyu) Kim
53887e992f Make KOBUS seat holds reproducible for checkout handoff
Capture the verified KOBUS non-member flow in a reusable helper that searches schedules, creates a temporary seat hold, saves an official payment-page autosubmit helper, and records cancellation fields.

Constraint: KOBUS requires session-backed POST fields and returns pcpyNoAll/satsNoAll from setPcpy.ajax before checkout entry.
Rejected: Opening payment page by URL alone | stplcfmpym.do requires the selected schedule, fare, seat, and hold POST body.
Confidence: high
Scope-risk: narrow
Directive: Never submit card/payment fields automatically; cancel abandoned holds with cancPcpy.ajax.
Tested: python3 -m py_compile express-bus-booking/scripts/kobus_express_booking.py; live 서울 센트럴시티(021)→광주 유스퀘어(500) 20260520 --hold-first-seat returned MSG_CD=S0000 pcpyNoAll and rendered payment-info page; /mrs/cancPcpy.ajax returned MSG_CD=S0000; ./scripts/validate-skills.sh
Not-tested: final payment submission, mobile in-app browser behavior, mixed passenger discounts
Co-authored-by: OpenAI Codex <codex@openai.com>
Co-authored-by: OmX <omx@oh-my-codex.local>
2026-05-13 16:36:23 +09:00
Jeffrey (Dongkyu) Kim
49bf262bb9 Route shared key APIs through the proxy
Move KOSIS general lookups and Kakao Local geocoding behind k-skill-proxy so users do not need to manage those API keys for common skill flows. Keep KOSIS bigdata/direct calls user-keyed because userStatsId is account-specific.

Constraint: Free API proxy policy allows proxying upstreams that require API keys while keeping routes narrow, cache-backed, and public.

Rejected: Proxy ODsay transit routing | Basic quota is low, time-limited, and IP-whitelist-bound, so centralizing it would create quota and operations risk.

Confidence: high

Scope-risk: moderate

Directive: Keep KOSIS bigdata direct unless a per-user credential design is added; do not route broad Kakao surfaces without explicit allowlists and rate limits.

Tested: npm run ci; local KOSIS proxy smoke via /v1/kosis/search and /v1/kosis/meta; local Kakao proxy smoke via /v1/kakao-local/geocode q=서울역.

Not-tested: Production proxy deployment after main merge/cron update.
2026-05-13 16:31:29 +09:00
TaeyoungPark
95d5e9d05b docs: document Daangn search skills 2026-05-13 16:01:42 +09:00
TaeyoungPark
12adac4eb8 feat: add Daangn search skills 2026-05-13 15:48:49 +09:00
Jeffrey (Dongkyu) Kim
34127550fa Let intercity booking helper create temporary seat holds
Extend the Tmoney intercity helper from read-only timetable lookup to the browser-equivalent seat-stage and temporary hold flow, saving the official card-information page and cancel/back fields while still avoiding card submission.

Constraint: readPcpySats.do creates a live sats_Pcpy_Id hold, so abandoned test holds must be released with the official pcpyCanc=C back flow.

Rejected: Automating final payment | card submission is irreversible and remains a manual user action.

Confidence: high

Scope-risk: narrow

Directive: Treat holds as short-lived; hand off immediately and cancel abandoned holds.

Tested: python3 -m py_compile intercity-bus-booking/scripts/intercity_bus_search.py ~/.agents/skills/intercity-bus-booking/scripts/intercity_bus_search.py; live --hold-first-seat for 동서울→속초 20260520 produced sats_Pcpy_Id and card-info page; posted cancel/back fields and verified timetable remained 24/28; ./scripts/validate-skills.sh; node --test scripts/skill-docs.test.js; npm run lint

Not-tested: card info entry, final payment, mixed passenger hold payloads
2026-05-13 15:43:26 +09:00
Jeffrey (Dongkyu) Kim
b4aae5b295 Make intercity timetable lookup follow Tmoney form contract
Add a read-only timetable helper that starts a cookie-backed Tmoney session, submits the hidden browser fields required by readAlcnList.do, and parses readSasFeeInf schedule rows into JSON.

Constraint: Tmoney returns a generic errorCont page unless bef_Aft_Dvs and req_Rec_Num are posted with the timetable form.

Rejected: Browser automation-first lookup | official HTTP flow works when the browser-submitted hidden fields are included.

Confidence: high

Scope-risk: narrow

Directive: Do not automate final card submission or payment from this skill without explicit user confirmation.

Tested: python3 -m py_compile intercity-bus-booking/scripts/intercity_bus_search.py; python3 intercity-bus-booking/scripts/intercity_bus_search.py --depart-code 0511601 --arrive-code 2482701 --depart-name 동서울 --arrive-name 속초 --date 20260520 --limit 1; rsync to ~/.claude/skills and ~/.agents/skills; ./scripts/validate-skills.sh; node --test scripts/skill-docs.test.js; npm run lint

Not-tested: temporary seat hold, cancellation, card entry, and payment flows
2026-05-13 14:49:22 +09:00
Jeffrey (Dongkyu) Kim
dd11fa9d30 Resolve dev-to-main integration conflicts
Merged origin/main into dev for PR #232 while preserving the dev-side contribution guide, KOSIS/Danawa CI coverage, and new workspace pack checks alongside main's Manus bundle workflow and docs.

Constraint: PR #232 targets main from dev and GitHub reported mergeable=false.

Rejected: choosing either side wholesale | would drop either dev's new skill validation or main's Manus bundle automation.

Confidence: high

Scope-risk: narrow

Directive: Keep root package scripts as the union of active workspace/package checks when resolving future branch integrations.

Tested: npm run ci

Not-tested: live GitHub mergeability after push before remote checks complete

Co-authored-by: OmX <omx@oh-my-codex.dev>
2026-05-13 14:47:03 +09:00
Hybirdss
83079cd4c8 feat(ticket-availability): YES24·인터파크 공연 일정·잔여석 조회 (조회 전용)
- 공개 endpoint (YES24 axPerfDay/PlayTime/RemainSeat, 인터파크 playSeq/REMAINSEAT) 만 단일 HTTP 호출
- httpx only, CloakBrowser/Playwright 없음, 로그인·시크릿·쿠키 없음
- 예매·결제·좌석 선택·자동화 의도적 제외 (공연법 §4조의2 매크로 부정구매 형사처벌)
- 20 unit test (mocked httpx) + validate-skills.sh PASS
- README + docs/features 가이드 추가
2026-05-13 02:50:26 +09:00
Jeffrey (Dongkyu) Kim
6389b73e28 Record Gangnam Unni sources for auditability
Constraint: PR #233 round-2 review requested central docs/sources.md ledger coverage for the new public Gangnam Unni search surface.
Rejected: Broader skill/package changes | The approved follow-up only needed source-ledger docs and stable regression coverage.
Confidence: high
Scope-risk: narrow
Directive: Keep source-ledger tests focused on stable public URLs and do not assert package versions or changeset file presence.
Tested: node --test scripts/skill-docs.test.js; npm test --workspace gangnamunni-clinic-search; node packages/gangnamunni-clinic-search/src/cli.js "강남 성형외과" --limit 1; npm run ci pre- and post-deslop
Not-tested: CI on GitHub Actions
2026-05-13 02:29:07 +09:00
Jeffrey (Dongkyu) Kim
fe8cb7db6e Harden Gangnam Unni clinic lookup for review follow-up
Address PR review blockers by aligning install docs, preserving raw Next.js JSON parsing semantics, bounding upstream fetches, and reducing sensitive query leakage in errors.\n\nConstraint: Issue #220 follow-up required TDD, full CI, live CLI smoke, deslop pass, push to feature/#220, and one signed PR comment.\nRejected: Pre-decoding the entire __NEXT_DATA__ script body before JSON.parse | corrupts valid JSON strings containing literal entity-looking text.\nConfidence: high\nScope-risk: narrow\nDirective: Keep entity-decoded parsing as a tested compatibility fallback only; do not make it the primary parse path.\nTested: npm test --workspace gangnamunni-clinic-search; node --test scripts/skill-docs.test.js; node packages/gangnamunni-clinic-search/src/cli.js "강남 성형외과" --limit 1; npm run ci twice, including post-deslop.\nNot-tested: Browser-rendered Gangnam Unni UI beyond the public Next.js payload smoke.
2026-05-13 02:13:39 +09:00
Jeffrey (Dongkyu) Kim
da0632c73d Enable public Gangnam Unni clinic lookup
Add a read-only Gangnam Unni search skill and npm helper that parses the public Next.js search payload, documents the discovered access path, and keeps login/app-only medical actions out of scope.\n\nConstraint: Issue #220 requested a Gangnam Unni plastic-surgery clinic lookup skill with TDD and PR delivery.\nRejected: Proxy route | upstream is an unauthenticated public web surface, so proxy policy says direct user-machine calls.\nConfidence: high\nScope-risk: narrow\nDirective: Keep this skill read-only; do not add login, booking, consultation, payment, or medical-advice automation.\nTested: npm test --workspace gangnamunni-clinic-search; live CLI search for "강남 성형외과"; npm run ci twice after implementation/deslop review.\nNot-tested: Logged-in/app-only Gangnam Unni flows, intentionally out of scope.
2026-05-13 01:49:16 +09:00
Jeffrey (Dongkyu) Kim
fc8edd61df
Merge pull request #224 from taeyoung1005/feat/flight-ticket-search
feat: 항공권 조회 스킬 추가
2026-05-12 19:21:34 +09:00
Jeffrey (Dongkyu) Kim
3e22a78bf3
Merge pull request #225 from taeyoung1005/feat/korean-bus-booking-skills
한국 고속버스·시외버스 예매 스킬 추가
2026-05-12 19:20:22 +09:00
Jeffrey (Dongkyu) Kim
568a4453b9
Merge pull request #226 from taeyoung1005/feat/danawa-price-search
feat: 다나와 최저가 비교 스킬 추가
2026-05-12 19:18:43 +09:00
Jeffrey (Dongkyu) Kim
552e5c646b
Merge pull request #229 from taeyoung1005/feat/myrealtrip-mcp-search
마이리얼트립 MCP 검색 스킬 추가
2026-05-12 19:17:16 +09:00
Jeffrey (Dongkyu) Kim
14fd8a4980
Merge pull request #231 from ce-dric/feat/ncard-v2
feat: N카드 할인 예매 지원 추가 (ktx-booking)
2026-05-12 19:16:07 +09:00
Jeffrey (Dongkyu) Kim
84b3c993df Validate flight search arguments before bootstrap
Parse CLI arguments before installing the cached fast-flights runtime, add numeric bounds, validate date relationships early, and always use the pinned private runtime instead of arbitrary global installs.

Constraint: PR #224 helper should keep --help and invalid input paths offline and deterministic.

Rejected: Importing any global fast_flights package opportunistically | version drift can break TFS URL generation and query behavior.

Confidence: high

Scope-risk: narrow

Directive: Keep provider execution behind explicit valid commands; do not bootstrap dependencies for help or parser errors.

Tested: python3 -m py_compile flight-ticket-search/scripts/flight_ticket_search.py; FLIGHT_TICKET_SEARCH_BOOTSTRAPPED=1 python3 flight-ticket-search/scripts/flight_ticket_search.py --help; invalid return-date and step-days parser checks; git diff --check

Not-tested: Live Google Flights fetch through fast-flights.
2026-05-12 19:11:29 +09:00
Jeffrey (Dongkyu) Kim
fed3d9e2e7 Align Danawa offers with total price contract
Sort offer rows by computed delivered total before limiting results, reject non-positive limits, and align the feature docs with the actual compare flags and url field.

Constraint: PR #226 documents total_price-first comparison and the helper must match that contract.

Rejected: Leaving Danawa minPrice order untouched | it can surface a higher delivered total before a cheaper offer.

Confidence: high

Scope-risk: narrow

Directive: Preserve total_price as the user-facing ranking key unless Danawa exposes a better delivered-price endpoint.

Tested: python3 -m py_compile danawa-price-search/scripts/danawa_search.py; ./scripts/validate-skills.sh; git diff --check

Not-tested: Full live Danawa offer scrape after patch.
2026-05-12 19:11:28 +09:00
Jeffrey (Dongkyu) Kim
e1a6031569 Cover MyRealTrip MCP wrapper parsing
Add focused unit tests for JSON object validation, key=value decoding, and override merge behavior so the new wrapper has offline regression coverage.

Constraint: PR #229 introduces a public MCP wrapper whose live endpoint should not be required for CI coverage.

Rejected: Live MCP smoke as the only validation | upstream availability would make the regression path flaky.

Confidence: high

Scope-risk: narrow

Directive: Keep wrapper argument parsing covered without requiring network or mcp package installation.

Tested: python3 -m unittest scripts.test_myrealtrip_mcp; node --test scripts/skill-docs.test.js; ./scripts/validate-skills.sh

Not-tested: Live MyRealTrip MCP endpoint call.
2026-05-12 19:10:00 +09:00
Jeffrey (Dongkyu) Kim
7a0cefb832 Cover NCard error branches
Add regression coverage for missing NCard package handling and zero-based NCard selection so the KTX helper keeps clear failures around optional korail2-ncard support.

Constraint: PR #231 adds optional NCard behavior that must still be safe when korail2-ncard is not installed.

Rejected: Changing runtime NCard behavior now | existing implementation already returns explicit SystemExit messages and only lacked regression coverage.

Confidence: high

Scope-risk: narrow

Directive: Keep NCard fallback behavior tested separately from normal korail2 imports.

Tested: python3 -m pytest scripts/test_ktx_booking.py -q

Not-tested: Live Korail NCard reservation against production account.
2026-05-12 19:09:45 +09:00
Jeffrey (Dongkyu) Kim
25db06795e Prevent invalid flight queries from bootstrapping runtime
Validate flight-ticket CLI inputs before initializing the fast-flights runtime so bad dates, same-airport routes, and impossible ranges fail quickly without network or environment-dependent setup.

Constraint: PR #224 adds a crawler-style skill whose helper must handle invalid user input conservatively before external provider access.

Rejected: Add broad round-trip comparison support | outside the minimal merge-blocking correctness fix.

Confidence: high

Scope-risk: narrow

Directive: Keep provider/runtime initialization after argparse help and local input validation.

Tested: python3 -m py_compile flight-ticket-search/scripts/flight_ticket_search.py; CLI help and invalid-input smoke checks; ./scripts/validate-skills.sh; npm run typecheck; npm run lint; npm run test

Not-tested: Live Google Flights search because local Homebrew Python has a pyexpat/libexpat linkage failure during dependency bootstrap.

Co-authored-by: OmX <omx@oh-my-codex.dev>
2026-05-12 19:09:19 +09:00
Jeffrey (Dongkyu) Kim
d31157cba3 Reduce N-card number exposure in KTX booking
Prefer N-card selection by list index so full card numbers are not echoed through JSON output or required in shell history. Keep direct card-number input as a compatibility escape hatch with an explicit warning.\n\nConstraint: PR #231 scope is limited to ktx-booking docs, helper, and tests.\nRejected: Require users to copy full card numbers from ncard-list | exposes sensitive identifiers in logs and shell history.\nConfidence: high\nScope-risk: narrow\nDirective: Keep N-card list outputs masked; prefer index-based selection for future reservation flows.\nTested: python3 -m py_compile scripts/ktx_booking.py scripts/test_ktx_booking.py; PYTHONPATH=scripts python3 -m unittest scripts.test_ktx_booking; npm run lint; npm run typecheck; npm test\nNot-tested: Live Korail N-card reservation; requires real user credentials and owned N-card.
2026-05-12 19:06:27 +09:00
Jeffrey (Dongkyu) Kim
5680497cb3 Keep Danawa price search mergeable and consistent
Merge the current dev branch so PR #226 no longer conflicts with the SH notice removal and marathon-schedule additions, while keeping the Danawa helper/documentation aligned with its 실구매가 contract. The PR diff is narrowed to Danawa files, README discovery, and the root lint hook for the new helper.

Constraint: PR #226 targets dev and GitHub reported DIRTY before this worker fix.

Rejected: Leaving inherited court-auction release metadata and SH-notice whitespace in the PR | they were unrelated to the Danawa skill and disappeared after reconciling with current dev.

Confidence: high

Scope-risk: narrow

Directive: Keep Danawa examples and JSON field docs synchronized with danawa_search.py CLI/output names.

Tested: python3 -m py_compile danawa-price-search/scripts/danawa_search.py; python3 danawa-price-search/scripts/danawa_search.py --help; python3 danawa-price-search/scripts/danawa_search.py search '에어팟 프로 2세대' --limit 1; npm run ci; git diff --check

Not-tested: Live Danawa offers endpoint after commit beyond search smoke.
2026-05-12 19:05:29 +09:00
Jeffrey (Dongkyu) Kim
62b6bfd8cc Clarify bus booking secret requirements
Keep the new bus booking docs aligned with repository credential semantics: checkout/payment remains manual, so the README login column should stay binary while setup documents that no user secrets are required.

Constraint: PR #225 scope is documentation and SKILL files only.

Rejected: editing package/test files to satisfy stale-branch failures | those files are outside this PR scope and already fixed on dev.

Confidence: high

Scope-risk: narrow

Directive: Keep bus booking payment steps as manual handoff unless a later PR adds explicit payment automation safeguards.

Tested: npm run lint; npm run typecheck; ./scripts/validate-skills.sh; node --test scripts/skill-docs.test.js (PR-head failures are stale dev drift in ktx/package files outside scope).

Not-tested: live KOBUS/Tmoney booking endpoints and payment pages.
2026-05-12 19:04:03 +09:00
Jeffrey (Dongkyu) Kim
78e505bfc0 Keep MyRealTrip MCP calls bounded and covered
Add a timeout guard around the remote Streamable HTTP MCP call and focused wrapper tests so the new skill fails predictably under upstream stalls without touching unrelated PR scope.

Constraint: Task scope allowed fixes only inside PR #229 files; package.json test wiring was left unchanged because it is outside this PR's original file set.

Rejected: Editing root package scripts to auto-run the new unittest | outside assigned PR file scope without leader approval.

Confidence: high

Scope-risk: narrow

Directive: Keep MyRealTrip live calls bounded; do not remove the timeout without replacing it with an equivalent cancellation mechanism.

Tested: python3 -m py_compile myrealtrip-search/scripts/myrealtrip_mcp.py myrealtrip-search/scripts/test_myrealtrip_mcp.py; PYTHONPATH=myrealtrip-search/scripts python3 -m unittest myrealtrip-search/scripts/test_myrealtrip_mcp.py; node --test scripts/skill-docs.test.js; ./scripts/validate-skills.sh; npm run typecheck; git diff --check; npm test

Not-tested: Live MyRealTrip MCP tools smoke because local environment lacks the optional mcp Python package and live network dependency is upstream-owned.
2026-05-12 19:03:34 +09:00
Jeffrey (Dongkyu) Kim
7e89bc3647 Avoid false session-expiry labels for validation errors
The toss wrapper now treats bare validation_error text as an upstream command failure instead of a session-expired signal. Structured auth doctor JSON remains the source of truth for empty portfolio/watchlist invalid-session promotion, while known stored-session-invalid stderr still maps to TossSessionExpiredError.\n\nConstraint: PR #192 follow-up must stay scoped to issue #126 toss-securities behavior.\nRejected: Keep validation_error in the global regex | it mislabels auth doctor transport failures and quote 403 validation errors as session expiry.\nConfidence: high\nScope-risk: narrow\nDirective: Do not broaden the free-text session classifier without regressions for auth doctor and quote upstream validation failures.\nTested: npm run lint --workspace toss-securities; npm run test --workspace toss-securities; npm run ci; manual mock tossctl validation_error checks; architect verification CLEAR\nNot-tested: Live tossctl network/auth session against real Toss upstream
2026-05-12 18:59:53 +09:00
Jeffrey (Dongkyu) Kim
6467f8b2db Clarify toss empty-output session expiry
Portfolio and watchlist reads can exit successfully with empty payloads when the stored Toss session has expired. The empty-output path now verifies the session before JSON parsing and only promotes confirmed invalid auth doctor data into TossSessionExpiredError.

Constraint: Scope is limited to toss-securities issue #126 follow-up on PR #192

Rejected: Treat auth doctor execution failures as expired sessions | unsupported or failing doctor output is inconclusive without parsed session.valid=false

Confidence: high

Scope-risk: narrow

Directive: Keep empty-result session expiry classification tied to explicit auth doctor confirmation

Tested: npm run test --workspace toss-securities; npm run lint --workspace toss-securities; npm run ci; manual mock tossctl blank stdout invalid/inconclusive doctor checks
2026-05-12 18:59:50 +09:00
galvaomica
ad7a9d2aee fix(toss-securities): clarify session expiry and quote 403 handling 2026-05-12 18:59:48 +09:00
Jeffrey (Dongkyu) Kim
01de419f73 Feature/#126 (#193)
* fix(toss-securities): clarify session expiry and quote 403 handling

* Clarify toss empty-output session expiry

Portfolio and watchlist reads can exit successfully with empty payloads when the stored Toss session has expired. The empty-output path now verifies the session before JSON parsing and only promotes confirmed invalid auth doctor data into TossSessionExpiredError.

Constraint: Scope is limited to toss-securities issue #126 follow-up on PR #192

Rejected: Treat auth doctor execution failures as expired sessions | unsupported or failing doctor output is inconclusive without parsed session.valid=false

Confidence: high

Scope-risk: narrow

Directive: Keep empty-result session expiry classification tied to explicit auth doctor confirmation

Tested: npm run test --workspace toss-securities; npm run lint --workspace toss-securities; npm run ci; manual mock tossctl blank stdout invalid/inconclusive doctor checks

* Avoid false session-expiry labels for validation errors

The toss wrapper now treats bare validation_error text as an upstream command failure instead of a session-expired signal. Structured auth doctor JSON remains the source of truth for empty portfolio/watchlist invalid-session promotion, while known stored-session-invalid stderr still maps to TossSessionExpiredError.\n\nConstraint: PR #192 follow-up must stay scoped to issue #126 toss-securities behavior.\nRejected: Keep validation_error in the global regex | it mislabels auth doctor transport failures and quote 403 validation errors as session expiry.\nConfidence: high\nScope-risk: narrow\nDirective: Do not broaden the free-text session classifier without regressions for auth doctor and quote upstream validation failures.\nTested: npm run lint --workspace toss-securities; npm run test --workspace toss-securities; npm run ci; manual mock tossctl validation_error checks; architect verification CLEAR\nNot-tested: Live tossctl network/auth session against real Toss upstream

* Preserve toss empty-response auth-doctor contract

The prior review identified the empty portfolio/watchlist promotion rule as an upstream-contract dependency worth making explicit. Add regression coverage for the non-invalid auth doctor path and document that only parsed JSON with session.valid false promotes empty results to TossSessionExpiredError.

Constraint: Scope is issue #126 / toss-securities only; public-restroom-nearby changes are excluded.
Rejected: Treat any auth doctor output as session-expiry evidence | false positives would relabel valid empty portfolio/watchlist responses.
Confidence: high
Scope-risk: narrow
Directive: Do not broaden empty-response promotion unless tossctl provides a stronger authenticated-empty-result contract.
Tested: npm run lint --workspace toss-securities
Tested: npm run test --workspace toss-securities (15/15)
Tested: npm run ci
Tested: Manual mock tossctl empty portfolio with session.valid true preserved []
Tested: Architect verification CLEAR
Not-tested: Live Toss Securities account session behavior.

---------

Co-authored-by: galvaomica <galvaomica@galvaomicaui-MacBookAir.local>
2026-05-12 18:59:45 +09:00
Jeffrey (Dongkyu) Kim
116bb5f58a omx(team): auto-checkpoint worker-5 [5] 2026-05-12 18:59:43 +09:00
Jeffrey (Dongkyu) Kim
300f1c9c93 omx(team): auto-checkpoint worker-5 [5] 2026-05-12 18:58:59 +09:00
Jeffrey (Dongkyu) Kim
043efe7f79 omx(team): auto-checkpoint worker-5 [5] 2026-05-12 18:57:33 +09:00
Jeffrey (Dongkyu) Kim
667e2e1347
Feature/#211 (#222)
* Add public marathon schedule lookup

Implement a read-only Korean marathon schedule skill so agents can report event dates, venues, registration deadlines, and categories from public race pages, with best-effort triathlon coverage.

Constraint: Issue #211 requires 장소, 신청 마감일, 종목, and possible triathlon inclusion without interactive clarification.

Constraint: Public unauthenticated GoRunning and triathlon.or.kr surfaces do not require k-skill-proxy.

Rejected: Proxy route | upstream pages are public and need no API key, so proxying would violate the free API proxy inclusion rule.

Confidence: high

Scope-risk: moderate

Directive: Keep source parsing fail-soft with explicit warnings when one public source changes or is temporarily unavailable.

Tested: npm test --workspace korean-marathon-schedule; live CLI smoke for 고령 2026 triathlon category; npm run ci; architect verification approved.

Not-tested: Real-time coverage of every future race page variant across both upstream sites.

Co-authored-by: OmX <omx@oh-my-codex.dev>

* Keep marathon locations authoritative

Fix the reviewed GoRunning region inference bug by ranking event location fields ahead of full-page text, and remove the unrelated public SH notice proxy/skill surface so the PR remains inside the approved marathon scope and proxy policy.

Constraint: PR #222 review required TDD, full verification, and removal of public unauthenticated SH proxy routes before merge-readiness.
Rejected: Keeping /v1/sh-notice as a proxy route | violates the repository free-API proxy inclusion rule for public unauthenticated HTML.
Confidence: high
Scope-risk: narrow
Directive: Do not reintroduce public unauthenticated SH scraping through k-skill-proxy without an explicit documented policy exception.
Tested: npm test --workspace korean-marathon-schedule; node packages/korean-marathon-schedule/src/cli.js 용인 --from 2026-05-01 --to 2026-06-30 --limit 3; node packages/korean-marathon-schedule/src/cli.js 고령 --from 2026-01-01 --to 2026-12-31 --include-triathlon --limit 5; npm run lint --workspace k-skill-proxy; npm test --workspace k-skill-proxy; grep -RIn 'sh-notice\|i-sh.co.kr' README.md docs packages package.json package-lock.json .changeset; npm run ci; git diff --check; architect verification CLEAR.
Not-tested: None.

* Bound marathon schedule crawling to trusted sources

Fix review-round false negatives by continuing beyond the old pre-filter windows while adding an explicit per-source detail budget and warnings for partial crawls. Keep race detail traversal constrained to documented hosts and filter triathlon non-race rows before fetching details.\n\nConstraint: Review round required TDD, live verification, full CI, and preserving the public no-proxy source boundary.\nRejected: Exhaustive unbounded detail traversal | it maximizes recall but can over-crawl public list pages.\nConfidence: high\nScope-risk: narrow\nDirective: Keep future crawling changes host-allowlisted, budgeted, and warning-producing when partial.\nTested: npm test --workspace korean-marathon-schedule; npm run lint --workspace korean-marathon-schedule; node packages/korean-marathon-schedule/src/cli.js 고령 --from 2026-01-01 --to 2026-12-31 --include-triathlon --limit 5; node packages/korean-marathon-schedule/src/cli.js 용인 --from 2026-05-01 --to 2026-06-30 --limit 3; npm run ci; architect verification CLEAR.\nNot-tested: Live off-origin or malformed upstream HTML beyond mocked regressions.

* Honor explicit public crawl budgets

Keep broad triathlon searches bounded by applying one detail budget across selected year lists and exposing the same budget control in the CLI.

Constraint: PR #222 review requested shared triathlon crawl budget and CLI access to maxDetailsPerSource.

Rejected: Per-year triathlon budget counters | they can exceed the documented per-source crawl cap on multi-year ranges.

Confidence: high

Scope-risk: narrow

Directive: Keep public-source crawl caps source-scoped and documented when adding more list partitions.

Tested: npm test --workspace korean-marathon-schedule; npm run lint --workspace korean-marathon-schedule; live CLI 고령 smoke; CLI help grep; npm run ci; git diff --check; architect verification CLEAR

Not-tested: Live multi-year low-budget triathlon crawl against upstream beyond mocked regression.

---------

Co-authored-by: OmX <omx@oh-my-codex.dev>
2026-05-12 18:49:06 +09:00
cedric
83a1dd1409 feat: N카드 할인 예매 지원 추가 (ktx-booking)
- ncard-list: 보유 N카드 목록 조회 (owned_ncards)
- ncard-search: N카드 할인 열차 조회 (search_owned_ncard_trains)
- reserve --ncard-no: N카드 번호로 할인 승객 예약
- NCardPassenger는 별도 try/except ImportError 블록으로 분리해
  표준 korail2 환경에서도 모듈이 정상 로드되도록 처리
- korail2-ncard 미설치 시 N카드 커맨드에서 설치 안내 출력
- 관련 테스트 7개 추가 (총 18개)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 23:24:02 +09:00
TaeyoungPark
568b9256be Add MyRealTrip MCP search skill 2026-05-11 14:43:55 +09:00
Jeffrey (Dongkyu) Kim
f348cb4f85
feat: Manus.ai 호환 import 경로 추가 (GitHub URL + rolling .skill 번들) (#227)
* docs: add Manus.ai GitHub skill import guide

Manus.ai의 'GitHub에서 프로젝트 스킬 가져오기' 기능은 폴더 루트에 SKILL.md(YAML frontmatter name/description 필수)가 있는 디렉토리 URL을 받는다. k-skill의 모든 스킬은 이미 이 포맷을 만족하므로 코드 변경 없이 문서만 추가한다.

- 사용자는 저장소 루트 URL(https://github.com/NomaDamas/k-skill) 대신 개별 스킬 폴더 URL(https://github.com/NomaDamas/k-skill/tree/main/<skill-name>)을 붙여 넣어야 한다.

- 기존 frontmatter(license, metadata.*)는 Manus가 무시하지만 다른 코딩 에이전트와의 호환을 위해 그대로 유지한다.

* feat: add build:manus-bundle for batch .skill upload to Manus.ai

Per-folder GitHub URL import is tedious for 61 skills, so add 'npm run build:manus-bundle' which emits one .skill (ZIP) per skill into dist/manus/, plus a single k-skill-manus-all.zip convenience bundle and an INDEX.md listing. Each archive nests its content under <skill-name>/ to match the public Anthropic skill-creator packager layout.

Manus does NOT support multi-skill bulk import in a single archive (verified against help.manus.im, manus.im/docs, and open.manus.ai API docs). The combined zip is purely a download convenience: users still drag-drop individual .skill files into Manus, but the file picker accepts multiple selections so it's still much faster than pasting 61 GitHub URLs.

- scripts/build-manus-bundle.js: discovers root-level skills (mirrors validate-skills.sh exclusions), shells out to system zip with -X for reproducible archives, excludes node_modules/__pycache__/.DS_Store.

- scripts/test_build_manus_bundle.js: validates discovery, frontmatter parsing, lockstep with validate-skills.sh, and docs coverage.

- scripts/validate-skills.sh: also skip dist/ and .sisyphus/ so the validator stays clean after a build.

- .gitignore: ignore dist/ and .sisyphus/.

- docs/install-manus.md: document both Method A (GitHub URL) and Method B (.skill bundle).

* ci: auto-publish Manus .skill bundle as rolling release on main push

Every push to main that touches a skill folder or the bundler now builds the .skill bundle and publishes it to the GitHub Releases tag 'manus-bundle-latest' (marked prerelease so it does not pollute the Latest release pointer used by the npm release flow).

Users get stable download URLs that always point to the latest build:

  - https://github.com/NomaDamas/k-skill/releases/download/manus-bundle-latest/k-skill-manus-all.zip

  - https://github.com/NomaDamas/k-skill/releases/download/manus-bundle-latest/INDEX.md

This removes the 'clone the repo and run npm' step for non-developers. The direct-build path remains documented as the developer fallback.

- .github/workflows/manus-bundle.yml: workflow_dispatch + push-to-main with paths filter, uses preinstalled gh CLI (no third-party release action), concurrency-grouped so overlapping pushes do not race on the same tag, --clobber upload to keep asset URLs stable.

- docs/install-manus.md: new 'quick path' section with the rolling-release URLs; existing local-build section reframed as a developer fallback.

- scripts/test_build_manus_bundle.js: 2 new tests pinning the doc URLs and key workflow invariants (trigger branch, build invocation, tag, asset name, prerelease flag, write permission).
2026-05-11 12:12:44 +09:00
TaeyoungPark
9f042642a5 chore: merge dev into danawa skill PR 2026-05-10 17:42:10 +09:00
TaeyoungPark
8f1044046f feat: add flight ticket search skill 2026-05-10 17:40:39 +09:00
TaeyoungPark
7f66f04c46 feat: add Danawa price comparison skill 2026-05-10 02:39:32 +09:00
TaeyoungPark
37cbcdb6dd Add Korean bus booking skills 2026-05-10 02:36:57 +09:00
TaeyoungPark
91eeaf607a
feat: add SH notice search skill (#218)
* feat: add SH notice search skill

* fix(sh-notice): require srchTp for keyword search, parse real attachments, cap pageSize

- Default srchTp to title ("1") when srchWord is provided without an explicit
  type. SH 게시판 ignores srchWord without srchTp and silently returns the full
  list, so /v1/sh-notice/search?q=행복주택 was returning all 1608 notices.
- Rewrite parseAttachments to ignore icon-template anchors (.pdf, .hwp, ...)
  and require existFile() onclick for real file rows. Multi-attachment notices
  now expose every real attachment with the correct filename.
- Drop unverified download_hint field from attachment objects; preview_url
  remains the only documented stable path.
- Cap pageSize at 10 to match the SH board's fixed page size and update docs
  to direct callers to use the page parameter for more results.
- Add multiItmSeq digits-only validation and a 100-char keyword length cap to
  bound cache cardinality.
- Add README, docs/install.md, packages/k-skill-proxy/README.md, and
  docs/features/sh-notice-search.md entries to register the skill in the
  repo's public surface.

Verified live against www.i-sh.co.kr:
- q=행복주택 → 96 hits (was 1608, unfiltered)
- seq=303994 → 11 real attachments with correct filenames (was 1 with '.pdf')
- pageSize=50 → caps at 10 with correct summary.page_size
- Validation errors return 400 with clear messages.

---------

Co-authored-by: Jeffrey (Dongkyu) Kim <vkehfdl1@gmail.com>
2026-05-09 00:09:45 +09:00
설코딩 -SeolCoding
4ba9876a57
feat: 국가데이터처 KOSIS 통계 조회(kosis-stats) 스킬 추가 (#216)
* feat: 국가데이터처 KOSIS 통계 조회(kosis-stats) 스킬 추가

KOSIS Open API 4개 endpoint(statisticsSearch / statisticsData getMeta /
statisticsParameterData / statisticsBigData) read-only 호출을 단일 Python
helper로 묶었다. 인증키는 KSKILL_KOSIS_API_KEY 환경변수(또는 기본
secrets.env)로 사용자별 발급한다 — proxy 미사용.

- kosis-stats/SKILL.md, scripts/run_kosis_stats.py: stdlib only,
  search/meta/data/bigdata 서브커맨드, --json/--text/--dry-run
- kosis-stats/references/kosis-openapi-guide.md: 인증키 발급, 호출 한도
  (분당 1000건/40k cells), 에러 코드, HTTPS 전용 정책 정리
- kosis-stats/tests/: stdlib unittest 36개, mock 기반 (네트워크 X) +
  KSKILL_KOSIS_API_KEY 가 있을 때만 도는 라이브 smoke 1개
- docs/features/kosis-stats.md, README, install/setup/security-and-secrets/
  sources, examples/secrets.env.example, package.json lint/test 등록

* fix(kosis-stats): 사용자 시나리오 e2e 검증 기반 UX 보강

4개 sonnet 서브에이전트 병렬 시나리오(단일수치/시계열/지역비교/실패회복)
검증에서 발견된 P1/P2 UX 부족함 보강. 4개 회복 시나리오 친절도 평균 2.75
→ 4.5 (S4c 코드 20 막힘 P1 해결).

- ERROR_CODE_HINTS: 코드 20/21/30/31 모두 next-step 명령 예시 포함
  (코드 20은 ITM 메타 우선 안내 — 실제 표 다수에서 OBJ 비어 있음)
- render_search_text: Next 액션 흐름 안내 추가
- render_meta_text: 빈 결과 시 다른 --meta-type 시도 안내
- render_data_text: 빈 결과 시 필터/meta 재확인 안내,
  새 [summary] 라인(rows/period/unit, UNIT_NM 누락 명시)
- SKILL.md Workflow: 코드 20 회복 절차, 행정구역 코드(시도 2자리/시군구
  5자리) 관례 명시
- SKILL.md Failure modes: 코드 20 추가, meta 30 분기, UNIT_NM 누락 처리,
  코드 20/31 회복 시나리오 예시
- docs/features/kosis-stats.md "흔한 문제 해결"에 코드 20 회복 절차 추가
- tests: 8개 회귀 테스트 추가 (hint 키워드/render 메시지/[summary] 라인)

* fix(kosis-stats): drop xls bigdata format and detect json error envelope in non-json formats

Reviewer follow-up on PR #216:

- Removes `xls` from bigdata --format choices. KOSIS returns xls as a
  binary Excel payload, but the helper streams text-only output, which
  would corrupt the file. json/sdmx/csv (text) remain supported.
- Detects KOSIS `{err, errMsg}` envelopes even when --format is csv/sdmx,
  so non-json bigdata responses surface auth/limit errors instead of
  printing a misleading error envelope as raw success output.
- Updates SKILL.md, references/kosis-openapi-guide.md, and
  docs/features/kosis-stats.md so the advertised contract matches the
  helper's actual capabilities.
- Adds 3 unit tests: xls rejection, json error envelope detection in csv
  mode, and clean csv passthrough when no error envelope is present.

---------

Co-authored-by: Jeffrey (Dongkyu) Kim <vkehfdl1@gmail.com>
2026-05-08 23:06:19 +09:00
Jeffrey (Dongkyu) Kim
4e5abf0861
Feature/#212 (#214)
* Help donors choose verified recipients by place and cause

Add a read-only donation-place search skill and npm helper that ranks Korean donation recipients by user-provided location/category while keeping final verification on official 1365 and recipient pages. The implementation avoids proxy routes because the chosen verification surface is public and does not require an API key.

Constraint: Issue #212 requested 기부처 조회 recommendations by place and category under TDD with a PR to dev.
Constraint: k-skill free API proxy policy allows proxying only when upstream requires API keys; 1365 verification links are public.
Rejected: Screen-scraping 1365 result pages | headless requests were slow/unstable and would be brittle for a recommendation helper.
Rejected: Treating general-purpose charities as matches for every requested category | architect review found it could return off-category results, so matching now requires explicit category tags.
Confidence: high
Scope-risk: narrow
Directive: Do not add automatic donation/payment submission; keep this skill read-only and require official-page verification before final donation decisions.
Tested: npm test --workspace donation-place-search
Tested: node smoke invocation of recommendDonationPlaces + formatDonationRecommendationReport for 서울 마포구/동물
Tested: npm run lint --workspace donation-place-search
Tested: npm run typecheck
Tested: npm run ci
Tested: architect verification approved after off-category regression fix
Not-tested: Live 1365 search result scraping; intentionally not used because the skill returns official verification links instead.
Co-authored-by: OmX <omx@oh-my-codex.dev>

* Keep donation recommendations on requested intent

Prioritize specific donation category keywords before broad general donation terms, and make item-level 1365 links candidate-specific while preserving the broad result search link.

Constraint: PR #214 review required TDD fixes for category normalization and per-candidate 1365 link semantics.

Rejected: Rewording item URLs as broad portal searches | the issue explicitly asks for candidate-specific verification links.

Confidence: high

Scope-risk: narrow

Directive: Keep item officialSearchUrl candidate-specific; use result officialSearchUrl for broad latest portal searches.

Tested: npm test --workspace donation-place-search; node smoke invocation; npm run lint --workspace donation-place-search; npm run typecheck; npm run ci; code-reviewer APPROVE; architect CLEAR.

Not-tested: Live 1365 HTTP availability, because the workflow only builds official read-only search links and prior review documented headless 1365 timeouts.

* Harden donation skill follow-up guarantees

Constraint: PR #214 review follow-up required TDD, empty category defaults, README discoverability, and release-pack coverage without pinning package versions.\nRejected: Static pack dry-run allowlist | it already missed a publishable workspace and would drift again.\nConfidence: high\nScope-risk: narrow\nDirective: Keep pack dry-run coverage dynamic over publishable workspaces; do not assert workspace package versions in tests.\nTested: npm test --workspace donation-place-search; node smoke for empty category URL/recommend/report; npm run lint --workspace donation-place-search; npm run typecheck; npm run ci; git diff --check; code-reviewer APPROVE; architect CLEAR.\nNot-tested: Live 1365 portal filtering semantics, by design; links remain read-only verification entry points.

* Clarify donation verification links

Reject misleading 1365 URL contracts and keep item search categories aligned with the candidate that is being recommended.

Constraint: PR #214 round-3 review required TDD fixes for multi-category candidate links, clean install docs, and evidence-safe 1365 wording.

Rejected: Keep broad first-request category on every item URL | It mislabels later-category candidates in multi-category requests.

Rejected: Preserve public baseUrl override | It conflicts with the official 1365 helper contract.

Confidence: high

Scope-risk: narrow

Directive: Keep 1365 URLs framed as best-effort verification assists unless browser-observed 1365 search parameters are documented.

Tested: npm test --workspace donation-place-search; node --test --test-name-pattern 'donation-place-search' scripts/skill-docs.test.js; npm run lint --workspace donation-place-search; npm run typecheck; npm run ci; node smoke for multi-category URLs, malformed limits, baseUrl rejection, and empty category.

Not-tested: Live 1365 parameter behavior; headless HTTP remains documented as unreliable.

Co-authored-by: OmX <omx@oh-my-codex.dev>

---------

Co-authored-by: OmX <omx@oh-my-codex.dev>
2026-05-08 15:41:21 +09:00
Jeffrey (Dongkyu) Kim
af55f58cb4
Feature/#207: Restore actionable Daiso pickup answer via selPkupStr fallback (#215)
* Restore actionable Daiso pickup answer when store pickup stock is blocked

Adds a public selPkupStr-backed getStorePickupEligibility() helper plus a
new pickupEligibility field on lookupStoreProductAvailability(). When
selStrPkupStck still returns 401/403 Unauthorized as in #207, the package
now reports whether the selected store is registered as a pickup-capable
store for the product (pickupEligible: true|false|null), instead of only
returning blocked/unknown.

Closes #207

* Make scope limits explicit in skill description and feature doc

Clarify across three high-traffic surfaces that this skill no longer
returns exact per-store stock quantities while the official Daiso
selStrPkupStck endpoint stays Unauthorized: only pickup eligibility
(yes/no) is reported in that state.

- daiso-product-search/SKILL.md frontmatter description rewritten
  so coding agents see the limit before triggering the skill
- daiso-product-search/SKILL.md adds explicit Scope and limits
  section plus reworked When to use / When not to use examples
- docs/features/daiso-product-search.md adds a new
  "이 기능으로 할 수 없는 일" section listing the quantity gap
- root README.md row clarifies the skill answers pickup eligibility,
  not exact per-store quantities, while the upstream block holds

* Prevent under-scoped Daiso pickup negatives

Return an explicit insufficient-coverage eligibility state when selPkupStr search input cannot prove absence, and require pkupYn=Y for positive eligibility. This preserves the actionable fallback while avoiding false negatives from broad or missing store keywords.

Constraint: Existing PR #215 already added selPkupStr fallback; this follow-up is limited to review-requested correctness fixes.

Rejected: Treating a missing first-page match as definitive false | broad or unkeyed selPkupStr searches can miss the target store.

Confidence: high

Scope-risk: narrow

Directive: Do not claim pickup ineligibility unless the searched selPkupStr coverage is sufficient to prove absence.

Tested: npm test --workspace daiso-product-search; npm run lint --workspace daiso-product-search; npm run ci; live Daiso smoke for 10224, missing keyword, and negative 99999.

Not-tested: Exhaustive multi-page live pagination across all Daiso store keywords.

Co-authored-by: OmX <omx@oh-my-codex.dev>

* Keep Daiso pickup fallback shape actionable

Stabilize blocked pickupEligibility responses with matchedStore:null and keep optional online-stock failures from preventing the selPkupStr pickup-eligibility fallback. This preserves the core store/product/pickup answer even when reference-only online stock is unavailable.

Constraint: Issue #207 requires an actionable pickup answer when the pickup-stock endpoint is blocked, and PR review required stable public response shape.

Rejected: Letting optional online stock reject the end-to-end helper | it can defeat the new actionable fallback even though online stock is reference-only.

Confidence: high

Scope-risk: narrow

Directive: Keep quantity-bearing pickupStock separate from quantity-free pickupEligibility, and do not let optional enrichments block core pickup fallback results.

Tested: npm test --workspace daiso-product-search; npm run lint --workspace daiso-product-search; npm run ci; live Daiso smoke for 10224, missing keyword, negative 99999, and end-to-end lookup.

Not-tested: Exhaustive live multi-page selPkupStr pagination across every store keyword.

---------

Co-authored-by: OmX <omx@oh-my-codex.dev>
2026-05-08 15:41:08 +09:00
Jeffrey (Dongkyu) Kim
f527515932
Enable property search by free auction conditions (#213)
Add Workflow C for court-auction-notice-search with direct PGJ151 property search payload mapping, representative frozen code tables, CLI/docs coverage, and normalized item rows.

Constraint: Issue #184 requires Workflow C region/usage/price/date/area/flbd filters and release automation requires a Changeset.

Rejected: Proxy route | courtauction.go.kr property search is a public site endpoint and does not require an API key.

Confidence: high

Scope-risk: moderate

Directive: Keep code-table lookups fail-open and avoid tests that pin package versions or changeset file presence.

Tested: npm test --workspace court-auction-notice-search; npm run lint --workspace court-auction-notice-search; npm run ci

Not-tested: Live courtauction.go.kr property search, to avoid unnecessary upstream calls and potential anti-bot blocking.
2026-05-08 10:14:33 +09:00
Jeffrey (Dongkyu) Kim
dc9a765e2a
Feature/#205 (#210)
* Align proxy defaults for hosted Korean routes

Constraint: Issue #205 requires unset or empty KSKILL_PROXY_BASE_URL to use the hosted proxy consistently while preserving explicit proxy overrides and server-side upstream keys.\nRejected: Keeping Seoul subway and Korea weather as self-host-only routes | it preserves the documented inconsistency and blocks zero-config usage.\nConfidence: high\nScope-risk: narrow\nDirective: Keep client docs pointing to hosted proxy defaults unless a route is intentionally removed from hosted service.\nTested: node --test scripts/skill-docs.test.js; npm run ci; hosted smoke curls for /v1/seoul-subway/arrival and /v1/korea-weather/forecast; architect verification approved.\nNot-tested: Private self-host proxy deployment.

* Preserve hosted proxy fallback in setup guidance

Make self-host proxy examples inactive by default so client setup no longer blocks the hosted proxy resolver contract for Seoul subway and Korea weather skills.

Constraint: PR #210 review required unset and empty KSKILL_PROXY_BASE_URL to fall back to https://k-skill-proxy.nomadamas.org while preserving explicit self-host overrides.\nRejected: Keep active https://your-proxy.example.com placeholder | It creates a non-empty override and prevents hosted fallback for users copying the default secrets file.\nConfidence: high\nScope-risk: narrow\nDirective: Keep upstream API keys documented as proxy-operator/server-side only; do not reintroduce active client-side proxy placeholders for hosted-default flows.\nTested: node --test scripts/skill-docs.test.js; npm run ci; hosted smoke checks for /v1/seoul-subway/arrival?stationName=강남 and /v1/korea-weather/forecast?lat=37.5665&lon=126.9780; resolver smoke for unset, empty, and custom KSKILL_PROXY_BASE_URL; git diff --check; Ralph architect verification CLEAR.\nNot-tested: none

* Clarify proxy guide override boundary

Document the hosted-client default in the proxy guide while keeping the self-host placeholder as an explicitly scoped override, so users do not mistake it for required setup.

Constraint: Issue #205 review round 2 left a WATCH concern on docs/features/k-skill-proxy.md client env-var wording.

Rejected: Leave the proxy guide unchanged | It preserved ambiguity between hosted-client defaults and self-host/operator overrides.

Rejected: Sentence-exact regression assertions | They were too brittle after code review; semantic assertions preserve wording flexibility while locking the policy.

Confidence: high

Scope-risk: narrow

Directive: Keep KSKILL_PROXY_BASE_URL examples inactive or clearly scoped unless documenting a self-host/alternate-proxy override.

Tested: node --test scripts/skill-docs.test.js; npm run ci; resolver smoke for unset empty and custom KSKILL_PROXY_BASE_URL; hosted Seoul subway and Korea weather smokes; git diff --check; code-reviewer APPROVE; architect CLEAR

Not-tested: none

* Keep proxy guide examples on the hosted-default path

Constraint: PR #210 issue #205 follow-up requires KSKILL_PROXY_BASE_URL unset/empty to resolve to hosted while preserving explicit self-host overrides.\nRejected: Labeling the existing 127.0.0.1 examples as operator-only | it would leave the general usage section less aligned with the hosted-client default.\nConfidence: high\nScope-risk: narrow\nDirective: Keep Seoul subway and Korea weather user-facing examples on the resolver pattern unless the section is explicitly scoped to local operator smoke tests.\nTested: node --test scripts/skill-docs.test.js; npm run ci; resolver smoke for unset empty custom KSKILL_PROXY_BASE_URL; hosted Seoul subway and Korea weather smoke; architect verification CLEAR.\nNot-tested: None.
2026-05-06 16:56:40 +09:00
Jeffrey (Dongkyu) Kim
95f0f042a9
Feature/#202 (#208) 2026-05-06 12:39:49 +09:00
Jeffrey (Dongkyu) Kim
e87330874b
Feature/#207 (#209) 2026-05-06 12:38:52 +09:00
Taekyun Kim
6b78624920
Correct limit description for free usage (#206)
오타 수정
2026-05-06 01:40:50 +09:00
Inho Jeong
2ff51db5d2
feat: 개별공시지가(gongsijiga-search) 스킬 추가 (#200)
* chore: version packages

* Merge dev into main (#197)

* fix(toss-securities): clarify session expiry and quote 403 handling

* Clarify toss empty-output session expiry

Portfolio and watchlist reads can exit successfully with empty payloads when the stored Toss session has expired. The empty-output path now verifies the session before JSON parsing and only promotes confirmed invalid auth doctor data into TossSessionExpiredError.

Constraint: Scope is limited to toss-securities issue #126 follow-up on PR #192

Rejected: Treat auth doctor execution failures as expired sessions | unsupported or failing doctor output is inconclusive without parsed session.valid=false

Confidence: high

Scope-risk: narrow

Directive: Keep empty-result session expiry classification tied to explicit auth doctor confirmation

Tested: npm run test --workspace toss-securities; npm run lint --workspace toss-securities; npm run ci; manual mock tossctl blank stdout invalid/inconclusive doctor checks

* Avoid false session-expiry labels for validation errors

The toss wrapper now treats bare validation_error text as an upstream command failure instead of a session-expired signal. Structured auth doctor JSON remains the source of truth for empty portfolio/watchlist invalid-session promotion, while known stored-session-invalid stderr still maps to TossSessionExpiredError.\n\nConstraint: PR #192 follow-up must stay scoped to issue #126 toss-securities behavior.\nRejected: Keep validation_error in the global regex | it mislabels auth doctor transport failures and quote 403 validation errors as session expiry.\nConfidence: high\nScope-risk: narrow\nDirective: Do not broaden the free-text session classifier without regressions for auth doctor and quote upstream validation failures.\nTested: npm run lint --workspace toss-securities; npm run test --workspace toss-securities; npm run ci; manual mock tossctl validation_error checks; architect verification CLEAR\nNot-tested: Live tossctl network/auth session against real Toss upstream

* Align court auction lookup with monthly site search (#196)

The court auction notice page posts a YYYYMM search key from its 조회 button and returns a month of rows. Keep day inputs as a compatibility filter over the monthly response and normalize the current nested detail payload shape.

Constraint: courtauction.go.kr has no public API and blocks bursty automated calls.

Rejected: querying every day independently | the upstream search surface is month-based and day calls return false empty results.

Confidence: high

Scope-risk: narrow

Directive: Preserve the site-observed YYYYMM notice search contract unless the PGJ143M01 XHR changes again.

Tested: npm --workspace packages/court-auction-notice-search test; npm run ci; live 서울중앙지방법원 2026-05 notice/detail smoke lookup.

Not-tested: PR CI after push.

Co-authored-by: OmX <omx@oh-my-codex.dev>

* Guide crawler skills toward reusable discovery (#195)

* chore: version packages

* Guide crawler skills toward reusable discovery

Constraint: User requested insane-search-style guidance for future crawling k-skills without unrelated implementation changes.
Rejected: Adding crawler code or a standalone template | too broad for a docs guidance change and risks dependency creep.
Confidence: high
Scope-risk: narrow
Directive: Keep site-specific access details inside individual skills after a site-agnostic discovery pass.
Tested: npm run ci
Not-tested: Live crawler behavior; documentation-only change.

* Clarify crawler skill discovery guidance

Constraint: Crawling k-skills need site-dependent recipes, but should derive them through a reusable discovery pass.
Rejected: Leaving guidance only in docs/adding-a-skill.md | AGENTS.md and CLAUDE.md also guide future agents.
Confidence: high
Scope-risk: narrow
Directive: Use site-agnostic discovery to find, then explicitly package, the target site's stable access path.
Tested: npm run ci
Not-tested: Live crawler behavior; documentation-only change.

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Ground corporate registration guidance in official form sources

Keep the consulting skill focused on draft/checklist support while pointing users to current IROS and law.go.kr form sources for submission-ready artifacts.

Constraint: official registry forms can change outside the repository and must be re-downloaded at use time

Rejected: committing copied official HWP/HWPX/PDF forms | they would become stale and risk misleading users

Confidence: high

Scope-risk: narrow

Directive: do not treat Markdown templates as substitutes for official registry submission forms

Tested: npm test

* Ground incorporation drafting in real HWP forms

Bundle official court incorporation forms plus public startup incorporation attachments, and make rhwp-filled HWP outputs the default drafting path for the corporate-registration skill. Replace the listed-company articles reference with a startup-suitable Ministry of Justice stock-company form and record source manifests for bundled binaries.

Constraint: user requires actual sourced HWP templates, not generated placeholder binaries.
Rejected: markdown-only drafting | it cannot produce submission-shaped Korean registry forms.
Rejected: listed-company standard articles as the default reference | it is mismatched for typical startup incorporation.
Confidence: high
Scope-risk: moderate
Directive: keep bundled HWP forms source-backed, sanitized, and edited only through copied working files.
Tested: node --test scripts/skill-docs.test.js; npm run lint; k-skill-rhwp info on bundled HWP files; kordoc conversion spot checks.
Not-tested: manual opening every HWP in Hancom Office and live registry submission.
Co-authored-by: OmX <omx@oh-my-codex.dev>

* Streamline corporate registration forms workflow

Prioritize saved HWP forms for ordinary stock-company promoter incorporations, make required court-registry receipts and director identity certificates explicit, and remove the redundant markdown articles template so the skill stays HWP-first.

Constraint: 법원등기소 기준 체크리스트 must include fee receipts, director seal/signature certificates, and resident-record documents.

Rejected: Keeping a separate markdown articles template | duplicated the stored HWP articles workflow and encouraged non-HWP drafting.

Confidence: high

Scope-risk: narrow

Directive: Keep corporate-registration-consulting focused on stored HWP form copies and explicit issued-document checklists.

Tested: node --test --test-name-pattern 'corporate-registration-consulting' scripts/skill-docs.test.js; node --check scripts/skill-docs.test.js; ./scripts/validate-skills.sh; git diff --check

Not-tested: Full npm run ci was not run because this is a skill documentation/template refactor, not release or package automation.

---------

Co-authored-by: galvaomica <galvaomica@galvaomicaui-MacBookAir.local>
Co-authored-by: OmX <omx@oh-my-codex.dev>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* chore: version packages (#198)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(realtyprice): add address parsing and sido code mapping

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(realtyprice): use string sido codes for consistency with upstream API

* feat(realtyprice): add response normalization and buildResponse

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(realtyprice): add upstream cascade fetch functions with timeout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(realtyprice): add lookupGongsijiga orchestrator with region matching

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(realtyprice): add simple in-memory cache with TTL

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(proxy): register GET /realtyprice route with caching

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add gongsijiga-search SKILL.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: add changeset for gongsijiga-search

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(realtyprice): align with actual realtyprice.kr API response format

- Response wraps data in model.list (not bjdList/gsiList)
- Field names are code/name (not bjd_cd/bjd_nm)
- bun2 empty → send "0000" (not empty string)
- eupmyeondong matching: try full string match first (API returns
  combined "면 리" names like "청계면 청천리")

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(gongsijiga-search): align /realtyprice route with v1 API convention

- Change route from /realtyprice to /v1/realtyprice for consistency with other proxy endpoints.
- Add realtypriceConfigured flag to /health upstreams.
- Normalize address cache key by collapsing multiple whitespaces.
- Update SKILL.md and README.md to reflect the new v1 path.

* feat(gongsijiga-search): add Sejong special-city support

- parseAddress: allow 3-token minimum for Sejong (no sigungu) and set sigungu to empty string.
- lookupGongsijiga: skip sigungu lookup for Sejong (sidoCode 29), use fixed sggCode 36110.
- Add Sejong parseAddress and lookupGongsijiga test cases.
- Update SKILL.md with Sejong address format examples.

* refactor(gongsijiga-search): split realtyprice.kr lookup into standalone package

realtyprice.kr is a fully public endpoint that needs no API key, so per
the new k-skill-proxy inclusion rule (proxy is for keyed upstreams only)
the helper now ships as `gongsijiga-search` and is invoked directly from
the user's machine.

- new workspace package packages/gongsijiga-search/ following the
  blue-ribbon-nearby/coupang-product-search convention (publishConfig,
  files, repository, keywords)
- remove /v1/realtyprice route, realtyprice.js, realtyprice.test.js, and
  the realtypriceConfigured health flag from k-skill-proxy
- document the inclusion rule in AGENTS.md and CLAUDE.md so future skills
  default to direct calls when no key is required
- advertise the new skill in README.md, docs/install.md, and add
  docs/features/gongsijiga-search.md
- drop the hardcoded toss-securities lockfile version assertion that
  pinned a workspace version (would block changesets version-packages)
  and document the anti-pattern in AGENTS.md / CLAUDE.md
- changesets: refresh the proxy refactor message and add a patch
  changeset so the new gongsijiga-search package gets published

---------

Co-authored-by: Jeffrey (Dongkyu) Kim <vkehfdl1@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: galvaomica <galvaomica@galvaomicaui-MacBookAir.local>
Co-authored-by: OmX <omx@oh-my-codex.dev>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 00:27:31 +09:00
SEONGYEON KIM
32162e4cd7
Add korean-transit-route skill (#201)
* Add korean-transit-route skill

* Apply review fixes to korean-transit-route skill

- Add Done when and Failure modes sections to align with SKILL.md template
- Fix Python geocode example to use with-statement for urlopen
- Add korean-transit-route to README.md skill table and feature list
- Add docs/features/korean-transit-route.md guide

---------

Co-authored-by: Jeffrey (Dongkyu) Kim <vkehfdl1@gmail.com>
2026-05-04 14:11:40 +09:00
github-actions[bot]
c58adebdd3
chore: version packages (#198)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-05-02 23:56:25 +09:00
Jeffrey (Dongkyu) Kim
d7aca1bcbe
Merge dev into main (#197)
* fix(toss-securities): clarify session expiry and quote 403 handling

* Clarify toss empty-output session expiry

Portfolio and watchlist reads can exit successfully with empty payloads when the stored Toss session has expired. The empty-output path now verifies the session before JSON parsing and only promotes confirmed invalid auth doctor data into TossSessionExpiredError.

Constraint: Scope is limited to toss-securities issue #126 follow-up on PR #192

Rejected: Treat auth doctor execution failures as expired sessions | unsupported or failing doctor output is inconclusive without parsed session.valid=false

Confidence: high

Scope-risk: narrow

Directive: Keep empty-result session expiry classification tied to explicit auth doctor confirmation

Tested: npm run test --workspace toss-securities; npm run lint --workspace toss-securities; npm run ci; manual mock tossctl blank stdout invalid/inconclusive doctor checks

* Avoid false session-expiry labels for validation errors

The toss wrapper now treats bare validation_error text as an upstream command failure instead of a session-expired signal. Structured auth doctor JSON remains the source of truth for empty portfolio/watchlist invalid-session promotion, while known stored-session-invalid stderr still maps to TossSessionExpiredError.\n\nConstraint: PR #192 follow-up must stay scoped to issue #126 toss-securities behavior.\nRejected: Keep validation_error in the global regex | it mislabels auth doctor transport failures and quote 403 validation errors as session expiry.\nConfidence: high\nScope-risk: narrow\nDirective: Do not broaden the free-text session classifier without regressions for auth doctor and quote upstream validation failures.\nTested: npm run lint --workspace toss-securities; npm run test --workspace toss-securities; npm run ci; manual mock tossctl validation_error checks; architect verification CLEAR\nNot-tested: Live tossctl network/auth session against real Toss upstream

* Align court auction lookup with monthly site search (#196)

The court auction notice page posts a YYYYMM search key from its 조회 button and returns a month of rows. Keep day inputs as a compatibility filter over the monthly response and normalize the current nested detail payload shape.

Constraint: courtauction.go.kr has no public API and blocks bursty automated calls.

Rejected: querying every day independently | the upstream search surface is month-based and day calls return false empty results.

Confidence: high

Scope-risk: narrow

Directive: Preserve the site-observed YYYYMM notice search contract unless the PGJ143M01 XHR changes again.

Tested: npm --workspace packages/court-auction-notice-search test; npm run ci; live 서울중앙지방법원 2026-05 notice/detail smoke lookup.

Not-tested: PR CI after push.

Co-authored-by: OmX <omx@oh-my-codex.dev>

* Guide crawler skills toward reusable discovery (#195)

* chore: version packages

* Guide crawler skills toward reusable discovery

Constraint: User requested insane-search-style guidance for future crawling k-skills without unrelated implementation changes.
Rejected: Adding crawler code or a standalone template | too broad for a docs guidance change and risks dependency creep.
Confidence: high
Scope-risk: narrow
Directive: Keep site-specific access details inside individual skills after a site-agnostic discovery pass.
Tested: npm run ci
Not-tested: Live crawler behavior; documentation-only change.

* Clarify crawler skill discovery guidance

Constraint: Crawling k-skills need site-dependent recipes, but should derive them through a reusable discovery pass.
Rejected: Leaving guidance only in docs/adding-a-skill.md | AGENTS.md and CLAUDE.md also guide future agents.
Confidence: high
Scope-risk: narrow
Directive: Use site-agnostic discovery to find, then explicitly package, the target site's stable access path.
Tested: npm run ci
Not-tested: Live crawler behavior; documentation-only change.

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Ground corporate registration guidance in official form sources

Keep the consulting skill focused on draft/checklist support while pointing users to current IROS and law.go.kr form sources for submission-ready artifacts.

Constraint: official registry forms can change outside the repository and must be re-downloaded at use time

Rejected: committing copied official HWP/HWPX/PDF forms | they would become stale and risk misleading users

Confidence: high

Scope-risk: narrow

Directive: do not treat Markdown templates as substitutes for official registry submission forms

Tested: npm test

* Ground incorporation drafting in real HWP forms

Bundle official court incorporation forms plus public startup incorporation attachments, and make rhwp-filled HWP outputs the default drafting path for the corporate-registration skill. Replace the listed-company articles reference with a startup-suitable Ministry of Justice stock-company form and record source manifests for bundled binaries.

Constraint: user requires actual sourced HWP templates, not generated placeholder binaries.
Rejected: markdown-only drafting | it cannot produce submission-shaped Korean registry forms.
Rejected: listed-company standard articles as the default reference | it is mismatched for typical startup incorporation.
Confidence: high
Scope-risk: moderate
Directive: keep bundled HWP forms source-backed, sanitized, and edited only through copied working files.
Tested: node --test scripts/skill-docs.test.js; npm run lint; k-skill-rhwp info on bundled HWP files; kordoc conversion spot checks.
Not-tested: manual opening every HWP in Hancom Office and live registry submission.
Co-authored-by: OmX <omx@oh-my-codex.dev>

* Streamline corporate registration forms workflow

Prioritize saved HWP forms for ordinary stock-company promoter incorporations, make required court-registry receipts and director identity certificates explicit, and remove the redundant markdown articles template so the skill stays HWP-first.

Constraint: 법원등기소 기준 체크리스트 must include fee receipts, director seal/signature certificates, and resident-record documents.

Rejected: Keeping a separate markdown articles template | duplicated the stored HWP articles workflow and encouraged non-HWP drafting.

Confidence: high

Scope-risk: narrow

Directive: Keep corporate-registration-consulting focused on stored HWP form copies and explicit issued-document checklists.

Tested: node --test --test-name-pattern 'corporate-registration-consulting' scripts/skill-docs.test.js; node --check scripts/skill-docs.test.js; ./scripts/validate-skills.sh; git diff --check

Not-tested: Full npm run ci was not run because this is a skill documentation/template refactor, not release or package automation.

---------

Co-authored-by: galvaomica <galvaomica@galvaomicaui-MacBookAir.local>
Co-authored-by: OmX <omx@oh-my-codex.dev>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-05-02 23:51:59 +09:00
Jeffrey (Dongkyu) Kim
8c2f31ad59 Streamline corporate registration forms workflow
Prioritize saved HWP forms for ordinary stock-company promoter incorporations, make required court-registry receipts and director identity certificates explicit, and remove the redundant markdown articles template so the skill stays HWP-first.

Constraint: 법원등기소 기준 체크리스트 must include fee receipts, director seal/signature certificates, and resident-record documents.

Rejected: Keeping a separate markdown articles template | duplicated the stored HWP articles workflow and encouraged non-HWP drafting.

Confidence: high

Scope-risk: narrow

Directive: Keep corporate-registration-consulting focused on stored HWP form copies and explicit issued-document checklists.

Tested: node --test --test-name-pattern 'corporate-registration-consulting' scripts/skill-docs.test.js; node --check scripts/skill-docs.test.js; ./scripts/validate-skills.sh; git diff --check

Not-tested: Full npm run ci was not run because this is a skill documentation/template refactor, not release or package automation.
2026-05-02 23:27:52 +09:00
Jeffrey (Dongkyu) Kim
ff92e77fa5 Ground incorporation drafting in real HWP forms
Bundle official court incorporation forms plus public startup incorporation attachments, and make rhwp-filled HWP outputs the default drafting path for the corporate-registration skill. Replace the listed-company articles reference with a startup-suitable Ministry of Justice stock-company form and record source manifests for bundled binaries.

Constraint: user requires actual sourced HWP templates, not generated placeholder binaries.
Rejected: markdown-only drafting | it cannot produce submission-shaped Korean registry forms.
Rejected: listed-company standard articles as the default reference | it is mismatched for typical startup incorporation.
Confidence: high
Scope-risk: moderate
Directive: keep bundled HWP forms source-backed, sanitized, and edited only through copied working files.
Tested: node --test scripts/skill-docs.test.js; npm run lint; k-skill-rhwp info on bundled HWP files; kordoc conversion spot checks.
Not-tested: manual opening every HWP in Hancom Office and live registry submission.
Co-authored-by: OmX <omx@oh-my-codex.dev>
2026-05-02 18:30:53 +09:00
Jeffrey (Dongkyu) Kim
b8849953d8 Ground corporate registration guidance in official form sources
Keep the consulting skill focused on draft/checklist support while pointing users to current IROS and law.go.kr form sources for submission-ready artifacts.

Constraint: official registry forms can change outside the repository and must be re-downloaded at use time

Rejected: committing copied official HWP/HWPX/PDF forms | they would become stale and risk misleading users

Confidence: high

Scope-risk: narrow

Directive: do not treat Markdown templates as substitutes for official registry submission forms

Tested: npm test
2026-05-02 12:31:07 +09:00
Jeffrey (Dongkyu) Kim
12289bd9a2
Guide crawler skills toward reusable discovery (#195)
* chore: version packages

* Guide crawler skills toward reusable discovery

Constraint: User requested insane-search-style guidance for future crawling k-skills without unrelated implementation changes.
Rejected: Adding crawler code or a standalone template | too broad for a docs guidance change and risks dependency creep.
Confidence: high
Scope-risk: narrow
Directive: Keep site-specific access details inside individual skills after a site-agnostic discovery pass.
Tested: npm run ci
Not-tested: Live crawler behavior; documentation-only change.

* Clarify crawler skill discovery guidance

Constraint: Crawling k-skills need site-dependent recipes, but should derive them through a reusable discovery pass.
Rejected: Leaving guidance only in docs/adding-a-skill.md | AGENTS.md and CLAUDE.md also guide future agents.
Confidence: high
Scope-risk: narrow
Directive: Use site-agnostic discovery to find, then explicitly package, the target site's stable access path.
Tested: npm run ci
Not-tested: Live crawler behavior; documentation-only change.

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-05-01 22:11:04 +09:00
Jeffrey (Dongkyu) Kim
593865f2e5
Merge pull request #192 from galvaomica396/fix/toss-session-126
fix(toss-securities): clarify session expiry handling (#126)
2026-05-01 22:08:39 +09:00
Jeffrey (Dongkyu) Kim
7f9835e8ec Unblock PR 192 by reconciling dev conflict context
Kept PR session-expiry behavior while accepting dev's explicit inconclusive-auth documentation and regression coverage.

Constraint: PR #192 targets dev and was DIRTY due to README/test conflicts after origin/dev advanced.

Rejected: Dropping dev regression cases | would reintroduce false session-expiry risk documented on dev.

Confidence: high

Scope-risk: narrow

Directive: Preserve empty-output session checks as inconclusive unless auth doctor confirms session.valid === false.

Tested: npm test --workspace packages/toss-securities; git diff --check

Co-authored-by: OmX <omx@oh-my-codex.dev>
2026-05-01 22:08:11 +09:00
Jeffrey (Dongkyu) Kim
a25d641d00
Align court auction lookup with monthly site search (#196)
The court auction notice page posts a YYYYMM search key from its 조회 button and returns a month of rows. Keep day inputs as a compatibility filter over the monthly response and normalize the current nested detail payload shape.

Constraint: courtauction.go.kr has no public API and blocks bursty automated calls.

Rejected: querying every day independently | the upstream search surface is month-based and day calls return false empty results.

Confidence: high

Scope-risk: narrow

Directive: Preserve the site-observed YYYYMM notice search contract unless the PGJ143M01 XHR changes again.

Tested: npm --workspace packages/court-auction-notice-search test; npm run ci; live 서울중앙지방법원 2026-05 notice/detail smoke lookup.

Not-tested: PR CI after push.

Co-authored-by: OmX <omx@oh-my-codex.dev>
2026-05-01 22:05:54 +09:00
Jeffrey (Dongkyu) Kim
fe3060678c
Merge pull request #194 from NomaDamas/changeset-release/main
chore: version packages
2026-04-30 20:28:39 +09:00
github-actions[bot]
ab1fa13762 chore: version packages 2026-04-30 11:15:17 +00:00
Jeffrey (Dongkyu) Kim
0be8c081b4
Merge pull request #190 from NomaDamas/dev
Release: dev → main 동기화 (신규 스킬 6개 + 기존 스킬 보강 + ktx fallback 수정)
2026-04-30 20:14:36 +09:00
Jeffrey (Dongkyu) Kim
dff4d1c323
Feature/#171 (#191)
* Protect explicit Korail train-type replay

Issue #171 added explicit train-type selection for non-KTX Korail routes. The merged implementation already wires search and reserve through TRAIN_TYPE_MAP; this follow-up locks that behavior with command-level and parser regression coverage, removes stale test import noise, and aligns the skill shortlist wording with the broader train_type output.

Constraint: The ktx-booking skill keeps ktx as the default train type for backward compatibility.

Rejected: Encode train type into train_id in this follow-up | larger selector schema change already marked as non-blocking UX follow-up.

Confidence: high

Scope-risk: narrow

Directive: Do not hardcode command_search or command_reserve back to TrainType.KTX; keep parser choices derived from TRAIN_TYPE_MAP.

Tested: python3 scripts/ktx_booking.py search --help

Tested: python3 scripts/ktx_booking.py reserve --help

Tested: PYTHONPATH=scripts python3 -m unittest scripts.test_ktx_booking

Tested: python3 -m py_compile scripts/ktx_booking.py scripts/test_ktx_booking.py

Tested: PYTHONNOUSERSITE=1 PYTHONPATH=scripts python3 -S -c 'import ktx_booking; print(ktx_booking._KORAIL_IMPORT_ERROR); print(ktx_booking.TRAIN_TYPE_MAP["itx-cheongchun"])'

Tested: PYTHONNOUSERSITE=1 PYTHONPATH=scripts python3 -m unittest discover -s scripts -p 'test_ktx_booking.py'

Tested: python3 -S scripts/ktx_booking.py search --help; python3 -S scripts/ktx_booking.py reserve --help

Tested: npm run lint

Tested: npm run typecheck

Tested: npm test

Not-tested: Live Korail search/reserve requiring credentials and external availability

* Guard KTX search default during train-type regression

Issue #171 locked explicit train-type choices for Korail search and reserve flows. The follow-up review found reserve default coverage but no direct search default assertion, so this adds the narrow parser regression without changing runtime behavior.\n\nConstraint: Issue #171 requires search and reserve train-type behavior to stay regression-covered.\nRejected: Broaden command-level tests for every train type | parser choices already loop over TRAIN_TYPE_MAP and command forwarding is covered for the non-KTX regression route.\nConfidence: high\nScope-risk: narrow\nTested: PYTHONPATH=scripts python3 -m unittest scripts.test_ktx_booking\nTested: python3 -m py_compile scripts/ktx_booking.py scripts/test_ktx_booking.py\nTested: npm run lint\nTested: npm run typecheck\nTested: npm test\nNot-tested: live Korail search/reserve requiring credentials and external service availability

* Tighten KTX train-type regression tests

The Issue #171 follow-up already locked the train-type CLI behavior. This pass addresses the remaining review cleanup in the modified test file by narrowing the normalized train_id before reuse and tidying formatting without changing behavior.

Constraint: Keep the PR scoped to ktx-booking regression coverage and documentation

Rejected: Encode train_type into train_id in this follow-up | broader selector migration is outside the approved regression scope

Confidence: high

Scope-risk: narrow

Tested: PYTHONPATH=scripts python3 -m unittest scripts.test_ktx_booking

Tested: python3 -m py_compile scripts/ktx_booking.py scripts/test_ktx_booking.py

Tested: pyright scripts/test_ktx_booking.py

Tested: npm run lint

Tested: npm run typecheck

Tested: npm test

Not-tested: live Korail search/reserve requiring credentials and external service availability
2026-04-30 19:58:55 +09:00
Jeffrey (Dongkyu) Kim
3cea4be6eb
Feature/#126 (#193)
* fix(toss-securities): clarify session expiry and quote 403 handling

* Clarify toss empty-output session expiry

Portfolio and watchlist reads can exit successfully with empty payloads when the stored Toss session has expired. The empty-output path now verifies the session before JSON parsing and only promotes confirmed invalid auth doctor data into TossSessionExpiredError.

Constraint: Scope is limited to toss-securities issue #126 follow-up on PR #192

Rejected: Treat auth doctor execution failures as expired sessions | unsupported or failing doctor output is inconclusive without parsed session.valid=false

Confidence: high

Scope-risk: narrow

Directive: Keep empty-result session expiry classification tied to explicit auth doctor confirmation

Tested: npm run test --workspace toss-securities; npm run lint --workspace toss-securities; npm run ci; manual mock tossctl blank stdout invalid/inconclusive doctor checks

* Avoid false session-expiry labels for validation errors

The toss wrapper now treats bare validation_error text as an upstream command failure instead of a session-expired signal. Structured auth doctor JSON remains the source of truth for empty portfolio/watchlist invalid-session promotion, while known stored-session-invalid stderr still maps to TossSessionExpiredError.\n\nConstraint: PR #192 follow-up must stay scoped to issue #126 toss-securities behavior.\nRejected: Keep validation_error in the global regex | it mislabels auth doctor transport failures and quote 403 validation errors as session expiry.\nConfidence: high\nScope-risk: narrow\nDirective: Do not broaden the free-text session classifier without regressions for auth doctor and quote upstream validation failures.\nTested: npm run lint --workspace toss-securities; npm run test --workspace toss-securities; npm run ci; manual mock tossctl validation_error checks; architect verification CLEAR\nNot-tested: Live tossctl network/auth session against real Toss upstream

* Preserve toss empty-response auth-doctor contract

The prior review identified the empty portfolio/watchlist promotion rule as an upstream-contract dependency worth making explicit. Add regression coverage for the non-invalid auth doctor path and document that only parsed JSON with session.valid false promotes empty results to TossSessionExpiredError.

Constraint: Scope is issue #126 / toss-securities only; public-restroom-nearby changes are excluded.
Rejected: Treat any auth doctor output as session-expiry evidence | false positives would relabel valid empty portfolio/watchlist responses.
Confidence: high
Scope-risk: narrow
Directive: Do not broaden empty-response promotion unless tossctl provides a stronger authenticated-empty-result contract.
Tested: npm run lint --workspace toss-securities
Tested: npm run test --workspace toss-securities (15/15)
Tested: npm run ci
Tested: Manual mock tossctl empty portfolio with session.valid true preserved []
Tested: Architect verification CLEAR
Not-tested: Live Toss Securities account session behavior.

---------

Co-authored-by: galvaomica <galvaomica@galvaomicaui-MacBookAir.local>
2026-04-30 19:58:39 +09:00
Jeffrey (Dongkyu) Kim
8243e231db Avoid false session-expiry labels for validation errors
The toss wrapper now treats bare validation_error text as an upstream command failure instead of a session-expired signal. Structured auth doctor JSON remains the source of truth for empty portfolio/watchlist invalid-session promotion, while known stored-session-invalid stderr still maps to TossSessionExpiredError.\n\nConstraint: PR #192 follow-up must stay scoped to issue #126 toss-securities behavior.\nRejected: Keep validation_error in the global regex | it mislabels auth doctor transport failures and quote 403 validation errors as session expiry.\nConfidence: high\nScope-risk: narrow\nDirective: Do not broaden the free-text session classifier without regressions for auth doctor and quote upstream validation failures.\nTested: npm run lint --workspace toss-securities; npm run test --workspace toss-securities; npm run ci; manual mock tossctl validation_error checks; architect verification CLEAR\nNot-tested: Live tossctl network/auth session against real Toss upstream
2026-04-30 02:30:18 +09:00
Jeffrey (Dongkyu) Kim
4e8f5865f8 Clarify toss empty-output session expiry
Portfolio and watchlist reads can exit successfully with empty payloads when the stored Toss session has expired. The empty-output path now verifies the session before JSON parsing and only promotes confirmed invalid auth doctor data into TossSessionExpiredError.

Constraint: Scope is limited to toss-securities issue #126 follow-up on PR #192

Rejected: Treat auth doctor execution failures as expired sessions | unsupported or failing doctor output is inconclusive without parsed session.valid=false

Confidence: high

Scope-risk: narrow

Directive: Keep empty-result session expiry classification tied to explicit auth doctor confirmation

Tested: npm run test --workspace toss-securities; npm run lint --workspace toss-securities; npm run ci; manual mock tossctl blank stdout invalid/inconclusive doctor checks
2026-04-30 02:10:51 +09:00
galvaomica
9967caf6b8 fix(toss-securities): clarify session expiry and quote 403 handling 2026-04-30 01:46:43 +09:00
Jeffrey (Dongkyu) Kim
69637b3a46 Merge origin/main into dev: absorb #164 version-packages bumps before dev → main release sync (#190)
Resolves merge conflicts so PR #190 can fast-forward.

Conflict resolutions:
- README.md, docs/install.md, docs/roadmap.md, scripts/skill-docs.test.js: keep dev (HEAD) — dev contains all the new skills (court-auction-notice-search, foresttrip-vacancy, iros-registry-automation, corporate-registration-consulting, korean-jangbu-for, k-skill-cleaner) plus the README skill-name column refactor (#166)
- package.json: keep dev (HEAD) — dev's lint/test scripts cover scripts/k_skill_cleaner.py and scripts/test_k_skill_cleaner.py from #178
- packages/k-skill-rhwp/package.json: take origin/main (0.2.0) — main already shipped #164 version-packages bump

Changeset cleanup (consumed by #164 chore: version packages):
- removed .changeset/lh-notice-search.md (k-skill-proxy minor → 0.2.0 already on main)
- removed .changeset/naver-news-search.md (same package, same release)
- removed .changeset/parking-lot-search.md (parking-lot-search patch → 0.1.3 already on main)
- removed .changeset/rhwp-edit-skill.md (k-skill-rhwp minor → 0.2.0 already on main)

Kept changesets pending the next release:
- .changeset/court-auction-notice-search.md (court-auction-notice-search@0.1.0, new package)
- .changeset/tall-restrooms-merge.md (public-restroom-nearby minor, Kakao supplemental layers from #180)

Verification:
- ./scripts/validate-skills.sh → skill layout looks valid
- node --test scripts/skill-docs.test.js → 128/128 pass
2026-04-30 01:44:00 +09:00
Donghun Seol
c51bcef87f
Merge pull request #188 from seolcoding/feature/foresttrip-vacancy
 feat: 자연휴양림 빈 객실 조회 스킬 추가
2026-04-30 01:28:57 +09:00
Jeffrey (Dongkyu) Kim
510aa46abd
Merge pull request #172 from taeyoung1005/feat/ktx-booking-train-type-flag
feat(ktx-booking): add --train-type flag for ITX/무궁화 routes
2026-04-30 01:25:54 +09:00
Jeffrey (Dongkyu) Kim
c52922999c
Merge pull request #183 from NomaDamas/feature/#167
Feature/#167
2026-04-30 01:21:36 +09:00
Jeffrey (Dongkyu) Kim
e36a727ecd
Merge pull request #179 from NomaDamas/feature/#52
Feature/#52
2026-04-30 01:21:00 +09:00
Jeffrey (Dongkyu) Kim
1eaa7eb7d8 fix(ktx-booking): make korail2 fallback TrainType import-safe
The fallback TrainType class only defined ALL/KTX, but TRAIN_TYPE_MAP
references ITX_SAEMAEUL, MUGUNGHWA, NURIRO, TONGGUEN, ITX_CHEONGCHUN,
and AIRPORT at import time, so the module crashed with AttributeError
in environments where korail2 is missing. This bypassed the controlled
guidance from ensure_runtime_dependencies() and broke even `--help`.

Mirror upstream korail2.TrainType numeric IDs in the fallback so the
import-time TRAIN_TYPE_MAP construction succeeds and the helper can
surface its install message. Add regression tests that exercise the
missing-korail2 path via subprocess to lock the behavior in.

Addresses round 2 review on #172.
2026-04-30 01:18:46 +09:00
Jeffrey (Dongkyu) Kim
d11c7d37bf Add court-auction-notice-search skill and package (#167)
Implement Workflow A (매각공고 → 사건/물건 펼치기) and Workflow B
(사건번호 직조회) MVP for the official 대법원경매정보 site
courtauction.go.kr. The package exposes searchSaleNotices,
getSaleNoticeDetail, getCaseByCaseNumber, and getCourtCodes plus a
court-auction-notice-search CLI mirror. Direct HTTP transport is the
default with a Playwright fallback (rebrowser-playwright /
playwright-core, dynamic import) for blocked/5xx situations.

Anti-bot guardrails: minimum 2s + jitter between calls, 10-call
session budget, immediate BLOCKED throw on data.ipcheck === false, and
no automatic retry to avoid extending the site's IP block. Fixtures
were captured from live courtauction.go.kr endpoints during discovery
and live smoke tests verify each public API end-to-end.

Workflow C (자유 조건검색), Workflow D (일별/월별 캘린더), 매각물건
사진/PDF, and 동산 경매는 follow-up issues로 분리됨.
2026-04-29 16:36:55 +09:00
Jeffrey (Dongkyu) Kim
f79308acfc
Merge pull request #181 from NomaDamas/feature/#173
Feature/#173
2026-04-29 01:13:40 +09:00
Jeffrey (Dongkyu) Kim
9c639455df
Merge pull request #180 from NomaDamas/feature/#170
Feature/#170
2026-04-29 01:11:28 +09:00
Jeffrey (Dongkyu) Kim
c934b4076b Prevent partial jangbu wrapper installs on collisions
Promoted upstream jangbu skills now preflight every Claude and agents top-level destination before the installer mutates home skill directories. This keeps unrelated user-authored skills from causing a mixed partial discovery state, while preserving the existing managed-marker overwrite path and the explicit override escape hatch.

The installer also prints the namespace re-sync warning next to the upstream runtime install command so users know to restore wrapper-managed top-level skills after running upstream's Claude-only installer.

Constraint: Upstream skill contents must be checked out before collision preflight can validate promoted SKILL.md files.

Rejected: Roll back cache checkout on collision | cache writes are outside the advertised home skill discovery namespace and are needed to inspect pinned upstream content.

Confidence: high

Scope-risk: narrow

Directive: Keep promoted-skill collision checks before install_wrapper_payload and sync_dir calls for home skill roots.

Tested: bash -n korean-jangbu-for/scripts/install.sh

Tested: node --test scripts/skill-docs.test.js --test-name-pattern='korean-jangbu-for'

Tested: temp HOME real pinned upstream install and .agents jangbu-tax collision preflight smoke

Tested: npm run ci
2026-04-29 01:09:11 +09:00
Jeffrey (Dongkyu) Kim
b20202c14e Keep restroom merge priority readable
The last PR review found only a readability nit in the merge/dedupe comparator. The comparator body is now indented consistently with adjacent sort callbacks, and a narrow regression locks the requested style expectation for this review follow-up.

Constraint: Existing PR #180 follow-up requested TDD even for the approved readability-only change
Rejected: Introduce a formatter dependency | outside the small approved follow-up scope
Confidence: high
Scope-risk: narrow
Reversibility: clean
Tested: npm test --workspace public-restroom-nearby
Tested: KAKAO_REST_API_KEY=env-test KAKAO_REST_APIKEY=env-alt npm test --workspace public-restroom-nearby
Tested: npm run lint --workspace public-restroom-nearby
Tested: npm run ci
Tested: mocked searchNearbyPublicRestroomsByCoordinates smoke run
Not-tested: live Kakao API calls
2026-04-29 01:03:28 +09:00
Jeffrey (Dongkyu) Kim
11b1150110 Protect jangbu wrapper installs from incomplete payloads
The promoted upstream subskills need to be discoverable without making home installs destructive or incomplete. The wrapper installer now copies its support payload into both home skill roots, allows installed-wrapper reruns, and refuses to overwrite unrelated top-level jangbu-* skills unless explicitly overridden.

Constraint: PR #181 review requires top-level subskill discovery under both Claude and agents roots.

Constraint: Home installs must remain re-runnable without a source checkout.

Rejected: Continue using generic sync_dir for promoted skills | it silently deletes unrelated user-authored skills.

Confidence: high

Scope-risk: narrow

Directive: Do not bypass the promoted-skill ownership check without preserving unrelated home skill directories.

Tested: node --test scripts/skill-docs.test.js --test-name-pattern='korean-jangbu-for'

Tested: bash -n korean-jangbu-for/scripts/install.sh

Tested: bash korean-jangbu-for/scripts/install.sh plus installed ~/.claude and ~/.agents wrapper reruns

Tested: bash ~/.claude/skills/korean-jangbu-for/upstream/scripts/install.sh and verify.sh with Python 3.11 shim

Tested: npm run ci

Tested: Architect verification APPROVED

Not-tested: Live CODEF collection requiring user BYOK credentials and external authentication
2026-04-29 00:50:37 +09:00
Jeffrey (Dongkyu) Kim
61e898815f Keep restroom CSV results when address correction fails
Kakao coord2address correction is optional display enrichment, so API failures should not abort CSV-backed restroom results. The correction loop now records a normalized warning and leaves the original CSV item intact, matching the fail-open behavior used by other Kakao enrichment layers.

Constraint: PR #180 review follow-up required TDD and warning metadata in meta.kakaoErrors

Rejected: Let coord2address failures bubble | optional display correction should not take down core CSV results

Confidence: high

Scope-risk: narrow

Tested: npm test --workspace public-restroom-nearby; KAKAO_REST_API_KEY=env-test KAKAO_REST_APIKEY=env-alt npm test --workspace public-restroom-nearby; npm run lint --workspace public-restroom-nearby; npm run ci; mocked coord2address 429 smoke run
2026-04-29 00:42:04 +09:00
Jeffrey (Dongkyu) Kim
5a4ff0759f Preserve jangbu attribution in top-level subskills
The wrapper advertised upstream /jangbu-* routing for both Claude and agent-compatible installs, but only nested the pinned upstream checkout. The installer now registers the upstream subskills at top level in both home skill roots while appending the wrapper attribution and accounting disclaimer policy to direct subskill use.

Constraint: PR review required top-level discovery for ~/.claude/skills and ~/.agents/skills

Constraint: korean-jangbu-for responses must retain original link, @kimlawtech (SpeciAI), Apache-2.0, and accounting/tax disclaimer

Rejected: Copy raw upstream subskills unchanged | direct /jangbu-* use would bypass mandatory wrapper response policy

Confidence: high

Scope-risk: narrow

Tested: node --test scripts/skill-docs.test.js --test-name-pattern='korean-jangbu-for'

Tested: bash korean-jangbu-for/scripts/install.sh plus top-level Claude/agents subskill marker checks

Tested: upstream runtime install and verify with Python 3.11 shim

Tested: npm run ci
2026-04-29 00:29:04 +09:00
Jeffrey (Dongkyu) Kim
c7ab1edc7e Bound optional Kakao restroom enrichment
Kakao CSV display correction now defaults to the requested visible limit instead of the whole normalized CSV set, while explicit csvCorrectionLimit can still widen the window. Optional Kakao keyword/category enrichment failures are isolated as metadata so CSV results remain usable when Kakao rate-limits or fails.

Constraint: PR #180 review identified quota and reliability blockers in the Kakao enrichment path

Rejected: Keep Promise.all fail-fast semantics | optional enrichment must not mask official CSV results

Rejected: Correct every CSV row by default | broad CSV responses can exceed latency and quota expectations

Confidence: high

Scope-risk: narrow

Directive: Keep official CSV as the primary source; Kakao enrichment failures should stay non-fatal unless a future explicit strict mode is added

Tested: KAKAO_REST_API_KEY=env-test npm test --workspace public-restroom-nearby

Tested: KAKAO_REST_API_KEY=env-test KAKAO_REST_APIKEY=env-alt npm test --workspace public-restroom-nearby

Tested: npm run lint --workspace public-restroom-nearby

Tested: npm run ci

Tested: mocked smoke run confirmed 1 returned CSV item, 1 coord2address call, and 3 Kakao layer warnings

Not-tested: live Kakao API behavior with a real API key
2026-04-29 00:17:47 +09:00
Jeffrey (Dongkyu) Kim
4a44cf6a0d Add korean jangbu automation skill wrapper
Issue #173 needs a k-skill entry point centered on kimlawtech/korean-jangbu-for while preserving upstream attribution and accounting/tax disclaimers. Add a thin wrapper with pinned upstream install, bundled Apache license/disclaimer/notice, docs, and regression coverage for the required original link and @kimlawtech (SpeciAI) mention.\n\nConstraint: Upstream implementation remains in kimlawtech/korean-jangbu-for and is installed by pinned SHA instead of vendoring runtime payload into k-skill\nConstraint: Generated accounting/tax outputs are reference drafts, not official audits or tax filings\nRejected: Reimplement ledger automation locally | would duplicate upstream and widen maintenance scope\nConfidence: high\nScope-risk: narrow\nDirective: Keep responses for this skill citing https://github.com/kimlawtech/korean-jangbu-for and @kimlawtech (SpeciAI); do not remove tax/accounting disclaimers\nTested: node --test scripts/skill-docs.test.js --test-name-pattern='korean-jangbu-for'\nTested: bash korean-jangbu-for/scripts/install.sh\nTested: bash ~/.claude/skills/korean-jangbu-for/upstream/scripts/install.sh with Python 3.11 PATH shim\nTested: bash ~/.claude/skills/korean-jangbu-for/upstream/scripts/verify.sh\nTested: npm run ci\nNot-tested: CODEF live collection flow; requires user BYOK credentials and external auth
2026-04-28 23:41:09 +09:00
Jeffrey (Dongkyu) Kim
ff255bf272 Improve restroom coverage with Kakao source merging
The public-restroom lookup now keeps the official CSV as the authoritative first layer while enriching sparse areas with Kakao Local keyword and gas-station searches when a REST API key is configured. All returned POIs are normalized onto local haversine distance calculations so Kakao's unreliable distance field cannot affect ordering, and map links encode names before coordinates.\n\nThe CSV path can optionally use Kakao coord2address to correct display names and addresses for known source-data coordinate mismatches without changing the default no-key behavior.\n\nConstraint: Kakao REST API requires caller-provided REST API key via option or KAKAO_REST_API_KEY\nConstraint: Existing no-key CSV-only behavior must continue to work\nRejected: Replace CSV with Kakao-only search | loses official open-time metadata and source priority\nRejected: Trust Kakao distance field | issue evidence shows user-origin mismatch\nConfidence: high\nScope-risk: moderate\nDirective: Keep CSV sourceLayer priority ahead of Kakao dedupe unless official data is explicitly deprecated\nTested: npm test --workspace public-restroom-nearby\nTested: npm run lint --workspace public-restroom-nearby\nTested: npm run ci\nNot-tested: Live Kakao REST API call with a production key
2026-04-28 23:28:12 +09:00
Jeffrey (Dongkyu) Kim
2120ca7cc0 Reject impossible LCK calendar dates
Date-driven LCK lookups should fail fast for impossible user input instead of silently returning an empty match list. The parser now validates month/day bounds with explicit leap-year handling, and the tests lock both direct normalization and schedule normalization behavior.

Constraint: PR #55 review requested a regression for 2026-02-31 before implementation
Rejected: Rely on Date parsing round-trip | explicit bounds avoid timezone and overflow normalization surprises
Confidence: high
Scope-risk: narrow
Directive: Keep date validation before schedule filtering so invalid user dates cannot become misleading no-match responses
Tested: node --test packages/lck-analytics/test/index.test.js scripts/skill-docs.test.js
Tested: npm run lint --workspace lck-analytics
Tested: npm test --workspace lck-analytics
Tested: npm run ci
Tested: getMatchResults('2026-02-31') rejection smoke
Tested: getLckSummary('2026-04-01', { team: '한화', includeStandings: true }) live smoke
Tested: lck-analytics script smokes for sync-oracle, build-match-report, and analyze-live-game
2026-04-28 23:14:42 +09:00
Jeffrey (Dongkyu) Kim
39c97ccacc
Merge pull request #178 from NomaDamas/feature/#174
Feature/#174
2026-04-28 18:10:47 +09:00
Jeffrey (Dongkyu) Kim
0b280839d6 Clarify cleaner usage evidence boundaries
The cleanup helper now streams local logs, reports which evidence sources were merged, and keeps README table coverage tied to the central skill-name fixture so the documented cleanup signal stays trustworthy for large local histories and mixed imported counts.

Constraint: Follow-up addresses PR #178 review comments without changing the non-destructive recommendation model.

Rejected: Filtering imported usage JSON by --days inside the helper | imported counts are already aggregated and lack per-record timestamps.

Confidence: high

Scope-risk: narrow

Directive: Keep --usage-json documented as pre-windowed unless the input schema gains timestamped per-record events.

Tested: PYTHONPATH=scripts python3 -m unittest scripts.test_k_skill_cleaner

Tested: node --test scripts/skill-docs.test.js

Tested: npm run lint

Tested: npm run typecheck && npm test

Tested: npm run ci
2026-04-28 18:08:17 +09:00
Jeffrey (Dongkyu) Kim
1935e641a6 Honor standalone cleaner skill roots
The standalone helper is advertised from inside the k-skill-cleaner directory, so --skills-root . now resolves that self-skill directory to its parent skills root before scanning siblings. A subprocess regression locks the installed-skill layout that previously returned skill_count 0.\n\nConstraint: Preserve the documented standalone command without requiring users to switch to --skills-root ..\nRejected: Documentation-only fix | would make the advertised command more brittle and leave existing users with empty reports\nConfidence: high\nScope-risk: narrow\nDirective: Keep standalone helper invocation from inside k-skill-cleaner covered when changing root detection\nTested: PYTHONPATH=scripts python3 -m unittest scripts.test_k_skill_cleaner\nTested: Standalone temp-layout smoke from inside k-skill-cleaner with --skills-root .\nTested: npm run lint && npm run typecheck && npm test && npm run ci
2026-04-28 17:59:33 +09:00
Jeffrey (Dongkyu) Kim
10cb419212 Align cleaner helper contract with selective installs
The cleanup helper is now shipped inside the k-skill-cleaner skill payload while the repo-root script remains as a compatibility wrapper. The CLI also enforces the interview-selected usage window with --days/--since and reports the effective cutoff so recommendations match the documented contract.\n\nConstraint: Selective skill installs only receive files under the skill directory.\nRejected: Remove k-skill-cleaner from selective install docs | the feature is intended to be installable standalone.\nConfidence: high\nScope-risk: narrow\nDirective: Keep the skill-local helper as the canonical implementation; the root script should stay a thin wrapper.\nTested: PYTHONPATH=scripts python3 -m unittest scripts.test_k_skill_cleaner\nTested: node --test scripts/skill-docs.test.js\nTested: CLI smoke via k-skill-cleaner/scripts/k_skill_cleaner.py with --days 90\nTested: npm run lint\nTested: npm run typecheck && npm test\nTested: npm run ci\nTested: Architect verification APPROVED\nNot-tested: Live agent transcript schemas beyond fixture-style local log samples
2026-04-28 17:50:54 +09:00
Jeffrey (Dongkyu) Kim
58717576ee Help users retire unused K-skills
Add a conservative k-skill-cleaner workflow that interviews users, scans best-effort agent trigger logs, and reports deletion or review candidates without mutating skill directories automatically.

Constraint: Trigger-count storage differs by coding agent and may be absent or rotated locally
Rejected: Auto-delete low-usage skills | cleanup recommendations need explicit user approval because log signals are incomplete
Confidence: high
Scope-risk: narrow
Directive: Keep deletion behavior recommendation-only unless a future issue explicitly approves mutation with stronger safeguards
Tested: PYTHONPATH=scripts python3 -m unittest scripts.test_k_skill_cleaner; helper CLI smoke; npm run lint; npm run typecheck && npm test; npm run ci; architect verification
Not-tested: Live exports from OpenClaw/ClawHub or Hermes Agent because no stable public local trigger-count schema is assumed
2026-04-28 17:40:16 +09:00
Jeffrey (Dongkyu) Kim
66c2c95082
Merge pull request #177 from NomaDamas/feature/#175
Feature/#175
2026-04-28 14:58:45 +09:00
Jeffrey (Dongkyu) Kim
c0b38fc517 Prevent IROS download path failures after payment
The IROS docs now make the corp-number happy path produce the company-name list that pinned upstream iros_download.py opens after payment, and route the customer workbook excel_path into the same private workdir boundary as other sensitive inputs and outputs.

Constraint: Live IROS login/payment smoke requires user credentials, certificate/authentication, and card payment authority

Rejected: Rely on upstream data/ defaults | leaves real customer workbook and company list paths inside the cloned repository

Confidence: high

Scope-risk: narrow

Tested: node --test --test-name-pattern='iros-registry-automation' scripts/skill-docs.test.js

Tested: ./scripts/validate-skills.sh

Tested: npm run lint && npm run typecheck && npm test

Tested: npm run ci

Tested: cloned pinned upstream SHA and verified companies_list, excel_path, and configured paths resolve under private temp workdir

Not-tested: Live IROS login/payment smoke; requires user credentials/authentication/payment authority
2026-04-28 14:47:30 +09:00
Jeffrey (Dongkyu) Kim
bb12f433b2 Constrain IROS automation to a reviewed upstream boundary
The IROS skill delegates sensitive browser automation to an upstream Playwright implementation, so the execution guide now checks out a reviewed SHA and keeps real inputs and generated files in a private workdir instead of the clone. Regression coverage locks the pin and privacy-path contract to prevent future docs drift.\n\nConstraint: PR #177 review required an enforceable upstream execution boundary before merge\nConstraint: Live IROS login and payment smoke requires user credentials and card authority\nRejected: Continue documenting mutable upstream HEAD | unsafe for authenticated legal-document/payment-adjacent flows\nConfidence: high\nScope-risk: narrow\nDirective: Do not update iros-registry-automation/scripts/upstream.pin without reviewing the new upstream diff and updating the documented checkout SHA\nTested: node --test --test-name-pattern='iros-registry-automation' scripts/skill-docs.test.js\nTested: ./scripts/validate-skills.sh\nTested: npm run lint && npm run typecheck && npm test\nTested: npm run ci\nTested: cloned upstream, checked out pinned SHA, rewired config.json to a private temp workdir, and verified selected paths stay under that workdir\nNot-tested: Live IROS login/payment smoke; requires user credentials, certificate/authentication, and payment authority
2026-04-28 14:38:14 +09:00
Jeffrey (Dongkyu) Kim
dac6e7b742 Add safe IROS registry certificate guidance
Issue #175 needs a 등기부등본 skill grounded in the challengekim reference implementation while preserving user control over IROS login, authentication, and payment. Add a docs-first skill with regression coverage that locks the upstream credit, safety boundaries, and repository documentation wiring.

Constraint: Original author link must be mentioned in documentation.\nConstraint: IROS login, certificate authentication, and card payment must remain user-controlled.\nRejected: Add a packaged automation wrapper | no dependency or executable implementation was required and credential/payment flows are high-risk.\nConfidence: high\nScope-risk: narrow\nDirective: Do not remove the challengekim upstream credit or weaken the manual login/payment boundary without a new review.\nTested: node --test --test-name-pattern='iros-registry-automation' scripts/skill-docs.test.js; ./scripts/validate-skills.sh; npm run ci\nNot-tested: Live IROS smoke with real login/payment, intentionally not run without user credentials and payment authority
2026-04-28 14:25:36 +09:00
Jeffrey (Dongkyu) Kim
aa1768a055 Keep KTX train-type changes covered by regression tests
The new reserve train-type argument is consumed by direct command_reserve
calls as well as the CLI parser, so the existing unit helpers need to
model the parser default and assert non-KTX replay behavior explicitly.

Constraint: PR #172 is now retargeted to dev and must be mergeable without weakening the train_id stability tests
Confidence: high
Scope-risk: narrow
Tested: python3 -m py_compile scripts/ktx_booking.py scripts/test_ktx_booking.py
Tested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_ktx_booking
Tested: npm run lint
2026-04-26 22:36:14 +09:00
TaeyoungPark
d1409b309c feat(ktx-booking): add --train-type flag for ITX/Mugunghwa routes
The CLI hardcoded TrainType.KTX in command_search and command_reserve,
which silently excluded ITX-청춘, ITX-새마을, 무궁화호, etc. Routes
served only by non-KTX trains (e.g. 남춘천→용산 via ITX-청춘) returned
zero results with no error.

Add an explicit --train-type flag (default: ktx) so the skill keeps
its KTX-first identity but lets users opt into other Korail train
types when needed:

  ktx, itx-saemaeul, mugunghwa, nuriro, tonggeun,
  itx-cheongchun, airport, all

Default stays as ktx — fully backward compatible. SKILL.md updated
with usage examples for both search and reserve.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 22:35:19 +09:00
Jeffrey (Dongkyu) Kim
46747b1dea
Merge pull request #169 from NomaDamas/feature/#168
Feature/#168
2026-04-25 16:05:38 +09:00
Jeffrey (Dongkyu) Kim
6ea5d5a47f Keep corporate registration docs aligned with README skill names
Merged origin/dev into feature/#168 and resolved the README table conflict by preserving dev's skill-name column while keeping the corporate-registration-consulting row. The README docs regression mapping now includes the new skill so future table checks cover it.

Constraint: PR #169 must remain reviewable after updating from dev without merging the PR itself
Rejected: Keep the old four-column README row | it would drop dev's issue #165 skill-name column contract
Confidence: high
Scope-risk: narrow
Tested: node --test --test-name-pattern='corporate-registration-consulting' scripts/skill-docs.test.js
Tested: node --test --test-name-pattern='README skill table' scripts/skill-docs.test.js
Tested: npx --yes k-skill-rhwp create-blank <tmp>/blank.hwp and info <tmp>/blank.hwp
Tested: npm run ci
2026-04-25 16:03:42 +09:00
Jeffrey (Dongkyu) Kim
275cc56d8f Prevent corporate registration drafts from overreaching
Tighten the Korean corporate registration skill after round-3 review by keeping filled HWP examples outside the repository, turning the investigation-report conclusion into a user/expert-confirmed placeholder, and locking human-only final action prohibitions with regression coverage.

Constraint: Legal-document workflow must avoid collecting or committing filled PII artifacts

Constraint: Agent may prepare drafts and checklists only, not final legal/tax conclusions or filings

Rejected: Add out/ to .gitignore | safer to teach non-repo private output for sensitive generated forms

Confidence: high

Scope-risk: narrow

Tested: node --test --test-name-pattern='corporate-registration-consulting' scripts/skill-docs.test.js

Tested: npx --yes k-skill-rhwp create-blank <tmp>/blank.hwp && npx --yes k-skill-rhwp info <tmp>/blank.hwp

Tested: npm run ci
2026-04-25 16:00:36 +09:00
Jeffrey (Dongkyu) Kim
a6e77545e4 Keep registration guidance inside safe v1 bounds
Round-two review showed that the new consulting skill still needed explicit stop gates for non-standard incorporation paths and tighter documentation regression locks. The follow-up keeps the user-facing flow beginner-friendly while routing materially different legal/tax structures to official or professional review before draft generation.

Constraint: Legal-document skill must remain reference-only and avoid implying filing, tax, or governance determinations for non-standard cases

Rejected: Keep broad regex alternations for human-only boundaries | they allowed prior safety requirements to regress while tests still passed

Confidence: high

Scope-risk: narrow

Directive: Do not collapse the separate safety-boundary assertions back into alternations without a stronger replacement

Tested: node --test --test-name-pattern='corporate-registration-consulting' scripts/skill-docs.test.js

Tested: npx --yes k-skill-rhwp create-blank <tmp>/blank.hwp && npx --yes k-skill-rhwp info <tmp>/blank.hwp

Tested: npm run ci
2026-04-25 15:51:47 +09:00
Jeffrey (Dongkyu) Kim
07ad8be4ed Tighten corporate registration safety boundaries
The review follow-up closes legal-document safety gaps before merge by locking the expected guidance in the docs regression test and updating the skill surfaces that users and agents rely on. The representative-director clause now avoids board-resolution wording for the common two-director/no-board case, registration-license tax points to the direct 지방세법 제28조 anchor, and PII plus human-only filing boundaries are repeated where filled document workflows can expose sensitive data.

Constraint: PR review requested TDD coverage for legal/tax safety wording and privacy guidance

Rejected: Keep a single generic disclaimer only | document templates also need local warnings where filled artifacts are produced

Confidence: high

Scope-risk: narrow

Directive: Do not remove the repeated privacy and human-only boundaries unless every user-facing generation surface keeps equivalent guidance

Tested: node --test --test-name-pattern='corporate-registration-consulting' scripts/skill-docs.test.js

Tested: npx --yes k-skill-rhwp create-blank <tmp>/blank.hwp && npx --yes k-skill-rhwp info <tmp>/blank.hwp

Tested: npm run ci
2026-04-25 15:42:30 +09:00
Jeffrey (Dongkyu) Kim
aea0b4a655 Support guided corporate registration preparation
Add a reference-only corporate registration consulting skill for first-time Korean incorporation workflows. The skill keeps user decisions explicit, provides conservative articles/document templates, and routes HWP automation through existing rhwp/kordoc surfaces instead of introducing new machinery.

Constraint: Issue #168 requires legal-disclaimer wording, tax pitfall guidance, rhwp-based form support, TDD, and PR delivery to dev.

Rejected: Add a new package or external automation dependency | existing skill docs and rhwp tooling are enough for this documentation/template workflow.

Confidence: high

Scope-risk: narrow

Directive: Do not present generated registration documents as legal or tax advice; keep official-source verification and professional review caveats visible.

Tested: node --test --test-name-pattern='corporate-registration-consulting' scripts/skill-docs.test.js; npx --yes k-skill-rhwp create-blank/info smoke; npm run ci

Not-tested: Live court registry submission or live tax payment, intentionally outside reference-skill scope
2026-04-25 15:32:24 +09:00
Jeffrey (Dongkyu) Kim
64c3f02014
Merge pull request #166 from NomaDamas/feature/#165
Feature/#165
2026-04-24 16:49:59 +09:00
Jeffrey (Dongkyu) Kim
b776a04d21 docs(readme): add skill-name column to feature table (#165)
Surface every skill's directory identifier (e.g. `kbl-results`,
`real-estate-search`) directly in the "어떤 걸 할 수 있나" table so users
can search README and copy-paste the exact name into install commands or
agent frontmatter without leaving the page.

- README.md: insert `스킬 이름` column after `할 수 있는 일` for all 54
  rows (5-column header, separator, and per-row inline-code identifier);
  apply matching strikethrough to the deprecated blue-ribbon-nearby cell.
- scripts/skill-docs.test.js: add 4 regression tests pinning the header
  shape, the 53 active label↔skill-name mappings, the strikethrough on
  the deprecated row, and a cross-check that every advertised skill
  identifier matches a real on-disk SKILL.md whose frontmatter `name`
  agrees (validate-skills.sh invariant).
2026-04-24 14:15:37 +09:00
github-actions[bot]
96f0d810ef
chore: version packages (#164)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-04-24 13:48:33 +09:00
Jeffrey (Dongkyu) Kim
4fc01391ce
Release: Merge dev into main (#163)
* Add a guided Hola Poke Yeoksam skill without widening repo scope

Issue #120 only needs a repository skill payload, discoverability docs,
and regression coverage. This change adds the new skill, wires it into
existing docs surfaces, and locks the remote-MCP-only contract in tests
so future edits keep the phone-only event flow and verbatim message
relay behavior.

Constraint: The upstream Hola Poke flow lives on a remote MCP server, so this repo should not add proxy/runtime code
Constraint: Tests must be written before refining the new docs/skill wording
Rejected: Add local package or proxy support for Hola Poke | would over-scope a docs-only skill addition
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep this skill limited to 올라포케 역삼점 and treat the MCP response message as the event source of truth
Tested: node --test scripts/skill-docs.test.js --test-name-pattern='hola-poke-yeoksam'
Tested: npm run ci
Tested: Live MCP initialize/tools/list/get_menu/get_shop_info/enter_event(phone_format) smoke checks against https://hola-poke-yeoksam-skill.onrender.com/mcp
Not-tested: Successful live event entry with a real phone number

* Help users find nearby public restrooms from Korean location queries

This adds a new public-restroom-nearby skill and reusable package that resolves a user-provided location, narrows the official 공중화장실정보 dataset by region when possible, and ranks nearby restroom results with opening-time hints and map links.

Constraint: Must use free official/open surfaces without introducing new dependencies
Constraint: Must follow TDD and keep release/docs metadata aligned in the same change
Rejected: Add a proxy route first | direct official CSV access already works and keeps scope narrower
Rejected: Use nationwide-only ranking without regional narrowing | too much noisy data for dense urban anchors
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: If Kakao place-panel or localdata CSV schema changes, update parser fixtures before broad logic changes
Tested: npm run ci; live smoke via searchNearbyPublicRestroomsByLocationQuery('광화문', { limit: 3 }); architect review APPROVED
Not-tested: Non-Seoul live smoke across every regional orgCode

* Pin the Hola Poke MCP contract in repo-owned regression fixtures

The earlier issue #120 regression only matched prose, so this follow-up records the verified remote MCP tool/result snapshot in a checked-in fixture and makes both docs surfaces byte-align to it. That keeps the discoverability docs honest while turning the review claim into a real contract lock for tools/list, get_menu, get_shop_info, and the invalid-phone event flow.

Constraint: The upstream remote MCP server can change independently of this repo
Rejected: Keep prose-only regex checks | would not catch contract drift
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Refresh the fixture, both JSON fences, and the live-smoke evidence together whenever the upstream contract changes
Tested: node --test scripts/skill-docs.test.js --test-name-pattern='hola-poke-yeoksam'; npm run ci; live MCP smoke check against https://hola-poke-yeoksam-skill.onrender.com/mcp (initialize, tools/list, get_menu, get_shop_info, invalid enter_event)
Not-tested: Successful enter_event with a real phone number (intentionally avoided to prevent live event participation)

* Keep nearby restroom lookups resilient to flaky Kakao place panels

The review caught two regressions in the new public-restroom-nearby package: a single broken Kakao panel aborted anchor resolution, and coordinate search dropped maxDistanceMeters before normalization. This change adds targeted regression coverage first, keeps per-candidate HTTP failures recoverable, and hardens request errors with explicit status/url metadata so fallback logic no longer depends on parsing error strings.

Constraint: Must preserve the published package surface and keep the fix scoped to PR #123 follow-up
Rejected: Swallow all panel errors | would hide non-HTTP failures like network faults
Rejected: Parse request error messages for status codes | brittle coupling to string formatting
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep recoverable Kakao panel handling aligned with request() error annotations if request() changes again
Tested: npm test --workspace public-restroom-nearby
Tested: npm run ci
Tested: live smoke searchNearbyPublicRestroomsByLocationQuery('광화문', { limit: 3 })
Tested: LSP diagnostics on packages/public-restroom-nearby/src/index.js and test/index.test.js
Not-tested: Live Kakao fallback against a real upstream 5xx place-panel response

* Keep the Hola Poke contract claims aligned with verified coverage

The reviewed fixture-based regression already locks the documented remote
snapshot, but the docs still implied the enter_event success path had
live proof. Narrow the docs and the regression so they explicitly say the
success fields are pinned by the recorded snapshot while the live smoke
only verifies the invalid-phone retry path.

Constraint: Live success-path verification would trigger a real event entry and is intentionally avoided
Rejected: Leave the broader wording in place | review feedback showed it overstated the live evidence
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If a safe non-mutating success-path probe becomes available, update the docs and fixture wording together
Tested: node --test scripts/skill-docs.test.js --test-name-pattern='hola-poke-yeoksam'; npm run ci; live MCP smoke against https://hola-poke-yeoksam-skill.onrender.com/mcp (initialize, tools/list, get_menu subset, get_shop_info subset, invalid enter_event)
Not-tested: Real enter_event success-path invocation

* Document the restroom distance-cap contract with regression coverage

The approved issue-117 code fix already restored maxDistanceMeters behavior, but the published docs did not lock or explain that contract. This follow-up adds a failing-first doc regression, then updates the feature guide and package README with the verified 100m example so users and future reviewers see the same behavior the package now ships.

Constraint: Must stay scoped to the existing PR #123 follow-up without reopening the implementation surface
Rejected: Leave the behavior implicit in code/tests only | published docs would lag the verified contract
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the public-restroom-nearby docs and skill-docs regression aligned with live maxDistanceMeters smoke evidence if the sample query changes
Tested: node --test scripts/skill-docs.test.js (red then green)
Tested: npm test --workspace public-restroom-nearby
Tested: npm run ci
Tested: live smoke searchNearbyPublicRestroomsByLocationQuery('광화문', { limit: 3 })
Tested: live smoke searchNearbyPublicRestroomsByLocationQuery('광화문', { limit: 3, maxDistanceMeters: 100 })
Tested: architect review APPROVED
Not-tested: Alternative landmark queries with a non-zero maxDistanceMeters hit set

* Expose KRX partial failures instead of misreporting stock lookups

The Korean stock proxy used to silently drop failed market snapshots during
search and could turn an empty holiday trade snapshot into a 502 by falling
back into base-info lookup.

This change surfaces degraded market metadata on partial search success,
short-circuits empty trade snapshots to not_found, and refreshes the user
docs to use a real trading day in examples.

Constraint: KOSPI base-info approval is granted separately from other KRX routes
Constraint: Healthy markets should still return usable search results during a partial outage
Rejected: Return 502 on every partial search failure | hides still-usable markets and breaks current clients unnecessarily
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep degraded search metadata when any market snapshot fetch fails so partial outages stay visible
Tested: npm test --workspace k-skill-proxy
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Not-tested: Live KOSPI base-info behavior after the new KRX permission is approved

* Adopt kordoc for the hwp skill workflow

Issue #119 replaces the previous HWP guidance with kordoc so the skill matches the newer agent-native document flow. The docs and regression tests now center the HWP skill on kordoc parsing, JSON extraction, diffing, form filling, and Markdown-to-HWPX round-tripping, while the install/source references stay in sync.

Constraint: The repository treats skill behavior as documentation contracts backed by regression tests
Constraint: The requested branch/PR flow must target dev with TDD and verified execution evidence
Rejected: Keep @ohah/hwpjs or hwp-mcp as fallback guidance | issue #119 explicitly approves replacing the prior stack with kordoc
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep future hwp skill/docs/tests aligned to a single kordoc-first contract unless a new issue explicitly reintroduces multi-backend routing
Tested: node --test scripts/skill-docs.test.js; npm run ci; temp-dir kordoc roundtrip via markdownToHwpx -> sample.hwpx -> kordoc CLI markdown output; architect review APPROVED
Not-tested: Live parsing of user-provided proprietary HWP/HWPX samples outside the generated roundtrip fixture

* Prevent degraded stock search outages from sticking in cache

Reviewer feedback showed that partial KRX market failures could be cached as full search answers, masking recovery on the next identical request. This change adds a regression that fails first, skips route-level caching for degraded search payloads, and keeps the trade-info empty-snapshot contract documented alongside the partial-failure response semantics.

Constraint: Existing PR #124 already targets dev and must remain the follow-up lane for issue #99
Constraint: Proxy behavior must stay read-only and dependency-free
Rejected: Cache degraded search payloads for a short TTL | still risks transient false negatives during the TTL window
Rejected: Broaden trade-info fallback behavior | empty snapshots should stay explicit not_found results
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep degraded search responses out of the long-lived route cache unless a future design adds explicit revalidation semantics
Tested: npm test --workspace k-skill-proxy; node --test scripts/skill-docs.test.js; npm run ci; explicit buildServer degraded-search recovery repro
Not-tested: Live KRX production endpoints from this branch

* Align HWP docs with the published kordoc surface

The issue #119 follow-up needs the repository contract to match what the
currently published kordoc package actually supports. This narrows the
HWP skill/docs/tests to the verified install requirement and supported
CLI/Node API surfaces, and removes unsupported fill/mcp claims.

Constraint: Published kordoc CLI fails at startup without pdfjs-dist
Constraint: Docs/tests must reflect the current npm package behavior, not intended future features
Rejected: Keep fill/mcp examples with caveats | still documents unsupported entrypoints
Confidence: high
Scope-risk: narrow
Directive: Reintroduce fill/mcp docs only after verifying the published package exposes them in both CLI and Node API
Tested: node --test scripts/skill-docs.test.js; npm run ci; temp-dir clean install smoke; temp-dir kordoc+pdfjs-dist watch/parse/extractFormFields/compare/markdownToHwpx/roundtrip smoke; Claude architect review
Not-tested: Real-world HWPX template that produces non-empty extractFormFields output

* Keep HWP docs runnable against the published kordoc package

The follow-up closes the last runnable-contract gaps from review by documenting the working one-shot npx form and separating Node API examples into a local project install path. The regression suite now locks both install notes so future edits do not drift back to broken command shapes.

Constraint: Published kordoc CLI still requires pdfjs-dist at startup
Constraint: Global NODE_PATH does not make ESM imports from kordoc resolvable in the documented examples
Rejected: Keep bare `npx kordoc` examples | fails in a clean environment
Rejected: Keep global-install Node API guidance | ESM import remains unresolved
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep HWP docs aligned to verified published kordoc surfaces until the package contract changes upstream
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: temp-dir local npm install kordoc pdfjs-dist plus markdownToHwpx -> sample.hwpx -> one-shot kordoc roundtrip smoke
Not-tested: upstream unpublished kordoc features beyond the verified CLI and Node API surfaces

* Add Korean scholarship search skill and reporting workflow (#116)

* Add nationwide scholarship search skill workflow

* Rename scholarship skill to 장학금 주세요 쮜에발

* Fix scholarship skill validation in CI

* Trigger GitHub PR diff refresh after dev rebase on main

* Fix scholarship helper status handling and test coverage

* Use KST as scholarship helper default date basis

* Rename scholarship skill display name

---------

Co-authored-by: Jeffrey (Dongkyu) Kim <vkehfdl1@gmail.com>

* Feature/#121 (#127)

* Recover KakaoTalk mac skill auth when upstream user_id detection fails

Issue #121 reproduces on a real MacBook because `kakaocli auth` can fail even when the encrypted hex-named DB exists. This change adds a thin repo-owned helper that recovers the active user_id from plist revision hashes, caches the validated DB/key tuple, and reuses it for read-only `kakaocli` commands. The skill and feature docs now steer users to the helper when upstream auto-detection stops at candidate key mismatch, and regression tests lock the recovery flow before the implementation.

Constraint: Must stay a thin adapter around upstream kakaocli rather than forking the CLI
Constraint: Must verify on a real local macOS KakaoTalk install where issue #121 reproduces
Rejected: Full kakaocli reimplementation inside k-skill | too broad for the user_id/key-derivation failure scope
Rejected: Docs-only workaround | does not actually fix the broken auth path for users
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep this helper limited to auth/key recovery and read-only passthrough unless upstream gaps widen materially
Tested: python3 -m unittest scripts.test_kakaotalk_mac
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: python3 scripts/kakaotalk_mac.py auth --refresh --max-user-id 800000000 --workers 8 --chunk-size 2000000
Tested: python3 scripts/kakaotalk_mac.py chats --limit 1 --json
Not-tested: Other kakaocli subcommands beyond auth/chats/messages/search/query/schema

* Protect the KakaoTalk helper's safe recovery path

Address the PR follow-up by treating malformed auth cache files as cache misses,
removing write-capable passthrough from the wrapper surface, and redacting
human-readable auth output so the cached SQLCipher key is not echoed back into
terminal history. The docs and regression suite now describe and enforce the
read-only contract that the helper is meant to preserve.

Constraint: Helper must remain a read-only recovery wrapper around local kakaocli access
Rejected: Keep query support with SQL validation | still leaves a risky write-capable escape hatch
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Do not re-expose arbitrary SQL passthrough or print the SQLCipher key in default text output
Tested: python3 -m unittest scripts.test_kakaotalk_mac; node --test scripts/skill-docs.test.js; npm run ci; python3 scripts/kakaotalk_mac.py auth --refresh --max-user-id 800000000 --workers 8 --chunk-size 2000000; python3 scripts/kakaotalk_mac.py chats --limit 1 --json; python3 scripts/kakaotalk_mac.py auth --cache-path <bad-json>; python3 scripts/kakaotalk_mac.py query --help
Not-tested: External automation consumers that depend on shell/json auth output beyond the documented helper flows

* Lock the helper CLI surface against accidental regressions

The approved issue #121 fixes already hardened the KakaoTalk Mac helper, but the test suite still only exercised the passthrough validator directly. Add an explicit parser-level regression so the public CLI contract stays read-only and `query` cannot quietly reappear in future edits.

Constraint: Follow-up is on the existing feature/#121 PR branch and must stay minimal
Rejected: Re-open helper implementation changes | current code already satisfies the approved review findings
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep parser exposure tests aligned with READ_ONLY_COMMANDS whenever helper subcommands change
Tested: python3 -m unittest scripts.test_kakaotalk_mac; node --test scripts/skill-docs.test.js; npm run ci; python3 scripts/kakaotalk_mac.py auth --refresh --max-user-id 800000000 --workers 8 --chunk-size 2000000; python3 scripts/kakaotalk_mac.py chats --limit 1 --json; python3 scripts/kakaotalk_mac.py auth --cache-path <bad-json>
Not-tested: No new production code paths changed in this follow-up

* Honor explicit Kakao auth recovery overrides

The helper now treats manual auth overrides as a cache-bypassing recovery request and rejects invalid brute-force tuning flags at the CLI boundary so users get deterministic behavior instead of stale cached tuples or Python tracebacks. Regression coverage locks both paths before the PR follow-up lands.

Constraint: The helper must remain a thin read-only wrapper around kakaocli auth recovery
Rejected: Require --refresh whenever --user-id/--uuid is passed | worse UX than honoring overrides directly
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep explicit auth overrides ahead of cache reuse unless the CLI contract is redesigned and documented
Tested: python3 -m unittest scripts.test_kakaotalk_mac; node --test scripts/skill-docs.test.js; npm run ci; python3 scripts/kakaotalk_mac.py auth --refresh --max-user-id 800000000 --workers 8 --chunk-size 2000000; python3 scripts/kakaotalk_mac.py chats --limit 1 --json; python3 scripts/kakaotalk_mac.py auth --cache-path <bad-json>; python3 scripts/kakaotalk_mac.py auth --refresh --max-user-id -1; python3 scripts/kakaotalk_mac.py auth --refresh --workers 2 --chunk-size 0 --max-user-id 10; python3 scripts/kakaotalk_mac.py auth --cache-path <temp-cache> --user-id 999; python3 scripts/kakaotalk_mac.py auth --cache-path <temp-cache> --uuid <live-uuid>
Not-tested: Manual override success with a truly alternate valid user_id/uuid pair on a multi-account local install

* Feature/#129 (#131)

* Add official KBL results support so basketball queries use live league data

Issue #129 needs a read-only skill and reusable package for KBL schedules, results, and standings. The implementation follows the existing sports package pattern and uses the league's live JSON APIs after verifying they respond successfully in real requests.

Constraint: Must use official KBL JSON surfaces before considering scraping
Constraint: Packaging changes must pass npm run ci and include docs plus Changesets updates
Rejected: Browser scraping first | official api.kbl.or.kr endpoints are live and simpler to maintain
Rejected: Reuse KBO/K League package shapes verbatim | KBL payload and team/status fields differ materially
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep seasonGrade=1 as the default KBL path unless future docs/tests explicitly widen to D-League flows
Tested: npm run ci; npm run lint --workspace kbl-results; npm test --workspace kbl-results; live getKBLSummary("2026-04-01", { team: "KCC", includeStandings: true })
Not-tested: Historical standings snapshots for past seasons via alternative KBL endpoints

* Prevent optional standings lookups from over-fetching the KBL API

The new kbl-results summary helper exposes includeStandings=false, so the
regression suite now proves that path stays schedule-only and never calls
the standings endpoint when the caller opts out.

Constraint: The KBL package should preserve the caller's no-standings contract
Rejected: Rely on manual inspection of the helper options | a targeted test is cheaper and safer
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep includeStandings=false side-effect free unless the public API contract changes explicitly
Tested: npm test --workspace kbl-results; npm run lint --workspace kbl-results
Not-tested: Full-repo CI before stacking this commit onto the rebased branch

* Add Naver Shopping price comparison skill

* Use Naver Shopping BFF fallback

* Fix naver shopping BFF page and sort fallback

* Clarify Naver OpenAPI review sort fallback

* Add library book search skill

* Add Data4Library route regression coverage

* Fix Data4Library book-exists ISBN-10 handling

* Refactor Coupang skill to retention MCP layer

* Add Coupang MCP wrapper follow-up coverage

* Clarify Coupang wrapper init guidance

* Document Coupang MCP init examples

* Add parking lot search skill

* Add korean-privacy-terms skill regression tests

* Add korean-privacy-terms thin-wrapper skill

* Document korean-privacy-terms skill across repo docs

* Bundle Apache-2.0 LICENSE with korean-privacy-terms wrapper

Addresses PR #149 review SHOULD FIX: ship the Apache-2.0 LICENSE text
alongside the thin wrapper so Apache License 2.0 §4(a) ('give any other
recipients of the Work or Derivative Works a copy of this License') is
satisfied even before `install.sh` fetches the upstream payload.

- Copy upstream LICENSE verbatim to `korean-privacy-terms/LICENSE.upstream`
  (byte-for-byte identical to upstream at pinned SHA
  e390f7b9feb825e368c26726363ea5ce11a34083; SHA256
  35ef947614c2f14df01c5fc553f987f644f0c9f6b011adda397bd788a87f1510).
- Update SKILL.md Notes to link LICENSE.upstream, clarify that repo-root
  LICENSE (MIT) is k-skill's own license not this skill's, and document
  that nested upstream SKILL.md is not discovered by agent platforms.
- Document the home-path `bash ~/.claude/skills/.../install.sh` variant in
  SKILL.md so users who pulled the wrapper via `npx skills add --skill` can
  install without a repo checkout (installer already resolves
  ${BASH_SOURCE[0]} absolutely).
- Update docs/features/korean-privacy-terms.md to document LICENSE.upstream
  and the §4(a) rationale.
- Strengthen skill-docs regression tests (NICE TO HAVE items from review):
  * Reject placeholder pins (all-zero / all-f 40-char strings).
  * Assert the literal upstream clone URL
    (https://github.com/kimlawtech/korean-privacy-terms.git).
  * Assert `git clone --filter=blob:none` is used for blobless fetches.
  * Add new regression test that verifies LICENSE.upstream exists, matches
    the Apache-2.0 preamble / §4 / APPENDIX structure, and is referenced
    from both SKILL.md and the feature doc.

* Assert APPENDIX anchor in korean-privacy-terms LICENSE.upstream

Close Round 3 NICE TO HAVE from PR #149. The LICENSE.upstream
regression block asserted preamble, Version 2.0, Redistribution,
END OF TERMS, and Copyright 2026 kimlawtech but not the APPENDIX
anchor at LICENSE.upstream:179, even though the Round 1 follow-up
and Round 2 review collectively described 'APPENDIX structure
verification'. Adding this one assertion closes that claim/test
parity gap and acts as tamper-detection if upstream reformats
LICENSE later.

Verified with TDD: temporarily stripped APPENDIX line from
LICENSE.upstream, confirmed test 108 FAILS with the expected
regex mismatch, then restored and re-confirmed 109/109 GREEN.
Byte-for-byte identity with upstream LICENSE still holds
(SHA256 35ef947614c2f14df01c5fc553f987f644f0c9f6b011adda397bd788a87f1510).

npm run ci exit 0 with 357 ok subtests (unchanged baseline,
additive assertion within existing test block).

* Fix extractDataGoItems to handle current data.go.kr JSON shapes

The MFDS data.go.kr drug and food endpoints now return body.items as a
flat array (DrbEasyDrugInfoService, SafeStadDrugService) or an array of
{item: {...}} wrappers (PrsecImproptFoodInfoService03), instead of the
legacy {items: {item: [...]}} XML→JSON auto-convert shape.

Our extractDataGoItems was still looking for body.items.item, so it
returned [] for every entry, silently breaking:
  - /v1/mfds/drug-safety/lookup
  - /v1/mfds/food-safety/search (improperFood portion)

Update extractDataGoItems to accept all three shapes and refresh the
mock fixtures in server.test.js to match what upstream actually returns,
while adding a backward-compat test for the legacy shape.

Note: this does not resolve the remaining FOODSAFETYKOREA_API_KEY being
rejected by upstream (issue #148 core symptom) - that is a separate
operational key rotation on the proxy server.

* Make proxy cache failure-aware and require route-prefixed cache keys

Two related issues surfaced while investigating issue #148:

1. Transient upstream failures were being cached for the full 5-minute
   TTL because every route handler called cache.set() unconditionally
   with whatever payload came back - including empty items + warnings
   from a flaky upstream like openapi.foodsafetykorea.go.kr. The user
   would then see "empty + warning" for 5 minutes even after upstream
   recovered.

2. makeCacheKey(payload) hashes the whole payload, but fine-dust/report
   was the only route calling it without a "route" prefix
   (makeCacheKey(normalized) instead of
   makeCacheKey({ route: "fine-dust-report", ...normalized })).
   Different routes with the same normalized shape could collide.

Fix both globally in the cache layer so every current and future route
benefits without per-route edits:

- createMemoryCache.set rejects any payload that isFailureResponse
  considers a failure (explicit error field, upstream.degraded flag,
  or empty items alongside warnings). Returns false on reject, true
  on accept, so callers can observe the decision if needed.
- makeCacheKey now throws if payload.route is missing or empty. This
  catches the fine-dust inconsistency and prevents new routes from
  reintroducing it.
- fine-dust/report now passes `route: "fine-dust-report"` like every
  other route.

New tests:
- makeCacheKey asserts distinct routes produce distinct keys and throws
  without a route.
- isFailureResponse covers all failure signatures plus graceful-
  fallback cases (items present alongside warnings) that must stay
  cacheable.
- createMemoryCache.set refuses each failure shape and still stores
  healthy payloads.
- End-to-end: food-safety/search with a flaky recall upstream serves
  the upstream failure, retries live when upstream recovers, and only
  caches once the payload is healthy.

TTL itself is unchanged - the value still protects upstream rate
limits; it just no longer amplifies transient errors.

* Document Coupang hosted fallback contract and affiliate disclosure

retention-corp/coupang_partners#1 is merged, so upstream now transparently falls back to the Retention Corp hosted backend at https://a.retn.kr/v1/public/assist when Coupang Partners API credentials are missing. The k-skill wrapper already passes environment variables through unchanged, so this commit lines up the documented contract with the actual two-path behavior without changing runtime logic.

- SKILL.md and docs/features/coupang-product-search.md describe both execution paths (operator local HMAC vs credentialless hosted fallback), the honored OPENCLAW_SHOPPING_* env vars, the allowlist client-id convention including the k-skill-specific coupang-mcp-fallback value, and the mandatory affiliate disclosure when a.retn.kr/s/ shortlinks or lptag=AF deeplinks appear in responses.
- docs/sources.md adds the hosted assist endpoint and the merged upstream PR so the source surface stays truthful.
- README.md reflects the 선택사항 semantics for the 쿠팡 상품 검색 row and extends the column legend so 선택사항 is distinct from 불필요.
- coupang_partners_mcp.py expands its --help epilog so operators discover the honored upstream env vars without reading the wrapper source; no runtime behavior change.
- scripts/test_coupang_partners_mcp_wrapper.py locks env pass-through as a regression, asserts the new --help contract, and adds an opt-in K_SKILL_COUPANG_SMOKE=1 live smoke test that verifies the credentialless hosted path returns a Coupang deeplink.
- scripts/skill-docs.test.js extends the docs regression to require the hosted assist URL, OPENCLAW_SHOPPING_* env prefix, affiliate disclosure wording, and hosted fallback concept while keeping the yuju777 HF Space negative assertion.

Verified: npm run ci exits 0, live smoke test (K_SKILL_COUPANG_SMOKE=1) returns a.retn.kr/s/ shortlinks via credentialless wrapper, and manual env -u COUPANG_ACCESS_KEY -u COUPANG_SECRET_KEY call returns isRocket+lptag=AF3727577 responses through the hosted fallback.

Refs: #134

* Drop non-allowlisted coupang-mcp-fallback recommendation from hosted fallback docs

Direct probes against https://a.retn.kr/v1/public/assist confirmed that
X-OpenClaw-Client-Id: coupang-mcp-fallback returns HTTP 403 Client is not
allowlisted, while the upstream default openclaw-skill returns HTTP 200.
The default wrapper path already works because upstream falls back to
openclaw-skill, but the explicit recommendation in SKILL.md and the
feature doc was luring users to a 403 path.

Remove the dead recommendation and lock in the working configuration:

- Docs describe openclaw-skill as the upstream-allowlisted default and
  note that k-skill does not override OPENCLAW_SHOPPING_CLIENT_ID.
- Wrapper --help epilog drops the Suggested k-skill value line and
  documents openclaw-skill as the allowlist value in play.
- New skill-docs regression asserts coupang-mcp-fallback is absent from
  SKILL.md, the feature doc, the wrapper, and docs/sources.md while
  openclaw-skill is documented across all three narrative surfaces.
- New Python wrapper regression asserts --help drops the dead value and
  surfaces openclaw-skill so the constraint stays locked.
- Existing env-forwarding test uses openclaw-skill as the pass-through
  sentinel so the repo no longer ships the non-allowlisted string at all.

* Add lh-notice-search skill and /v1/lh-notice/{search,detail} proxy routes

Wraps the official data.go.kr LH (Korea Land & Housing Corporation) 청약
공고 Open API (B552555/lhLeaseNoticeInfo1/*) so agents can look up LH
임대/분양/주거복지/토지/상가 공고 by region, status, category, keyword,
and notice ID without asking users for a ServiceKey. Reuses the shared
DATA_GO_KR_API_KEY the proxy already manages; users see '불필요'.

Adapter handles both the LH-specific [CMN, dsList] JSON envelope and the
standard data.go.kr <OpenAPI_ServiceResponse> XML error envelope; refuses
to cache failure responses so transient upstream errors self-heal.

Closes #145.

* Document LH extractNoticeEnvelope success-code accept-list as deliberate

Per review note #4 on PR #158, extractNoticeEnvelope accepts four upstream
CMN.CODE values ("SUCCESS", "0", "00", "000") and three header.resultCode
values ("0", "00", "000") as success. This is deliberate: the data.go.kr
platform has surfaced different forms across catalog eras, and a future
normalization that flips SUCCESS to a numeric form must not regress into
502'ing otherwise-valid responses.

- Add an inline comment above the array-envelope success-code check in
  src/lh-notice.js explaining why the accept-list is NOT redundant.
- Add regression tests in test/lh-notice.test.js that explicitly exercise
  each accepted success code (SUCCESS/0/00/000 for array envelope; 0/00/000
  for object envelope) so a future refactor cannot silently collapse the
  accept-list.
- Add a paired rejection test that numeric-looking non-success codes like
  "22" and "10" still raise as upstream_error, disambiguating the
  accept-list from a blanket 'any numeric string passes' rule.

Test count: lh-notice.test.js 30 -> 38 (all pass); npm run ci exits 0.

* Pin LH /v1/lh-notice/detail failure-not-cached contract with regression test

Round 2 review noted that /v1/lh-notice/detail failure-not-cached
behavior was only verified via manual QA, while /search had an
explicit automated regression test.

This adds an equivalent automated test for /detail that:
- fails upstream once (XML SERVICE_KEY error, upstream_code=30)
- confirms first call returns 502 with cache.hit=false
- switches upstream to success and retries the same URL
- confirms second call returns 200 with cache.hit=false (failure was
  NOT cached, retry hit upstream again)
- sabotages upstream back to failing and verifies the third call
  serves the previously-cached success (cache.hit=true, no new fetch)

Verified the test genuinely catches regressions by temporarily
monkey-patching the detail route to cache error payloads — the test
correctly fails in that sabotaged state and passes when the route is
correct. Full server.test.js suite goes from 95 to 96 tests, all pass.

* Document LH /detail test pins both cache-protection layers

Adds a 12-line header comment to the 'lh-notice detail does not cache
upstream XML auth errors so retries self-heal' test in server.test.js
naming the two cache-protection layers it pins:

  (a) the early-return catch block in the route handler (no cache.set
      on upstream failure), and
  (b) the isFailureResponse() guard inside cache.set (refuses any
      payload with .error set).

Points future maintainers to the independent sabotage audit in PR #158
Round 3 review that proved bypassing either layer alone makes the
State 2 self-heal assertion fail, and cross-links the sibling /search
failure-not-cached test for symmetric coverage.

Addresses the Round 3 non-blocking observation #2 nice-to-have.
Test-only, comment-only: +12 lines, 0 source changes, 0 behavior
changes, 0 doc changes, 0 changeset changes. server.test.js remains
96/96, lh-notice.test.js remains 38/38, full proxy workspace 184/184.

* Add naver-news-search skill and /v1/naver-news/search proxy route

Closes #143. Proxies the official Naver Search Open API news endpoint
(openapi.naver.com/v1/search/news.json) through k-skill-proxy so users do
not need to issue their own Naver Client ID/Secret. Reuses the existing
NAVER_SEARCH_CLIENT_ID/NAVER_SEARCH_CLIENT_SECRET that naver-shopping already
consumes, since the Naver Developer application enables the 'Search' scope
covering both news and shopping.

Implementation details:
- src/naver-news.js normalizes q/display/start/sort, builds the official URL,
  calls upstream with X-Naver-Client-Id/Secret headers, and parses the JSON
  response into rank/title/description/link/original_link/pub_date items.
- Strips <b> highlight tags and decodes HTML entities in title/description
  using zero-width replacement so compound Korean words like '주식형' are
  preserved (not split into '주식 형').
- Parses RFC822 pubDate into pub_date_iso (ISO-8601 UTC) for clients.
- Deduplicates items by normalized link; drops entries missing title/link.
- Returns 503 upstream_not_configured when proxy keys are absent (no public
  BFF fallback exists for news like it does for shopping, so keys are
  required).
- Failure responses are not cached (failure-aware cache layer).
- Exposes naverNewsApiConfigured on /health.

14 new tests in test/naver-news.test.js cover query validation, URL
building, payload normalization (HTML stripping, entity decoding,
deduplication, missing-field tolerance), plus Fastify integration tests
for 200/400/401/429/500/503 paths, cache hit/miss, header wiring, and
the health flag.

* Add rhwp-edit and rhwp-advanced skills with k-skill-rhwp CLI

Splits HWP handling into three focused skills per issue #155:

- hwp (kept): kordoc-based read/convert (Markdown, JSON, diffing, form
  fields, Markdown->HWPX). Description narrowed to 'read-only' to make
  the routing policy explicit.
- rhwp-edit (new): HWP binary editing via new k-skill-rhwp npm package
  that wraps the @rhwp/core WASM bindings as CLI subcommands: info,
  list-paragraphs, search, insert-text, delete-text, replace-all,
  create-table, set-cell-text, create-blank, and render.
- rhwp-advanced (new): guidance for the upstream Rust rhwp CLI
  (export-svg --debug-overlay, dump, dump-pages, ir-diff, thumbnail,
  convert) for layout debugging, IR inspection, version comparison,
  and read-only-document unlocking.

The new k-skill-rhwp package under packages/ ships a Node.js 18+ CLI
and library that round-trips HWP 5.x documents entirely in-process; no
Rust toolchain is required. It auto-installs the WASM-required
globalThis.measureTextWidth shim for headless Node, and all editing
subcommands always write to a distinct output path so the source file
is never mutated. HWPX save remains disabled per the upstream rhwp
#196 data-safety gate; HWPX input is accepted but output is written as
HWP 5.x.

Includes 24 node:test cases covering init, round-trip insertText,
replaceAll, createTable + setCellText, deleteText, searchText,
listParagraphs, renderPage (SVG/HTML), and full CLI arg-parse +
end-to-end round-trip through the CLI layer.

Wires README feature table (3 rows for hwp / rhwp-edit / rhwp-advanced),
docs/install.md optional-install list, docs/roadmap.md (marks HWP
advanced editing as shipped while keeping Windows/security-module
automation out of scope), docs/sources.md (adds rhwp upstream, CLI
source, @rhwp/core, @rhwp/editor, and rhwp #196 references), and the
root pack:dry-run script. Adds a Changesets entry for k-skill-rhwp
minor.

Closes #155.

*  feat: add k-dart skill for DART OpenAPI financial disclosures (#147)

*  feat: add k-dart skill for DART OpenAPI financial disclosures

금감원 전자공시시스템(DART) 14개 endpoint 조회 스킬 추가.
공시검색, 기업개황, 재무제표, 배당, 증자/감자, 전환사채, 소송 등.
API_K_DART 환경변수로 직접 호출하며 프록시 불필요.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* 📝 docs(k-dart): remove redundant korean-stock-search dependency

corpCode.xml 자체에 회사명·종목코드·고유번호가 모두 포함되어 있으므로
korean-stock-search 스킬 연계 절차 제거

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* 📝 docs: add k-dart to README feature table

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* 📝 docs: add k-dart feature guide and fix README link format

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* 🐛 fix(k-dart): correct status code 013, remove invalid corp_name filter, update daily limit

3개 critical 정확성 오류 수정:

1. 상태코드 013은 "조회된 데이터 없음"이며 "접근 권한 없음"이 아님 (012=접근 불가 IP).
   상태코드 표를 공식 명세 기준으로 재정리하고 누락된 014/021 코드 추가.
2. list.json은 corp_name 파라미터를 검색 필터로 지원하지 않음. SKILL.md의
   잘못된 진술과 corp_name을 사용한 misleading example을 제거하고, corp_code
   확보 절차를 거치도록 명시.
3. DART 일일 한도는 키당 10,000건이 아닌 20,000건이며 분당 약 1,000회
   throttle도 별도로 존재함. SKILL.md 및 docs/features/k-dart.md 모두 정정.

추가로 status: "013" 발생 시 사용자 안내 정책을 Response policy에 추가하고,
오픈API 이용현황 페이지 링크를 Notes에 추가함.

* 🐛 fix(k-dart): correct pifricDecsn endpoint, list.json corp_code optional, add empSttus, soften throttle claim

Codex adversarial review에서 식별된 4건의 추가 정확성 이슈 수정:

1. endpoint #8 유무상증자 결정이 잘못된 API에 연결됨. piicDecsn.json은
   유상증자 결정 (apiId=2020023)이며, 유무상증자 결정은 pifricDecsn.json
   (apiId=2020025)이 맞음. endpoint를 정정하고 piicDecsn (유상증자) 및
   fricDecsn (무상증자)와의 차이를 주의문으로 추가.

2. list.json의 corp_code 는 사실 선택사항이며, 미지정 시 검색 기간이
   3개월 이내로 제한될 뿐임. 이전 commit의 "corp_code 필수" 표현을
   정정하고, 두 가지 호출 패턴(corp_code 지정/미지정)을 Example
   requests에 모두 추가.

3. "분당 약 1,000회 throttle"은 공식 공개 가이드에 근거 없음
   (apiUsageStatusView.do 는 로그인 게이트). 공식 가이드가 명시한
   "일반적으로 20,000건 이상 요청 시 020 발생"만 유지하고 분당
   throttle 주장을 제거. 상태코드 표·Response policy도 일관되게 정리.

4. docs/features/k-dart.md가 "직원 현황" 기능을 광고하지만 SKILL.md
   에는 endpoint가 누락됨. empSttus.json (apiGrpCd=DS002,
   apiId=2019011)을 endpoint #8로 추가하고 example도 함께 등록.
   기존 endpoint 9~14는 10~15로 재번호.

* 🐛 fix(k-dart): align list.json signature and 020 caveat with official spec

Codex 2nd-round review에서 식별된 정확성 이슈 2건 수정:

1) list.json 요청 인자 signature가 공식 가이드(DS001/2019001)와 정확히
   일치하도록 재작성. crtfc_key 외 모든 파라미터가 선택사항임을 분명히
   하고, 각 파라미터의 default 동작과 pblntf_ty 값(A/B/C/D/E)도 명시.
   "corp_code 지정 시 기간 제한 없음" 표현은 공식 가이드가 보장하지
   않으므로 제거. corp_name이 공식 파라미터에 "존재하지 않는다"는
   사실로 수정 (이전: "지원하지 않는다").
   "corp_code 미지정 시 3개월 제한"은 외부 사용 사례에서 관찰된
   동작으로 약화 (공식 가이드에 별도 명시 없음).

2) 020 (요청 제한 초과) 안내가 일일 20,000건 cap 으로 너무 단정적
   해석되던 표현을 공식 메시지 그대로 보존: "일반적으로 20,000건
   이상 요청 시 발생하며, 키별로 별도 한도가 설정된 경우 다른
   임계치에서도 동일 코드가 반환될 수 있음". 상태코드 표·Response
   policy·Notes·docs/features/k-dart.md 모두 일관되게 정정.

* 🐛 fix(k-dart): mirror official Korean DS001/2019001 list.json spec exactly

Codex 3rd-round review에서 식별된 잔존 정확성 이슈 수정.

영어 가이드(DE001/AE00001)와 한국어 가이드(DS001/2019001)가 list.json
필수여부에서 다르게 표기되어 있어 이전 commit이 영어 가이드를 따랐으나,
한국어 공식 가이드를 직접 확인한 결과(opendart.fss.or.kr/guide/detail.do
?apiGrpCd=DS001&apiId=2019001) 다음이 한국어 공식 spec임을 확인:

- bgn_de, end_de는 Y(필수) (기본값은 명시되어 있으나 표기상 필수)
- corp_code 미지정 시 검색기간 3개월 제한은 공식 spec에 명시된 룰
  (외부 사용 사례 관찰이 아님)
- pblntf_ty는 A~J 전체 enum (정기공시/주요사항보고/발행공시/지분공시/
  기타공시/외부감사관련/펀드공시/자산유동화/거래소공시/공정위공시)
- page_count 기본값 10, 최대값 100
- corp_cls 복수 조건 불가
- last_reprt_at, sort, sort_mth 각 default 동작 명시

list.json 섹션을 공식 가이드 표와 1:1 일치하는 마크다운 표로 재작성.
3개월 제한 표현을 "외부 사례"에서 "공식 spec"으로 정정. Response policy
에 잔존하던 corp_name "지원하지 않는다" 표현도 "공식 파라미터에 존재하지
않는다"로 통일하여 #1 endpoint 섹션과 일관성 확보. docs/features/k-dart.md
도 동일하게 정정.

* 🐛 fix(k-dart): make list.json table 1:1 mirror of DS001/2019001 + unify corp_name wording

Codex 4th-round review가 식별한 잔존 이슈 2건 마무리.

1) list.json 파라미터 표를 공식 가이드 행 순서 그대로(crtfc_key,
   corp_code, bgn_de, end_de, last_reprt_at, pblntf_ty,
   pblntf_detail_ty, corp_cls, sort, sort_mth, page_no, page_count)
   재정리하고 공식 표의 모든 컬럼(요청키/명칭/타입/필수여부/값설명)을
   포함. page_no(1~n) / page_count(1~100, 기본10, 최대100) 범위
   값을 공식 표 그대로 표기. pblntf_detail_ty 값설명도 공식 표
   그대로 "(※ 상세 유형 참조: pblntf_detail_ty)"로 두고, 자주 쓰는
   코드 예시(A001/B001/F001/D001)는 표 아래 별도 단락으로 분리해
   표의 1:1 mirror 성격을 유지.

2) corp_name 관련 canonical 문장 "공식 요청 파라미터 표에
   corp_name 은 존재하지 않는다" 를 다음 3곳 모두 verbatim 일치
   시킴 (이전 commit에서 SKILL.md는 '않는다', docs/features는
   '않음' 으로 어미 차이가 잔존했음):
   - k-dart/SKILL.md #1 endpoint 섹션 주의문
   - k-dart/SKILL.md Response policy
   - docs/features/k-dart.md 에러/제약 섹션

* 🐛 fix(k-dart): unify corp_name canonical sentence verbatim + soften list.json table claim

Codex 5th-round review가 식별한 fine-grained 이슈 마무리.

1) corp_name canonical 문장을 self-contained 형태로 재작성하여
   3곳 모두 byte-for-byte 동일하게 통일:
   "DART OpenAPI list.json 의 공식 요청 파라미터 표에 corp_name 은
   존재하지 않는다."
   - SKILL.md #1 endpoint 섹션 주의문
   - SKILL.md Response policy
   - docs/features/k-dart.md 에러/제약 섹션
   이전에는 SKILL.md는 "위 공식 요청 파라미터 표에"로 docs/features는
   "list.json 공식 요청 파라미터 표에" 로 prefix가 달라 verbatim
   일치하지 않았음.

2) list.json 표 헤더 문구를 "공식 가이드 표를 그대로 옮긴 것"에서
   "공식 가이드 요청 인자 정리 (필수여부·기본값·허용값은 공식 표
   기준, 식별자는 코드 폰트로 표기)"로 약화. 마크다운 backtick 등
   포매팅 차이가 "1:1 mirror" 약속과 모순되지 않게 정확히 표현.

---------

Co-authored-by: hon2be <hon2be>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Jeffrey (Dongkyu) Kim <vkehfdl1@gmail.com>

* WIP korean-slang-writing (#133): scaffold slang_search.py

* WIP korean-slang-writing (#133): add http + lookup scripts

* WIP korean-slang-writing (#133): add seed index of 30 curated trending slang

* WIP korean-slang-writing (#133): add test suite

* korean-slang-writing (#133): fix module-loader sys.modules registration

* korean-slang-writing (#133): add SKILL.md

* korean-slang-writing (#133): add feature doc

* korean-slang-writing (#133): register skill in README and root lint/test pipeline

* Revert out-of-scope HWP README edits to unblock CI

The prior commit 4c7877a on this branch renamed the HWP feature row to
'HWP 문서 조회/변환' and added two new rows ('HWP 문서 편집',
'HWP 레이아웃·IR 디버깅') pointing at docs/features/rhwp-edit.md and
docs/features/rhwp-advanced.md. Those docs do not exist on any branch
in this repo, and the rename violates scripts/skill-docs.test.js
assertions at lines 210, 223, 224, which caused the CI 'validate' job
to fail.

Those changes belong to a separate rhwp-edit/rhwp-advanced feature
effort (tracked elsewhere), not to issue #143 'naver-news-search'.
Revert README.md in both the feature table and the list section so the
only additions in this PR relative to origin/dev are the two
in-scope naver-news-search entries.

Verified by running 'npm run ci' locally (EXIT=0). skill-docs.test.js
now passes 110/110 (previously failed 2/110) and the full
k-skill-proxy suite remains 198/198 including the 14 naver-news tests.

* Update skill-docs tests to cover rhwp-edit, rhwp-advanced, and the k-skill-rhwp package

Pins the HWP table row rename to 'HWP 문서 조회/변환', asserts the new
'HWP 문서 편집' and 'HWP 레이아웃·IR 디버깅' README rows and their linked
feature docs, pins the new SKILL.md routing policy for rhwp-edit and
rhwp-advanced (k-skill-rhwp CLI + @rhwp/core for editing vs upstream
Rust CLI for layout/IR debugging), and asserts the k-skill-rhwp
package.json wiring (bin mapping, @rhwp/core dependency, Node 18+
engines, wasm-init shim + CLI bin files).

Per AGENTS.md rule, no assertion is added on the presence of any
.changeset/*.md file so the changeset release flow can consume the
rhwp-edit-skill.md entry without breaking CI at version-bump time.

Also captures the package-lock.json delta introduced by adding the
k-skill-rhwp workspace (pulls @rhwp/core@0.7.3 and its WASM binary).

Refs #155.

* Polish naver-news: preflight, link canonicalization, /health docs (#143)

Address the three non-blocking items flagged in the round 1/2 reviews. All
were explicitly deferred by the reviewer as "follow-up if the maintainer
wants" — picking them up now so the feature lands with a tighter surface.

1) Preflight 400 for start + display - 1 > 1000
   Naver's official news endpoint only exposes the first 1000 items
   (start 1..1000, display 1..100). Asking for start=1000 & display=100
   would send a request that silently returns no usable items, wasting
   an upstream quota call. Reject the combination before calling upstream
   with a 400 bad_request and a message that tells the caller which item
   the request would have needed and what the cap is. Boundary values
   (start + display - 1 === 1000) are still accepted.

2) Canonical link dedup
   The previous dedup key was link.toLowerCase(), which failed to merge
   the same article when Naver's redirect URLs differed only by query-param
   order, trailing slash, host-name casing, or fragment. Added
   canonicalizeLinkForDedup() which parses the URL, sorts search params by
   key, strips a single trailing pathname slash, drops the fragment, and
   lowercases the result — conservative on purpose so different paths or
   different query values stay as distinct articles. The visible
   items[].link value is still the original URL returned by Naver; only
   the dedup key is canonicalized.

3) Clarify the naverSearchApiConfigured vs naverNewsApiConfigured split
   The two flags currently evaluate the same boolean, but their semantic
   contracts differ: naverSearchApiConfigured reports "are the Naver
   Open API keys configured" (which is advisory for the shopping route
   since shopping has a BFF fallback), while naverNewsApiConfigured
   reports "is the news route operational end-to-end" (no fallback — 503
   when false). Hoist the shared expression into a local, and add a
   `/health 업스트림 플래그 의미` section to packages/k-skill-proxy/README.md
   documenting the split. Also update naver-news-search SKILL.md and
   docs/features/naver-news-search.md to mention the new preflight and
   the canonical-link dedup behavior.

TDD verification: added 4 new node:test cases exercising the boundary,
overflow, and URL-dedup paths; ran the full k-skill-proxy workspace
suite (202/202 pass) plus the root `npm run ci` (exit 0). Manual QA on
a proxy started from this commit reproduces every round-1 case plus the
new preflight: start=1000 & display=100 → 400 bad_request before
upstream; start=1000 & display=1 and start=901 & display=100 → 503 (or
200/401 depending on keys), confirming the boundary passes preflight.

* korean-slang-writing (#133): fix broken seed namuwiki URLs + add encoding invariant test

Reviewer flagged 4/30 seed namuwiki_url values returning HTTP 404 on live
Namu Wiki. These URLs are part of the documented response contract and get
surfaced directly to agents, so broken links are a functional bug, not a
cosmetic one.

Root causes per entry:
- 중꺾마: wrong 꺾 codepoint (U+AFFA 꿺 instead of U+AEBE 꺾).
- 아아: typo in aliased title (아이스 아메리칸노 instead of 아메리카노).
- 어쩔티비: missing 받침 (어쩌티비 instead of 어쩔티비).
- 당모치: encoding correct but no live Namu Wiki article exists; dropped.

Also fixes two separately-broken 중꺾마 example URLs in SKILL.md
(U+AFBE 꾾 instead of U+AEBE 꺾) — these were discovered while auditing
the seed and would have surfaced as 404 to agents following the example
snippets.

Adds two regression tests:
- test_each_seed_url_decodes_to_term_or_alias: decodes every seed URL's
  path segment and asserts it equals the term or one of its aliases.
  Catches Hangul-codepoint typos offline (no network dependency) and
  would have caught all 3 encoding bugs in this PR.
- test_no_seed_entry_points_at_known_missing_namuwiki_page: locks the
  당모치 drop so nobody re-adds an entry pointing at a page that does
  not exist on Namu Wiki.

Fixes the existing LookupNetworkTest assertion that was hard-coding the
broken URL — it now derives the expected URL via build_namuwiki_url()
so the test cannot drift out of sync with the helper again.

Verification:
- PYTHONPATH=.:scripts python3 -m unittest scripts.test_korean_slang_writing -> 40/40 pass
- Live GET with browser headers against all 29 remaining seed URLs -> 29/29 return 200
- npm run ci -> exit 0
- Manual QA: slang_search on 중꺾마, 어쩔티비, 아이스 아메리카노 returns
  correct URLs; slang_lookup live-fetches 중꺾마 and extracts the
  canonical title '중요한 것은 꺾이지 않는 마음'.

* korean-slang-writing (#133): extract summaries via h2 section anchor + og:description fallback

Namu Wiki's current HTML layout uses build-time-obfuscated CSS class
names (e.g. _36R8DWTn, OZVChh+l) and has no <article>/<main>/<section>
tags, so all six MAIN_CONTENT_CLASSES anchors fail to match and
extract_summary() returned empty with a 'Main content region not
detected' warning on every live page.

Replace the single class-based strategy with a three-tier fallback
chain that pins to progressively weaker but more structurally stable
anchors:

  1. First h2 section boundary. Namu Wiki articles consistently open
     with '<h2>1. 개요[편집]</h2>' and mark subsequent sections with
     numbered h2 headings. Extracting text between the first and
     second h2 reliably captures the overview section on every page
     sampled (중꺾마, 갓생, 럭키비키, 어쩔티비).
  2. MAIN_CONTENT_CLASSES / <article> - kept as a legacy fallback
     for older Namu Wiki layouts and for third-party fixtures.
  3. og:description meta tag - final safety net before returning
     empty, gives the agent at least a ~64-char preview when the
     article has unusual structure.

Strip '[편집]' edit-affordance markers and numbered section prefixes
(e.g. '1.2.') from the extracted text so headings don't leak through
as noise.

Live verification (text format):
  slang_lookup.py 중꺾마   -> Title + 286-char summary
  slang_lookup.py 갓생     -> Title + 96-char summary
  slang_lookup.py 럭키비키 -> Title + 59-char summary
  slang_lookup.py 어쩔티비 -> Title + 20-char summary

All previously-empty. Not-found / blocked / upstream-error paths and
exit codes are unchanged.

* korean-slang-writing (#133): harden extractor with numbered-h2 gate + category-nav strip

Implements the three non-blocking observations from PR #161 round-3 review:

1. Numbered-h2 gate (reviewer-flagged fragility):
   Refactored _extract_first_section_between_h2 to extract h2 inner text
   (stripping nested tags) and filter by '^\\s*\\d+(?:\\.\\d+)*\\.\\s+\\S'.
   Sidebar widgets like <h2>관련 문서</h2> or <h2>외부 링크</h2> can no longer
   anchor the extractor - only numbered section headers (1., 1.2., 2.3.4.) do.
   Handles live Namu Wiki structure where the number sits inside an <a> tag
   (<a>1.</a> <span>개요</span>), which the round-3 suggested regex-only gate
   missed. All 29 seed pages continue to produce valid summaries on live
   fetches.

2. Category-nav template strip (reviewer-flagged long-page noise):
   a. CATEGORY_NAV_RE strips the inline '[펼치기 · 접기]' marker plus its
      same-line aftermath (the category list items on the same line).
   b. DETAILS_PELCHIGI_RE strips the entire <details> block whose <summary>
      contains 펼치기. Namu Wiki today wraps category nav in exactly this
      structure, so the strip removes the full noise block (not just the
      marker line).
   꿀잼 summary drops from 3482 chars of category dump to 562 chars
   starting with the real definition '무언가가 매우 재미있다는 의미의 인터넷
   유행어'. Non-category <details> blocks (spoilers, footnotes) are
   preserved.

3. TDD + mutation coverage:
   6 new tests total: 2 numbered-h2 gate tests, 2 inline category-nav tests,
   1 <details>-block strip test, 1 <details>-keep test (negative case).
   All 6 were written first and confirmed RED against the round-2 baseline,
   then made GREEN after the implementation landed. Each fix path was also
   mutation-tested (revert regex, remove .sub line) to confirm the tests
   genuinely catch the target bug class.

Suite grows from 45 to 51 tests. All pass. npm run ci exits 0.

* rhwp-edit (#155): fix replace-all silent no-op and document body-only scope

Upstream @rhwp/core HwpDocument.replaceAll returns {ok:true, count:N} but
does not persist the mutation into exportHwp() serialization, so the output
bytes are byte-identical to the input. This is confirmed against
@rhwp/core@0.7.3 with SHA diffing and round-trip searchText.

Rewrite the Node wrapper replaceAll to compose engine primitives that do
persist: for each body paragraph, read the full text via getTextRange,
compute all non-overlapping match offsets in JS, then apply replaceText
right-to-left so earlier offsets are unaffected by length changes. This
restores the documented '2025 → 2026 일괄 치환' headline workflow.

Guard rails in the new replaceAll:
- Reject replacements containing newline or paragraph-break characters
  (\n, \r, U+2028, U+2029) with a descriptive error. Splitting a paragraph
  via replaceText would invalidate subsequent offsets.
- Non-overlapping semantics against the original text, so
  --query a --replacement aa against 'aaa' yields 'aaaaaa' (3 replacements)
  instead of looping on the freshly inserted 'a' characters.

Tighten the regression tests to assert content, not just length:
- Same-length replacement: output SHA must differ from input, searchText
  must find the replacement and must NOT find the original query.
- Longer-length replacement: paragraph length must grow by the correct
  amount and output SHA must differ.
- Shorter-length replacement: paragraph length must shrink by the correct
  amount and output SHA must differ.
- Empty replacement: deletes every match and output no longer contains
  the query.
- Replacement contains query (a→aa on aaa): expects count 3 and length 6.
- Zero matches: count 0, output still written.
- Case-sensitive flag skips mismatched case.
- Newline replacement is rejected synchronously.

Document the body-only scope of search and replace-all in the SKILL.md
routing policy, failure-modes, CLI USAGE text, feature doc, and package
README so users know to use set-cell-text for cell content. This matches
the upstream searchText contract, which does not descend into table cells,
headers, footers, or footnotes.

Add a matching regression assertion to scripts/skill-docs.test.js so the
body-only scope note cannot be silently removed from SKILL.md or the
feature doc.

Closes review round 1 for PR #162.

* rhwp-edit (#155): guard replace-all case-insensitive path against UTF-16 length-drift

Round 2 review flagged a latent Unicode safety bug: when replaceAll's
caseSensitive=false branch encounters characters whose toLowerCase()
changes UTF-16 length (e.g. Turkish İ U+0130 → i + U+0307 combining dot
above), offsets taken in the lowercased haystack drift by the expansion
delta for every subsequent match and silently corrupt the document.
Reviewer repro: 'ABCİABCİXYZ' + case-insensitive İ→Z reported
{ok:true,count:2} but rendered 'ABCZABCİZYZ' instead of 'ABCZABCZXYZ'
(the X at index 8 was corrupted while the second İ survived).

Surface a descriptive error rather than silently drift:
- findAllMatchOffsets: in the case-insensitive branch, verify that the
  paragraph text and the query each preserve UTF-16 length under
  toLowerCase; otherwise throw with an actionable message pointing the
  user to --case-sensitive or input normalization.
- This is strictly a safety guard: the 2025→2026 headline workflow,
  ASCII, Hangul, and every existing test are unaffected.

Tests (TDD red → green, net +4 in packages/k-skill-rhwp):
- 'replaceAll refuses case-insensitive matching when source text
  contains case-folding length-changing chars (e.g. Turkish İ U+0130)'
  reproduces the exact reviewer input and asserts rejection + no output
  file
- 'replaceAll refuses case-insensitive matching when the query itself
  contains case-folding length-changing chars' covers the query-side path
- 'replaceAll with --case-sensitive succeeds on inputs containing İ'
  confirms the guard only fires in the case-insensitive path and that
  case-sensitive produces ABCZABCZXYZ with no X corruption
- 'replaceAll case-insensitive still works for normal ASCII/Hangul'
  regression-guards against the fix over-rejecting the common case

Doc disclosure in all 4 surfaces called out by the reviewer:
- rhwp-edit/SKILL.md: new failure-mode bullet naming U+0130 specifically
- docs/features/rhwp-edit.md: Unicode 대소문자 무시 주의 paragraph
  under scenario 3 (replace-all)
- packages/k-skill-rhwp/README.md: extended Scope section
- packages/k-skill-rhwp/src/cli.js: USAGE 'Scope note' appended
- scripts/skill-docs.test.js: 2 new assertions locking the SKILL.md and
  feature-doc disclosure so they can't be silently removed
- .changeset: note the guard in the pending v0.1.0 release notes

Manual QA (end-to-end via the published CLI):
  $ k-skill-rhwp replace-all … --query İ --replacement Z
  → exit 1 + 'case-insensitive matching is unsafe because case folding
    changes the UTF-16 length …'
  → no output file written
  $ k-skill-rhwp replace-all … --query İ --replacement Z --case-sensitive
  → {ok:true,count:2}, render shows 'ABCZABCZXYZ', search İ ⇒ found:false
  $ replace-all '2025'→'2026' on '2025 2025 2025' ⇒ {ok:true,count:3}
  $ replace-all 'hello'→'hi' (case-insens.) on 'hello WORLD 안녕 HELLO'
    ⇒ {ok:true,count:2}

Verification:
- npm test --workspace k-skill-rhwp: 35 pass / 0 fail (+4 vs Round 2)
- node --test scripts/skill-docs.test.js: 114 pass / 0 fail
- npm run ci: exit 0 (lint + typecheck + all workspace tests +
  pack:dry-run + validate-skills.sh all green)

Refs PR #162 Round 2 review 'Non-blocking residual risk — Unicode
case-insensitive offset drift'.

* Document preflight 400 and full canonical dedup contract in naver-news feature doc

Round-3 review flagged two non-blocking doc-completeness nits in docs/features/naver-news-search.md:

- 실패 모드의 `400 bad_request` 항목이 preflight 케이스(`start + display - 1 > 1000`)를 누락하고 있었음. SKILL.md line 94 와 본문 line 128 의 '운영 팁' 과 대칭이 되도록 업데이트.
- 운영 팁의 canonical dedup 설명이 쿼리 파라미터 순서와 trailing slash 만 언급해서, 실제 구현(`canonicalizeLinkForDedup`)이 같이 정규화하는 host 대소문자와 URL fragment 를 빠뜨리고 있었음. test/naver-news.test.js line 273 이 네 가지 모두 검증하고 있으므로 공개 문서를 구현과 테스트에 맞춰 정정.

* feat: add catchtable-sniper skill (#146)

* feat: add catchtable-sniper skill

* Make the Catchtable skill loadable and discoverable

The submitted skill landed under skills/ without YAML frontmatter, which broke the repo's auto-discovery contract and Codex skill loading. Move it to the root-level skill layout, add the required metadata block, and document the feature in the main README plus a dedicated guide so the PR ships in a usable state.

Constraint: This repository auto-discovers skills from root-level directories only
Constraint: Skill manifests must start with YAML frontmatter for Codex to load them
Rejected: Keep the nested skills/catchtable-sniper layout | validate-skills and the repo's documented convention reject it
Rejected: Add only README links without a feature guide | would create a broken documentation target
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Future skill PRs should follow docs/adding-a-skill.md and place each skill in its own root directory
Tested: node --test scripts/skill-docs.test.js
Tested: ./scripts/validate-skills.sh
Tested: git diff --check
Not-tested: End-to-end Catchtable reservation completion on a logged-in account

---------

Co-authored-by: Jeffrey (Dongkyu) Kim <vkehfdl1@gmail.com>

---------

Co-authored-by: minsing-jin <ironman0722@naver.com>
Co-authored-by: hon2be <saysun34@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: choihyun-1110 <74152226+choihyun-1110@users.noreply.github.com>
2026-04-24 10:41:21 +09:00
Jeffrey (Dongkyu) Kim
069fc0a4f4 Merge origin/main into dev: resolve parking-lot conflicts, keep PR #156 fixes + dev routes 2026-04-23 16:39:43 +09:00
choihyun-1110
a01a164a31
feat: add catchtable-sniper skill (#146)
* feat: add catchtable-sniper skill

* Make the Catchtable skill loadable and discoverable

The submitted skill landed under skills/ without YAML frontmatter, which broke the repo's auto-discovery contract and Codex skill loading. Move it to the root-level skill layout, add the required metadata block, and document the feature in the main README plus a dedicated guide so the PR ships in a usable state.

Constraint: This repository auto-discovers skills from root-level directories only
Constraint: Skill manifests must start with YAML frontmatter for Codex to load them
Rejected: Keep the nested skills/catchtable-sniper layout | validate-skills and the repo's documented convention reject it
Rejected: Add only README links without a feature guide | would create a broken documentation target
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Future skill PRs should follow docs/adding-a-skill.md and place each skill in its own root directory
Tested: node --test scripts/skill-docs.test.js
Tested: ./scripts/validate-skills.sh
Tested: git diff --check
Not-tested: End-to-end Catchtable reservation completion on a logged-in account

---------

Co-authored-by: Jeffrey (Dongkyu) Kim <vkehfdl1@gmail.com>
2026-04-22 16:20:38 +09:00
Jeffrey (Dongkyu) Kim
e0981abd08
Merge pull request #160 from NomaDamas/feature/#143
Feature/#143
2026-04-22 16:13:08 +09:00
Jeffrey (Dongkyu) Kim
f46f634bf0 Document preflight 400 and full canonical dedup contract in naver-news feature doc
Round-3 review flagged two non-blocking doc-completeness nits in docs/features/naver-news-search.md:

- 실패 모드의 `400 bad_request` 항목이 preflight 케이스(`start + display - 1 > 1000`)를 누락하고 있었음. SKILL.md line 94 와 본문 line 128 의 '운영 팁' 과 대칭이 되도록 업데이트.
- 운영 팁의 canonical dedup 설명이 쿼리 파라미터 순서와 trailing slash 만 언급해서, 실제 구현(`canonicalizeLinkForDedup`)이 같이 정규화하는 host 대소문자와 URL fragment 를 빠뜨리고 있었음. test/naver-news.test.js line 273 이 네 가지 모두 검증하고 있으므로 공개 문서를 구현과 테스트에 맞춰 정정.
2026-04-22 16:03:18 +09:00
Jeffrey (Dongkyu) Kim
fb591955e3
Merge pull request #162 from NomaDamas/feature/#155
Feature/#155
2026-04-22 15:45:00 +09:00
Jeffrey (Dongkyu) Kim
c563ef535b rhwp-edit (#155): guard replace-all case-insensitive path against UTF-16 length-drift
Round 2 review flagged a latent Unicode safety bug: when replaceAll's
caseSensitive=false branch encounters characters whose toLowerCase()
changes UTF-16 length (e.g. Turkish İ U+0130 → i + U+0307 combining dot
above), offsets taken in the lowercased haystack drift by the expansion
delta for every subsequent match and silently corrupt the document.
Reviewer repro: 'ABCİABCİXYZ' + case-insensitive İ→Z reported
{ok:true,count:2} but rendered 'ABCZABCİZYZ' instead of 'ABCZABCZXYZ'
(the X at index 8 was corrupted while the second İ survived).

Surface a descriptive error rather than silently drift:
- findAllMatchOffsets: in the case-insensitive branch, verify that the
  paragraph text and the query each preserve UTF-16 length under
  toLowerCase; otherwise throw with an actionable message pointing the
  user to --case-sensitive or input normalization.
- This is strictly a safety guard: the 2025→2026 headline workflow,
  ASCII, Hangul, and every existing test are unaffected.

Tests (TDD red → green, net +4 in packages/k-skill-rhwp):
- 'replaceAll refuses case-insensitive matching when source text
  contains case-folding length-changing chars (e.g. Turkish İ U+0130)'
  reproduces the exact reviewer input and asserts rejection + no output
  file
- 'replaceAll refuses case-insensitive matching when the query itself
  contains case-folding length-changing chars' covers the query-side path
- 'replaceAll with --case-sensitive succeeds on inputs containing İ'
  confirms the guard only fires in the case-insensitive path and that
  case-sensitive produces ABCZABCZXYZ with no X corruption
- 'replaceAll case-insensitive still works for normal ASCII/Hangul'
  regression-guards against the fix over-rejecting the common case

Doc disclosure in all 4 surfaces called out by the reviewer:
- rhwp-edit/SKILL.md: new failure-mode bullet naming U+0130 specifically
- docs/features/rhwp-edit.md: Unicode 대소문자 무시 주의 paragraph
  under scenario 3 (replace-all)
- packages/k-skill-rhwp/README.md: extended Scope section
- packages/k-skill-rhwp/src/cli.js: USAGE 'Scope note' appended
- scripts/skill-docs.test.js: 2 new assertions locking the SKILL.md and
  feature-doc disclosure so they can't be silently removed
- .changeset: note the guard in the pending v0.1.0 release notes

Manual QA (end-to-end via the published CLI):
  $ k-skill-rhwp replace-all … --query İ --replacement Z
  → exit 1 + 'case-insensitive matching is unsafe because case folding
    changes the UTF-16 length …'
  → no output file written
  $ k-skill-rhwp replace-all … --query İ --replacement Z --case-sensitive
  → {ok:true,count:2}, render shows 'ABCZABCZXYZ', search İ ⇒ found:false
  $ replace-all '2025'→'2026' on '2025 2025 2025' ⇒ {ok:true,count:3}
  $ replace-all 'hello'→'hi' (case-insens.) on 'hello WORLD 안녕 HELLO'
    ⇒ {ok:true,count:2}

Verification:
- npm test --workspace k-skill-rhwp: 35 pass / 0 fail (+4 vs Round 2)
- node --test scripts/skill-docs.test.js: 114 pass / 0 fail
- npm run ci: exit 0 (lint + typecheck + all workspace tests +
  pack:dry-run + validate-skills.sh all green)

Refs PR #162 Round 2 review 'Non-blocking residual risk — Unicode
case-insensitive offset drift'.
2026-04-22 15:23:23 +09:00
Jeffrey (Dongkyu) Kim
6dbdeb1912 rhwp-edit (#155): fix replace-all silent no-op and document body-only scope
Upstream @rhwp/core HwpDocument.replaceAll returns {ok:true, count:N} but
does not persist the mutation into exportHwp() serialization, so the output
bytes are byte-identical to the input. This is confirmed against
@rhwp/core@0.7.3 with SHA diffing and round-trip searchText.

Rewrite the Node wrapper replaceAll to compose engine primitives that do
persist: for each body paragraph, read the full text via getTextRange,
compute all non-overlapping match offsets in JS, then apply replaceText
right-to-left so earlier offsets are unaffected by length changes. This
restores the documented '2025 → 2026 일괄 치환' headline workflow.

Guard rails in the new replaceAll:
- Reject replacements containing newline or paragraph-break characters
  (\n, \r, U+2028, U+2029) with a descriptive error. Splitting a paragraph
  via replaceText would invalidate subsequent offsets.
- Non-overlapping semantics against the original text, so
  --query a --replacement aa against 'aaa' yields 'aaaaaa' (3 replacements)
  instead of looping on the freshly inserted 'a' characters.

Tighten the regression tests to assert content, not just length:
- Same-length replacement: output SHA must differ from input, searchText
  must find the replacement and must NOT find the original query.
- Longer-length replacement: paragraph length must grow by the correct
  amount and output SHA must differ.
- Shorter-length replacement: paragraph length must shrink by the correct
  amount and output SHA must differ.
- Empty replacement: deletes every match and output no longer contains
  the query.
- Replacement contains query (a→aa on aaa): expects count 3 and length 6.
- Zero matches: count 0, output still written.
- Case-sensitive flag skips mismatched case.
- Newline replacement is rejected synchronously.

Document the body-only scope of search and replace-all in the SKILL.md
routing policy, failure-modes, CLI USAGE text, feature doc, and package
README so users know to use set-cell-text for cell content. This matches
the upstream searchText contract, which does not descend into table cells,
headers, footers, or footnotes.

Add a matching regression assertion to scripts/skill-docs.test.js so the
body-only scope note cannot be silently removed from SKILL.md or the
feature doc.

Closes review round 1 for PR #162.
2026-04-22 14:54:16 +09:00
Jeffrey (Dongkyu) Kim
cc4a270043 Merge origin/dev into feature/#155: resolve package.json workspace list conflict
# Conflicts:
#	package.json
2026-04-22 14:51:06 +09:00
Jeffrey (Dongkyu) Kim
4647d56a9a
Merge pull request #161 from NomaDamas/feature/#133
Feature/#133
2026-04-22 14:25:24 +09:00
Jeffrey (Dongkyu) Kim
cc91e55682 korean-slang-writing (#133): harden extractor with numbered-h2 gate + category-nav strip
Implements the three non-blocking observations from PR #161 round-3 review:

1. Numbered-h2 gate (reviewer-flagged fragility):
   Refactored _extract_first_section_between_h2 to extract h2 inner text
   (stripping nested tags) and filter by '^\\s*\\d+(?:\\.\\d+)*\\.\\s+\\S'.
   Sidebar widgets like <h2>관련 문서</h2> or <h2>외부 링크</h2> can no longer
   anchor the extractor - only numbered section headers (1., 1.2., 2.3.4.) do.
   Handles live Namu Wiki structure where the number sits inside an <a> tag
   (<a>1.</a> <span>개요</span>), which the round-3 suggested regex-only gate
   missed. All 29 seed pages continue to produce valid summaries on live
   fetches.

2. Category-nav template strip (reviewer-flagged long-page noise):
   a. CATEGORY_NAV_RE strips the inline '[펼치기 · 접기]' marker plus its
      same-line aftermath (the category list items on the same line).
   b. DETAILS_PELCHIGI_RE strips the entire <details> block whose <summary>
      contains 펼치기. Namu Wiki today wraps category nav in exactly this
      structure, so the strip removes the full noise block (not just the
      marker line).
   꿀잼 summary drops from 3482 chars of category dump to 562 chars
   starting with the real definition '무언가가 매우 재미있다는 의미의 인터넷
   유행어'. Non-category <details> blocks (spoilers, footnotes) are
   preserved.

3. TDD + mutation coverage:
   6 new tests total: 2 numbered-h2 gate tests, 2 inline category-nav tests,
   1 <details>-block strip test, 1 <details>-keep test (negative case).
   All 6 were written first and confirmed RED against the round-2 baseline,
   then made GREEN after the implementation landed. Each fix path was also
   mutation-tested (revert regex, remove .sub line) to confirm the tests
   genuinely catch the target bug class.

Suite grows from 45 to 51 tests. All pass. npm run ci exits 0.
2026-04-22 14:18:42 +09:00
Jeffrey (Dongkyu) Kim
4f31dae11f korean-slang-writing (#133): extract summaries via h2 section anchor + og:description fallback
Namu Wiki's current HTML layout uses build-time-obfuscated CSS class
names (e.g. _36R8DWTn, OZVChh+l) and has no <article>/<main>/<section>
tags, so all six MAIN_CONTENT_CLASSES anchors fail to match and
extract_summary() returned empty with a 'Main content region not
detected' warning on every live page.

Replace the single class-based strategy with a three-tier fallback
chain that pins to progressively weaker but more structurally stable
anchors:

  1. First h2 section boundary. Namu Wiki articles consistently open
     with '<h2>1. 개요[편집]</h2>' and mark subsequent sections with
     numbered h2 headings. Extracting text between the first and
     second h2 reliably captures the overview section on every page
     sampled (중꺾마, 갓생, 럭키비키, 어쩔티비).
  2. MAIN_CONTENT_CLASSES / <article> - kept as a legacy fallback
     for older Namu Wiki layouts and for third-party fixtures.
  3. og:description meta tag - final safety net before returning
     empty, gives the agent at least a ~64-char preview when the
     article has unusual structure.

Strip '[편집]' edit-affordance markers and numbered section prefixes
(e.g. '1.2.') from the extracted text so headings don't leak through
as noise.

Live verification (text format):
  slang_lookup.py 중꺾마   -> Title + 286-char summary
  slang_lookup.py 갓생     -> Title + 96-char summary
  slang_lookup.py 럭키비키 -> Title + 59-char summary
  slang_lookup.py 어쩔티비 -> Title + 20-char summary

All previously-empty. Not-found / blocked / upstream-error paths and
exit codes are unchanged.
2026-04-22 13:44:13 +09:00
Jeffrey (Dongkyu) Kim
541967e96c korean-slang-writing (#133): fix broken seed namuwiki URLs + add encoding invariant test
Reviewer flagged 4/30 seed namuwiki_url values returning HTTP 404 on live
Namu Wiki. These URLs are part of the documented response contract and get
surfaced directly to agents, so broken links are a functional bug, not a
cosmetic one.

Root causes per entry:
- 중꺾마: wrong 꺾 codepoint (U+AFFA 꿺 instead of U+AEBE 꺾).
- 아아: typo in aliased title (아이스 아메리칸노 instead of 아메리카노).
- 어쩔티비: missing 받침 (어쩌티비 instead of 어쩔티비).
- 당모치: encoding correct but no live Namu Wiki article exists; dropped.

Also fixes two separately-broken 중꺾마 example URLs in SKILL.md
(U+AFBE 꾾 instead of U+AEBE 꺾) — these were discovered while auditing
the seed and would have surfaced as 404 to agents following the example
snippets.

Adds two regression tests:
- test_each_seed_url_decodes_to_term_or_alias: decodes every seed URL's
  path segment and asserts it equals the term or one of its aliases.
  Catches Hangul-codepoint typos offline (no network dependency) and
  would have caught all 3 encoding bugs in this PR.
- test_no_seed_entry_points_at_known_missing_namuwiki_page: locks the
  당모치 drop so nobody re-adds an entry pointing at a page that does
  not exist on Namu Wiki.

Fixes the existing LookupNetworkTest assertion that was hard-coding the
broken URL — it now derives the expected URL via build_namuwiki_url()
so the test cannot drift out of sync with the helper again.

Verification:
- PYTHONPATH=.:scripts python3 -m unittest scripts.test_korean_slang_writing -> 40/40 pass
- Live GET with browser headers against all 29 remaining seed URLs -> 29/29 return 200
- npm run ci -> exit 0
- Manual QA: slang_search on 중꺾마, 어쩔티비, 아이스 아메리카노 returns
  correct URLs; slang_lookup live-fetches 중꺾마 and extracts the
  canonical title '중요한 것은 꺾이지 않는 마음'.
2026-04-22 13:23:11 +09:00
Jeffrey (Dongkyu) Kim
71d577b24d Polish naver-news: preflight, link canonicalization, /health docs (#143)
Address the three non-blocking items flagged in the round 1/2 reviews. All
were explicitly deferred by the reviewer as "follow-up if the maintainer
wants" — picking them up now so the feature lands with a tighter surface.

1) Preflight 400 for start + display - 1 > 1000
   Naver's official news endpoint only exposes the first 1000 items
   (start 1..1000, display 1..100). Asking for start=1000 & display=100
   would send a request that silently returns no usable items, wasting
   an upstream quota call. Reject the combination before calling upstream
   with a 400 bad_request and a message that tells the caller which item
   the request would have needed and what the cap is. Boundary values
   (start + display - 1 === 1000) are still accepted.

2) Canonical link dedup
   The previous dedup key was link.toLowerCase(), which failed to merge
   the same article when Naver's redirect URLs differed only by query-param
   order, trailing slash, host-name casing, or fragment. Added
   canonicalizeLinkForDedup() which parses the URL, sorts search params by
   key, strips a single trailing pathname slash, drops the fragment, and
   lowercases the result — conservative on purpose so different paths or
   different query values stay as distinct articles. The visible
   items[].link value is still the original URL returned by Naver; only
   the dedup key is canonicalized.

3) Clarify the naverSearchApiConfigured vs naverNewsApiConfigured split
   The two flags currently evaluate the same boolean, but their semantic
   contracts differ: naverSearchApiConfigured reports "are the Naver
   Open API keys configured" (which is advisory for the shopping route
   since shopping has a BFF fallback), while naverNewsApiConfigured
   reports "is the news route operational end-to-end" (no fallback — 503
   when false). Hoist the shared expression into a local, and add a
   `/health 업스트림 플래그 의미` section to packages/k-skill-proxy/README.md
   documenting the split. Also update naver-news-search SKILL.md and
   docs/features/naver-news-search.md to mention the new preflight and
   the canonical-link dedup behavior.

TDD verification: added 4 new node:test cases exercising the boundary,
overflow, and URL-dedup paths; ran the full k-skill-proxy workspace
suite (202/202 pass) plus the root `npm run ci` (exit 0). Manual QA on
a proxy started from this commit reproduces every round-1 case plus the
new preflight: start=1000 & display=100 → 400 bad_request before
upstream; start=1000 & display=1 and start=901 & display=100 → 503 (or
200/401 depending on keys), confirming the boundary passes preflight.
2026-04-22 13:17:51 +09:00
Jeffrey (Dongkyu) Kim
e7e049993b Update skill-docs tests to cover rhwp-edit, rhwp-advanced, and the k-skill-rhwp package
Pins the HWP table row rename to 'HWP 문서 조회/변환', asserts the new
'HWP 문서 편집' and 'HWP 레이아웃·IR 디버깅' README rows and their linked
feature docs, pins the new SKILL.md routing policy for rhwp-edit and
rhwp-advanced (k-skill-rhwp CLI + @rhwp/core for editing vs upstream
Rust CLI for layout/IR debugging), and asserts the k-skill-rhwp
package.json wiring (bin mapping, @rhwp/core dependency, Node 18+
engines, wasm-init shim + CLI bin files).

Per AGENTS.md rule, no assertion is added on the presence of any
.changeset/*.md file so the changeset release flow can consume the
rhwp-edit-skill.md entry without breaking CI at version-bump time.

Also captures the package-lock.json delta introduced by adding the
k-skill-rhwp workspace (pulls @rhwp/core@0.7.3 and its WASM binary).

Refs #155.
2026-04-22 12:50:00 +09:00
Jeffrey (Dongkyu) Kim
ffcd8a5a13 Revert out-of-scope HWP README edits to unblock CI
The prior commit 4c7877a on this branch renamed the HWP feature row to
'HWP 문서 조회/변환' and added two new rows ('HWP 문서 편집',
'HWP 레이아웃·IR 디버깅') pointing at docs/features/rhwp-edit.md and
docs/features/rhwp-advanced.md. Those docs do not exist on any branch
in this repo, and the rename violates scripts/skill-docs.test.js
assertions at lines 210, 223, 224, which caused the CI 'validate' job
to fail.

Those changes belong to a separate rhwp-edit/rhwp-advanced feature
effort (tracked elsewhere), not to issue #143 'naver-news-search'.
Revert README.md in both the feature table and the list section so the
only additions in this PR relative to origin/dev are the two
in-scope naver-news-search entries.

Verified by running 'npm run ci' locally (EXIT=0). skill-docs.test.js
now passes 110/110 (previously failed 2/110) and the full
k-skill-proxy suite remains 198/198 including the 14 naver-news tests.
2026-04-22 12:48:27 +09:00
Jeffrey (Dongkyu) Kim
2608f3fb97 korean-slang-writing (#133): register skill in README and root lint/test pipeline 2026-04-22 12:48:21 +09:00
Jeffrey (Dongkyu) Kim
e21d70d904 korean-slang-writing (#133): add feature doc 2026-04-22 12:48:21 +09:00
Jeffrey (Dongkyu) Kim
2b4709b29e korean-slang-writing (#133): add SKILL.md 2026-04-22 12:48:21 +09:00
Jeffrey (Dongkyu) Kim
2785bc3c17 korean-slang-writing (#133): fix module-loader sys.modules registration 2026-04-22 12:48:21 +09:00
Jeffrey (Dongkyu) Kim
3080b535ec WIP korean-slang-writing (#133): add test suite 2026-04-22 12:48:21 +09:00
Jeffrey (Dongkyu) Kim
0ab3b450e6 WIP korean-slang-writing (#133): add seed index of 30 curated trending slang 2026-04-22 12:48:21 +09:00
Jeffrey (Dongkyu) Kim
3ac765d436 WIP korean-slang-writing (#133): add http + lookup scripts 2026-04-22 12:48:21 +09:00
Jeffrey (Dongkyu) Kim
2c63bb97bb WIP korean-slang-writing (#133): scaffold slang_search.py 2026-04-22 12:48:21 +09:00
hon2be
ca6d560227
feat: add k-dart skill for DART OpenAPI financial disclosures (#147)
*  feat: add k-dart skill for DART OpenAPI financial disclosures

금감원 전자공시시스템(DART) 14개 endpoint 조회 스킬 추가.
공시검색, 기업개황, 재무제표, 배당, 증자/감자, 전환사채, 소송 등.
API_K_DART 환경변수로 직접 호출하며 프록시 불필요.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* 📝 docs(k-dart): remove redundant korean-stock-search dependency

corpCode.xml 자체에 회사명·종목코드·고유번호가 모두 포함되어 있으므로
korean-stock-search 스킬 연계 절차 제거

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* 📝 docs: add k-dart to README feature table

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* 📝 docs: add k-dart feature guide and fix README link format

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* 🐛 fix(k-dart): correct status code 013, remove invalid corp_name filter, update daily limit

3개 critical 정확성 오류 수정:

1. 상태코드 013은 "조회된 데이터 없음"이며 "접근 권한 없음"이 아님 (012=접근 불가 IP).
   상태코드 표를 공식 명세 기준으로 재정리하고 누락된 014/021 코드 추가.
2. list.json은 corp_name 파라미터를 검색 필터로 지원하지 않음. SKILL.md의
   잘못된 진술과 corp_name을 사용한 misleading example을 제거하고, corp_code
   확보 절차를 거치도록 명시.
3. DART 일일 한도는 키당 10,000건이 아닌 20,000건이며 분당 약 1,000회
   throttle도 별도로 존재함. SKILL.md 및 docs/features/k-dart.md 모두 정정.

추가로 status: "013" 발생 시 사용자 안내 정책을 Response policy에 추가하고,
오픈API 이용현황 페이지 링크를 Notes에 추가함.

* 🐛 fix(k-dart): correct pifricDecsn endpoint, list.json corp_code optional, add empSttus, soften throttle claim

Codex adversarial review에서 식별된 4건의 추가 정확성 이슈 수정:

1. endpoint #8 유무상증자 결정이 잘못된 API에 연결됨. piicDecsn.json은
   유상증자 결정 (apiId=2020023)이며, 유무상증자 결정은 pifricDecsn.json
   (apiId=2020025)이 맞음. endpoint를 정정하고 piicDecsn (유상증자) 및
   fricDecsn (무상증자)와의 차이를 주의문으로 추가.

2. list.json의 corp_code 는 사실 선택사항이며, 미지정 시 검색 기간이
   3개월 이내로 제한될 뿐임. 이전 commit의 "corp_code 필수" 표현을
   정정하고, 두 가지 호출 패턴(corp_code 지정/미지정)을 Example
   requests에 모두 추가.

3. "분당 약 1,000회 throttle"은 공식 공개 가이드에 근거 없음
   (apiUsageStatusView.do 는 로그인 게이트). 공식 가이드가 명시한
   "일반적으로 20,000건 이상 요청 시 020 발생"만 유지하고 분당
   throttle 주장을 제거. 상태코드 표·Response policy도 일관되게 정리.

4. docs/features/k-dart.md가 "직원 현황" 기능을 광고하지만 SKILL.md
   에는 endpoint가 누락됨. empSttus.json (apiGrpCd=DS002,
   apiId=2019011)을 endpoint #8로 추가하고 example도 함께 등록.
   기존 endpoint 9~14는 10~15로 재번호.

* 🐛 fix(k-dart): align list.json signature and 020 caveat with official spec

Codex 2nd-round review에서 식별된 정확성 이슈 2건 수정:

1) list.json 요청 인자 signature가 공식 가이드(DS001/2019001)와 정확히
   일치하도록 재작성. crtfc_key 외 모든 파라미터가 선택사항임을 분명히
   하고, 각 파라미터의 default 동작과 pblntf_ty 값(A/B/C/D/E)도 명시.
   "corp_code 지정 시 기간 제한 없음" 표현은 공식 가이드가 보장하지
   않으므로 제거. corp_name이 공식 파라미터에 "존재하지 않는다"는
   사실로 수정 (이전: "지원하지 않는다").
   "corp_code 미지정 시 3개월 제한"은 외부 사용 사례에서 관찰된
   동작으로 약화 (공식 가이드에 별도 명시 없음).

2) 020 (요청 제한 초과) 안내가 일일 20,000건 cap 으로 너무 단정적
   해석되던 표현을 공식 메시지 그대로 보존: "일반적으로 20,000건
   이상 요청 시 발생하며, 키별로 별도 한도가 설정된 경우 다른
   임계치에서도 동일 코드가 반환될 수 있음". 상태코드 표·Response
   policy·Notes·docs/features/k-dart.md 모두 일관되게 정정.

* 🐛 fix(k-dart): mirror official Korean DS001/2019001 list.json spec exactly

Codex 3rd-round review에서 식별된 잔존 정확성 이슈 수정.

영어 가이드(DE001/AE00001)와 한국어 가이드(DS001/2019001)가 list.json
필수여부에서 다르게 표기되어 있어 이전 commit이 영어 가이드를 따랐으나,
한국어 공식 가이드를 직접 확인한 결과(opendart.fss.or.kr/guide/detail.do
?apiGrpCd=DS001&apiId=2019001) 다음이 한국어 공식 spec임을 확인:

- bgn_de, end_de는 Y(필수) (기본값은 명시되어 있으나 표기상 필수)
- corp_code 미지정 시 검색기간 3개월 제한은 공식 spec에 명시된 룰
  (외부 사용 사례 관찰이 아님)
- pblntf_ty는 A~J 전체 enum (정기공시/주요사항보고/발행공시/지분공시/
  기타공시/외부감사관련/펀드공시/자산유동화/거래소공시/공정위공시)
- page_count 기본값 10, 최대값 100
- corp_cls 복수 조건 불가
- last_reprt_at, sort, sort_mth 각 default 동작 명시

list.json 섹션을 공식 가이드 표와 1:1 일치하는 마크다운 표로 재작성.
3개월 제한 표현을 "외부 사례"에서 "공식 spec"으로 정정. Response policy
에 잔존하던 corp_name "지원하지 않는다" 표현도 "공식 파라미터에 존재하지
않는다"로 통일하여 #1 endpoint 섹션과 일관성 확보. docs/features/k-dart.md
도 동일하게 정정.

* 🐛 fix(k-dart): make list.json table 1:1 mirror of DS001/2019001 + unify corp_name wording

Codex 4th-round review가 식별한 잔존 이슈 2건 마무리.

1) list.json 파라미터 표를 공식 가이드 행 순서 그대로(crtfc_key,
   corp_code, bgn_de, end_de, last_reprt_at, pblntf_ty,
   pblntf_detail_ty, corp_cls, sort, sort_mth, page_no, page_count)
   재정리하고 공식 표의 모든 컬럼(요청키/명칭/타입/필수여부/값설명)을
   포함. page_no(1~n) / page_count(1~100, 기본10, 최대100) 범위
   값을 공식 표 그대로 표기. pblntf_detail_ty 값설명도 공식 표
   그대로 "(※ 상세 유형 참조: pblntf_detail_ty)"로 두고, 자주 쓰는
   코드 예시(A001/B001/F001/D001)는 표 아래 별도 단락으로 분리해
   표의 1:1 mirror 성격을 유지.

2) corp_name 관련 canonical 문장 "공식 요청 파라미터 표에
   corp_name 은 존재하지 않는다" 를 다음 3곳 모두 verbatim 일치
   시킴 (이전 commit에서 SKILL.md는 '않는다', docs/features는
   '않음' 으로 어미 차이가 잔존했음):
   - k-dart/SKILL.md #1 endpoint 섹션 주의문
   - k-dart/SKILL.md Response policy
   - docs/features/k-dart.md 에러/제약 섹션

* 🐛 fix(k-dart): unify corp_name canonical sentence verbatim + soften list.json table claim

Codex 5th-round review가 식별한 fine-grained 이슈 마무리.

1) corp_name canonical 문장을 self-contained 형태로 재작성하여
   3곳 모두 byte-for-byte 동일하게 통일:
   "DART OpenAPI list.json 의 공식 요청 파라미터 표에 corp_name 은
   존재하지 않는다."
   - SKILL.md #1 endpoint 섹션 주의문
   - SKILL.md Response policy
   - docs/features/k-dart.md 에러/제약 섹션
   이전에는 SKILL.md는 "위 공식 요청 파라미터 표에"로 docs/features는
   "list.json 공식 요청 파라미터 표에" 로 prefix가 달라 verbatim
   일치하지 않았음.

2) list.json 표 헤더 문구를 "공식 가이드 표를 그대로 옮긴 것"에서
   "공식 가이드 요청 인자 정리 (필수여부·기본값·허용값은 공식 표
   기준, 식별자는 코드 폰트로 표기)"로 약화. 마크다운 backtick 등
   포매팅 차이가 "1:1 mirror" 약속과 모순되지 않게 정확히 표현.

---------

Co-authored-by: hon2be <hon2be>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Jeffrey (Dongkyu) Kim <vkehfdl1@gmail.com>
2026-04-22 12:46:42 +09:00
Jeffrey (Dongkyu) Kim
dadc5f4ffa Add rhwp-edit and rhwp-advanced skills with k-skill-rhwp CLI
Splits HWP handling into three focused skills per issue #155:

- hwp (kept): kordoc-based read/convert (Markdown, JSON, diffing, form
  fields, Markdown->HWPX). Description narrowed to 'read-only' to make
  the routing policy explicit.
- rhwp-edit (new): HWP binary editing via new k-skill-rhwp npm package
  that wraps the @rhwp/core WASM bindings as CLI subcommands: info,
  list-paragraphs, search, insert-text, delete-text, replace-all,
  create-table, set-cell-text, create-blank, and render.
- rhwp-advanced (new): guidance for the upstream Rust rhwp CLI
  (export-svg --debug-overlay, dump, dump-pages, ir-diff, thumbnail,
  convert) for layout debugging, IR inspection, version comparison,
  and read-only-document unlocking.

The new k-skill-rhwp package under packages/ ships a Node.js 18+ CLI
and library that round-trips HWP 5.x documents entirely in-process; no
Rust toolchain is required. It auto-installs the WASM-required
globalThis.measureTextWidth shim for headless Node, and all editing
subcommands always write to a distinct output path so the source file
is never mutated. HWPX save remains disabled per the upstream rhwp
#196 data-safety gate; HWPX input is accepted but output is written as
HWP 5.x.

Includes 24 node:test cases covering init, round-trip insertText,
replaceAll, createTable + setCellText, deleteText, searchText,
listParagraphs, renderPage (SVG/HTML), and full CLI arg-parse +
end-to-end round-trip through the CLI layer.

Wires README feature table (3 rows for hwp / rhwp-edit / rhwp-advanced),
docs/install.md optional-install list, docs/roadmap.md (marks HWP
advanced editing as shipped while keeping Windows/security-module
automation out of scope), docs/sources.md (adds rhwp upstream, CLI
source, @rhwp/core, @rhwp/editor, and rhwp #196 references), and the
root pack:dry-run script. Adds a Changesets entry for k-skill-rhwp
minor.

Closes #155.
2026-04-22 12:45:13 +09:00
Jeffrey (Dongkyu) Kim
4c7877a5c9 Add naver-news-search skill and /v1/naver-news/search proxy route
Closes #143. Proxies the official Naver Search Open API news endpoint
(openapi.naver.com/v1/search/news.json) through k-skill-proxy so users do
not need to issue their own Naver Client ID/Secret. Reuses the existing
NAVER_SEARCH_CLIENT_ID/NAVER_SEARCH_CLIENT_SECRET that naver-shopping already
consumes, since the Naver Developer application enables the 'Search' scope
covering both news and shopping.

Implementation details:
- src/naver-news.js normalizes q/display/start/sort, builds the official URL,
  calls upstream with X-Naver-Client-Id/Secret headers, and parses the JSON
  response into rank/title/description/link/original_link/pub_date items.
- Strips <b> highlight tags and decodes HTML entities in title/description
  using zero-width replacement so compound Korean words like '주식형' are
  preserved (not split into '주식 형').
- Parses RFC822 pubDate into pub_date_iso (ISO-8601 UTC) for clients.
- Deduplicates items by normalized link; drops entries missing title/link.
- Returns 503 upstream_not_configured when proxy keys are absent (no public
  BFF fallback exists for news like it does for shopping, so keys are
  required).
- Failure responses are not cached (failure-aware cache layer).
- Exposes naverNewsApiConfigured on /health.

14 new tests in test/naver-news.test.js cover query validation, URL
building, payload normalization (HTML stripping, entity decoding,
deduplication, missing-field tolerance), plus Fastify integration tests
for 200/400/401/429/500/503 paths, cache hit/miss, header wiring, and
the health flag.
2026-04-22 12:30:02 +09:00
Jeffrey (Dongkyu) Kim
9d7da7bb8d
Merge pull request #158 from NomaDamas/feature/#145
Feature/#145
2026-04-22 12:12:09 +09:00
Jeffrey (Dongkyu) Kim
4614fb49b0 Document LH /detail test pins both cache-protection layers
Adds a 12-line header comment to the 'lh-notice detail does not cache
upstream XML auth errors so retries self-heal' test in server.test.js
naming the two cache-protection layers it pins:

  (a) the early-return catch block in the route handler (no cache.set
      on upstream failure), and
  (b) the isFailureResponse() guard inside cache.set (refuses any
      payload with .error set).

Points future maintainers to the independent sabotage audit in PR #158
Round 3 review that proved bypassing either layer alone makes the
State 2 self-heal assertion fail, and cross-links the sibling /search
failure-not-cached test for symmetric coverage.

Addresses the Round 3 non-blocking observation #2 nice-to-have.
Test-only, comment-only: +12 lines, 0 source changes, 0 behavior
changes, 0 doc changes, 0 changeset changes. server.test.js remains
96/96, lh-notice.test.js remains 38/38, full proxy workspace 184/184.
2026-04-22 12:07:51 +09:00
Jeffrey (Dongkyu) Kim
595e7170c3 Pin LH /v1/lh-notice/detail failure-not-cached contract with regression test
Round 2 review noted that /v1/lh-notice/detail failure-not-cached
behavior was only verified via manual QA, while /search had an
explicit automated regression test.

This adds an equivalent automated test for /detail that:
- fails upstream once (XML SERVICE_KEY error, upstream_code=30)
- confirms first call returns 502 with cache.hit=false
- switches upstream to success and retries the same URL
- confirms second call returns 200 with cache.hit=false (failure was
  NOT cached, retry hit upstream again)
- sabotages upstream back to failing and verifies the third call
  serves the previously-cached success (cache.hit=true, no new fetch)

Verified the test genuinely catches regressions by temporarily
monkey-patching the detail route to cache error payloads — the test
correctly fails in that sabotaged state and passes when the route is
correct. Full server.test.js suite goes from 95 to 96 tests, all pass.
2026-04-22 11:52:52 +09:00
Jeffrey (Dongkyu) Kim
876c578298 Document LH extractNoticeEnvelope success-code accept-list as deliberate
Per review note #4 on PR #158, extractNoticeEnvelope accepts four upstream
CMN.CODE values ("SUCCESS", "0", "00", "000") and three header.resultCode
values ("0", "00", "000") as success. This is deliberate: the data.go.kr
platform has surfaced different forms across catalog eras, and a future
normalization that flips SUCCESS to a numeric form must not regress into
502'ing otherwise-valid responses.

- Add an inline comment above the array-envelope success-code check in
  src/lh-notice.js explaining why the accept-list is NOT redundant.
- Add regression tests in test/lh-notice.test.js that explicitly exercise
  each accepted success code (SUCCESS/0/00/000 for array envelope; 0/00/000
  for object envelope) so a future refactor cannot silently collapse the
  accept-list.
- Add a paired rejection test that numeric-looking non-success codes like
  "22" and "10" still raise as upstream_error, disambiguating the
  accept-list from a blanket 'any numeric string passes' rule.

Test count: lh-notice.test.js 30 -> 38 (all pass); npm run ci exits 0.
2026-04-22 11:37:05 +09:00
github-actions[bot]
602e7f9545
chore: version packages (#157)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-04-22 11:22:00 +09:00
Jeffrey (Dongkyu) Kim
617a025931 Add lh-notice-search skill and /v1/lh-notice/{search,detail} proxy routes
Wraps the official data.go.kr LH (Korea Land & Housing Corporation) 청약
공고 Open API (B552555/lhLeaseNoticeInfo1/*) so agents can look up LH
임대/분양/주거복지/토지/상가 공고 by region, status, category, keyword,
and notice ID without asking users for a ServiceKey. Reuses the shared
DATA_GO_KR_API_KEY the proxy already manages; users see '불필요'.

Adapter handles both the LH-specific [CMN, dsList] JSON envelope and the
standard data.go.kr <OpenAPI_ServiceResponse> XML error envelope; refuses
to cache failure responses so transient upstream errors self-heal.

Closes #145.
2026-04-22 10:58:03 +09:00
Jeffrey (Dongkyu) Kim
e1656541a4
Fix parking lot lookups: force HTTPS, cache full dataset, normalize provider fields (#156)
Data.go.kr 이 tn_pubr_prkplce_info_api 를 HTTPS 로만 서비스하고 HTTP 요청은 301 로 리다이렉트하기 때문에 Node fetch 가 `response.ok=false` 로 떨어져 기능이 전체 실패하고 있었다. 이 커밋은 HTTPS 로 직접 호출하도록 수정하면서, 업스트림의 주소/지역 필터가 실제로는 동작하지 않고 페이지당 응답이 1000rows 기준 26s 에 달해 20s fetch timeout 에 꾸준히 걸리던 문제까지 함께 해결한다.

## What changed

- packages/k-skill-proxy/src/parking-lots.js
  - PARKING_LOT_API_URL 을 `http://` → `https://` 로 고정 (root cause).
  - 업스트림 address/geo 필터가 신뢰 불가하므로 full-dataset 을 한 번 로드해 프로세스 메모리에 6시간 TTL 로 캐시하고, 동시 호출자는 in-flight promise 를 공유하도록 한다. nearby 쿼리는 캐시된 행을 좌표 거리로 필터링해 서비스한다.
  - DATASET_PAGE_SIZE=300, fetch timeout 30s 로 페이지당 응답이 20s 를 넘기지 않도록 맞췄다.
- packages/k-skill-proxy/src/server.js
  - 더 이상 의미 없어진 numOfRows / maxPages 쿼리 파라미터를 라우트에서 제거하고, 응답 payload 의 query echo 도 정리했다.
- packages/k-skill-proxy/test/server.test.js
  - 새 캐시 기반 동작을 검증하는 테스트로 교체: (1) full dataset load + 좌표 필터 + 프록시 응답 캐시 재사용, (2) public_only 기본값 및 해제 시 동작, (3) 좌표 검증 실패 400, (4) 업스트림 키 미설정 시 503.
- packages/parking-lot-search/src/index.js
  - OFFICIAL_API_URL 도 HTTPS 로 맞춰 직접 호출 모드 사용자도 같은 버그를 밟지 않게 한다.
- packages/parking-lot-search/src/parse.js
  - 업스트림이 `insttCode` / `insttNm` (camelCase) 를 돌려주는데 parser 가 snake_case (`instt_code`, `instt_nm`) 만 인식해 providerCode/providerName 이 비어 있던 문제를 수정.
- packages/parking-lot-search/test/* 및 fixtures
  - HTTPS URL 매칭으로 업데이트하고, insttCode/insttNm 회귀 테스트를 fixture/assertion 에 추가.
- docs/features/parking-lot-search.md, parking-lot-search/SKILL.md, packages/parking-lot-search/README.md
  - 공식 endpoint 표기를 HTTPS 로 통일.
- .changeset/parking-lot-https-fix.md
  - parking-lot-search 패키지 patch 릴리즈 노트 추가.

## How it was verified

- `npm run ci` (lint + typecheck + tests + pack:dry-run) 통과.
- 로컬에서 실제 `DATA_GO_KR_API_KEY` 로 k-skill-proxy 를 기동해 live 호출 검증:
  - 광화문 (37.573713, 126.978338) cold cache: 30s 내 전체 18,868 rows 로드, 2km 내 47개 공영주차장 반환 (세종로 414m, 서린노외 456m 등).
  - 강남역 (37.497952, 127.027621) warm cache: 31ms 응답, 1.5km 내 13개 반환 (역삼문화공원 380m, 역삼푸른솔도서관 421m 등).
- 업스트림 직접 HTTPS 호출로 `resultCode=00 NORMAL_SERVICE` 정상 동작 확인.
2026-04-22 10:52:57 +09:00
github-actions[bot]
f94b049613
chore: version packages (#154)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-04-21 10:28:43 +09:00
Jeffrey (Dongkyu) Kim
c002561f34
Sync dev → main: MFDS proxy fixes, cache hardening, HWP kordoc, KRX degraded handling + new skills (#152)
* Add a guided Hola Poke Yeoksam skill without widening repo scope

Issue #120 only needs a repository skill payload, discoverability docs,
and regression coverage. This change adds the new skill, wires it into
existing docs surfaces, and locks the remote-MCP-only contract in tests
so future edits keep the phone-only event flow and verbatim message
relay behavior.

Constraint: The upstream Hola Poke flow lives on a remote MCP server, so this repo should not add proxy/runtime code
Constraint: Tests must be written before refining the new docs/skill wording
Rejected: Add local package or proxy support for Hola Poke | would over-scope a docs-only skill addition
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep this skill limited to 올라포케 역삼점 and treat the MCP response message as the event source of truth
Tested: node --test scripts/skill-docs.test.js --test-name-pattern='hola-poke-yeoksam'
Tested: npm run ci
Tested: Live MCP initialize/tools/list/get_menu/get_shop_info/enter_event(phone_format) smoke checks against https://hola-poke-yeoksam-skill.onrender.com/mcp
Not-tested: Successful live event entry with a real phone number

* Help users find nearby public restrooms from Korean location queries

This adds a new public-restroom-nearby skill and reusable package that resolves a user-provided location, narrows the official 공중화장실정보 dataset by region when possible, and ranks nearby restroom results with opening-time hints and map links.

Constraint: Must use free official/open surfaces without introducing new dependencies
Constraint: Must follow TDD and keep release/docs metadata aligned in the same change
Rejected: Add a proxy route first | direct official CSV access already works and keeps scope narrower
Rejected: Use nationwide-only ranking without regional narrowing | too much noisy data for dense urban anchors
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: If Kakao place-panel or localdata CSV schema changes, update parser fixtures before broad logic changes
Tested: npm run ci; live smoke via searchNearbyPublicRestroomsByLocationQuery('광화문', { limit: 3 }); architect review APPROVED
Not-tested: Non-Seoul live smoke across every regional orgCode

* Pin the Hola Poke MCP contract in repo-owned regression fixtures

The earlier issue #120 regression only matched prose, so this follow-up records the verified remote MCP tool/result snapshot in a checked-in fixture and makes both docs surfaces byte-align to it. That keeps the discoverability docs honest while turning the review claim into a real contract lock for tools/list, get_menu, get_shop_info, and the invalid-phone event flow.

Constraint: The upstream remote MCP server can change independently of this repo
Rejected: Keep prose-only regex checks | would not catch contract drift
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Refresh the fixture, both JSON fences, and the live-smoke evidence together whenever the upstream contract changes
Tested: node --test scripts/skill-docs.test.js --test-name-pattern='hola-poke-yeoksam'; npm run ci; live MCP smoke check against https://hola-poke-yeoksam-skill.onrender.com/mcp (initialize, tools/list, get_menu, get_shop_info, invalid enter_event)
Not-tested: Successful enter_event with a real phone number (intentionally avoided to prevent live event participation)

* Keep nearby restroom lookups resilient to flaky Kakao place panels

The review caught two regressions in the new public-restroom-nearby package: a single broken Kakao panel aborted anchor resolution, and coordinate search dropped maxDistanceMeters before normalization. This change adds targeted regression coverage first, keeps per-candidate HTTP failures recoverable, and hardens request errors with explicit status/url metadata so fallback logic no longer depends on parsing error strings.

Constraint: Must preserve the published package surface and keep the fix scoped to PR #123 follow-up
Rejected: Swallow all panel errors | would hide non-HTTP failures like network faults
Rejected: Parse request error messages for status codes | brittle coupling to string formatting
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep recoverable Kakao panel handling aligned with request() error annotations if request() changes again
Tested: npm test --workspace public-restroom-nearby
Tested: npm run ci
Tested: live smoke searchNearbyPublicRestroomsByLocationQuery('광화문', { limit: 3 })
Tested: LSP diagnostics on packages/public-restroom-nearby/src/index.js and test/index.test.js
Not-tested: Live Kakao fallback against a real upstream 5xx place-panel response

* Keep the Hola Poke contract claims aligned with verified coverage

The reviewed fixture-based regression already locks the documented remote
snapshot, but the docs still implied the enter_event success path had
live proof. Narrow the docs and the regression so they explicitly say the
success fields are pinned by the recorded snapshot while the live smoke
only verifies the invalid-phone retry path.

Constraint: Live success-path verification would trigger a real event entry and is intentionally avoided
Rejected: Leave the broader wording in place | review feedback showed it overstated the live evidence
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If a safe non-mutating success-path probe becomes available, update the docs and fixture wording together
Tested: node --test scripts/skill-docs.test.js --test-name-pattern='hola-poke-yeoksam'; npm run ci; live MCP smoke against https://hola-poke-yeoksam-skill.onrender.com/mcp (initialize, tools/list, get_menu subset, get_shop_info subset, invalid enter_event)
Not-tested: Real enter_event success-path invocation

* Document the restroom distance-cap contract with regression coverage

The approved issue-117 code fix already restored maxDistanceMeters behavior, but the published docs did not lock or explain that contract. This follow-up adds a failing-first doc regression, then updates the feature guide and package README with the verified 100m example so users and future reviewers see the same behavior the package now ships.

Constraint: Must stay scoped to the existing PR #123 follow-up without reopening the implementation surface
Rejected: Leave the behavior implicit in code/tests only | published docs would lag the verified contract
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the public-restroom-nearby docs and skill-docs regression aligned with live maxDistanceMeters smoke evidence if the sample query changes
Tested: node --test scripts/skill-docs.test.js (red then green)
Tested: npm test --workspace public-restroom-nearby
Tested: npm run ci
Tested: live smoke searchNearbyPublicRestroomsByLocationQuery('광화문', { limit: 3 })
Tested: live smoke searchNearbyPublicRestroomsByLocationQuery('광화문', { limit: 3, maxDistanceMeters: 100 })
Tested: architect review APPROVED
Not-tested: Alternative landmark queries with a non-zero maxDistanceMeters hit set

* Expose KRX partial failures instead of misreporting stock lookups

The Korean stock proxy used to silently drop failed market snapshots during
search and could turn an empty holiday trade snapshot into a 502 by falling
back into base-info lookup.

This change surfaces degraded market metadata on partial search success,
short-circuits empty trade snapshots to not_found, and refreshes the user
docs to use a real trading day in examples.

Constraint: KOSPI base-info approval is granted separately from other KRX routes
Constraint: Healthy markets should still return usable search results during a partial outage
Rejected: Return 502 on every partial search failure | hides still-usable markets and breaks current clients unnecessarily
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep degraded search metadata when any market snapshot fetch fails so partial outages stay visible
Tested: npm test --workspace k-skill-proxy
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Not-tested: Live KOSPI base-info behavior after the new KRX permission is approved

* Adopt kordoc for the hwp skill workflow

Issue #119 replaces the previous HWP guidance with kordoc so the skill matches the newer agent-native document flow. The docs and regression tests now center the HWP skill on kordoc parsing, JSON extraction, diffing, form filling, and Markdown-to-HWPX round-tripping, while the install/source references stay in sync.

Constraint: The repository treats skill behavior as documentation contracts backed by regression tests
Constraint: The requested branch/PR flow must target dev with TDD and verified execution evidence
Rejected: Keep @ohah/hwpjs or hwp-mcp as fallback guidance | issue #119 explicitly approves replacing the prior stack with kordoc
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep future hwp skill/docs/tests aligned to a single kordoc-first contract unless a new issue explicitly reintroduces multi-backend routing
Tested: node --test scripts/skill-docs.test.js; npm run ci; temp-dir kordoc roundtrip via markdownToHwpx -> sample.hwpx -> kordoc CLI markdown output; architect review APPROVED
Not-tested: Live parsing of user-provided proprietary HWP/HWPX samples outside the generated roundtrip fixture

* Prevent degraded stock search outages from sticking in cache

Reviewer feedback showed that partial KRX market failures could be cached as full search answers, masking recovery on the next identical request. This change adds a regression that fails first, skips route-level caching for degraded search payloads, and keeps the trade-info empty-snapshot contract documented alongside the partial-failure response semantics.

Constraint: Existing PR #124 already targets dev and must remain the follow-up lane for issue #99
Constraint: Proxy behavior must stay read-only and dependency-free
Rejected: Cache degraded search payloads for a short TTL | still risks transient false negatives during the TTL window
Rejected: Broaden trade-info fallback behavior | empty snapshots should stay explicit not_found results
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep degraded search responses out of the long-lived route cache unless a future design adds explicit revalidation semantics
Tested: npm test --workspace k-skill-proxy; node --test scripts/skill-docs.test.js; npm run ci; explicit buildServer degraded-search recovery repro
Not-tested: Live KRX production endpoints from this branch

* Align HWP docs with the published kordoc surface

The issue #119 follow-up needs the repository contract to match what the
currently published kordoc package actually supports. This narrows the
HWP skill/docs/tests to the verified install requirement and supported
CLI/Node API surfaces, and removes unsupported fill/mcp claims.

Constraint: Published kordoc CLI fails at startup without pdfjs-dist
Constraint: Docs/tests must reflect the current npm package behavior, not intended future features
Rejected: Keep fill/mcp examples with caveats | still documents unsupported entrypoints
Confidence: high
Scope-risk: narrow
Directive: Reintroduce fill/mcp docs only after verifying the published package exposes them in both CLI and Node API
Tested: node --test scripts/skill-docs.test.js; npm run ci; temp-dir clean install smoke; temp-dir kordoc+pdfjs-dist watch/parse/extractFormFields/compare/markdownToHwpx/roundtrip smoke; Claude architect review
Not-tested: Real-world HWPX template that produces non-empty extractFormFields output

* Keep HWP docs runnable against the published kordoc package

The follow-up closes the last runnable-contract gaps from review by documenting the working one-shot npx form and separating Node API examples into a local project install path. The regression suite now locks both install notes so future edits do not drift back to broken command shapes.

Constraint: Published kordoc CLI still requires pdfjs-dist at startup
Constraint: Global NODE_PATH does not make ESM imports from kordoc resolvable in the documented examples
Rejected: Keep bare `npx kordoc` examples | fails in a clean environment
Rejected: Keep global-install Node API guidance | ESM import remains unresolved
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep HWP docs aligned to verified published kordoc surfaces until the package contract changes upstream
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: temp-dir local npm install kordoc pdfjs-dist plus markdownToHwpx -> sample.hwpx -> one-shot kordoc roundtrip smoke
Not-tested: upstream unpublished kordoc features beyond the verified CLI and Node API surfaces

* Add Korean scholarship search skill and reporting workflow (#116)

* Add nationwide scholarship search skill workflow

* Rename scholarship skill to 장학금 주세요 쮜에발

* Fix scholarship skill validation in CI

* Trigger GitHub PR diff refresh after dev rebase on main

* Fix scholarship helper status handling and test coverage

* Use KST as scholarship helper default date basis

* Rename scholarship skill display name

---------

Co-authored-by: Jeffrey (Dongkyu) Kim <vkehfdl1@gmail.com>

* Feature/#121 (#127)

* Recover KakaoTalk mac skill auth when upstream user_id detection fails

Issue #121 reproduces on a real MacBook because `kakaocli auth` can fail even when the encrypted hex-named DB exists. This change adds a thin repo-owned helper that recovers the active user_id from plist revision hashes, caches the validated DB/key tuple, and reuses it for read-only `kakaocli` commands. The skill and feature docs now steer users to the helper when upstream auto-detection stops at candidate key mismatch, and regression tests lock the recovery flow before the implementation.

Constraint: Must stay a thin adapter around upstream kakaocli rather than forking the CLI
Constraint: Must verify on a real local macOS KakaoTalk install where issue #121 reproduces
Rejected: Full kakaocli reimplementation inside k-skill | too broad for the user_id/key-derivation failure scope
Rejected: Docs-only workaround | does not actually fix the broken auth path for users
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep this helper limited to auth/key recovery and read-only passthrough unless upstream gaps widen materially
Tested: python3 -m unittest scripts.test_kakaotalk_mac
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: python3 scripts/kakaotalk_mac.py auth --refresh --max-user-id 800000000 --workers 8 --chunk-size 2000000
Tested: python3 scripts/kakaotalk_mac.py chats --limit 1 --json
Not-tested: Other kakaocli subcommands beyond auth/chats/messages/search/query/schema

* Protect the KakaoTalk helper's safe recovery path

Address the PR follow-up by treating malformed auth cache files as cache misses,
removing write-capable passthrough from the wrapper surface, and redacting
human-readable auth output so the cached SQLCipher key is not echoed back into
terminal history. The docs and regression suite now describe and enforce the
read-only contract that the helper is meant to preserve.

Constraint: Helper must remain a read-only recovery wrapper around local kakaocli access
Rejected: Keep query support with SQL validation | still leaves a risky write-capable escape hatch
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Do not re-expose arbitrary SQL passthrough or print the SQLCipher key in default text output
Tested: python3 -m unittest scripts.test_kakaotalk_mac; node --test scripts/skill-docs.test.js; npm run ci; python3 scripts/kakaotalk_mac.py auth --refresh --max-user-id 800000000 --workers 8 --chunk-size 2000000; python3 scripts/kakaotalk_mac.py chats --limit 1 --json; python3 scripts/kakaotalk_mac.py auth --cache-path <bad-json>; python3 scripts/kakaotalk_mac.py query --help
Not-tested: External automation consumers that depend on shell/json auth output beyond the documented helper flows

* Lock the helper CLI surface against accidental regressions

The approved issue #121 fixes already hardened the KakaoTalk Mac helper, but the test suite still only exercised the passthrough validator directly. Add an explicit parser-level regression so the public CLI contract stays read-only and `query` cannot quietly reappear in future edits.

Constraint: Follow-up is on the existing feature/#121 PR branch and must stay minimal
Rejected: Re-open helper implementation changes | current code already satisfies the approved review findings
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep parser exposure tests aligned with READ_ONLY_COMMANDS whenever helper subcommands change
Tested: python3 -m unittest scripts.test_kakaotalk_mac; node --test scripts/skill-docs.test.js; npm run ci; python3 scripts/kakaotalk_mac.py auth --refresh --max-user-id 800000000 --workers 8 --chunk-size 2000000; python3 scripts/kakaotalk_mac.py chats --limit 1 --json; python3 scripts/kakaotalk_mac.py auth --cache-path <bad-json>
Not-tested: No new production code paths changed in this follow-up

* Honor explicit Kakao auth recovery overrides

The helper now treats manual auth overrides as a cache-bypassing recovery request and rejects invalid brute-force tuning flags at the CLI boundary so users get deterministic behavior instead of stale cached tuples or Python tracebacks. Regression coverage locks both paths before the PR follow-up lands.

Constraint: The helper must remain a thin read-only wrapper around kakaocli auth recovery
Rejected: Require --refresh whenever --user-id/--uuid is passed | worse UX than honoring overrides directly
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep explicit auth overrides ahead of cache reuse unless the CLI contract is redesigned and documented
Tested: python3 -m unittest scripts.test_kakaotalk_mac; node --test scripts/skill-docs.test.js; npm run ci; python3 scripts/kakaotalk_mac.py auth --refresh --max-user-id 800000000 --workers 8 --chunk-size 2000000; python3 scripts/kakaotalk_mac.py chats --limit 1 --json; python3 scripts/kakaotalk_mac.py auth --cache-path <bad-json>; python3 scripts/kakaotalk_mac.py auth --refresh --max-user-id -1; python3 scripts/kakaotalk_mac.py auth --refresh --workers 2 --chunk-size 0 --max-user-id 10; python3 scripts/kakaotalk_mac.py auth --cache-path <temp-cache> --user-id 999; python3 scripts/kakaotalk_mac.py auth --cache-path <temp-cache> --uuid <live-uuid>
Not-tested: Manual override success with a truly alternate valid user_id/uuid pair on a multi-account local install

* Feature/#129 (#131)

* Add official KBL results support so basketball queries use live league data

Issue #129 needs a read-only skill and reusable package for KBL schedules, results, and standings. The implementation follows the existing sports package pattern and uses the league's live JSON APIs after verifying they respond successfully in real requests.

Constraint: Must use official KBL JSON surfaces before considering scraping
Constraint: Packaging changes must pass npm run ci and include docs plus Changesets updates
Rejected: Browser scraping first | official api.kbl.or.kr endpoints are live and simpler to maintain
Rejected: Reuse KBO/K League package shapes verbatim | KBL payload and team/status fields differ materially
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep seasonGrade=1 as the default KBL path unless future docs/tests explicitly widen to D-League flows
Tested: npm run ci; npm run lint --workspace kbl-results; npm test --workspace kbl-results; live getKBLSummary("2026-04-01", { team: "KCC", includeStandings: true })
Not-tested: Historical standings snapshots for past seasons via alternative KBL endpoints

* Prevent optional standings lookups from over-fetching the KBL API

The new kbl-results summary helper exposes includeStandings=false, so the
regression suite now proves that path stays schedule-only and never calls
the standings endpoint when the caller opts out.

Constraint: The KBL package should preserve the caller's no-standings contract
Rejected: Rely on manual inspection of the helper options | a targeted test is cheaper and safer
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep includeStandings=false side-effect free unless the public API contract changes explicitly
Tested: npm test --workspace kbl-results; npm run lint --workspace kbl-results
Not-tested: Full-repo CI before stacking this commit onto the rebased branch

* Add Naver Shopping price comparison skill

* Use Naver Shopping BFF fallback

* Fix naver shopping BFF page and sort fallback

* Clarify Naver OpenAPI review sort fallback

* Add library book search skill

* Add Data4Library route regression coverage

* Fix Data4Library book-exists ISBN-10 handling

* Refactor Coupang skill to retention MCP layer

* Add Coupang MCP wrapper follow-up coverage

* Clarify Coupang wrapper init guidance

* Document Coupang MCP init examples

* Add parking lot search skill

* Add korean-privacy-terms skill regression tests

* Add korean-privacy-terms thin-wrapper skill

* Document korean-privacy-terms skill across repo docs

* Bundle Apache-2.0 LICENSE with korean-privacy-terms wrapper

Addresses PR #149 review SHOULD FIX: ship the Apache-2.0 LICENSE text
alongside the thin wrapper so Apache License 2.0 §4(a) ('give any other
recipients of the Work or Derivative Works a copy of this License') is
satisfied even before `install.sh` fetches the upstream payload.

- Copy upstream LICENSE verbatim to `korean-privacy-terms/LICENSE.upstream`
  (byte-for-byte identical to upstream at pinned SHA
  e390f7b9feb825e368c26726363ea5ce11a34083; SHA256
  35ef947614c2f14df01c5fc553f987f644f0c9f6b011adda397bd788a87f1510).
- Update SKILL.md Notes to link LICENSE.upstream, clarify that repo-root
  LICENSE (MIT) is k-skill's own license not this skill's, and document
  that nested upstream SKILL.md is not discovered by agent platforms.
- Document the home-path `bash ~/.claude/skills/.../install.sh` variant in
  SKILL.md so users who pulled the wrapper via `npx skills add --skill` can
  install without a repo checkout (installer already resolves
  ${BASH_SOURCE[0]} absolutely).
- Update docs/features/korean-privacy-terms.md to document LICENSE.upstream
  and the §4(a) rationale.
- Strengthen skill-docs regression tests (NICE TO HAVE items from review):
  * Reject placeholder pins (all-zero / all-f 40-char strings).
  * Assert the literal upstream clone URL
    (https://github.com/kimlawtech/korean-privacy-terms.git).
  * Assert `git clone --filter=blob:none` is used for blobless fetches.
  * Add new regression test that verifies LICENSE.upstream exists, matches
    the Apache-2.0 preamble / §4 / APPENDIX structure, and is referenced
    from both SKILL.md and the feature doc.

* Assert APPENDIX anchor in korean-privacy-terms LICENSE.upstream

Close Round 3 NICE TO HAVE from PR #149. The LICENSE.upstream
regression block asserted preamble, Version 2.0, Redistribution,
END OF TERMS, and Copyright 2026 kimlawtech but not the APPENDIX
anchor at LICENSE.upstream:179, even though the Round 1 follow-up
and Round 2 review collectively described 'APPENDIX structure
verification'. Adding this one assertion closes that claim/test
parity gap and acts as tamper-detection if upstream reformats
LICENSE later.

Verified with TDD: temporarily stripped APPENDIX line from
LICENSE.upstream, confirmed test 108 FAILS with the expected
regex mismatch, then restored and re-confirmed 109/109 GREEN.
Byte-for-byte identity with upstream LICENSE still holds
(SHA256 35ef947614c2f14df01c5fc553f987f644f0c9f6b011adda397bd788a87f1510).

npm run ci exit 0 with 357 ok subtests (unchanged baseline,
additive assertion within existing test block).

* Fix extractDataGoItems to handle current data.go.kr JSON shapes

The MFDS data.go.kr drug and food endpoints now return body.items as a
flat array (DrbEasyDrugInfoService, SafeStadDrugService) or an array of
{item: {...}} wrappers (PrsecImproptFoodInfoService03), instead of the
legacy {items: {item: [...]}} XML→JSON auto-convert shape.

Our extractDataGoItems was still looking for body.items.item, so it
returned [] for every entry, silently breaking:
  - /v1/mfds/drug-safety/lookup
  - /v1/mfds/food-safety/search (improperFood portion)

Update extractDataGoItems to accept all three shapes and refresh the
mock fixtures in server.test.js to match what upstream actually returns,
while adding a backward-compat test for the legacy shape.

Note: this does not resolve the remaining FOODSAFETYKOREA_API_KEY being
rejected by upstream (issue #148 core symptom) - that is a separate
operational key rotation on the proxy server.

* Make proxy cache failure-aware and require route-prefixed cache keys

Two related issues surfaced while investigating issue #148:

1. Transient upstream failures were being cached for the full 5-minute
   TTL because every route handler called cache.set() unconditionally
   with whatever payload came back - including empty items + warnings
   from a flaky upstream like openapi.foodsafetykorea.go.kr. The user
   would then see "empty + warning" for 5 minutes even after upstream
   recovered.

2. makeCacheKey(payload) hashes the whole payload, but fine-dust/report
   was the only route calling it without a "route" prefix
   (makeCacheKey(normalized) instead of
   makeCacheKey({ route: "fine-dust-report", ...normalized })).
   Different routes with the same normalized shape could collide.

Fix both globally in the cache layer so every current and future route
benefits without per-route edits:

- createMemoryCache.set rejects any payload that isFailureResponse
  considers a failure (explicit error field, upstream.degraded flag,
  or empty items alongside warnings). Returns false on reject, true
  on accept, so callers can observe the decision if needed.
- makeCacheKey now throws if payload.route is missing or empty. This
  catches the fine-dust inconsistency and prevents new routes from
  reintroducing it.
- fine-dust/report now passes `route: "fine-dust-report"` like every
  other route.

New tests:
- makeCacheKey asserts distinct routes produce distinct keys and throws
  without a route.
- isFailureResponse covers all failure signatures plus graceful-
  fallback cases (items present alongside warnings) that must stay
  cacheable.
- createMemoryCache.set refuses each failure shape and still stores
  healthy payloads.
- End-to-end: food-safety/search with a flaky recall upstream serves
  the upstream failure, retries live when upstream recovers, and only
  caches once the payload is healthy.

TTL itself is unchanged - the value still protects upstream rate
limits; it just no longer amplifies transient errors.

* Document Coupang hosted fallback contract and affiliate disclosure

retention-corp/coupang_partners#1 is merged, so upstream now transparently falls back to the Retention Corp hosted backend at https://a.retn.kr/v1/public/assist when Coupang Partners API credentials are missing. The k-skill wrapper already passes environment variables through unchanged, so this commit lines up the documented contract with the actual two-path behavior without changing runtime logic.

- SKILL.md and docs/features/coupang-product-search.md describe both execution paths (operator local HMAC vs credentialless hosted fallback), the honored OPENCLAW_SHOPPING_* env vars, the allowlist client-id convention including the k-skill-specific coupang-mcp-fallback value, and the mandatory affiliate disclosure when a.retn.kr/s/ shortlinks or lptag=AF deeplinks appear in responses.
- docs/sources.md adds the hosted assist endpoint and the merged upstream PR so the source surface stays truthful.
- README.md reflects the 선택사항 semantics for the 쿠팡 상품 검색 row and extends the column legend so 선택사항 is distinct from 불필요.
- coupang_partners_mcp.py expands its --help epilog so operators discover the honored upstream env vars without reading the wrapper source; no runtime behavior change.
- scripts/test_coupang_partners_mcp_wrapper.py locks env pass-through as a regression, asserts the new --help contract, and adds an opt-in K_SKILL_COUPANG_SMOKE=1 live smoke test that verifies the credentialless hosted path returns a Coupang deeplink.
- scripts/skill-docs.test.js extends the docs regression to require the hosted assist URL, OPENCLAW_SHOPPING_* env prefix, affiliate disclosure wording, and hosted fallback concept while keeping the yuju777 HF Space negative assertion.

Verified: npm run ci exits 0, live smoke test (K_SKILL_COUPANG_SMOKE=1) returns a.retn.kr/s/ shortlinks via credentialless wrapper, and manual env -u COUPANG_ACCESS_KEY -u COUPANG_SECRET_KEY call returns isRocket+lptag=AF3727577 responses through the hosted fallback.

Refs: #134

* Drop non-allowlisted coupang-mcp-fallback recommendation from hosted fallback docs

Direct probes against https://a.retn.kr/v1/public/assist confirmed that
X-OpenClaw-Client-Id: coupang-mcp-fallback returns HTTP 403 Client is not
allowlisted, while the upstream default openclaw-skill returns HTTP 200.
The default wrapper path already works because upstream falls back to
openclaw-skill, but the explicit recommendation in SKILL.md and the
feature doc was luring users to a 403 path.

Remove the dead recommendation and lock in the working configuration:

- Docs describe openclaw-skill as the upstream-allowlisted default and
  note that k-skill does not override OPENCLAW_SHOPPING_CLIENT_ID.
- Wrapper --help epilog drops the Suggested k-skill value line and
  documents openclaw-skill as the allowlist value in play.
- New skill-docs regression asserts coupang-mcp-fallback is absent from
  SKILL.md, the feature doc, the wrapper, and docs/sources.md while
  openclaw-skill is documented across all three narrative surfaces.
- New Python wrapper regression asserts --help drops the dead value and
  surfaces openclaw-skill so the constraint stays locked.
- Existing env-forwarding test uses openclaw-skill as the pass-through
  sentinel so the repo no longer ships the non-allowlisted string at all.

---------

Co-authored-by: minsing-jin <ironman0722@naver.com>
2026-04-21 09:53:03 +09:00
Jeffrey (Dongkyu) Kim
b8f928be0b
Merge pull request #153 from NomaDamas/feature/#134
Feature/#134
2026-04-21 01:12:08 +09:00
Jeffrey (Dongkyu) Kim
83263c564f Drop non-allowlisted coupang-mcp-fallback recommendation from hosted fallback docs
Direct probes against https://a.retn.kr/v1/public/assist confirmed that
X-OpenClaw-Client-Id: coupang-mcp-fallback returns HTTP 403 Client is not
allowlisted, while the upstream default openclaw-skill returns HTTP 200.
The default wrapper path already works because upstream falls back to
openclaw-skill, but the explicit recommendation in SKILL.md and the
feature doc was luring users to a 403 path.

Remove the dead recommendation and lock in the working configuration:

- Docs describe openclaw-skill as the upstream-allowlisted default and
  note that k-skill does not override OPENCLAW_SHOPPING_CLIENT_ID.
- Wrapper --help epilog drops the Suggested k-skill value line and
  documents openclaw-skill as the allowlist value in play.
- New skill-docs regression asserts coupang-mcp-fallback is absent from
  SKILL.md, the feature doc, the wrapper, and docs/sources.md while
  openclaw-skill is documented across all three narrative surfaces.
- New Python wrapper regression asserts --help drops the dead value and
  surfaces openclaw-skill so the constraint stays locked.
- Existing env-forwarding test uses openclaw-skill as the pass-through
  sentinel so the repo no longer ships the non-allowlisted string at all.
2026-04-21 00:38:50 +09:00
Jeffrey (Dongkyu) Kim
f2204f752e Document Coupang hosted fallback contract and affiliate disclosure
retention-corp/coupang_partners#1 is merged, so upstream now transparently falls back to the Retention Corp hosted backend at https://a.retn.kr/v1/public/assist when Coupang Partners API credentials are missing. The k-skill wrapper already passes environment variables through unchanged, so this commit lines up the documented contract with the actual two-path behavior without changing runtime logic.

- SKILL.md and docs/features/coupang-product-search.md describe both execution paths (operator local HMAC vs credentialless hosted fallback), the honored OPENCLAW_SHOPPING_* env vars, the allowlist client-id convention including the k-skill-specific coupang-mcp-fallback value, and the mandatory affiliate disclosure when a.retn.kr/s/ shortlinks or lptag=AF deeplinks appear in responses.
- docs/sources.md adds the hosted assist endpoint and the merged upstream PR so the source surface stays truthful.
- README.md reflects the 선택사항 semantics for the 쿠팡 상품 검색 row and extends the column legend so 선택사항 is distinct from 불필요.
- coupang_partners_mcp.py expands its --help epilog so operators discover the honored upstream env vars without reading the wrapper source; no runtime behavior change.
- scripts/test_coupang_partners_mcp_wrapper.py locks env pass-through as a regression, asserts the new --help contract, and adds an opt-in K_SKILL_COUPANG_SMOKE=1 live smoke test that verifies the credentialless hosted path returns a Coupang deeplink.
- scripts/skill-docs.test.js extends the docs regression to require the hosted assist URL, OPENCLAW_SHOPPING_* env prefix, affiliate disclosure wording, and hosted fallback concept while keeping the yuju777 HF Space negative assertion.

Verified: npm run ci exits 0, live smoke test (K_SKILL_COUPANG_SMOKE=1) returns a.retn.kr/s/ shortlinks via credentialless wrapper, and manual env -u COUPANG_ACCESS_KEY -u COUPANG_SECRET_KEY call returns isRocket+lptag=AF3727577 responses through the hosted fallback.

Refs: #134
2026-04-21 00:19:08 +09:00
Jeffrey (Dongkyu) Kim
f16e4c162c Merge main into dev to absorb #137 version bumps before dev → main sync
Main was 2 commits ahead of dev due to #137 (chore: version packages)
bumping kbl-results and public-restroom-nearby from 0.1.0 → 0.2.0 and
consuming their changesets. This caused PR #152 (dev → main sync) to
report CONFLICTING.

Resolution, per standard Changeset post-release sync:
- Take main's 0.2.0 in both package.json files (already published).
- Take main's 0.2.0 CHANGELOG entries.
- Delete .changeset/bright-penguins-tickle.md and
  .changeset/issue-129-kbl-results.md — both were consumed by #137 on
  main, so keeping them on dev would make the Changeset bot emit a
  duplicate 0.3.0 version-packages PR after the next sync.
- Regenerate package-lock.json via `npm install --ignore-scripts`.

All other conflicting paths (README.md, docs/roadmap.md, docs/sources.md,
scripts/skill-docs.test.js, root package.json scripts, and
packages/k-skill-proxy/src/server.js) contained dev-side additions
(parking-lot-search row, coupang_partners source swap, library-book-
search regression, coupang wrapper lint/test wiring, MFDS shape fix from
#150, and cache failure-aware guard from #151). All kept as-is on dev.

Tests: npm test --workspaces --if-present passes (9/9 root; 119/119
k-skill-proxy from the prior commits). No code changes in this merge.

This unblocks PR #152 so production (main-branch, cron-deployed proxy)
can pick up #150 + #151 and the other 44 commits on dev.
2026-04-20 22:57:12 +09:00
Jeffrey (Dongkyu) Kim
25fe6085be
Merge pull request #151 from NomaDamas/fix/proxy-cache-failure-aware-guard
Make proxy cache failure-aware and require route-prefixed cache keys
2026-04-20 22:16:57 +09:00
Jeffrey (Dongkyu) Kim
d06ff4eb37 Merge origin/dev into fix/proxy-cache-failure-aware-guard
Resolve import conflict in packages/k-skill-proxy/test/server.test.js by
combining both import sets (PR #151 added createMemoryCache, isFailureResponse,
makeCacheKey; dev added data4library normalizers from merged PRs #138/#140/#141/#149/#150).

All other server.js/test additions auto-merged cleanly; PR semantics preserved
(isFailureResponse gating, route-required makeCacheKey, fine-dust-report route prefix).
2026-04-20 22:03:03 +09:00
Jeffrey (Dongkyu) Kim
ffe6da29c8 Make proxy cache failure-aware and require route-prefixed cache keys
Two related issues surfaced while investigating issue #148:

1. Transient upstream failures were being cached for the full 5-minute
   TTL because every route handler called cache.set() unconditionally
   with whatever payload came back - including empty items + warnings
   from a flaky upstream like openapi.foodsafetykorea.go.kr. The user
   would then see "empty + warning" for 5 minutes even after upstream
   recovered.

2. makeCacheKey(payload) hashes the whole payload, but fine-dust/report
   was the only route calling it without a "route" prefix
   (makeCacheKey(normalized) instead of
   makeCacheKey({ route: "fine-dust-report", ...normalized })).
   Different routes with the same normalized shape could collide.

Fix both globally in the cache layer so every current and future route
benefits without per-route edits:

- createMemoryCache.set rejects any payload that isFailureResponse
  considers a failure (explicit error field, upstream.degraded flag,
  or empty items alongside warnings). Returns false on reject, true
  on accept, so callers can observe the decision if needed.
- makeCacheKey now throws if payload.route is missing or empty. This
  catches the fine-dust inconsistency and prevents new routes from
  reintroducing it.
- fine-dust/report now passes `route: "fine-dust-report"` like every
  other route.

New tests:
- makeCacheKey asserts distinct routes produce distinct keys and throws
  without a route.
- isFailureResponse covers all failure signatures plus graceful-
  fallback cases (items present alongside warnings) that must stay
  cacheable.
- createMemoryCache.set refuses each failure shape and still stores
  healthy payloads.
- End-to-end: food-safety/search with a flaky recall upstream serves
  the upstream failure, retries live when upstream recovers, and only
  caches once the payload is healthy.

TTL itself is unchanged - the value still protects upstream rate
limits; it just no longer amplifies transient errors.
2026-04-20 21:46:56 +09:00
Jeffrey (Dongkyu) Kim
4030815d74
Merge pull request #150 from NomaDamas/fix/issue-148-data-go-kr-items-shape
Fix extractDataGoItems to handle current data.go.kr JSON shapes
2026-04-20 16:35:05 +09:00
Jeffrey (Dongkyu) Kim
4309ad8495 Fix extractDataGoItems to handle current data.go.kr JSON shapes
The MFDS data.go.kr drug and food endpoints now return body.items as a
flat array (DrbEasyDrugInfoService, SafeStadDrugService) or an array of
{item: {...}} wrappers (PrsecImproptFoodInfoService03), instead of the
legacy {items: {item: [...]}} XML→JSON auto-convert shape.

Our extractDataGoItems was still looking for body.items.item, so it
returned [] for every entry, silently breaking:
  - /v1/mfds/drug-safety/lookup
  - /v1/mfds/food-safety/search (improperFood portion)

Update extractDataGoItems to accept all three shapes and refresh the
mock fixtures in server.test.js to match what upstream actually returns,
while adding a backward-compat test for the legacy shape.

Note: this does not resolve the remaining FOODSAFETYKOREA_API_KEY being
rejected by upstream (issue #148 core symptom) - that is a separate
operational key rotation on the proxy server.
2026-04-20 16:16:34 +09:00
Jeffrey (Dongkyu) Kim
09e0a82568
Merge pull request #149 from NomaDamas/feature/#144
Feature/#144
2026-04-20 12:59:30 +09:00
Jeffrey (Dongkyu) Kim
d52a580598 Assert APPENDIX anchor in korean-privacy-terms LICENSE.upstream
Close Round 3 NICE TO HAVE from PR #149. The LICENSE.upstream
regression block asserted preamble, Version 2.0, Redistribution,
END OF TERMS, and Copyright 2026 kimlawtech but not the APPENDIX
anchor at LICENSE.upstream:179, even though the Round 1 follow-up
and Round 2 review collectively described 'APPENDIX structure
verification'. Adding this one assertion closes that claim/test
parity gap and acts as tamper-detection if upstream reformats
LICENSE later.

Verified with TDD: temporarily stripped APPENDIX line from
LICENSE.upstream, confirmed test 108 FAILS with the expected
regex mismatch, then restored and re-confirmed 109/109 GREEN.
Byte-for-byte identity with upstream LICENSE still holds
(SHA256 35ef947614c2f14df01c5fc553f987f644f0c9f6b011adda397bd788a87f1510).

npm run ci exit 0 with 357 ok subtests (unchanged baseline,
additive assertion within existing test block).
2026-04-20 12:54:33 +09:00
Jeffrey (Dongkyu) Kim
bf07c25a43 Bundle Apache-2.0 LICENSE with korean-privacy-terms wrapper
Addresses PR #149 review SHOULD FIX: ship the Apache-2.0 LICENSE text
alongside the thin wrapper so Apache License 2.0 §4(a) ('give any other
recipients of the Work or Derivative Works a copy of this License') is
satisfied even before `install.sh` fetches the upstream payload.

- Copy upstream LICENSE verbatim to `korean-privacy-terms/LICENSE.upstream`
  (byte-for-byte identical to upstream at pinned SHA
  e390f7b9feb825e368c26726363ea5ce11a34083; SHA256
  35ef947614c2f14df01c5fc553f987f644f0c9f6b011adda397bd788a87f1510).
- Update SKILL.md Notes to link LICENSE.upstream, clarify that repo-root
  LICENSE (MIT) is k-skill's own license not this skill's, and document
  that nested upstream SKILL.md is not discovered by agent platforms.
- Document the home-path `bash ~/.claude/skills/.../install.sh` variant in
  SKILL.md so users who pulled the wrapper via `npx skills add --skill` can
  install without a repo checkout (installer already resolves
  ${BASH_SOURCE[0]} absolutely).
- Update docs/features/korean-privacy-terms.md to document LICENSE.upstream
  and the §4(a) rationale.
- Strengthen skill-docs regression tests (NICE TO HAVE items from review):
  * Reject placeholder pins (all-zero / all-f 40-char strings).
  * Assert the literal upstream clone URL
    (https://github.com/kimlawtech/korean-privacy-terms.git).
  * Assert `git clone --filter=blob:none` is used for blobless fetches.
  * Add new regression test that verifies LICENSE.upstream exists, matches
    the Apache-2.0 preamble / §4 / APPENDIX structure, and is referenced
    from both SKILL.md and the feature doc.
2026-04-20 12:25:05 +09:00
Jeffrey (Dongkyu) Kim
d0c014e38b Document korean-privacy-terms skill across repo docs 2026-04-20 12:06:57 +09:00
Jeffrey (Dongkyu) Kim
f8cc836add Add korean-privacy-terms thin-wrapper skill 2026-04-20 12:06:57 +09:00
Jeffrey (Dongkyu) Kim
f9c89a54a0 Add korean-privacy-terms skill regression tests 2026-04-20 12:06:57 +09:00
Jeffrey (Dongkyu) Kim
f9a32f661f
Merge pull request #141 from NomaDamas/feature/#135
Feature/#135
2026-04-18 23:31:44 +09:00
Jeffrey (Dongkyu) Kim
f1b1f1e3b9 Add parking lot search skill 2026-04-18 23:03:11 +09:00
Jeffrey (Dongkyu) Kim
7d4bc86f63
Merge pull request #140 from NomaDamas/feature/#134
Feature/#134
2026-04-18 22:48:30 +09:00
Jeffrey (Dongkyu) Kim
423555a9ac Document Coupang MCP init examples 2026-04-18 22:44:55 +09:00
Jeffrey (Dongkyu) Kim
3cb4b9b7f1 Clarify Coupang wrapper init guidance 2026-04-18 22:35:57 +09:00
Jeffrey (Dongkyu) Kim
4d2d4a99bf Add Coupang MCP wrapper follow-up coverage 2026-04-18 22:27:33 +09:00
Jeffrey (Dongkyu) Kim
303374aaa6 Refactor Coupang skill to retention MCP layer 2026-04-18 22:18:51 +09:00
Jeffrey (Dongkyu) Kim
c252fdc20d
Merge pull request #139 from NomaDamas/feature/#132
Feature/#132
2026-04-18 22:11:28 +09:00
Jeffrey (Dongkyu) Kim
a9c96cad82 Fix Data4Library book-exists ISBN-10 handling 2026-04-18 21:59:36 +09:00
Jeffrey (Dongkyu) Kim
4cd3a3cdec Add Data4Library route regression coverage 2026-04-18 21:37:13 +09:00
Jeffrey (Dongkyu) Kim
a1dccc6f41 Add library book search skill 2026-04-18 21:24:04 +09:00
Jeffrey (Dongkyu) Kim
4ecaa624fd
Merge pull request #138 from NomaDamas/feature/#118
Feature/#118
2026-04-18 21:13:24 +09:00
Jeffrey (Dongkyu) Kim
a01a816416 Clarify Naver OpenAPI review sort fallback 2026-04-18 21:09:38 +09:00
Jeffrey (Dongkyu) Kim
127a6276b4 Fix naver shopping BFF page and sort fallback 2026-04-18 21:00:39 +09:00
Jeffrey (Dongkyu) Kim
56797dde18 Use Naver Shopping BFF fallback 2026-04-18 20:46:34 +09:00
Jeffrey (Dongkyu) Kim
c8c7263dac Add Naver Shopping price comparison skill 2026-04-18 20:34:34 +09:00
github-actions[bot]
f6b76e7d40
chore: version packages (#137)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-04-18 11:58:59 +09:00
Jeffrey (Dongkyu) Kim
68e6829052
Sync dev → main: scholarship, public restroom, KBL, Hola Poke + HWP/stock proxy upgrades (#136)
* Add a guided Hola Poke Yeoksam skill without widening repo scope

Issue #120 only needs a repository skill payload, discoverability docs,
and regression coverage. This change adds the new skill, wires it into
existing docs surfaces, and locks the remote-MCP-only contract in tests
so future edits keep the phone-only event flow and verbatim message
relay behavior.

Constraint: The upstream Hola Poke flow lives on a remote MCP server, so this repo should not add proxy/runtime code
Constraint: Tests must be written before refining the new docs/skill wording
Rejected: Add local package or proxy support for Hola Poke | would over-scope a docs-only skill addition
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep this skill limited to 올라포케 역삼점 and treat the MCP response message as the event source of truth
Tested: node --test scripts/skill-docs.test.js --test-name-pattern='hola-poke-yeoksam'
Tested: npm run ci
Tested: Live MCP initialize/tools/list/get_menu/get_shop_info/enter_event(phone_format) smoke checks against https://hola-poke-yeoksam-skill.onrender.com/mcp
Not-tested: Successful live event entry with a real phone number

* Help users find nearby public restrooms from Korean location queries

This adds a new public-restroom-nearby skill and reusable package that resolves a user-provided location, narrows the official 공중화장실정보 dataset by region when possible, and ranks nearby restroom results with opening-time hints and map links.

Constraint: Must use free official/open surfaces without introducing new dependencies
Constraint: Must follow TDD and keep release/docs metadata aligned in the same change
Rejected: Add a proxy route first | direct official CSV access already works and keeps scope narrower
Rejected: Use nationwide-only ranking without regional narrowing | too much noisy data for dense urban anchors
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: If Kakao place-panel or localdata CSV schema changes, update parser fixtures before broad logic changes
Tested: npm run ci; live smoke via searchNearbyPublicRestroomsByLocationQuery('광화문', { limit: 3 }); architect review APPROVED
Not-tested: Non-Seoul live smoke across every regional orgCode

* Pin the Hola Poke MCP contract in repo-owned regression fixtures

The earlier issue #120 regression only matched prose, so this follow-up records the verified remote MCP tool/result snapshot in a checked-in fixture and makes both docs surfaces byte-align to it. That keeps the discoverability docs honest while turning the review claim into a real contract lock for tools/list, get_menu, get_shop_info, and the invalid-phone event flow.

Constraint: The upstream remote MCP server can change independently of this repo
Rejected: Keep prose-only regex checks | would not catch contract drift
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Refresh the fixture, both JSON fences, and the live-smoke evidence together whenever the upstream contract changes
Tested: node --test scripts/skill-docs.test.js --test-name-pattern='hola-poke-yeoksam'; npm run ci; live MCP smoke check against https://hola-poke-yeoksam-skill.onrender.com/mcp (initialize, tools/list, get_menu, get_shop_info, invalid enter_event)
Not-tested: Successful enter_event with a real phone number (intentionally avoided to prevent live event participation)

* Keep nearby restroom lookups resilient to flaky Kakao place panels

The review caught two regressions in the new public-restroom-nearby package: a single broken Kakao panel aborted anchor resolution, and coordinate search dropped maxDistanceMeters before normalization. This change adds targeted regression coverage first, keeps per-candidate HTTP failures recoverable, and hardens request errors with explicit status/url metadata so fallback logic no longer depends on parsing error strings.

Constraint: Must preserve the published package surface and keep the fix scoped to PR #123 follow-up
Rejected: Swallow all panel errors | would hide non-HTTP failures like network faults
Rejected: Parse request error messages for status codes | brittle coupling to string formatting
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep recoverable Kakao panel handling aligned with request() error annotations if request() changes again
Tested: npm test --workspace public-restroom-nearby
Tested: npm run ci
Tested: live smoke searchNearbyPublicRestroomsByLocationQuery('광화문', { limit: 3 })
Tested: LSP diagnostics on packages/public-restroom-nearby/src/index.js and test/index.test.js
Not-tested: Live Kakao fallback against a real upstream 5xx place-panel response

* Keep the Hola Poke contract claims aligned with verified coverage

The reviewed fixture-based regression already locks the documented remote
snapshot, but the docs still implied the enter_event success path had
live proof. Narrow the docs and the regression so they explicitly say the
success fields are pinned by the recorded snapshot while the live smoke
only verifies the invalid-phone retry path.

Constraint: Live success-path verification would trigger a real event entry and is intentionally avoided
Rejected: Leave the broader wording in place | review feedback showed it overstated the live evidence
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If a safe non-mutating success-path probe becomes available, update the docs and fixture wording together
Tested: node --test scripts/skill-docs.test.js --test-name-pattern='hola-poke-yeoksam'; npm run ci; live MCP smoke against https://hola-poke-yeoksam-skill.onrender.com/mcp (initialize, tools/list, get_menu subset, get_shop_info subset, invalid enter_event)
Not-tested: Real enter_event success-path invocation

* Document the restroom distance-cap contract with regression coverage

The approved issue-117 code fix already restored maxDistanceMeters behavior, but the published docs did not lock or explain that contract. This follow-up adds a failing-first doc regression, then updates the feature guide and package README with the verified 100m example so users and future reviewers see the same behavior the package now ships.

Constraint: Must stay scoped to the existing PR #123 follow-up without reopening the implementation surface
Rejected: Leave the behavior implicit in code/tests only | published docs would lag the verified contract
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the public-restroom-nearby docs and skill-docs regression aligned with live maxDistanceMeters smoke evidence if the sample query changes
Tested: node --test scripts/skill-docs.test.js (red then green)
Tested: npm test --workspace public-restroom-nearby
Tested: npm run ci
Tested: live smoke searchNearbyPublicRestroomsByLocationQuery('광화문', { limit: 3 })
Tested: live smoke searchNearbyPublicRestroomsByLocationQuery('광화문', { limit: 3, maxDistanceMeters: 100 })
Tested: architect review APPROVED
Not-tested: Alternative landmark queries with a non-zero maxDistanceMeters hit set

* Expose KRX partial failures instead of misreporting stock lookups

The Korean stock proxy used to silently drop failed market snapshots during
search and could turn an empty holiday trade snapshot into a 502 by falling
back into base-info lookup.

This change surfaces degraded market metadata on partial search success,
short-circuits empty trade snapshots to not_found, and refreshes the user
docs to use a real trading day in examples.

Constraint: KOSPI base-info approval is granted separately from other KRX routes
Constraint: Healthy markets should still return usable search results during a partial outage
Rejected: Return 502 on every partial search failure | hides still-usable markets and breaks current clients unnecessarily
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep degraded search metadata when any market snapshot fetch fails so partial outages stay visible
Tested: npm test --workspace k-skill-proxy
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Not-tested: Live KOSPI base-info behavior after the new KRX permission is approved

* Adopt kordoc for the hwp skill workflow

Issue #119 replaces the previous HWP guidance with kordoc so the skill matches the newer agent-native document flow. The docs and regression tests now center the HWP skill on kordoc parsing, JSON extraction, diffing, form filling, and Markdown-to-HWPX round-tripping, while the install/source references stay in sync.

Constraint: The repository treats skill behavior as documentation contracts backed by regression tests
Constraint: The requested branch/PR flow must target dev with TDD and verified execution evidence
Rejected: Keep @ohah/hwpjs or hwp-mcp as fallback guidance | issue #119 explicitly approves replacing the prior stack with kordoc
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep future hwp skill/docs/tests aligned to a single kordoc-first contract unless a new issue explicitly reintroduces multi-backend routing
Tested: node --test scripts/skill-docs.test.js; npm run ci; temp-dir kordoc roundtrip via markdownToHwpx -> sample.hwpx -> kordoc CLI markdown output; architect review APPROVED
Not-tested: Live parsing of user-provided proprietary HWP/HWPX samples outside the generated roundtrip fixture

* Prevent degraded stock search outages from sticking in cache

Reviewer feedback showed that partial KRX market failures could be cached as full search answers, masking recovery on the next identical request. This change adds a regression that fails first, skips route-level caching for degraded search payloads, and keeps the trade-info empty-snapshot contract documented alongside the partial-failure response semantics.

Constraint: Existing PR #124 already targets dev and must remain the follow-up lane for issue #99
Constraint: Proxy behavior must stay read-only and dependency-free
Rejected: Cache degraded search payloads for a short TTL | still risks transient false negatives during the TTL window
Rejected: Broaden trade-info fallback behavior | empty snapshots should stay explicit not_found results
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep degraded search responses out of the long-lived route cache unless a future design adds explicit revalidation semantics
Tested: npm test --workspace k-skill-proxy; node --test scripts/skill-docs.test.js; npm run ci; explicit buildServer degraded-search recovery repro
Not-tested: Live KRX production endpoints from this branch

* Align HWP docs with the published kordoc surface

The issue #119 follow-up needs the repository contract to match what the
currently published kordoc package actually supports. This narrows the
HWP skill/docs/tests to the verified install requirement and supported
CLI/Node API surfaces, and removes unsupported fill/mcp claims.

Constraint: Published kordoc CLI fails at startup without pdfjs-dist
Constraint: Docs/tests must reflect the current npm package behavior, not intended future features
Rejected: Keep fill/mcp examples with caveats | still documents unsupported entrypoints
Confidence: high
Scope-risk: narrow
Directive: Reintroduce fill/mcp docs only after verifying the published package exposes them in both CLI and Node API
Tested: node --test scripts/skill-docs.test.js; npm run ci; temp-dir clean install smoke; temp-dir kordoc+pdfjs-dist watch/parse/extractFormFields/compare/markdownToHwpx/roundtrip smoke; Claude architect review
Not-tested: Real-world HWPX template that produces non-empty extractFormFields output

* Keep HWP docs runnable against the published kordoc package

The follow-up closes the last runnable-contract gaps from review by documenting the working one-shot npx form and separating Node API examples into a local project install path. The regression suite now locks both install notes so future edits do not drift back to broken command shapes.

Constraint: Published kordoc CLI still requires pdfjs-dist at startup
Constraint: Global NODE_PATH does not make ESM imports from kordoc resolvable in the documented examples
Rejected: Keep bare `npx kordoc` examples | fails in a clean environment
Rejected: Keep global-install Node API guidance | ESM import remains unresolved
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep HWP docs aligned to verified published kordoc surfaces until the package contract changes upstream
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: temp-dir local npm install kordoc pdfjs-dist plus markdownToHwpx -> sample.hwpx -> one-shot kordoc roundtrip smoke
Not-tested: upstream unpublished kordoc features beyond the verified CLI and Node API surfaces

* Add Korean scholarship search skill and reporting workflow (#116)

* Add nationwide scholarship search skill workflow

* Rename scholarship skill to 장학금 주세요 쮜에발

* Fix scholarship skill validation in CI

* Trigger GitHub PR diff refresh after dev rebase on main

* Fix scholarship helper status handling and test coverage

* Use KST as scholarship helper default date basis

* Rename scholarship skill display name

---------

Co-authored-by: Jeffrey (Dongkyu) Kim <vkehfdl1@gmail.com>

* Feature/#121 (#127)

* Recover KakaoTalk mac skill auth when upstream user_id detection fails

Issue #121 reproduces on a real MacBook because `kakaocli auth` can fail even when the encrypted hex-named DB exists. This change adds a thin repo-owned helper that recovers the active user_id from plist revision hashes, caches the validated DB/key tuple, and reuses it for read-only `kakaocli` commands. The skill and feature docs now steer users to the helper when upstream auto-detection stops at candidate key mismatch, and regression tests lock the recovery flow before the implementation.

Constraint: Must stay a thin adapter around upstream kakaocli rather than forking the CLI
Constraint: Must verify on a real local macOS KakaoTalk install where issue #121 reproduces
Rejected: Full kakaocli reimplementation inside k-skill | too broad for the user_id/key-derivation failure scope
Rejected: Docs-only workaround | does not actually fix the broken auth path for users
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep this helper limited to auth/key recovery and read-only passthrough unless upstream gaps widen materially
Tested: python3 -m unittest scripts.test_kakaotalk_mac
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: python3 scripts/kakaotalk_mac.py auth --refresh --max-user-id 800000000 --workers 8 --chunk-size 2000000
Tested: python3 scripts/kakaotalk_mac.py chats --limit 1 --json
Not-tested: Other kakaocli subcommands beyond auth/chats/messages/search/query/schema

* Protect the KakaoTalk helper's safe recovery path

Address the PR follow-up by treating malformed auth cache files as cache misses,
removing write-capable passthrough from the wrapper surface, and redacting
human-readable auth output so the cached SQLCipher key is not echoed back into
terminal history. The docs and regression suite now describe and enforce the
read-only contract that the helper is meant to preserve.

Constraint: Helper must remain a read-only recovery wrapper around local kakaocli access
Rejected: Keep query support with SQL validation | still leaves a risky write-capable escape hatch
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Do not re-expose arbitrary SQL passthrough or print the SQLCipher key in default text output
Tested: python3 -m unittest scripts.test_kakaotalk_mac; node --test scripts/skill-docs.test.js; npm run ci; python3 scripts/kakaotalk_mac.py auth --refresh --max-user-id 800000000 --workers 8 --chunk-size 2000000; python3 scripts/kakaotalk_mac.py chats --limit 1 --json; python3 scripts/kakaotalk_mac.py auth --cache-path <bad-json>; python3 scripts/kakaotalk_mac.py query --help
Not-tested: External automation consumers that depend on shell/json auth output beyond the documented helper flows

* Lock the helper CLI surface against accidental regressions

The approved issue #121 fixes already hardened the KakaoTalk Mac helper, but the test suite still only exercised the passthrough validator directly. Add an explicit parser-level regression so the public CLI contract stays read-only and `query` cannot quietly reappear in future edits.

Constraint: Follow-up is on the existing feature/#121 PR branch and must stay minimal
Rejected: Re-open helper implementation changes | current code already satisfies the approved review findings
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep parser exposure tests aligned with READ_ONLY_COMMANDS whenever helper subcommands change
Tested: python3 -m unittest scripts.test_kakaotalk_mac; node --test scripts/skill-docs.test.js; npm run ci; python3 scripts/kakaotalk_mac.py auth --refresh --max-user-id 800000000 --workers 8 --chunk-size 2000000; python3 scripts/kakaotalk_mac.py chats --limit 1 --json; python3 scripts/kakaotalk_mac.py auth --cache-path <bad-json>
Not-tested: No new production code paths changed in this follow-up

* Honor explicit Kakao auth recovery overrides

The helper now treats manual auth overrides as a cache-bypassing recovery request and rejects invalid brute-force tuning flags at the CLI boundary so users get deterministic behavior instead of stale cached tuples or Python tracebacks. Regression coverage locks both paths before the PR follow-up lands.

Constraint: The helper must remain a thin read-only wrapper around kakaocli auth recovery
Rejected: Require --refresh whenever --user-id/--uuid is passed | worse UX than honoring overrides directly
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep explicit auth overrides ahead of cache reuse unless the CLI contract is redesigned and documented
Tested: python3 -m unittest scripts.test_kakaotalk_mac; node --test scripts/skill-docs.test.js; npm run ci; python3 scripts/kakaotalk_mac.py auth --refresh --max-user-id 800000000 --workers 8 --chunk-size 2000000; python3 scripts/kakaotalk_mac.py chats --limit 1 --json; python3 scripts/kakaotalk_mac.py auth --cache-path <bad-json>; python3 scripts/kakaotalk_mac.py auth --refresh --max-user-id -1; python3 scripts/kakaotalk_mac.py auth --refresh --workers 2 --chunk-size 0 --max-user-id 10; python3 scripts/kakaotalk_mac.py auth --cache-path <temp-cache> --user-id 999; python3 scripts/kakaotalk_mac.py auth --cache-path <temp-cache> --uuid <live-uuid>
Not-tested: Manual override success with a truly alternate valid user_id/uuid pair on a multi-account local install

* Feature/#129 (#131)

* Add official KBL results support so basketball queries use live league data

Issue #129 needs a read-only skill and reusable package for KBL schedules, results, and standings. The implementation follows the existing sports package pattern and uses the league's live JSON APIs after verifying they respond successfully in real requests.

Constraint: Must use official KBL JSON surfaces before considering scraping
Constraint: Packaging changes must pass npm run ci and include docs plus Changesets updates
Rejected: Browser scraping first | official api.kbl.or.kr endpoints are live and simpler to maintain
Rejected: Reuse KBO/K League package shapes verbatim | KBL payload and team/status fields differ materially
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep seasonGrade=1 as the default KBL path unless future docs/tests explicitly widen to D-League flows
Tested: npm run ci; npm run lint --workspace kbl-results; npm test --workspace kbl-results; live getKBLSummary("2026-04-01", { team: "KCC", includeStandings: true })
Not-tested: Historical standings snapshots for past seasons via alternative KBL endpoints

* Prevent optional standings lookups from over-fetching the KBL API

The new kbl-results summary helper exposes includeStandings=false, so the
regression suite now proves that path stays schedule-only and never calls
the standings endpoint when the caller opts out.

Constraint: The KBL package should preserve the caller's no-standings contract
Rejected: Rely on manual inspection of the helper options | a targeted test is cheaper and safer
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep includeStandings=false side-effect free unless the public API contract changes explicitly
Tested: npm test --workspace kbl-results; npm run lint --workspace kbl-results
Not-tested: Full-repo CI before stacking this commit onto the rebased branch

---------

Co-authored-by: minsing-jin <ironman0722@naver.com>
2026-04-18 11:50:47 +09:00
Jeffrey (Dongkyu) Kim
7c0bfa4c93
Feature/#129 (#131)
* Add official KBL results support so basketball queries use live league data

Issue #129 needs a read-only skill and reusable package for KBL schedules, results, and standings. The implementation follows the existing sports package pattern and uses the league's live JSON APIs after verifying they respond successfully in real requests.

Constraint: Must use official KBL JSON surfaces before considering scraping
Constraint: Packaging changes must pass npm run ci and include docs plus Changesets updates
Rejected: Browser scraping first | official api.kbl.or.kr endpoints are live and simpler to maintain
Rejected: Reuse KBO/K League package shapes verbatim | KBL payload and team/status fields differ materially
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep seasonGrade=1 as the default KBL path unless future docs/tests explicitly widen to D-League flows
Tested: npm run ci; npm run lint --workspace kbl-results; npm test --workspace kbl-results; live getKBLSummary("2026-04-01", { team: "KCC", includeStandings: true })
Not-tested: Historical standings snapshots for past seasons via alternative KBL endpoints

* Prevent optional standings lookups from over-fetching the KBL API

The new kbl-results summary helper exposes includeStandings=false, so the
regression suite now proves that path stays schedule-only and never calls
the standings endpoint when the caller opts out.

Constraint: The KBL package should preserve the caller's no-standings contract
Rejected: Rely on manual inspection of the helper options | a targeted test is cheaper and safer
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep includeStandings=false side-effect free unless the public API contract changes explicitly
Tested: npm test --workspace kbl-results; npm run lint --workspace kbl-results
Not-tested: Full-repo CI before stacking this commit onto the rebased branch
2026-04-18 11:20:42 +09:00
Jeffrey (Dongkyu) Kim
c9116b555f
Feature/#121 (#127)
* Recover KakaoTalk mac skill auth when upstream user_id detection fails

Issue #121 reproduces on a real MacBook because `kakaocli auth` can fail even when the encrypted hex-named DB exists. This change adds a thin repo-owned helper that recovers the active user_id from plist revision hashes, caches the validated DB/key tuple, and reuses it for read-only `kakaocli` commands. The skill and feature docs now steer users to the helper when upstream auto-detection stops at candidate key mismatch, and regression tests lock the recovery flow before the implementation.

Constraint: Must stay a thin adapter around upstream kakaocli rather than forking the CLI
Constraint: Must verify on a real local macOS KakaoTalk install where issue #121 reproduces
Rejected: Full kakaocli reimplementation inside k-skill | too broad for the user_id/key-derivation failure scope
Rejected: Docs-only workaround | does not actually fix the broken auth path for users
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep this helper limited to auth/key recovery and read-only passthrough unless upstream gaps widen materially
Tested: python3 -m unittest scripts.test_kakaotalk_mac
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: python3 scripts/kakaotalk_mac.py auth --refresh --max-user-id 800000000 --workers 8 --chunk-size 2000000
Tested: python3 scripts/kakaotalk_mac.py chats --limit 1 --json
Not-tested: Other kakaocli subcommands beyond auth/chats/messages/search/query/schema

* Protect the KakaoTalk helper's safe recovery path

Address the PR follow-up by treating malformed auth cache files as cache misses,
removing write-capable passthrough from the wrapper surface, and redacting
human-readable auth output so the cached SQLCipher key is not echoed back into
terminal history. The docs and regression suite now describe and enforce the
read-only contract that the helper is meant to preserve.

Constraint: Helper must remain a read-only recovery wrapper around local kakaocli access
Rejected: Keep query support with SQL validation | still leaves a risky write-capable escape hatch
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Do not re-expose arbitrary SQL passthrough or print the SQLCipher key in default text output
Tested: python3 -m unittest scripts.test_kakaotalk_mac; node --test scripts/skill-docs.test.js; npm run ci; python3 scripts/kakaotalk_mac.py auth --refresh --max-user-id 800000000 --workers 8 --chunk-size 2000000; python3 scripts/kakaotalk_mac.py chats --limit 1 --json; python3 scripts/kakaotalk_mac.py auth --cache-path <bad-json>; python3 scripts/kakaotalk_mac.py query --help
Not-tested: External automation consumers that depend on shell/json auth output beyond the documented helper flows

* Lock the helper CLI surface against accidental regressions

The approved issue #121 fixes already hardened the KakaoTalk Mac helper, but the test suite still only exercised the passthrough validator directly. Add an explicit parser-level regression so the public CLI contract stays read-only and `query` cannot quietly reappear in future edits.

Constraint: Follow-up is on the existing feature/#121 PR branch and must stay minimal
Rejected: Re-open helper implementation changes | current code already satisfies the approved review findings
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep parser exposure tests aligned with READ_ONLY_COMMANDS whenever helper subcommands change
Tested: python3 -m unittest scripts.test_kakaotalk_mac; node --test scripts/skill-docs.test.js; npm run ci; python3 scripts/kakaotalk_mac.py auth --refresh --max-user-id 800000000 --workers 8 --chunk-size 2000000; python3 scripts/kakaotalk_mac.py chats --limit 1 --json; python3 scripts/kakaotalk_mac.py auth --cache-path <bad-json>
Not-tested: No new production code paths changed in this follow-up

* Honor explicit Kakao auth recovery overrides

The helper now treats manual auth overrides as a cache-bypassing recovery request and rejects invalid brute-force tuning flags at the CLI boundary so users get deterministic behavior instead of stale cached tuples or Python tracebacks. Regression coverage locks both paths before the PR follow-up lands.

Constraint: The helper must remain a thin read-only wrapper around kakaocli auth recovery
Rejected: Require --refresh whenever --user-id/--uuid is passed | worse UX than honoring overrides directly
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep explicit auth overrides ahead of cache reuse unless the CLI contract is redesigned and documented
Tested: python3 -m unittest scripts.test_kakaotalk_mac; node --test scripts/skill-docs.test.js; npm run ci; python3 scripts/kakaotalk_mac.py auth --refresh --max-user-id 800000000 --workers 8 --chunk-size 2000000; python3 scripts/kakaotalk_mac.py chats --limit 1 --json; python3 scripts/kakaotalk_mac.py auth --cache-path <bad-json>; python3 scripts/kakaotalk_mac.py auth --refresh --max-user-id -1; python3 scripts/kakaotalk_mac.py auth --refresh --workers 2 --chunk-size 0 --max-user-id 10; python3 scripts/kakaotalk_mac.py auth --cache-path <temp-cache> --user-id 999; python3 scripts/kakaotalk_mac.py auth --cache-path <temp-cache> --uuid <live-uuid>
Not-tested: Manual override success with a truly alternate valid user_id/uuid pair on a multi-account local install
2026-04-18 01:08:06 +09:00
Jeffrey (Dongkyu) Kim
213b3f8b98
Merge pull request #125 from NomaDamas/feature/#119
Feature/#119
2026-04-16 15:25:56 +09:00
Jeffrey (Dongkyu) Kim
f65262b783 Keep Feature/#119 mergeable with latest dev additions
Merged origin/dev into feature/#119 and reconciled the install/test contract around the kordoc-based HWP workflow so the branch keeps the public-restroom and proxy updates from dev without regressing the new HWP docs contract.

Constraint: PR #125 must stay on feature/#119 and remain reviewable without self-merging\nRejected: Reintroduce @ohah/hwpjs install guidance | conflicts with the kordoc-first contract under test\nConfidence: high\nScope-risk: narrow\nReversibility: clean\nDirective: Keep docs/install.md and scripts/skill-docs.test.js aligned whenever global install guidance changes\nTested: node --test scripts/skill-docs.test.js; npm run ci; temp-dir kordoc markdownToHwpx roundtrip back to Markdown\nNot-tested: GitHub-side mergeability checks before remote push
2026-04-16 15:22:30 +09:00
Jeffrey (Dongkyu) Kim
e1bc04bb0d
Merge pull request #124 from NomaDamas/fix/issue-99-korean-stock-proxy
Fix Korean stock proxy degraded search and holiday no-data handling
2026-04-16 15:16:18 +09:00
minsing-jin
006acbd631
Add Korean scholarship search skill and reporting workflow (#116)
* Add nationwide scholarship search skill workflow

* Rename scholarship skill to 장학금 주세요 쮜에발

* Fix scholarship skill validation in CI

* Trigger GitHub PR diff refresh after dev rebase on main

* Fix scholarship helper status handling and test coverage

* Use KST as scholarship helper default date basis

* Rename scholarship skill display name

---------

Co-authored-by: Jeffrey (Dongkyu) Kim <vkehfdl1@gmail.com>
2026-04-16 14:58:58 +09:00
Jeffrey (Dongkyu) Kim
e2385d8152 Keep HWP docs runnable against the published kordoc package
The follow-up closes the last runnable-contract gaps from review by documenting the working one-shot npx form and separating Node API examples into a local project install path. The regression suite now locks both install notes so future edits do not drift back to broken command shapes.

Constraint: Published kordoc CLI still requires pdfjs-dist at startup
Constraint: Global NODE_PATH does not make ESM imports from kordoc resolvable in the documented examples
Rejected: Keep bare `npx kordoc` examples | fails in a clean environment
Rejected: Keep global-install Node API guidance | ESM import remains unresolved
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep HWP docs aligned to verified published kordoc surfaces until the package contract changes upstream
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: temp-dir local npm install kordoc pdfjs-dist plus markdownToHwpx -> sample.hwpx -> one-shot kordoc roundtrip smoke
Not-tested: upstream unpublished kordoc features beyond the verified CLI and Node API surfaces
2026-04-16 14:45:38 +09:00
Jeffrey (Dongkyu) Kim
f204bd72e5 Align HWP docs with the published kordoc surface
The issue #119 follow-up needs the repository contract to match what the
currently published kordoc package actually supports. This narrows the
HWP skill/docs/tests to the verified install requirement and supported
CLI/Node API surfaces, and removes unsupported fill/mcp claims.

Constraint: Published kordoc CLI fails at startup without pdfjs-dist
Constraint: Docs/tests must reflect the current npm package behavior, not intended future features
Rejected: Keep fill/mcp examples with caveats | still documents unsupported entrypoints
Confidence: high
Scope-risk: narrow
Directive: Reintroduce fill/mcp docs only after verifying the published package exposes them in both CLI and Node API
Tested: node --test scripts/skill-docs.test.js; npm run ci; temp-dir clean install smoke; temp-dir kordoc+pdfjs-dist watch/parse/extractFormFields/compare/markdownToHwpx/roundtrip smoke; Claude architect review
Not-tested: Real-world HWPX template that produces non-empty extractFormFields output
2026-04-16 14:14:06 +09:00
Jeffrey (Dongkyu) Kim
8d6896d1cc Prevent degraded stock search outages from sticking in cache
Reviewer feedback showed that partial KRX market failures could be cached as full search answers, masking recovery on the next identical request. This change adds a regression that fails first, skips route-level caching for degraded search payloads, and keeps the trade-info empty-snapshot contract documented alongside the partial-failure response semantics.

Constraint: Existing PR #124 already targets dev and must remain the follow-up lane for issue #99
Constraint: Proxy behavior must stay read-only and dependency-free
Rejected: Cache degraded search payloads for a short TTL | still risks transient false negatives during the TTL window
Rejected: Broaden trade-info fallback behavior | empty snapshots should stay explicit not_found results
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep degraded search responses out of the long-lived route cache unless a future design adds explicit revalidation semantics
Tested: npm test --workspace k-skill-proxy; node --test scripts/skill-docs.test.js; npm run ci; explicit buildServer degraded-search recovery repro
Not-tested: Live KRX production endpoints from this branch
2026-04-16 13:59:34 +09:00
Jeffrey (Dongkyu) Kim
cd1c2d1503
Merge pull request #123 from NomaDamas/feature/#117
Feature/#117
2026-04-16 13:35:13 +09:00
Jeffrey (Dongkyu) Kim
b3689c4bf9 Adopt kordoc for the hwp skill workflow
Issue #119 replaces the previous HWP guidance with kordoc so the skill matches the newer agent-native document flow. The docs and regression tests now center the HWP skill on kordoc parsing, JSON extraction, diffing, form filling, and Markdown-to-HWPX round-tripping, while the install/source references stay in sync.

Constraint: The repository treats skill behavior as documentation contracts backed by regression tests
Constraint: The requested branch/PR flow must target dev with TDD and verified execution evidence
Rejected: Keep @ohah/hwpjs or hwp-mcp as fallback guidance | issue #119 explicitly approves replacing the prior stack with kordoc
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep future hwp skill/docs/tests aligned to a single kordoc-first contract unless a new issue explicitly reintroduces multi-backend routing
Tested: node --test scripts/skill-docs.test.js; npm run ci; temp-dir kordoc roundtrip via markdownToHwpx -> sample.hwpx -> kordoc CLI markdown output; architect review APPROVED
Not-tested: Live parsing of user-provided proprietary HWP/HWPX samples outside the generated roundtrip fixture
2026-04-16 13:30:35 +09:00
Jeffrey (Dongkyu) Kim
34a1dbf80c
Merge pull request #122 from NomaDamas/feature/#120
Feature/#120
2026-04-16 13:18:28 +09:00
Jeffrey (Dongkyu) Kim
4903256f27 Expose KRX partial failures instead of misreporting stock lookups
The Korean stock proxy used to silently drop failed market snapshots during
search and could turn an empty holiday trade snapshot into a 502 by falling
back into base-info lookup.

This change surfaces degraded market metadata on partial search success,
short-circuits empty trade snapshots to not_found, and refreshes the user
docs to use a real trading day in examples.

Constraint: KOSPI base-info approval is granted separately from other KRX routes
Constraint: Healthy markets should still return usable search results during a partial outage
Rejected: Return 502 on every partial search failure | hides still-usable markets and breaks current clients unnecessarily
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep degraded search metadata when any market snapshot fetch fails so partial outages stay visible
Tested: npm test --workspace k-skill-proxy
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Not-tested: Live KOSPI base-info behavior after the new KRX permission is approved
2026-04-16 13:17:36 +09:00
Jeffrey (Dongkyu) Kim
be82f0a049 Document the restroom distance-cap contract with regression coverage
The approved issue-117 code fix already restored maxDistanceMeters behavior, but the published docs did not lock or explain that contract. This follow-up adds a failing-first doc regression, then updates the feature guide and package README with the verified 100m example so users and future reviewers see the same behavior the package now ships.

Constraint: Must stay scoped to the existing PR #123 follow-up without reopening the implementation surface
Rejected: Leave the behavior implicit in code/tests only | published docs would lag the verified contract
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the public-restroom-nearby docs and skill-docs regression aligned with live maxDistanceMeters smoke evidence if the sample query changes
Tested: node --test scripts/skill-docs.test.js (red then green)
Tested: npm test --workspace public-restroom-nearby
Tested: npm run ci
Tested: live smoke searchNearbyPublicRestroomsByLocationQuery('광화문', { limit: 3 })
Tested: live smoke searchNearbyPublicRestroomsByLocationQuery('광화문', { limit: 3, maxDistanceMeters: 100 })
Tested: architect review APPROVED
Not-tested: Alternative landmark queries with a non-zero maxDistanceMeters hit set
2026-04-16 12:51:54 +09:00
Jeffrey (Dongkyu) Kim
7a2c7656dc Keep the Hola Poke contract claims aligned with verified coverage
The reviewed fixture-based regression already locks the documented remote
snapshot, but the docs still implied the enter_event success path had
live proof. Narrow the docs and the regression so they explicitly say the
success fields are pinned by the recorded snapshot while the live smoke
only verifies the invalid-phone retry path.

Constraint: Live success-path verification would trigger a real event entry and is intentionally avoided
Rejected: Leave the broader wording in place | review feedback showed it overstated the live evidence
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If a safe non-mutating success-path probe becomes available, update the docs and fixture wording together
Tested: node --test scripts/skill-docs.test.js --test-name-pattern='hola-poke-yeoksam'; npm run ci; live MCP smoke against https://hola-poke-yeoksam-skill.onrender.com/mcp (initialize, tools/list, get_menu subset, get_shop_info subset, invalid enter_event)
Not-tested: Real enter_event success-path invocation
2026-04-16 12:43:53 +09:00
Jeffrey (Dongkyu) Kim
b40cb2f1d1 Keep nearby restroom lookups resilient to flaky Kakao place panels
The review caught two regressions in the new public-restroom-nearby package: a single broken Kakao panel aborted anchor resolution, and coordinate search dropped maxDistanceMeters before normalization. This change adds targeted regression coverage first, keeps per-candidate HTTP failures recoverable, and hardens request errors with explicit status/url metadata so fallback logic no longer depends on parsing error strings.

Constraint: Must preserve the published package surface and keep the fix scoped to PR #123 follow-up
Rejected: Swallow all panel errors | would hide non-HTTP failures like network faults
Rejected: Parse request error messages for status codes | brittle coupling to string formatting
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep recoverable Kakao panel handling aligned with request() error annotations if request() changes again
Tested: npm test --workspace public-restroom-nearby
Tested: npm run ci
Tested: live smoke searchNearbyPublicRestroomsByLocationQuery('광화문', { limit: 3 })
Tested: LSP diagnostics on packages/public-restroom-nearby/src/index.js and test/index.test.js
Not-tested: Live Kakao fallback against a real upstream 5xx place-panel response
2026-04-16 12:25:35 +09:00
Jeffrey (Dongkyu) Kim
58e9c0224a Pin the Hola Poke MCP contract in repo-owned regression fixtures
The earlier issue #120 regression only matched prose, so this follow-up records the verified remote MCP tool/result snapshot in a checked-in fixture and makes both docs surfaces byte-align to it. That keeps the discoverability docs honest while turning the review claim into a real contract lock for tools/list, get_menu, get_shop_info, and the invalid-phone event flow.

Constraint: The upstream remote MCP server can change independently of this repo
Rejected: Keep prose-only regex checks | would not catch contract drift
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Refresh the fixture, both JSON fences, and the live-smoke evidence together whenever the upstream contract changes
Tested: node --test scripts/skill-docs.test.js --test-name-pattern='hola-poke-yeoksam'; npm run ci; live MCP smoke check against https://hola-poke-yeoksam-skill.onrender.com/mcp (initialize, tools/list, get_menu, get_shop_info, invalid enter_event)
Not-tested: Successful enter_event with a real phone number (intentionally avoided to prevent live event participation)
2026-04-16 12:15:47 +09:00
Jeffrey (Dongkyu) Kim
91d01eeec1 Help users find nearby public restrooms from Korean location queries
This adds a new public-restroom-nearby skill and reusable package that resolves a user-provided location, narrows the official 공중화장실정보 dataset by region when possible, and ranks nearby restroom results with opening-time hints and map links.

Constraint: Must use free official/open surfaces without introducing new dependencies
Constraint: Must follow TDD and keep release/docs metadata aligned in the same change
Rejected: Add a proxy route first | direct official CSV access already works and keeps scope narrower
Rejected: Use nationwide-only ranking without regional narrowing | too much noisy data for dense urban anchors
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: If Kakao place-panel or localdata CSV schema changes, update parser fixtures before broad logic changes
Tested: npm run ci; live smoke via searchNearbyPublicRestroomsByLocationQuery('광화문', { limit: 3 }); architect review APPROVED
Not-tested: Non-Seoul live smoke across every regional orgCode
2026-04-16 11:42:42 +09:00
Jeffrey (Dongkyu) Kim
b56df8551d Add a guided Hola Poke Yeoksam skill without widening repo scope
Issue #120 only needs a repository skill payload, discoverability docs,
and regression coverage. This change adds the new skill, wires it into
existing docs surfaces, and locks the remote-MCP-only contract in tests
so future edits keep the phone-only event flow and verbatim message
relay behavior.

Constraint: The upstream Hola Poke flow lives on a remote MCP server, so this repo should not add proxy/runtime code
Constraint: Tests must be written before refining the new docs/skill wording
Rejected: Add local package or proxy support for Hola Poke | would over-scope a docs-only skill addition
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep this skill limited to 올라포케 역삼점 and treat the MCP response message as the event source of truth
Tested: node --test scripts/skill-docs.test.js --test-name-pattern='hola-poke-yeoksam'
Tested: npm run ci
Tested: Live MCP initialize/tools/list/get_menu/get_shop_info/enter_event(phone_format) smoke checks against https://hola-poke-yeoksam-skill.onrender.com/mcp
Not-tested: Successful live event entry with a real phone number
2026-04-16 11:17:17 +09:00
Jeffrey (Dongkyu) Kim
0ec0ab929e
Merge pull request #112 from NomaDamas/changeset-release/main
chore: version packages
2026-04-14 03:11:10 +09:00
github-actions[bot]
ca221f5a1c chore: version packages 2026-04-13 18:09:29 +00:00
Jeffrey (Dongkyu) Kim
e7df15c234
Merge pull request #111 from NomaDamas/dev
Sync dev → main: MFDS food-safety enhancement, school lunch, market kurly, and more
2026-04-14 03:08:51 +09:00
Jeffrey (Dongkyu) Kim
4c1989fdb2 Add product-report proxy route for health food manufacturing data (I0030)
The existing health-food-ingredient route only covers individually-approved
ingredients (I-0040/I-0050), missing gazetted ingredients like psyllium husk
(차전자피). I0030 (건강기능식품 품목제조 신고사항) has 44k+ registered
products with raw materials, functionality, intake precautions, and standards.

The new /v1/mfds/food-safety/product-report route queries I0030 with
server-side PRDLST_NM and RAWMTRL_NM filters in parallel, deduplicates
by report number, and returns normalized results. Live-verified with
FOODSAFETYKOREA_API_KEY returning 차전자피 products with full precautions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 02:44:25 +09:00
Jeffrey (Dongkyu) Kim
9dd5438f5f Add health-food ingredient and inspection-fail proxy routes for food-safety
The existing food-safety skill only queried recall/sales-suspension records
(I0490), which was too edge-case for "can I eat this?" questions. This adds
two new foodsafetykorea.go.kr surfaces through k-skill-proxy:

- /v1/mfds/food-safety/health-food-ingredient (I-0040 + I-0050):
  official health food ingredient recognition, daily intake, precautions
- /v1/mfds/food-safety/inspection-fail (I2620):
  domestic inspection failure records with test results and standards

All three routes share the same FOODSAFETYKOREA_API_KEY with sample-feed
fallback. Python helper gains health-food-ingredient and inspection-fail
subcommands. SKILL.md updated with new surfaces, workflow, and CLI examples.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 02:22:50 +09:00
Jeffrey (Dongkyu) Kim
2550d19974 Route MFDS drug-safety and food-safety lookups through k-skill-proxy
Move direct API calls out of the skill Python scripts and into shared
proxy routes so end-users no longer need their own DATA_GO_KR_API_KEY
for MFDS surfaces. Adds mfds.js helper, proxy tests, and updates all
docs and setup guidance to reflect the hosted proxy workflow.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 00:07:18 +09:00
Jeffrey (Dongkyu) Kim
c6d362a9e9
Merge pull request #105 from hon2be/feature/k-schoollunch-menu
fix: #102, #103  수정사항 반영
2026-04-13 17:32:15 +09:00
Jeffrey (Dongkyu) Kim
774e3e2d9d
Merge pull request #103 from NomaDamas/feature/#0
Feature/#0
2026-04-13 17:32:12 +09:00
Jeffrey (Dongkyu) Kim
68372bafbc
Merge pull request #94 from greekr4/feat/blue-ribbon-browser-fallback
blue-ribbon-nearby: rebrowser-playwright 기반 브라우저 fallback 추가
2026-04-13 17:32:09 +09:00
Jeffrey (Dongkyu) Kim
6d5f7288c8 Ensure the browser fallback ships through the Changesets release flow
The PR changes the published blue-ribbon-nearby package behavior, so it needs a changeset for the automated version-packages PR and publish pipeline. This keeps the release metadata aligned with the repository's Changesets workflow without touching package versions by hand.

Constraint: Node package releases in this repo must use Changesets rather than manual version bumps
Rejected: Skip the changeset and rely on the feature PR alone | the automated release PR would have no release intent to consume
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep feature PRs release-ready by adding a changeset whenever published package behavior changes
Tested: npm --workspace packages/blue-ribbon-nearby test; npm run ci (fails in this environment because tsc is unavailable)
Not-tested: Full repo typecheck/test pipeline with TypeScript installed
2026-04-13 17:08:02 +09:00
Jeffrey (Dongkyu) Kim
5b22fb7546 Keep PR #105 mergeable with the current dev branch
Merged origin/dev into feature/k-schoollunch-menu and resolved the stale
conflict surfaces by matching the current dev tree. The branch keeps its
review history, but its content now aligns with the already-verified dev
integration so the cross-repo PR stops reporting merge conflicts.

Constraint: The school-lunch and household-waste changes already live on dev and should remain the source of truth
Rejected: Force-reset the contributor branch to origin/dev | destructive for a cross-repo PR and unnecessary
Rejected: Keep branch-only drift on docs/tests/proxy files | preserves conflict noise without new shipped behavior
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: For long-lived cross-repo follow-up PRs, prefer sync merges that converge to the verified base branch
Tested: Tree equality against origin/dev after merge resolution
Tested: npm run ci before final tree convergence on this branch; origin/dev npm run ci at commit 7b82d4d
Not-tested: Separate full CI rerun after replacing the remaining branch-only files with origin/dev equivalents
2026-04-13 16:38:45 +09:00
Jeffrey (Dongkyu) Kim
f551ab208e Keep PR #103 mergeable with the current dev branch
Merged origin/dev into feature/#0 and resolved the stale conflict surfaces by
matching the current dev tree. This preserves the branch history while making
the branch content equivalent to the already-verified integration branch so the
open PR no longer carries outdated merge blockers.

Constraint: The approved household-waste and school-lunch work is already integrated on dev
Rejected: Force-reset feature/#0 to origin/dev | would drop branch history and require a force push
Rejected: Preserve stale branch-only doc/test drift | kept the PR conflicting without new product value
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If this PR stays open for coordination only, prefer merge-sync commits over force-push rewrites
Tested: Tree equality against origin/dev after merge resolution
Tested: origin/dev npm run ci at commit 7b82d4d
Not-tested: Separate full CI rerun on feature/#0 after the tree-equivalent merge commit
2026-04-13 16:38:45 +09:00
Jeffrey (Dongkyu) Kim
8b544b4868 Keep PR #94 current with the latest mainline docs and release state
Merged origin/main into feat/blue-ribbon-browser-fallback so the feature branch
carries the latest main-branch packaging and documentation context while
preserving the blue-ribbon browser fallback implementation. No manual conflict
resolution was required.

Constraint: PR #94 still needs its browser-fallback feature diff intact while main keeps moving
Rejected: Rebase the feature branch onto main | unnecessary risk for an already-reviewable cross-repo branch
Rejected: Leave the branch behind main | makes future review and merge harder as main continues to move
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep package-lock and optional dependency behavior validated whenever this branch is re-synced with main
Tested: npm test --workspace blue-ribbon-nearby
Tested: npm run lint --workspace blue-ribbon-nearby
Not-tested: Full workspace npm run ci on the feature branch after the merge-sync commit
2026-04-13 16:38:45 +09:00
Jeffrey (Dongkyu) Kim
7b82d4d409 Keep dev aligned with main after the Naver blog skill landed
Merged origin/main into dev so the branch regains direct ancestry with the
mainline release history while preserving newer dev-side docs, proxy, and
helper updates. Conflict resolution kept the richer dev documentation and
proxy behavior, took the mainline Naver blog skill files as the canonical
source, and preserved the released package-version/changelog updates from
main where they were the authoritative record of published state.

Constraint: dev already contained newer docs/proxy work that would have regressed if main-side older variants won
Constraint: main carried release metadata that should remain authoritative for published package versions
Rejected: Cherry-pick main-only commits again | fixes content but does not restore formal dev<-main merge ancestry
Rejected: Prefer main on all conflicts | would drop newer dev-only skill and proxy guidance
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: On future dev<-main syncs, preserve newer dev docs/proxy behavior first, then re-run full workspace CI before concluding
Tested: npm run ci; inspected README/proxy/setup/package metadata after conflict resolution
Not-tested: push to origin/dev; GitHub mergeability after remote update
2026-04-13 16:18:19 +09:00
Jeffrey (Dongkyu) Kim
77001d7202
Merge pull request #109 from NomaDamas/feature/#108
Feature/#108
2026-04-13 00:54:38 +09:00
Jeffrey (Dongkyu) Kim
5e34ee041b Normalize NEIS proxy failures and repair the selective install command
The GeekNews follow-up review found two merge blockers outside the new helper itself: rejected NEIS upstream fetches escaped as Fastify 500s, and the documented selective install block split into two shell commands. This change adds regression coverage first, catches rejected NEIS fetches at the route boundary with the established proxy_error/502 contract, and locks the multiline install snippet so copy-paste works.

Constraint: Keep NEIS proxy failures aligned with existing public proxy error handling
Constraint: Preserve the published GeekNews verification commands and current PR scope
Rejected: Wrapping fetch inside the NEIS helper functions | route-level handling matches the surrounding Fastify endpoint pattern
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep public proxy routes fail-closed on rejected upstream fetches and keep multiline install snippets copy-pasteable
Tested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_geeknews_search; node --test scripts/skill-docs.test.js; node --test packages/k-skill-proxy/test/server.test.js; python3 scripts/geeknews_search.py list --limit 3; python3 scripts/geeknews_search.py search --query Claude --limit 3; python3 scripts/geeknews_search.py detail --id 28439; node NEIS rejected-fetch repro; npm run ci
Not-tested: Live NEIS upstream success responses against the real public API in this follow-up
2026-04-13 00:28:34 +09:00
Jeffrey (Dongkyu) Kim
43e8625986 Add a repeatable GeekNews lookup path without unofficial APIs
Issue #108 needs a dedicated read-only skill that can browse,
search, and inspect GeekNews posts using the public feed alone.
The implementation adds a fixture-backed Atom helper, skill/docs
surfaces, and install/README wiring, then verifies the helper
against the live GeekNews feed.

Constraint: Must stay RSS-first and avoid new dependencies or unofficial APIs
Constraint: Skill development requires syncing the skill into ~/.claude/skills and ~/.agents/skills during verification
Rejected: Fetch article pages directly for v1 | expands scope beyond the approved RSS-driven workflow
Rejected: Use XML parser modules | current python3 environment has expat issues, so regex + HTML parsing is safer here
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the root helper and geeknews-search/scripts copy behaviorally identical because the installed skill must remain self-contained
Tested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_geeknews_search; node --test scripts/skill-docs.test.js; python3 scripts/geeknews_search.py list --limit 3; python3 scripts/geeknews_search.py search --query Claude --limit 3; python3 scripts/geeknews_search.py detail --id 28439; npm run ci
Not-tested: Non-default feed mirrors or future Atom schema changes beyond the current public GeekNews feed shape
Related: Issue #108
2026-04-13 00:16:35 +09:00
owen
4f015c5680
feat: 네이버 블로그 리서치 스킬 추가 (#107)
* feat: 네이버 블로그 리서치 스킬 추가

API 키 없이 python3 표준 라이브러리만으로 네이버 블로그 검색, 원문 읽기, 이미지 다운로드를 수행하는 스킬.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Restore Naver blog search paging and newest-sort behavior

Naver's current blog search surface does not honor the older
where=blog + sort query pattern used by this skill. The request
now targets the blog tab surface, uses the observed NSO sort
controls, and trims each parsed page to the visible 15-result
window so count-based pagination returns distinct results.

Constraint: Must keep using stdlib-only HTTP scraping without adding dependencies
Constraint: Current Naver blog tab behavior requires ssc/tab parameters plus nso sort controls
Rejected: Keep where=blog and tune start values only | still returned repeated first-page results
Rejected: Leave sort=date as-is | current endpoint ignored it and returned relevance ordering
Confidence: medium
Scope-risk: narrow
Reversibility: clean
Directive: Re-verify request params against live Naver markup before changing paging or sort semantics again
Tested: python3 -m py_compile on naver-blog-research scripts and new regression test; PYTHONPATH=.:scripts python3 -m unittest scripts.test_naver_blog_search; npm run lint; live naver_search.py --count 20/30 --sort sim; live naver_search.py --count 10/20 --sort date
Not-tested: Full npm run test remains blocked by unrelated local pyexpat/libexpat environment failures in patent-search tests

* Surface the new Naver blog skill in the main README

PR 107 adds the skill and feature guide, but the repository landing page
still omitted it from the user-facing capability list. This commit keeps the
README aligned with the actual shipped skill set so users can discover the
new entry point from the main docs.

Constraint: README capability tables and feature lists should stay aligned with docs/features entries
Rejected: Leave README unchanged until merge | hides the new skill from the main index during PR review
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: When adding a new skill guide, update both the summary table and the included-features list together
Tested: README diff review; verified docs/features/naver-blog-research.md link target exists
Not-tested: Full npm run ci (docs-only change)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Jeffrey (Dongkyu) Kim <vkehfdl1@gmail.com>
2026-04-13 00:06:18 +09:00
hyeongr
0b37193fd6 🔀 Merge branch 'feature/#0' into feature/k-schoollunch-menu
NEIS 급식·생활쓰레기 프록시·문서·skill-docs 테스트 정렬 충돌을 해소했다.

Made-with: Cursor
2026-04-11 14:42:31 +09:00
Jeffrey (Dongkyu) Kim
59073f1e30 Close the remaining household-waste validation loopholes before merge
Review follow-up found that mixed alias pagination params could still bypass the narrowed public route contract even after duplicate scalar handling was added. This tightens the query normalizer so pageNo/page_no and numOfRows/num_of_rows must appear exactly once and match the fixed allowed values, expands regression coverage for those alias combinations, and aligns the public docs/examples with the exact pageNo=1 + numOfRows=100 contract. The household-waste test setup was also de-duplicated within the scoped suite to keep the follow-up readable without widening behavior.

Constraint: The public household-waste proxy route must stay narrow and reject ambiguous scalar query shapes before any upstream fetch
Rejected: Keep alias resolution as first-non-null | mixed pageNo/page_no and numOfRows/num_of_rows would still bypass the documented 400 path
Rejected: Broaden the route to support alternate page sizes | outside the approved issue scope and contrary to the fixed-value contract
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep household-waste docs and regression tests aligned whenever the allowed query surface changes, including alias handling
Tested: node --test packages/k-skill-proxy/test/server.test.js
Tested: node --test scripts/skill-docs.test.js
Tested: npm run lint
Tested: npm run typecheck
Tested: npm run test
Tested: local proxy smoke on port 4121 for mixed pageNo/page_no 400, mixed numOfRows/num_of_rows 400, and returnType override reaching upstream with fixed pagination
Not-tested: Live successful household-waste upstream response with a real DATA_GO_KR_API_KEY
2026-04-11 04:58:36 +09:00
Jeffrey (Dongkyu) Kim
8482ddf847 Prevent duplicate household-waste filters from reaching upstream
Fastify parses repeated query parameters as arrays, so the household-waste
normalizer now rejects repeated scalar fields before any upstream request.
This adds a regression test for duplicated cond[SGG_NM::LIKE] values and keeps
invalid requests on the documented 400 bad_request path.

Constraint: Household-waste validation must fail before any upstream fetch
Rejected: Join repeated query values into one filter | ambiguous request semantics
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep repeated scalar query params on the 400 path unless the public contract is expanded intentionally
Tested: node --test packages/k-skill-proxy/test/server.test.js; npm test; npm run lint; npm run typecheck; local proxy smoke on duplicated cond[SGG_NM::LIKE]
Not-tested: Live household-waste upstream success path with a real DATA_GO_KR_API_KEY
2026-04-11 04:36:59 +09:00
hyeongr
d3d048e822 📝 docs: 생활쓰레기·급식 hosted proxy 문서 정합성 및 skill-docs 회귀 테스트
- setup.md·k-skill-setup: 생활쓰레기/급식을 기본 hosted·unset base URL 문구에 포함, 서버 키(DATA_GO_KR·KEDU) 명시
- 표·다음 문서: 생활쓰레기 행·가이드 링크, pageNo/numOfRows 필수
- proxy README·feature doc·household-waste 스킬: 키·페이지네이션·400 계약 보강
- skill-docs.test.js: hosted 생활쓰레기/급식 흐름 회귀 테스트, 예시 secrets에 KEDU_INFO_KEY 금지

Made-with: Cursor
2026-04-11 03:34:11 +09:00
hyeongr
23dd424dd7 feat(k-skill-proxy): 생활쓰레기 페이지네이션 검증 및 문서 보강
- /v1/household-waste/info에 pageNo·numOfRows 필수, 값은 1·100만 허용(미충족·비정수 문자열은 400)
- validateHouseholdWastePaginationQuery 오동작 수정
- validate-skills.sh에서 .cursor·.vscode 디렉터리 제외
- household-waste·k-skill-proxy 문서, 스킬, 패키지 README에 NEIS·생활쓰레기 curl 예시 정리

Made-with: Cursor
2026-04-11 03:02:09 +09:00
hyeongr
0ceff05e20 fix: PR #102 관련 잘못된 머지 충돌 처리 및 API 키 누락 방지를 위한 proxy 서버에 설명 추가 2026-04-10 19:31:50 +09:00
Jeffrey (Dongkyu) Kim
08408cab4a Keep setup guidance aligned with hosted proxy capabilities
The setup skill had drifted behind docs/setup.md after the household-waste
and school-lunch proxy follow-ups. This updates the skill guidance and locks it
with a regression so future doc edits keep the hosted-proxy and server-only-key
story consistent across the user-facing setup surfaces.

Constraint: Follow-up scope is limited to the approved docs/setup consistency gap from PR #103 review
Rejected: Broad setup-doc sweep | unnecessary beyond the approved drift fix
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep k-skill-setup/SKILL.md aligned with docs/setup.md whenever hosted proxy coverage changes
Tested: npm run lint; npm run typecheck; node --test packages/k-skill-proxy/test/server.test.js; bash scripts/validate-skills.sh; node --test scripts/skill-docs.test.js; local buildServer smoke for /health and household-waste pagination
Not-tested: Live upstream NEIS/Data.go.kr network calls with real server credentials
Related: PR #103
2026-04-10 14:59:01 +09:00
Jeffrey (Dongkyu) Kim
9e3f2aff85 Keep hosted-proxy school lunch guidance and waste guard coverage aligned
The feature/#0 follow-up already had the proxy/runtime fixes in place,
but the branch still needed the final regression locks and doc wording
that keep the review fixes durable. This commit adds lower-bound
household-waste pagination tests and aligns install/security docs with
the proxy-only KEDU_INFO_KEY policy so the school lunch feature stays
on the hosted-proxy / no-user-key path.

Constraint: Follow-up had to stay on feature/#0 so PR #103 updates in place
Constraint: User-facing secrets guidance must not imply KEDU_INFO_KEY belongs in the default client env file
Rejected: Broaden the pass into more proxy/doc rewrites | unnecessary beyond the approved Issue #0 follow-up
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep school lunch docs and regression text aligned with the hosted proxy policy whenever setup/security wording changes
Tested: npm run lint; npm run typecheck; node --test packages/k-skill-proxy/test/server.test.js; bash scripts/validate-skills.sh; node --test scripts/skill-docs.test.js; local proxy smoke for /health, invalid household-waste 400, valid household-waste 200, NEIS without KEDU_INFO_KEY 503
Not-tested: Live NEIS curl against a valid KEDU_INFO_KEY-enabled proxy
Related: PR #102
Related: PR #103
2026-04-10 14:42:56 +09:00
Jeffrey (Dongkyu) Kim
d18c8a5917 Keep hosted proxy setup guidance aligned for school-lunch follow-up
The approved PR #103 follow-up extends the setup-doc regression
coverage so it checks the hosted KSKILL_PROXY_BASE_URL guidance and
both household-waste + school-lunch setup entries, then updates the
guide text to match the shipped hosted-proxy behavior.

Constraint: Must stay within the approved PR #103 follow-up scope
Rejected: Broader setup-doc wording sweep across unrelated features | unnecessary for this approved follow-up
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep docs/setup.md and scripts/skill-docs.test.js aligned whenever hosted proxy coverage changes
Tested: node --test scripts/skill-docs.test.js
Tested: npm run lint
Tested: npm run typecheck
Tested: node --test packages/k-skill-proxy/test/server.test.js
Tested: bash scripts/validate-skills.sh
Tested: local buildServer(...).inject(...) smoke for /health and household-waste pagination
Not-tested: Live NEIS curl smoke with a real KEDU_INFO_KEY
2026-04-10 14:32:52 +09:00
Jeffrey (Dongkyu) Kim
b7a39dbd4e Keep setup guidance consistent with hosted school lunch access
The setup guide already described proxy-hosted household-waste and NEIS flows elsewhere, but the opening summary still omitted school lunch from the no-user-key hosted-proxy list. This adds a regression test first, then aligns the intro sentence and test label so the doc stays consistent with the shipped proxy-backed feature set.

Constraint: Keep the follow-up scoped to existing Issue #0/PR #103 documentation surfaces
Rejected: Broader setup guide rewrite | unnecessary for the approved follow-up
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep hosted-proxy/no-user-key summaries aligned with the per-feature setup matrix when adding new proxy-backed skills
Tested: Targeted red/green skill-docs regression; npm run lint; npm run typecheck; node --test packages/k-skill-proxy/test/server.test.js; bash scripts/validate-skills.sh; node --test scripts/skill-docs.test.js; buildServer runtime smoke for /health, household-waste validation, and NEIS missing-key behavior
Not-tested: Live NEIS upstream call with a real KEDU_INFO_KEY
Related: PR #102
Related: PR #103
2026-04-10 14:11:45 +09:00
Jeffrey (Dongkyu) Kim
42a6ee176f Keep setup guidance aligned with the shipped household-waste proxy route
Issue #0's core proxy and NEIS changes were already present on feature/#0, but the
setup guide still omitted the hosted/self-host household-waste entry that users
need when checking which features require local secrets. Add a regression test
first, then document the household-waste row and guide link so the setup matrix
stays aligned with the shipped proxy routes.

Constraint: Keep the follow-up diff narrow and avoid touching already-approved proxy behavior
Rejected: Leave the setup matrix as-is | it would keep user-facing docs inconsistent with the shipped route list
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: When proxy-backed skills move between hosted and self-host flows, update docs/setup.md alongside the feature docs
Tested: node --test scripts/skill-docs.test.js
Tested: npm run lint
Tested: npm run typecheck
Tested: node --test packages/k-skill-proxy/test/server.test.js
Tested: bash scripts/validate-skills.sh
Not-tested: Live hosted proxy deployment docs rendering on GitHub
2026-04-10 14:05:28 +09:00
hon2be
1e89bace8b
Merge branch 'dev' into feature/k-schoollunch-menu 2026-04-10 13:53:51 +09:00
Jeffrey (Dongkyu) Kim
99d174a951 Close the proxy review gaps before merging school lunch support
The existing school lunch feature branch was mergeable in shape, but review
found that the public household-waste route still forwarded unchecked
pagination inputs, user-facing secrets docs suggested storing a server-only
KEDU key locally, and the proxy/operator docs omitted the new
household-waste env requirements. This follow-up validates the public query
surface before fetch, aligns docs with the proxy-only secret policy, and
keeps the requested validation workflow green by ignoring hidden metadata
folders in the skill-layout checker.

Constraint: Public proxy routes must stay narrow and keep upstream keys server-only
Rejected: Forward arbitrary pageNo values | violates the documented public-route contract
Rejected: Leave validate-skills unchanged | hidden metadata directories break the requested verification flow
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep household-waste parameter support and docs/tests in lockstep when widening this route
Tested: npm run lint
Tested: npm run typecheck
Tested: node --test packages/k-skill-proxy/test/server.test.js
Tested: node --test scripts/skill-docs.test.js
Tested: bash scripts/validate-skills.sh
Tested: local proxy smoke (/health, household-waste invalid 400, household-waste valid 200)
Not-tested: Live NEIS curl smoke test (KEDU_INFO_KEY unavailable locally)
2026-04-10 13:49:20 +09:00
hyeongr
eacd087359 🔀 origin/main 병합 및 NEIS·문서 충돌 정리
- upstream 생활쓰레기 프록시/스킬·skill-docs 변경 반영
- README 표에 학교 급식 행 복원, security에 KEDU_INFO_KEY·household 라우트 문구 정리
- NEIS 프록시 단위 테스트 블록 복원

Made-with: Cursor
2026-04-10 13:25:02 +09:00
hyeongr
fb9a5c6f0d NEIS 급식·학교검색 프록시, k-schoollunch-menu 스킬 및 문서
- KEDU_INFO_KEY로 /v1/neis/school-search, /v1/neis/school-meal 중계
- 시도교육청 자연어 해석(neis-office-codes.js)
- k-schoollunch-menu 스킬, README·설치/설정/보안·프록시 문서 반영
- docs/adding-a-skill.md 스킬 추가 가이드

Made-with: Cursor
2026-04-10 13:14:15 +09:00
Jeffrey (Dongkyu) Kim
d7e0406a0a
Merge pull request #101 from NomaDamas/feature/#98
Feature/#98
2026-04-10 13:11:30 +09:00
Jeffrey (Dongkyu) Kim
32a33c3ce5 Keep LOST112 curl guidance usable under live latency
The helper's emitted POST example still timed out against LOST112 in live
verification because the 20-second timeout budget was too short for the
observed form-submit path. Raise the generated timeout to 60 seconds,
lock that contract in the regression, and align the skill/doc wording so
users receive guidance that matches the runnable command.

Constraint: LOST112 live POST latency can exceed 20 seconds even when reachability probes succeed
Rejected: Keep 20 seconds and rely on manual retry | still ships a broken default curl_example
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If the timeout budget changes again, rerun the exact emitted curl_example against LOST112 rather than only the lightweight reachability probe
Tested: python3 -m unittest scripts.test_subway_lost_property
Tested: python3 scripts/subway_lost_property.py --station 강남역 --item 지갑 --days 14 --verify-live
Tested: npm run ci
Not-tested: Long-term LOST112 latency drift beyond the currently observed 60-second budget
2026-04-10 13:01:01 +09:00
Jeffrey (Dongkyu) Kim
748967c21c Make LOST112 curl guidance runnable in practice
The review found that the generated subway lost-property curl example still timed out even when the helper's reachability probe passed. This change updates the helper to emit the command shape that actually succeeds against LOST112, tightens the regression test to lock that contract, and aligns the published skill/docs text with the runnable output users now receive.

Constraint: LOST112 POST flow requires a referer and does not behave reliably with the prior redirect-following command shape
Constraint: Keep the v1 scope hybrid/guidance-only without adding new dependencies or scraping guarantees
Rejected: Only add a referer header while leaving the rest of the curl shape unchanged | still reproduced timeouts with the generated command during verification
Rejected: Add a live network regression test for the emitted curl command | would make CI flaky and violate deterministic regression expectations
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If the LOST112 request shape changes again, re-verify the emitted curl command end-to-end before updating docs or tests
Tested: python3 -m unittest scripts.test_subway_lost_property; python3 scripts/subway_lost_property.py --station 강남역 --item 지갑 --days 14 --verify-live; generated curl_example executed via shell and wrote lost112-search-result.html; npm run ci
Not-tested: Alternate LOST112 item/station combinations beyond the verified 강남역/지갑 example
2026-04-10 12:29:14 +09:00
Jeffrey (Dongkyu) Kim
8d61339634 Add an official subway lost-property guidance path
Issue #98 was approved as a conservative implementation: structure the official LOST112 and 서울교통공사 workflows instead of pretending a stable public API exists. The new helper builds the documented search payload, the docs explain the v1 hybrid scope, and regression coverage locks the helper plus repo docs into the shipped flow.

Constraint: Public subway lost-property APIs remain unconfirmed, so v1 must stay guidance-first
Rejected: Full automated scraping of live result tables | unstable without confirmed API/session guarantees
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep this skill on official HTTPS entry points until a stable public API is verified end-to-end
Tested: python3 -m unittest scripts.test_subway_lost_property; python3 scripts/subway_lost_property.py --station 강남역 --item 지갑 --days 14 --verify-live; npm run ci
Not-tested: Browser-only user journey through LOST112 search submission beyond payload generation
2026-04-10 12:05:50 +09:00
Jeffrey (Dongkyu) Kim
8f28e44324 Keep PR 94 CI aligned with Blue Ribbon fallback behavior
The browser fallback landed without regenerating package-lock, so npm ci failed immediately. Once the lockfile was synced, the blue-ribbon-nearby direct-API tests also showed that structured 403 domain errors were being retried through Playwright whenever the optional dependency was present.

This commit refreshes the workspace lockfile and restricts browser fallback to unclassified 403 automation blocks so PREMIUM_REQUIRED and other structured upstream errors still surface deterministically.

Constraint: CI runs npm ci from the root lockfile and installs optional dependencies when they are captured there
Rejected: Update tests to stub browser fallback | would hide the incorrect production retry path
Confidence: high
Scope-risk: narrow
Directive: Preserve structured upstream 403 errors from bluer.co.kr; only browser-fallback opaque automation blocks
Tested: npm ci
Tested: npm test --workspace blue-ribbon-nearby (python3.12 first on PATH)
Tested: npm run ci (python3.12 first on PATH)
Not-tested: GitHub Actions rerun on ubuntu after push
2026-04-10 11:53:48 +09:00
Jeffrey (Dongkyu) Kim
4bceaefef4
Merge pull request #100 from NomaDamas/feature/#96
Feature/#96
2026-04-10 11:13:19 +09:00
Jeffrey (Dongkyu) Kim
a6ad86dc3e Document direct zipcode helper execution so the executable follow-up stays discoverable
The approved Issue #96 branch already shipped the ePost English-address helper
and executable shebang/mode fix. This follow-up only locks the last user-facing
surface: docs and docs regression now mention the direct `./scripts/zipcode_search.py`
entrypoint alongside the `python3` form, so the published guidance matches the
actual executable helper behavior.

Constraint: Keep the branch diff narrow and aligned with the already-approved Issue #96 scope
Rejected: Add more helper code changes | executable behavior was already correct and verified
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep docs/tests aligned whenever helper entrypoint contracts change
Tested: node --test scripts/skill-docs.test.js; python3 -m unittest scripts.test_zipcode_search; python3 scripts/zipcode_search.py "서울특별시 강남구 테헤란로 123"; ./scripts/zipcode_search.py "서울특별시 강남구 테헤란로 123"; npm run build; PYTHONPATH=.:scripts python3 -m unittest scripts.test_patent_search; npm run ci
Not-tested: No additional live address samples beyond the documented Gangnam example
2026-04-10 10:59:22 +09:00
Jeffrey (Dongkyu) Kim
35c91a0717 Keep the zipcode helper directly executable
The feature branch already routed zipcode lookup through the integrated ePost English-address surface, but the helper scripts themselves still lacked a shebang and executable mode. This follow-up locks the packaging/CLI contract with a regression test and marks both entrypoints executable so the documented helper can be invoked directly as a script.

Constraint: Issue #96 requires an executable zipcode_search.py helper in both the repo script wrapper and bundled skill helper

Rejected: Document python3-only invocation and leave file modes unchanged | would not satisfy the executable-helper requirement or catch future regressions

Confidence: high

Scope-risk: narrow

Reversibility: clean

Directive: Keep the wrapper and bundled helper executable together because tests assert the pair as the public entrypoints

Tested: python3 -m unittest scripts.test_zipcode_search; node --test scripts/skill-docs.test.js; python3 scripts/zipcode_search.py "서울특별시 강남구 테헤란로 123"; ./scripts/zipcode_search.py "서울특별시 강남구 테헤란로 123"; npm run build; PYTHONPATH=.:scripts python3 -m unittest scripts.test_patent_search; npm run ci

Not-tested: Direct execution of the bundled zipcode-search/scripts/zipcode_search.py helper against the live endpoint
2026-04-10 10:48:00 +09:00
Jeffrey (Dongkyu) Kim
5c95e9e742 Enable official English address lookup for Korean postcode searches
The zipcode-search feature now uses the official ePost integrated search surface
for postcode plus English-address lookups, ships a runnable helper, and
locks the behavior with regression coverage plus aligned docs.

A narrow compatibility fallback was also added to the KIPRIS XML parser so
repository CI stays green on the current Python 3.14 environment where
pyexpat is unavailable.

Constraint: Must use official public ePost output instead of custom romanization rules
Constraint: Repository verification must pass under the current local Python 3.14 toolchain
Rejected: Implement our own Hangul-to-English address formatter | would diverge from the official postal rendering
Rejected: Leave the KIPRIS parser untouched | npm run ci currently fails in this environment without the XML fallback
Confidence: medium
Scope-risk: moderate
Reversibility: clean
Directive: Keep zipcode-search tied to the official ePost integrated surface unless a new approved source is added
Tested: python3 -m unittest scripts.test_zipcode_search
Tested: node --test scripts/skill-docs.test.js
Tested: python3 scripts/zipcode_search.py '서울특별시 강남구 테헤란로 123'
Tested: npm run build
Tested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_patent_search
Tested: npm run ci
Not-tested: Live no-result and multi-result zipcode queries beyond the verified Teheran-ro example
2026-04-10 10:34:25 +09:00
Jeffrey (Dongkyu) Kim
224fd3aad5
Merge pull request #95 from NomaDamas/feature/#88
Feature/#88
2026-04-09 15:49:06 +09:00
Jeffrey (Dongkyu) Kim
eb0757009c Preserve discounted Market Kurly detail fields from live goods pages
Discounted goods pages are currently exposing original-price and image data
through retail/showable fields that the detail normalizer was not reading,
so the follow-up fix prefers those live fields and locks the behavior with
a discounted-detail regression fixture.

Constraint: Kurly detail payloads do not mirror the search API price field semantics
Rejected: Trust top-level basePrice alone | discounted live pages currently use retailPrice/showablePrices for the original price
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep detail normalization aligned with live goods-page payloads; verify real discounted products before changing price precedence again
Tested: node --test packages/market-kurly-search/test/index.test.js; npm run ci; live Node smoke for countProducts('우유'), searchProducts('우유'), getProductDetail(5063110), getProductDetail(5048935) on 2026-04-09
Not-tested: Additional undiscovered Market Kurly detail payload variants beyond the covered discounted fixture and live sample
2026-04-09 15:13:11 +09:00
Jeffrey (Dongkyu) Kim
4ce29ae009 Add a supported Market Kurly price-lookup skill
Implement a reusable Market Kurly workspace package plus a repo skill/doc set
that uses the unauthenticated Kurly search and goods-page surfaces.
The change keeps the scope read-only, adds regression coverage, updates
release/docs metadata, and records the new publishable package through
Changesets.

Constraint: Must rely on unauthenticated public web surfaces instead of login/session flows
Constraint: Release workflow requires Changesets for publishable Node packages
Rejected: Docs-only skill | issue approval called for real lookup helpers and live verification
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Kurly endpoints are web-internal surfaces; verify schema behavior before extending fields or adding action flows
Tested: npm run ci; live node smoke for countProducts/searchProducts/getProductDetail on 2026-04-09
Not-tested: Pagination beyond page 1; long-term stability of Kurly internal response schema
2026-04-09 14:51:30 +09:00
TKman
0d0497719d [FEAT] blue-ribbon-nearby: rebrowser-playwright 기반 브라우저 fallback 추가
bluer.co.kr이 자동화 접근을 차단(403)할 때,
rebrowser-playwright가 설치되어 있으면 실제 Chrome 브라우저를 통해
자동으로 fallback하여 zone 카탈로그와 nearby 검색을 수행한다.

- browser-fallback.js: stealth 패치 적용 브라우저 모듈 (8개 패치)
- index.js: fetchZoneCatalog/fetchNearbyMap에서 403 시 자동 fallback
- package.json: rebrowser-playwright를 optionalDependencies로 추가
- SKILL.md: 브라우저 fallback 사용법 안내

rebrowser-playwright 미설치 시 기존 동작과 완전히 동일하다.
2026-04-09 13:33:52 +09:00
Jeffrey (Dongkyu) Kim
ba45df42d6
Merge pull request #91 from NomaDamas/feature/#89
Feature/#89
2026-04-09 12:49:51 +09:00
Jeffrey (Dongkyu) Kim
e9af2985e6
Merge pull request #90 from NomaDamas/hotfix/remove-used-car-changeset-assertion-dev
fix(ci): backport used-car-price-search changeset assertion removal
2026-04-09 12:38:58 +09:00
Jeffrey (Dongkyu) Kim
b517e71f14 Prevent dev release CI from depending on consumed Changesets files
The dev backport matches the approved hotfix by deleting the
used-car-price-search assertion that scanned .changeset entries.
That assertion fails after changeset versioning consumes release
notes, while the remaining docs and pack coverage still protect the
published workspace invariants that matter here.

Constraint: Repository policy forbids tests that assert .changeset/*.md files exist after release automation consumes them
Rejected: Add replacement coverage that still reads .changeset | keeps the same brittle release-flow dependency
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep release-flow regression coverage focused on durable docs/package invariants, not transient .changeset files
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: LSP diagnostics on scripts/skill-docs.test.js
Not-tested: GitHub Actions on the remote PR branch
2026-04-09 11:43:50 +09:00
Jeffrey (Dongkyu) Kim
c2ba43bc8a fix(ci): remove consumed changeset assertion for used-car-price-search
Backport of the main hotfix (#89). Same anti-pattern documented in
CLAUDE.md: the test asserts a changeset entry for used-car-price-search
exists in .changeset/*.md, but `changeset version` deletes those files
during release. On the version-bump commit the test fails and blocks
the release pipeline — same issue previously fixed in #50 and #78.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 11:32:47 +09:00
Jeffrey (Dongkyu) Kim
55e565cebb
fix(ci): remove consumed changeset assertion for used-car-price-search (#89)
The test asserted a changeset entry for used-car-price-search existed in
.changeset/*.md, but changesets are deleted by `changeset version` during
the release flow. On the resulting version-bump commit (#85) the test
fails and blocks the release pipeline — same anti-pattern already
documented in CLAUDE.md and fixed before in #50 / #78.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 11:32:22 +09:00
github-actions[bot]
5f35b6f1e9
chore: version packages (#85)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-04-09 11:23:51 +09:00
Jeffrey (Dongkyu) Kim
c09b022889
Merge pull request #87 from NomaDamas/feature/#65
Feature/#65
2026-04-09 00:31:44 +09:00
Jeffrey (Dongkyu) Kim
b2d0e364b8 Sync feature/#65 with dev so PR #87 can be reviewed again
Merged origin/dev into feature/#65, resolved the package.json and docs-test conflicts by keeping the Korean character count helper coverage while also preserving the newer MFDS safety skill wiring from dev. This keeps the branch reviewable without dropping either side's verification surface.

Constraint: PR #87 had to be updated from dev without regressing either shipped korean-character-count checks or newer dev-only MFDS coverage
Rejected: Re-resolve by favoring one side of the conflict wholesale | would silently drop verified test coverage from the other branch
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: If package.json test wiring changes again, keep both the korean-character-count and MFDS helper suites in the root scripts unless the underlying features are intentionally removed
Tested: node --test scripts/skill-docs.test.js scripts/test_korean_character_count.js; node scripts/korean_character_count.js --text '가나다' --format json; node scripts/korean_character_count.js --file <tmpfile> --profile neis --format text; printf '한\r\n나' | node scripts/korean_character_count.js --stdin --format json; npm test; npm run ci
Not-tested: Live authenticated MFDS API success path still depends on external API key state
2026-04-09 00:28:31 +09:00
Jeffrey (Dongkyu) Kim
b6ef7130c2 Record verified completion of the approved Korean count follow-up
The approved Issue #65 fixes were already present on feature/#65 at 1180f41, so this checkpoint commit captures fresh verification on the same branch before updating PR #87.

Constraint: Existing PR #87 follow-up required a push on feature/#65 even with no missing code delta
Rejected: Touch implementation files without a behavior change | would add churn without improving the shipped feature
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the NEIS example, fallback rules, and duplicate-input guard aligned with the live helper and docs regression
Tested: node --test scripts/skill-docs.test.js scripts/test_korean_character_count.js; node scripts/korean_character_count.js --text '가나다' --format json; node scripts/korean_character_count.js --file <tmpfile> --profile neis --format text; printf '한\r\n나' | node scripts/korean_character_count.js --stdin --format json; node scripts/korean_character_count.js --text $'\u0301' --profile neis --format json; duplicate --text repro exits non-zero; npm test; npm run ci; LSP diagnostics 0 on affected files
Not-tested: No new code-path edits were introduced in this checkpoint commit
2026-04-09 00:22:13 +09:00
Jeffrey (Dongkyu) Kim
e3988adf3e
Merge pull request #86 from NomaDamas/feature/#83
Feature/#83
2026-04-09 00:17:36 +09:00
Jeffrey (Dongkyu) Kim
8cb90d76b2 Clarify invalid Foodsafety Korea key failures
Foodsafety Korea returns HTML with HTTP 200 for invalid recall API keys,
so the helper now converts non-JSON responses into a stable ApiError
instead of leaking a JSON parser failure. A regression test locks the
invalid-key path to the documented foodsafetykorea-key guidance.

Constraint: Upstream invalid-key responses return HTML with HTTP 200 instead of JSON errors
Rejected: Let JSONDecodeError bubble up | opaque CLI output for a predictable auth/config failure
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep Foodsafety Korea non-JSON responses mapped to stable user-facing ApiError text unless the upstream starts returning structured JSON failures
Tested: invalid-key CLI search warning, MFDS helper commands, Python unit tests, skill docs regression, npm run ci
Not-tested: live approved Foodsafety Korea API key success path (still depends on upstream key approval)
2026-04-09 00:05:10 +09:00
Jeffrey (Dongkyu) Kim
1180f41b82 Keep published Korean count examples aligned with the shipped helper
The korean-character-count feature guide drifted from the verified NEIS helper output, so this follow-up adds a docs regression that executes the live command and corrects the stale byte example in the feature doc.

Constraint: The skill promises deterministic exact counts in both docs and CLI output
Rejected: Hardcode a separate doc-only expected value in tests | would still allow the published example to drift from the helper
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: When docs publish exact count values, keep them pinned to live helper output rather than a copied constant
Tested: node --test scripts/skill-docs.test.js scripts/test_korean_character_count.js
Tested: node scripts/korean_character_count.js --text '가나다' --format json
Tested: node scripts/korean_character_count.js --file <tmpfile> --profile neis --format text
Tested: printf '한\r\n나' | node scripts/korean_character_count.js --stdin --format json
Tested: npm test
Tested: npm run ci
Not-tested: none
2026-04-08 23:57:31 +09:00
Jeffrey (Dongkyu) Kim
482aedf882 Reconfirm MFDS HTTPS follow-up before merge
Feature/#83 already contains the approved Foodsafety Korea HTTPS follow-up, so this checkpoint records fresh end-to-end verification on 2026-04-08 before finalizing PR #86.

No additional code or docs were changed because the reviewed transport fix and its regressions were already present on the branch.

Constraint: Review round 2 already approved the existing MFDS HTTPS fix
Constraint: Current branch already contains the required implementation at commit 40e0ad3
Rejected: Add another code/doc change | would add churn without improving correctness
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep Foodsafety Korea recall URLs and published examples on HTTPS only
Tested: python3 scripts/mfds_drug_safety.py --help; python3 scripts/mfds_drug_safety.py interview --question '타이레놀이랑 판콜 같이 먹어도 되나요?' --symptoms '두드러기와 어지러움'; python3 scripts/mfds_food_safety.py --help; python3 scripts/mfds_food_safety.py interview --question '이 김밥 먹어도 되나요?' --symptoms '복통과 설사'; python3 scripts/mfds_food_safety.py search --query '대장균' --sample-recalls --limit 1; PYTHONPATH=.:scripts python3 -m unittest scripts.test_mfds_drug_safety scripts.test_mfds_food_safety; node --test scripts/skill-docs.test.js; npm run ci
Not-tested: Live authenticated data.go.kr success path still depends on upstream key approval/status (HTTP 403 observed on 2026-04-08)
2026-04-08 23:35:24 +09:00
Jeffrey (Dongkyu) Kim
488a9f129e Preserve documented Korean count semantics in edge-case inputs
The NEIS compatibility path was treating mark-only graphemes as Hangul,
and the CLI accepted repeated input flags by silently overwriting earlier
values. Tighten the Hangul branch to require an actual Hangul-script code
point and reject any second input-source flag, then lock both fixes with
helper and CLI regressions.

Constraint: Must preserve the published default and NEIS counting contracts without new dependencies
Rejected: Doc-only clarification | would leave the shipped behavior incorrect
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the NEIS 3-byte branch limited to graphemes that actually contain Hangul script code points
Tested: node --test scripts/skill-docs.test.js scripts/test_korean_character_count.js; node scripts/korean_character_count.js --text '가나다' --format json; node scripts/korean_character_count.js --file <tmpfile> --profile neis --format text; printf '한\r\n나' | node scripts/korean_character_count.js --stdin --format json; mark-only NEIS and duplicate-input repros; npm test; npm run ci
Not-tested: repeated --stdin as a separate committed regression case
2026-04-08 23:28:42 +09:00
Jeffrey (Dongkyu) Kim
40e0ad31b7 Protect Foodsafety Korea recall lookups with HTTPS
Review feedback found the MFDS food-safety helper and published
examples were still sending Foodsafety Korea recall traffic over
plain HTTP even though the upstream host already supports HTTPS.
This follow-up swaps the sample and live recall URLs to HTTPS and
adds narrow regressions that lock both the helper constants and
documented sample links against transport regressions.

Constraint: Keep the change limited to the approved PR #86 transport-safety follow-up
Rejected: Broader MFDS helper refactor | unnecessary for the HTTPS-only fix
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep Foodsafety Korea recall URLs on HTTPS in both helper constants and published examples, including API-key paths
Tested: python3 scripts/mfds_drug_safety.py --help; python3 scripts/mfds_drug_safety.py interview --question '타이레놀이랑 판콜 같이 먹어도 되나요?' --symptoms '두드러기와 어지러움'; python3 scripts/mfds_food_safety.py --help; python3 scripts/mfds_food_safety.py interview --question '이 김밥 먹어도 되나요?' --symptoms '복통과 설사'; python3 scripts/mfds_food_safety.py search --query '대장균' --sample-recalls --limit 1; PYTHONPATH=.:scripts python3 -m unittest scripts.test_mfds_drug_safety scripts.test_mfds_food_safety; node --test scripts/skill-docs.test.js; npm run ci
Not-tested: Live Foodsafety Korea authenticated recall lookup with an approved API key
2026-04-08 23:08:30 +09:00
Jeffrey (Dongkyu) Kim
b2eedd827b Standardize Korean text counts for form-limited writing
Issue #65 needs deterministic Korean character, line, and byte counting for self-intros and similar forms, so this adds a bundled helper plus skill/docs/tests around a fixed grapheme/UTF-8 contract with an explicit NEIS compatibility profile.

Constraint: Must avoid LLM estimation and keep results deterministic across repeated runs
Constraint: No new dependencies; rely on Node 18+ Intl.Segmenter and Buffer.byteLength
Rejected: Per-request ad hoc scripts only | contract drift would make repeated counts inconsistent
Rejected: Legacy encoding heuristics as the default | modern UTF-8 contract is safer unless the target explicitly says otherwise
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep  and  profile semantics in sync with docs/tests before changing counting rules
Tested: node --test scripts/skill-docs.test.js scripts/test_korean_character_count.js; helper smoke via text/file/stdin; npm test; npm run ci; LSP diagnostics on changed JS files
Not-tested: Proprietary site-specific counters beyond the documented default/NEIS contracts
2026-04-08 22:55:31 +09:00
Jeffrey (Dongkyu) Kim
5793381517 Ship interview-first MFDS safety skills for drugs and food
The approved issue narrowed scope to two public-health skills: one for
official MFDS drug safety lookups and one for food safety / recall
lookups. This change adds both skills, bundles runnable Python helpers,
locks the docs and helper behavior with TDD regressions, and updates
repo docs so the interview-first safety flow is installed and verified
with the rest of k-skill.

Constraint: User-approved scope requires mandatory symptom follow-up before answering and forbids direct diagnosis
Constraint: Live MFDS data.go.kr success paths depend on upstream key approval/status; local verification on 2026-04-08 surfaced HTTP 403 with the available key
Rejected: Single generic health skill | weaker safety boundaries and less explicit user routing
Confidence: medium
Scope-risk: moderate
Reversibility: clean
Directive: Keep red-flag escalation and interview-first wording intact unless official clinical scope is expanded deliberately
Tested: python3 scripts/mfds_drug_safety.py --help; python3 scripts/mfds_drug_safety.py interview --question '타이레놀이랑 판콜 같이 먹어도 되나요?' --symptoms '두드러기와 어지러움'; python3 scripts/mfds_food_safety.py --help; python3 scripts/mfds_food_safety.py interview --question '이 김밥 먹어도 되나요?' --symptoms '복통과 설사'; python3 scripts/mfds_food_safety.py search --query '대장균' --sample-recalls --limit 1; PYTHONPATH=.:scripts python3 -m unittest scripts.test_mfds_drug_safety scripts.test_mfds_food_safety; node --test scripts/skill-docs.test.js; npm run ci
Not-tested: Successful authenticated MFDS live data.go.kr lookup path with a fully approved service key
2026-04-08 22:23:14 +09:00
Jeffrey (Dongkyu) Kim
1be3f44eea
Sync dev → main: 84 commits incl. korea-weather, korean-stock-search, korean-patent-search, bunjang-search (#80)
* Remove client-side Seoul subway key setup

Route Seoul subway arrival lookups through k-skill-proxy so the
hosted proxy owns the Seoul Open Data upstream key and end users
only need the proxy base URL. Add proxy route coverage, update
skill/docs guidance, and align setup materials with the hosted
proxy flow used for fine dust.

Constraint: Must keep the proxy public, read-only, and dependency-free
Constraint: Must satisfy TDD-first verification and ship on feature/#35 targeting dev
Rejected: Add a separate client helper package | unnecessary extra layer for a single proxy route
Rejected: Keep SEOUL_OPEN_API_KEY as an end-user requirement | defeats the issue goal
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep the Seoul subway proxy surface limited to station-arrival passthrough unless tests/docs expand the contract
Tested: npm run ci; local proxy runtime on 127.0.0.1:4120 for /health and /v1/seoul-subway/arrival on 2026-03-31 with an invalid upstream key
Not-tested: Live success response with a valid Seoul Open API key
Related: #35

* Prevent broken Seoul subway proxy defaults before hosted rollout

The Seoul subway proxy endpoint code is present locally, but the hosted public route is not live yet. This change turns the user-facing subway docs back into an explicit proxy configuration flow, replaces the misleading hosted default in setup examples, and keeps subway proxy examples on self-host/local URLs until rollout is verified.

Constraint: Hosted k-skill-proxy.nomadamas.org/v1/seoul-subway/arrival is not live yet
Rejected: Keep the hosted Seoul subway URL as the default path | would send default users to a 404 route
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Do not restore a hosted Seoul subway default until the public proxy route is deployed and smoke-verified
Tested: node --test scripts/skill-docs.test.js; npm run ci; local proxy smoke on 127.0.0.1:4120 with stubbed Seoul upstream (GET /health, GET /v1/seoul-subway/arrival?stationName=강남)
Not-tested: Live hosted proxy smoke after deployment

* Reduce Seoul subway proxy upstream pressure with request caching

The public subway arrival route already normalized caller input but still re-fetched the Seoul upstream for every identical poll. This change adds a short-TTL cache keyed from the normalized subway query, annotates JSON responses with cache metadata, and locks the repeated-read collapse with a regression that proves alias/default normalization still reuses the cached result.

Constraint: The endpoint must stay public/no-auth while protecting a shared server-side SEOUL_OPEN_API_KEY from unnecessary repeated upstream hits
Rejected: Add a new shared cache abstraction for proxy metadata | unnecessary for this narrow fix and would enlarge the diff
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep subway cache keys tied to normalizeSeoulSubwayQuery output so alias/default query forms continue collapsing to one upstream request
Tested: node --test packages/k-skill-proxy/test/server.test.js
Tested: npm run ci
Tested: Local proxy runtime on 127.0.0.1:4120 with SEOUL_OPEN_API_KEY=test-seoul-key and stubbed fetch for /health plus repeated /v1/seoul-subway/arrival requests
Not-tested: Live hosted proxy rollout state

* Document OpenClaw support in the public compatibility list

The approved scope for issue #29 was narrowed to README support messaging,
so this change adds OpenClaw/ClawHub to the supported-client line and locks
that wording with a regression test in the docs suite.

Constraint: Issue #29 was approved as a README-only compatibility/docs change
Rejected: Broader install-doc updates | out of approved scope for this issue
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep OpenClaw support wording aligned with the supported-client README line and its docs regression test
Tested: Focused Node docs regression, npm run lint, npm run typecheck, npm run build, npm test
Not-tested: Live OpenClaw or ClawHub install flow (issue scope was documentation-only)

* Protect README client-support claims from ClawHub regressions

The README already advertises OpenClaw/ClawHub, but the docs
regression only matched OpenClaw. Tighten the assertion to the
exact supported-client fragment so a future edit cannot silently
remove ClawHub while keeping the issue verification command stable.

Constraint: PR #39 already publishes a verification command keyed to the current test name
Rejected: Rename the test to mention ClawHub explicitly | would drift from the published verification command
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep this regression synchronized with the README supported-client line whenever that copy changes
Tested: node --test scripts/skill-docs.test.js --test-name-pattern 'README advertises OpenClaw among the supported coding agents'
Tested: npm run lint
Tested: npm run typecheck
Tested: npm run build
Tested: npm test
Tested: lsp diagnostics for scripts/skill-docs.test.js (0 errors)
Not-tested: N/A

* Make Coupang shopper research usable without pretending scraping is stable

Coupang blocks unattended shopper queries in this environment, so the new package focuses on the durable pieces we can ship honestly: official URL builders, browser-captured HTML parsers, and explicit automation probes. The docs and skill now explain the seller-API limitation, the verified anti-bot behavior, and the browser-capture fallback expected by callers.

Constraint: No general shopper Open API surfaced in Coupang's official developer docs

Constraint: Headless/direct retrieval is anti-bot blocked in verified local probes

Rejected: Bundle a scraping bypass or hidden browser dependency | violates repo policy and would over-claim reliability

Confidence: high

Scope-risk: moderate

Reversibility: clean

Directive: Keep probe/docs behavior aligned with fresh live verification before claiming unattended Coupang access works

Tested: npm run ci; live probeAutomation(query=생수) with direct fetch + Playwright-core browserFetchHtml; LSP diagnostics on changed JS/test files

Not-tested: Headed/manual browser sessions with a human-authenticated Coupang context

* Keep the Coupang workspace installable and its entrypoint honest

Review follow-up found two contract gaps in the first issue #36 rollout: fresh installs were missing the new workspace link in package-lock, and the package README documented parser helpers that were not exported from the package entrypoint. Refreshing the lockfile and re-exporting the parsers keeps npm ci and consumer imports aligned with the published API.

Constraint: npm ci must succeed from a clean checkout
Rejected: Narrow the README API list to only client helpers | would hide useful parsers that the package already ships and tests
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: When adding a new workspace, refresh package-lock and verify the package entrypoint matches README public API claims
Tested: npm run ci; workspace coupang-product-search tests; LSP diagnostics on coupang-product-search JS/test files
Not-tested: published npm install from registry (local pack dry-run only)

* Keep Coupang anti-bot docs honest as probe results drift

A same-day PR rerun showed the published mobile probe snapshot was already stale, so the follow-up locks the 403/access-denied mobile path with regression coverage and softens the docs/skill contract to describe blocked outcome classes instead of one frozen signature. The published guidance now also explains that browser results only appear when browserFetchHtml is injected, matching what a clean checkout can actually reproduce.

Constraint: Coupang anti-bot responses vary by edge/challenge between reruns on the same day
Rejected: Freeze one exact mobile probe snapshot | same-day reruns already diverged
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Refresh Coupang anti-bot docs only with same-day live probe evidence, and prefer blocked outcome classes over a single exact signature when reruns disagree
Tested: npm run lint; npm run typecheck; npm test; npm run ci; live probeAutomation("생수") with direct fetch + Playwright-core browser fetch
Not-tested: Non-Chrome Playwright-core executables

* Keep the Coupang skill honest about clean-checkout browser probes

The approved follow-up already documented that clean-checkout probeAutomation runs leave browser unset unless a browser fetcher is injected, but the skill text itself had not made that null contract explicit. This commit adds a regression test that locks the browser-null wording across the published docs surfaces and updates the skill so the stated behavior matches the package implementation and clean-checkout reality.

Constraint: probeAutomation() only populates browser when browserFetchHtml is supplied by the caller
Rejected: Leave the skill wording implicit and rely on README examples alone | the skill could drift away from the shipped package contract again
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the README, feature doc, and skill aligned whenever the probeAutomation browser contract changes
Tested: node --test packages/coupang-product-search/test/index.test.js; npm run lint; npm run typecheck; npm test; npm run ci; live probeAutomation("생수") with direct fetch + Playwright-core browser fetch; codex exec review --uncommitted
Not-tested: Alternative browser engines beyond local Google Chrome for the manual browserFetchHtml probe

* Keep Coupang browser probe docs aligned with manual verification paths

The approved PR #40 follow-up still had one wording gap: the skill said when browser results appear, but it did not explicitly say those populated results come from an injected manual/external browserFetchHtml path. This change locks that contract with a failing regression first, then updates the skill text to match the already-shipped README/feature-doc guidance and runtime behavior.

Constraint: clean-checkout probeAutomation() must keep browser null unless browserFetchHtml is supplied
Rejected: change runtime browser probe behavior | approved follow-up was docs/test only and runtime contract is already correct
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: keep Coupang probe docs aligned with the shipped browserFetchHtml injection contract; do not imply built-in browser probing in clean checkouts
Tested: node --test packages/coupang-product-search/test/index.test.js; npm run lint; npm run typecheck; npm test; npm run ci; live probeAutomation("생수") with direct fetch + Playwright-core browser fetch; lsp diagnostics on changed files; architect review approved
Not-tested: alternate browser engines beyond local Google Chrome + playwright-core manual runner

* Route Korean law lookups through korean-law-mcp

Add a documentation-first korean-law-search skill and lock the repo docs around the rule that Korean law queries must go through korean-law-mcp rather than a new in-repo package.

The change updates install/setup/security guidance, publishes the new feature doc, and adds regression tests so future edits keep the LAW_OC + korean-law-mcp contract intact.

Constraint: Issue #41 requires korean-law-mcp for every Korean law lookup
Constraint: Must not add a new npm or python package in this repository
Rejected: Add a repo-local law package | violates the no-new-package requirement and duplicates upstream MCP work
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep Korean law lookup guidance pinned to korean-law-mcp unless issue requirements explicitly change
Tested: node --test scripts/skill-docs.test.js
Tested: npm install -g korean-law-mcp && korean-law list && korean-law help search_law
Tested: npm run ci
Tested: npx tsc --noEmit --pretty false --project /Users/jeffrey/Projects/k-skill/tsconfig.json
Not-tested: live search_law/get_law_text against a real LAW_OC credential

* Clarify Korean law setup modes to avoid credential confusion

A review found the new korean-law-search docs treated LAW_OC as an unconditional prerequisite even though the upstream remote MCP endpoint is configured separately from the local CLI/server path. This update makes the docs and regression tests mode-specific: local CLI/MCP uses LAW_OC, while the remote endpoint stays a korean-law-mcp-only url fallback without a user-supplied credential.

Constraint: Upstream korean-law-mcp uses LAW_OC on the local CLI/server path while the documented remote MCP endpoint is configured with url only
Rejected: Keep LAW_OC mandatory for every korean-law-mcp mode | contradicts upstream docs and reviewer evidence
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep README/setup/skill docs and regression tests aligned on the local-vs-remote korean-law-mcp contract
Tested: node --test scripts/skill-docs.test.js; npm install -g korean-law-mcp && korean-law list && korean-law help search_law; npx tsc --noEmit --pretty false --project /Users/jeffrey/Projects/k-skill/tsconfig.json; npm run ci; lsp_diagnostics scripts/skill-docs.test.js
Not-tested: Live remote MCP endpoint connection against https://korean-law-mcp.fly.dev/mcp

* Record issue #41 follow-up after approved verification

The approved korean-law-search change was already present on feature/#41, so this follow-up records a fresh verification pass and updates the PR without introducing unnecessary code churn.

Constraint: Existing branch already contained the approved implementation
Rejected: Touch repo files without need | would create noise without improving behavior
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the korean-law-mcp-only + mode-specific LAW_OC contract aligned with upstream docs before changing these surfaces again
Tested: node --test scripts/skill-docs.test.js; npx tsc --noEmit --pretty false --project /Users/jeffrey/Projects/k-skill/tsconfig.json; npm install -g korean-law-mcp && korean-law list && korean-law help search_law; npm run ci
Not-tested: Live korean-law queries that require a real LAW_OC or remote endpoint session

* Keep the Korean law feature diff merge-ready

Verification uncovered trailing whitespace in the new korean-law feature guide when checking the branch diff. I added a regression to the skill docs suite and removed the whitespace so the approved documentation contract stays clean and reviewable.

Constraint: Preserve the existing korean-law-mcp + mode-specific LAW_OC contract without widening scope
Rejected: Leave the whitespace issue as-is | git diff --check stayed dirty on the branch
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep docs/features/korean-law-search.md free of trailing whitespace so diff hygiene stays enforced by the regression
Tested: node --test scripts/skill-docs.test.js; npx tsc --noEmit --pretty false --project /Users/jeffrey/Projects/k-skill/tsconfig.json; npm install -g korean-law-mcp && korean-law list && korean-law help search_law; npm run ci; git diff --check
Not-tested: Live credentialed LAW_OC search execution against the upstream API

* Keep Korean law skill guidance aligned with supported lookups

The approved issue #41 feature already worked, but the shipped skill text still under-described ordinance and interpretation lookups compared with the documented capability set. This follow-up tightens the skill copy and locks that contract with a doc regression so the branch stays merge-ready without changing the underlying korean-law-mcp routing rules.

Constraint: Must preserve the existing korean-law-mcp-only and mode-specific LAW_OC setup contract
Rejected: Leave the skill wording as-is | drift from the documented lookup surface would remain
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the skill examples and doc regression aligned whenever supported korean-law-mcp search surfaces change
Tested: node --test scripts/skill-docs.test.js
Not-tested: live LAW_OC-backed upstream API queries

* Keep Korean law completion guidance in sync with enforced lookups

The previous follow-up aligned the main skill examples, but the done-criteria text still left interpretation and ordinance routing implicit. This commit makes the completion checklist explicit and extends the doc regression so future edits cannot silently drop those lookup paths.

Constraint: Must stay within the existing issue #41 korean-law-mcp-only contract
Rejected: Leave the done checklist implicit | reviewers and future edits could drift from the enforced lookup set
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: When the supported Korean-law lookup set changes, update the done checklist and regression together
Tested: node --test scripts/skill-docs.test.js
Not-tested: full CI before commit

* Enable repeatable used-car price lookups from a rental-company source

Issue #46 required surveying major Korean rental companies before implementation and then choosing the easiest stable provider. SK렌터카 다이렉트 exposes 타고BUY inventory in public Next.js page data, so the feature stays dependency-free while still supporting live repeated lookups and documented provider rationale.

Constraint: Must compare major Korean rental companies before implementation
Constraint: Must verify 10+ live lookups against a real provider surface
Rejected: 롯데오토옥션 as v1 provider | public list contract was unstable and legacy .do flows returned inconsistent or 404 pages
Rejected: 레드캡렌터카 as v1 provider | no public used-car inventory or API surface was found
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep the v1 provider read-only and inventory-snapshot-based unless a stable documented public API is confirmed
Tested: npm run ci
Tested: Live 10-query run against https://www.skdirect.co.kr/tb at 2026-04-02T07:22:46Z
Tested: LSP diagnostics on affected files
Not-tested: Seller-specific detail drilldowns or non-SK providers
Related: #46

* Keep used-car-price-search releasable after merge

Review feedback found that the new used-car workspace would not ship because it lacked a changeset, and the fallback install docs still omitted the runtime package. This follow-up adds regression coverage first, then restores both release and install-path coverage with the smallest possible diff.

Constraint: New publishable workspaces must be wired through Changesets to reach npm release automation
Constraint: Fallback install docs must list runtime packages users need when skill files are present but global packages are missing
Rejected: Fix only the changeset gap | would leave the documented fallback install path incomplete
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep used-car-price-search in both the fallback global install example and a Changesets entry whenever release wiring changes
Tested: node --test scripts/skill-docs.test.js; npx changeset status; live 10-query used-car run against SK direct at 2026-04-02T07:38:44.949Z; npm run ci; LSP diagnostics on used-car package files and scripts/skill-docs.test.js; architect verification
Not-tested: No additional live provider permutations beyond the verified 10-query smoke run

* Keep used-car verification docs resilient to live inventory churn

A fresh rerun showed the SK direct inventory total continues to move during the day, so the feature doc now records the verified smoke-run timestamp without freezing a brittle exact count. The regression suite was updated first so the docs stay variability-aware and diff-clean in future follow-ups.

Constraint: Live SK direct inventory totals change over time even when the parsing contract stays stable
Rejected: Keep documenting a fixed total from one smoke run | it immediately drifted and made the docs stale
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the used-car live verification note timestamped and variability-aware instead of pinning an exact inventory total
Tested: node --test scripts/skill-docs.test.js; git diff --check; npm run ci; npx changeset status; live 10-query run against https://www.skdirect.co.kr/tb at 2026-04-02T07:59:41.391Z; LSP diagnostics on scripts/skill-docs.test.js; architect verification
Not-tested: No additional content changes outside the used-car feature doc

* Keep used-car query summaries honest when results are limited

The review found that query-level stats were being computed from the
post-limit slice, so common searches like 아반떼 under-reported both
match counts and price ranges. This change locks the regression first,
then computes the full filtered set before slicing only the returned
items.

Constraint: PR #48 must preserve the existing API shape while fixing the review-blocking stats bug
Rejected: Expanding the response with separate limited/full summary fields | unnecessary API churn for a targeted bug fix
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Query-level summary and matchedCount must stay derived from the full filtered set, not the display limit slice
Tested: npm test --workspace used-car-price-search; npm run ci; git diff --check; npx changeset status; live SK direct smoke run on 2026-04-02T08:23:59Z; LSP diagnostics on used-car-price-search source and test files; architect verification APPROVED
Not-tested: limit=0 semantics remain unchanged and still coerce to the default limit
Related: PR #48

* Keep korean-law-search available during upstream outages (#45)

Issue #44 adds Beopmang as the documented fallback when the primary korean-law-mcp path is unavailable, and locks the new routing into the doc regression suite so future edits do not silently revert the policy.

Constraint: Existing guidance must still prefer korean-law-mcp and keep LAW_OC scoped to the local CLI/MCP path
Rejected: Add a repo-local Beopmang client package | issue only requires fallback registration, not a new implementation surface
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the primary-first rule intact; Beopmang is an outage fallback, not the default path
Tested: node --test scripts/skill-docs.test.js; npm run ci; korean-law list; python3 live Beopmang search smoke for 관세법
Not-tested: Live Beopmang MCP handshake from a GUI MCP client

* Replace coupang scraping package with coupang-mcp server integration

Drop the browser-based scraping package (packages/coupang-product-search)
and switch to the uju777/coupang-mcp MCP server for all Coupang product
searches. This removes the anti-bot workaround complexity and provides
8 ready-to-use tools via MCP Streamable HTTP with no API key required.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Add disclaimer to used-car-price-search README

Clarify that the package queries SK렌터카 타고BUY public data with
no affiliation or sponsorship, and that ad/partnership inquiries
are welcome.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Refactor used-car-price-search into provider-based architecture

SK 타고BUY 전용 로직을 src/providers/sk-tagobuy.js로 분리하고,
공급자를 수평 확장할 수 있는 registry 구조로 전환한다.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Ship LCK analytics inside k-skill's managed release flow

Adapt jerjangmin's upstream lck-analytics skill/package into a new
workspace and skill pack, wire docs/install/release surfaces, and add
regression fixtures/tests plus script smoke coverage so the feature is
verifiable before publish.

Constraint: Upstream package is not published to npm yet
Constraint: Must preserve attribution to original source and author in shipped docs
Rejected: Keep the upstream lck-results install wording in k-skill | conflicts with repo workspace/package naming
Rejected: Ship only the npm package without the local skill scripts | issue explicitly requested the skill as well
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep Changesets as the only version-bump path for lck-analytics; do not hand-edit versions for release
Tested: node --test packages/lck-analytics/test/index.test.js scripts/skill-docs.test.js
Tested: npm run lint --workspace lck-analytics
Tested: npm test --workspace lck-analytics
Tested: live getLckSummary('2026-04-01', { team: '한화', includeStandings: true })
Tested: node lck-analytics/scripts/sync-oracle.js --csv lck-analytics/samples/oracle-lck-sample.csv --cache .tmp/lck-cache && node lck-analytics/scripts/build-match-report.js --date 2026-04-01 --team 한화 --cache .tmp/lck-cache && node lck-analytics/scripts/analyze-live-game.js --game game-1 --window packages/lck-analytics/test/fixtures/live-window-game-1.json --details packages/lck-analytics/test/fixtures/live-details-game-1.json --cache .tmp/lck-cache
Tested: npm run ci
Tested: npx tsc --noEmit --project /Users/jeffrey/Projects/k-skill/tsconfig.json
Not-tested: Riot live feed behavior for arbitrary future game ids outside the fixture-backed smoke path

* Enable policy-aware Korean spell checking from the official Nara surface

Add a skill guide and Python helper that use the approved old_speller HTML flow, chunk long text conservatively, and report original/suggestion/reason deltas. The docs also record the public-site limits, Cloudflare behavior, and non-commercial usage policy so agents do not overreach the free surface.

Constraint: Public site is HTML-only and may return 403 to non-browser clients
Constraint: Must not add new dependencies or high-volume crawling behavior
Rejected: Node fetch client | Cloudflare returned 403 in this environment
Rejected: Paid API integration | no public contract or credentials were available for this task
Confidence: medium
Scope-risk: moderate
Reversibility: clean
Directive: Keep usage low-rate and non-commercial unless supplier-approved API terms are added
Tested: npm run lint
Tested: npm run typecheck
Tested: npm test
Tested: npm run build
Tested: python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json
Tested: python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json
Not-tested: Paid API/order flow
Not-tested: High-volume commercial workloads
Not-tested: Non-UTF-8 file inputs

* Preserve korean spell-check layout in corrected output

The Nara payload can collapse paragraph separators into normalized page text, so the helper now maps corrections back onto the original chunk before rebuilding corrected_text. The CLI also rejects non-positive --max-chars values, and regression tests cover both the layout-preservation path and invalid argument handling.

Constraint: Nara result pages can normalize blank lines and sentence spacing before exposing errInfo offsets
Rejected: Narrow docs away from file/Markdown proofreading | preserving original chunk separators keeps the documented workflow intact
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep multiline separator preservation tied to the original chunk whenever a suggestion only changes whitespace across collapsed boundaries
Tested: python3 -m unittest scripts.test_korean_spell_check; npm run lint; npm run typecheck; npm test; npm run build; python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json; python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json; python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n왠지 않되요.' --format json; python3 scripts/korean_spell_check.py --text 테스트 --max-chars 0 --format json
Not-tested: Live multi-page Nara payloads with separator-sensitive corrections across multiple returned pages

* Preserve file-proofreading layout in Korean spell check output

The spell-check helper now keeps exact blank-line runs and paragraph
indentation when chunking and reassembling file-style input, while still
allowing the official service's spacing corrections to flow through.
Regression coverage now locks the collapsed-layout, triple-blank-line,
and cross-boundary spacing cases that triggered the PR review.

Constraint: Official Nara/PNU payloads can collapse layout and sometimes merge corrections across preserved paragraph boundaries
Rejected: Narrow docs away from file-level proofreading | the existing feature scope explicitly supports file and Markdown checks
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Preserve exact layout tokens only at original newline boundaries; do not reintroduce global strip/join normalization without real file-mode verification
Tested: npm run lint; npm run typecheck; npm test; npm run build; python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json; python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json; python3 scripts/korean_spell_check.py --file <tmpfile> --format json; python3 scripts/korean_spell_check.py --text 테스트 --max-chars 0 --format json
Not-tested: Live upstream behavior for extremely long leading/trailing-whitespace-only files
Related: PR #60

* Keep layout-preserving chunk splits from tripping on separator-boundary fixes

The follow-up layout preservation pass renamed the working unit variable, but one separator-length guard still referenced the old paragraph name. This commit finishes the refactor so exact-boundary paragraph chunks keep the preserved-separator logic reachable and the new regression coverage stays green.

Constraint: Must preserve the existing feature/#47 branch and PR #60 flow while fixing the approved review follow-up
Rejected: Revert the layout-preserving chunking refactor | would discard the verified file-layout fix and its regressions
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep chunk reassembly lossless; new chunking changes should continue to round-trip original separators byte-for-byte
Tested: npm run lint; npm run typecheck; npm test; npm run build; python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json; python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json; python3 scripts/korean_spell_check.py --file <tempfile-with-triple-blank-lines> --format json; python3 scripts/korean_spell_check.py --text 테스트 --max-chars 0 --format json (expected argument error)
Not-tested: Live multi-page service responses beyond the local smoke cases

* Preserve spell-check chunk separators when units overflow

The file-layout follow-up already preserved blank runs and indentation,
but the overlong-unit path still checked the wrong variable when
extracting a trailing separator. That could strand separators in
a standalone chunk and break exact reassembly for tight max-char
limits.

This commit fixes the separator extraction guard and locks the
behavior with a regression that proves chunk concatenation still
matches the original text when an overlong paragraph is followed
by a blank-line separator.

Constraint: File-mode reconstruction must preserve exact layout while chunking long input
Rejected: Broader chunking rewrite | existing structure only needed the overflow guard corrected
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep split_text_into_chunks chunk concatenation identical to the original input for layout-sensitive file checks
Tested: npm run lint; npm run typecheck; npm test; npm run build; python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json; python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json; python3 scripts/korean_spell_check.py --text 테스트 --max-chars 0 --format json; manual --file smoke with triple blank lines and indentation
Not-tested: Live-service failure modes such as Cloudflare/browser challenges

* Prevent clean spell-check chunks from crashing mixed file runs

The Nara/PNU surface can return a plain 'no issues found' HTML page
without the embedded result payload when a chunk is already clean.
Chunked file and markdown runs could hit that response on earlier
chunks, raise a ValueError, and never reach later chunks that still
needed corrections. Treat the no-issues page as an empty result set
and lock the behavior with narrow regression coverage.

Constraint: Upstream no-issue responses omit the JavaScript payload entirely and can split the status message across HTML whitespace
Rejected: Fabricate a synthetic result page | empty-page handling already preserves the original clean chunk
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep empty-result detection aligned with the upstream no-issues message unless the service publishes a structured empty payload contract
Tested: python3 -m unittest scripts.test_korean_spell_check
Tested: npm run lint
Tested: npm run typecheck
Tested: npm test
Tested: npm run build
Tested: python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json
Tested: python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json
Tested: python3 scripts/korean_spell_check.py --text 테스트 --max-chars 0 --format json
Tested: python3 scripts/korean_spell_check.py --file <tmpfile> --format json
Tested: python3 scripts/korean_spell_check.py --file <tmpfile> --max-chars 18 --format json
Not-tested: Other upstream empty-result templates beyond the current no-issues HTML wording

* Make Joseon Sillok lookups reproducible from the official site

Add a joseon-sillok-search skill and a Python helper that scrape the
official Joseon Annals search/detail pages. The helper normalizes
king/year metadata, fetches detail excerpts, and locks the repository
docs plus regression coverage around the shipped workflow.

Constraint: v1 must stay on official public HTML surfaces only
Constraint: Must avoid adding new dependencies for a simple scraping helper
Constraint: Shell connectivity to sillok.history.go.kr became intermittent during final live reruns
Rejected: Ship a new npm workspace | repo skill/docs pattern is enough for v1
Rejected: Add BeautifulSoup or another parser dependency | unnecessary for the bounded HTML patterns
Confidence: medium
Scope-risk: narrow
Reversibility: clean
Directive: Keep year filtering Gregorian and derived from official regnal metadata unless the upstream site exposes a better structured contract
Tested: npm run ci
Tested: Earlier live POST/detail probes against search/searchResultList.do and /id/kda_12512030_002 during implementation
Tested: Live official article inspection for kda_12512030_002 via the public site
Not-tested: Final end-to-end CLI live run after the last refactor, because the shell hit transient TCP timeouts to sillok.history.go.kr
Related: #59

* Prevent sillok follow-up fixes from missing filtered results or weakening trust

This follow-up addresses the blocking PR review items on the Joseon Sillok helper. The search loop now derives total pages from the first live page size so king/year filtering can reach later pages, TLS verification stays enabled on both requests and urllib paths, detail parsing accepts the live classification brackets, and repo docs stop linking to missing Korean spell-check assets on this branch.

Constraint: Must preserve the existing joseon-sillok-search surface and avoid new dependencies
Rejected: Keep verify=False behind an implicit fallback | still weakens authenticity for the default path
Rejected: Infer pagination from the hardcoded 50-row default | misses valid later-page matches when the site serves smaller pages
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If the official site changes page size or pagination markup again, update the first-page pagination regression before altering search_sillok pagination math
Tested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_sillok_search
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: python3 scripts/sillok_search.py --query '훈민정음' --king 세종 --year 1443 --limit 1 --timeout 20
Tested: python3 - <<'PY' ... fetch_detail_page(..., article_id='kda_12512030_002', timeout=20) ... PY
Not-tested: Successful live sillok.history.go.kr responses in this environment (POST and detail GET both timed out at 20s)
Related: PR #62

* Make joseon-sillok-search installs work outside the repo

The skill installer only ships the skill directory, so the sillok helper has to live inside that payload. This moves the authoritative helper into joseon-sillok-search/scripts/, keeps the repo-root script as a thin shim for tests and docs, and trims footer metadata from parsed article bodies so excerpts match the published examples.

Constraint: skills add exposes only the installed skill payload, not repo-root helpers

Rejected: Keep the helper only under scripts/ | installed skill commands fail after copy-only installs

Confidence: high

Scope-risk: narrow

Directive: Keep the repo-root sillok wrapper thin and update the bundled helper first when behavior changes

Tested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_sillok_search

Tested: node --test scripts/skill-docs.test.js

Tested: temp installed-skill smoke run of python3 scripts/sillok_search.py --help plus footer-cleaning parse_detail_page check

Tested: npm run ci

Not-tested: live sillok.history.go.kr shell requests still hit connect timeouts in this environment

* Restore the sillok CLI's verified stdlib transport fallback

The helper already shipped a stdlib urllib opener that can reach the
official search endpoint in environments where requests/urllib3 aborts.
This change keeps that opener available even when requests imports
successfully and falls back to it on retryable requests transport
failures. Added regression coverage for opener availability and the
requests-to-urllib fallback so the default CLI path matches the live
verified behavior.

Constraint: Official sillok detail GETs can still time out transiently in this environment
Constraint: Keep TLS verification enabled and preserve the documented CLI entrypoints
Rejected: Force urllib for every request | keep the existing requests fast path when it succeeds
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Preserve the stdlib fallback tests whenever the transport layer changes
Tested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_sillok_search
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: live forced-fallback probe against searchResultList.do with requests.post patched to OSError(22)
Not-tested: full live CLI completion through the detail GET in this environment

* Document the approved real-estate MCP skill without vendoring upstream

Issue #53 is intentionally doc-only, so this change adds the
real-estate-search skill, feature guide, setup/security notes,
and regression coverage around the upstream real-estate-mcp
integration instead of importing server code.

The new docs keep the original MCP link, cover Codex/Claude
registration, and spell out the self-host + Cloudflare Tunnel +
launchd path for environments where no fixed hosted endpoint is
available.

Constraint: Must use tae0y/real-estate-mcp without copying its source into this repository
Constraint: Must include the original MCP link and a stable self-host fallback when no hosted endpoint is available
Rejected: Vendor the upstream MCP source | issue explicitly requires skill docs only
Rejected: Assume a public hosted MCP endpoint exists | upstream docs did not publish one
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep this integration doc-only; do not add a workspace or vendored server without revisiting issue #53 constraints
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: upstream bootstrap smoke (`uv sync`, `uv run real-estate-mcp --help`, HTTP initialize on 127.0.0.1:8017)
Not-tested: live property data queries with a valid DATA_GO_KR_API_KEY

* Prevent misleading real-estate self-host instructions

Tighten the real-estate skill docs so the launchd fallback stays operational
and the Onbid bid-result tools are described with the same WIP caveat the
upstream project still publishes.

Constraint: Upstream Docker compose already uses restart: unless-stopped while `docker compose ... up -d` daemonizes immediately
Rejected: Keep a separate server LaunchAgent with RunAtLoad + KeepAlive | launchd would restart-loop on the exiting compose command
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Mirror upstream capability caveats in k-skill docs and do not wrap daemonized server commands in launchd KeepAlive jobs
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: uv sync
Tested: uv run real-estate-mcp --help
Tested: DATA_GO_KR_API_KEY=dummy uv run real-estate-mcp --transport http --host 127.0.0.1 --port 8017
Tested: curl initialize on http://127.0.0.1:8017/mcp returned protocolVersion 2024-11-05
Not-tested: Live 거래 조회 with a real DATA_GO_KR_API_KEY

* Keep install docs from reintroducing broken launchd guidance

The install guide had drifted from the real-estate skill and feature docs and still implied that macOS launchd should auto-run both the server and tunnel. This narrows the guidance back to tunnel-only launchd ownership and extends regression coverage so docs/install.md cannot silently reintroduce the server-side loop wording.

Constraint: Upstream docker compose already uses restart: unless-stopped
Constraint: Review-round-2 scope is limited to docs and regression coverage
Rejected: Leave docs/install.md under a generic launchd presence check | it missed the exact server/터널 regression
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep docs/install.md aligned with the detailed real-estate feature guide when self-host guidance changes
Tested: node --test scripts/skill-docs.test.js; npm run ci; fresh-clone upstream smoke with uv sync, uv run real-estate-mcp --help, and HTTP initialize on 127.0.0.1:8017/mcp
Not-tested: None

* Allow Han River water-level lookups without user API keys

The proxy now resolves HRFCO water-level stations via waterlevel/info and
fetches the latest 10-minute measurement via waterlevel/list, exposing a
public summary route plus a hosted skill/docs path.

Constraint: HRFCO requires a ServiceKey and station-code-based latest lookup
Rejected: Raw passthrough only | forces users to manage upstream details and misses the approved no-key UX
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep this route summary-only and public; expand rainfall/dam/bo/fldfct in separate issues
Tested: npm run ci; local server smoke test with HRFCO sample key against /v1/han-river/water-level; tsc diagnostics on packages/k-skill-proxy/src/server.js and src/hrfco.js
Not-tested: Hosted production proxy rollout

* Expose official nearby fuel prices for location-based gas station lookups

Add the first cheap-gas-nearby skill/package pair so nearby gas-price
queries can resolve a user-supplied location, translate it into Opinet's
KATEC search contract, and return the cheapest nearby stations with
address and facility detail. The docs and setup surfaces now advertise
the new skill and its Opinet API key requirement.

Constraint: Nearby fuel prices must come from the official KNOC Opinet API when available
Constraint: No new external dependencies were allowed for coordinate conversion or location resolution
Rejected: Map-only gas price scraping | official Opinet Open API exists and is the preferred source
Rejected: Require lat/lng input only | poorer UX than supporting landmark/station queries through anchor resolution
Confidence: medium
Scope-risk: moderate
Reversibility: clean
Directive: Keep `OPINET_API_KEY` as the only supported official-price credential unless the repo adopts an Opinet proxy later
Tested: npm run ci; node --test packages/cheap-gas-nearby/test/index.test.js; offline fixture smoke via searchCheapGasStationsByLocationQuery('서울역', ...)
Not-tested: Live Opinet API call with a real `OPINET_API_KEY` (no non-placeholder key was configured locally)
Related: #54

* Explain Blue Ribbon premium gating instead of failing opaquely

Blue Ribbon's nearby endpoint now returns PREMIUM_REQUIRED for public
requests, so the package now upgrades that response into a stable domain
error and updates user-facing docs to describe the degraded nearby state.
Regression tests cover both location-query and coordinate-query entrypoints
while keeping the existing happy path intact.

Constraint: Must not attempt to bypass Blue Ribbon premium access controls
Constraint: Package releases must use Changesets metadata
Rejected: Silently return empty results on PREMIUM_REQUIRED | would hide the upstream contract change from callers
Rejected: Keep generic 403 error and docs unchanged | leaves the main failure mode opaque and misleading
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If Blue Ribbon reopens a public nearby surface later, update the docs and replace the premium_required remap only after fresh live verification
Tested: npm test --workspace blue-ribbon-nearby; npm run lint --workspace blue-ribbon-nearby; npm run ci; live node repro now returns premium_required metadata
Not-tested: Official premium-authenticated nearby flow, because no approved premium credentials are available

* Reduce duplicate premium-required test assertions

The implementation diff was already verified, so this follow-up keeps the
new regression coverage easier to read by sharing one assertion helper
instead of repeating the same predicate twice.

Constraint: Must preserve the verified PREMIUM_REQUIRED regression coverage exactly
Rejected: Leave duplicated inline predicates in both tests | repeats the same contract and adds noise to future edits
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep location-query and coordinate-query regressions aligned when the premium_required error contract changes
Tested: npm test --workspace blue-ribbon-nearby; npm run lint --workspace blue-ribbon-nearby; npm run ci
Not-tested: Additional live Blue Ribbon requests beyond the previously verified premium_required repro

* Document an Olive Young search skill around the upstream daiso CLI

Issue #61 asked for an Olive Young lookup workflow without vendoring the upstream daiso-mcp server into k-skill, so this change adds a docs-only skill, threads it through repository docs, and locks the guidance with regression assertions.

The new guidance prefers CLI-first verification (`npx daiso`) and a clone fallback (`git clone https://github.com/hmmhmmhm/daiso-mcp.git`) instead of requiring direct Claude Code MCP installation. The existing daiso-product-search skill remains untouched.

Constraint: Must mention the original https://github.com/hmmhmmhm/daiso-mcp repo
Constraint: Must avoid changing the existing daiso-product-search skill
Constraint: Must prefer upstream CLI/package usage over direct Claude Code MCP installation
Rejected: Vendor upstream Olive Young code into k-skill | contradicts the minimal-addition design request
Rejected: Make Claude Code MCP setup the default path | issue explicitly asked for CLI-first usage
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the Olive Young docs aligned with upstream `daiso` CLI behavior and note public endpoint instability when live verification shows it
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: 2026-04-05 live /tmp/daiso-mcp CLI checks for health, /api/oliveyoung/stores, /api/oliveyoung/products, /api/oliveyoung/inventory
Not-tested: Authenticated/private Olive Young flows or ordering/payment paths

* Keep Han River docs honest until hosted rollout lands

The local HRFCO proxy route already works, but the deployed public proxy still
lacks /v1/han-river/water-level as of 2026-04-05. This follow-up changes the
user-facing docs to require a self-hosted or deployment-verified proxy URL,
keeps the intended hosted path documented as rollout-pending, and locks that
caveat with a regression test.

Constraint: Hosted k-skill-proxy deployment still returns 404 for /v1/han-river/water-level on 2026-04-05
Rejected: Keep advertising the hosted route as the default live path | current deployment is not live yet
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Restore hosted-default wording only after the public proxy route and HRFCO configuration are verified live
Tested: node --test scripts/skill-docs.test.js; npm run ci; local mocked smoke via node packages/k-skill-proxy/src/server.js + GET /v1/han-river/water-level?stationName=한강대교
Not-tested: Live hosted k-skill-proxy behavior after deployment

* Keep cheap gas lookups from failing on recoverable Kakao and input issues

The lookup now walks ranked Kakao anchor candidates until one returns usable coordinates, and it rejects invalid limit inputs instead of silently collapsing non-empty results into an empty list. The regression suite now locks both review repros plus the null-coordinate normalization edge case that surfaced while implementing the fallback.

Constraint: Kakao place panels can be partially populated or return 404 for otherwise valid search candidates
Rejected: Default invalid limit/detailLimit values silently | still masks caller bugs and can look like no results nearby
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep anchor resolution tolerant of unusable Kakao panels before failing the whole lookup
Tested: node --test packages/cheap-gas-nearby/test/index.test.js; npm run ci; offline fixture smoke for searchCheapGasStationsByLocationQuery('서울역', ...)
Not-tested: Live Opinet/Kakao network path without a real OPINET_API_KEY

* Keep Olive Young install guidance aligned with live retry behavior

Issue #61's initial branch already shipped the olive-young-search skill/docs set, but the install guide did not have a regression lock for the public endpoint instability note captured during live verification. This follow-up adds that test first and updates the install quickstart so retry-or-clone fallback guidance stays in sync with the verified daiso CLI workflow.

Constraint: Must keep the follow-up scoped to the approved issue #61 docs behavior
Constraint: Public olive-young endpoint can intermittently return 5xx/503 during live verification
Rejected: Re-open the broader feature docs set | the existing branch already covered the main feature scope
Rejected: Leave the retry guidance untested in install docs | risk of future doc drift across surfaces
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If live olive-young verification changes again, update install docs and the regression together so retry/fallback guidance stays honest
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: cd /tmp/daiso-mcp && npx daiso health
Tested: cd /tmp/daiso-mcp && npx daiso get /api/oliveyoung/stores --keyword 명동 --limit 3 --json
Tested: cd /tmp/daiso-mcp && npx daiso get /api/oliveyoung/products --keyword 선크림 --size 3 --json
Tested: cd /tmp/daiso-mcp && npx daiso get /api/oliveyoung/inventory --keyword 선크림 --storeKeyword 명동 --size 2 --json
Not-tested: Authenticated Olive Young flows or sustained-rate retry behavior beyond the documented smoke checks

* Preserve ranked Kakao anchor fallbacks after panel failures

The resolver already retried later Kakao panels, but after the best panel failed it
walked the remaining candidates in raw HTML order. Centralizing the full
anchor ranking in parse.js keeps fallback iteration aligned with the
existing scoring rules and locks the review repro with a regression test
for 강남역 ordering.

Constraint: Kakao place panels can 404 or omit coordinates even when later ranked candidates are usable
Rejected: Re-rank only after a failed panel | duplicates scoring logic and drifts from selectAnchorCandidate
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep resolveAnchor fallback order tied to the shared anchor scoring logic rather than raw Kakao result order
Tested: node --test packages/cheap-gas-nearby/test/index.test.js; npm run ci; offline fixture smoke for searchCheapGasStationsByLocationQuery('서울역', ...); lsp diagnostics on affected files
Not-tested: Live Kakao/Opinet network calls with a non-placeholder OPINET_API_KEY

* Clarify that Blue Ribbon coordinate lookups fail the same way

The premium gate now affects both location-query and coordinate-entry nearby flows. Record that parity in the skill and docs so the live 2026-04-05 repro evidence stays aligned with the shipped public contract.

Constraint: /restaurants/map remains premium-gated for public requests as of 2026-04-05
Rejected: Add more code changes without a behavior gap | would add churn after the approved fix already landed
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the docs/examples aligned with live repro evidence whenever Blue Ribbon changes nearby access behavior
Tested: npm test --workspace blue-ribbon-nearby; npm run lint --workspace blue-ribbon-nearby; npm run ci; live node repro for location + coordinates premium_required contract
Not-tested: New upstream success-path live nearby payloads, because public access is still premium-gated

* Document a runnable Olive Young clone fallback

The follow-up review found that clone-local `npx daiso` commands do not run from a built `hmmhmmhm/daiso-mcp` checkout because the generated bin file is not executable. This updates the olive-young skill and docs to use the verified `node dist/bin.js ...` path for clone fallback, and locks that behavior with regression tests while keeping the public CLI-first path unchanged.\n\nConstraint: Upstream clone checkouts can fail with `Permission denied` when invoked through clone-local `npx daiso`\nConstraint: Keep the existing daiso-product-search skill untouched\nRejected: Leave install docs unchanged | PR body and docs would keep a broken clone fallback path\nRejected: Vendor or patch upstream daiso-mcp inside k-skill | issue explicitly requires documenting upstream flow instead\nConfidence: high\nScope-risk: narrow\nReversibility: clean\nDirective: Keep clone-fallback docs and PR verification commands aligned with the actually runnable local invocation\nTested: node --test scripts/skill-docs.test.js\nTested: npm run ci\nTested: cd /tmp/daiso-mcp && npm install && npm run build && node dist/bin.js health\nTested: cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/stores --keyword 명동 --limit 3 --json\nTested: cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/products --keyword 선크림 --size 3 --json\nTested: cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/inventory --keyword 선크림 --storeKeyword 명동 --size 2 --json\nNot-tested: Public npx endpoint stability beyond the verified smoke-test window

* Record fresh verification for the approved cheap-gas follow-up

The ranked Kakao fallback-order and invalid-limit fixes are already present on feature/#54, so no further code edits were necessary. This empty follow-up commit records the requested rerun verification for PR #67 before posting the implementation update.\n\nConstraint: Existing branch head already contains the approved cheap-gas-nearby follow-up\nRejected: Invent another code/doc change just to force a non-empty diff | unnecessary risk after approval\nConfidence: high\nScope-risk: narrow\nReversibility: clean\nDirective: Keep Kakao anchor fallback iteration tied to the shared ranking helper and preserve finite-number validation for limit inputs\nTested: node --test packages/cheap-gas-nearby/test/index.test.js; npm run ci; offline fixture smoke for searchCheapGasStationsByLocationQuery('서울역', ...); lsp diagnostics on affected files\nNot-tested: Live Kakao/Opinet network calls with a non-placeholder OPINET_API_KEY

* Record verified delivery for approved cheap-gas-nearby rollout

The approved Issue #54 implementation was already present on feature/#54 when this follow-up began, so this checkpoint records fresh verification evidence and keeps PR #67 moving without introducing extra code churn.

Constraint: Existing approved fixes were already on the branch and the follow-up still required a pushed update plus implementation comment
Rejected: Add additional code or docs churn | approved scope was already satisfied and green locally
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Preserve the Kakao fallback-order and invalid-count regression coverage unless equivalent runtime repros replace it
Tested: node --test packages/cheap-gas-nearby/test/index.test.js; offline fixture smoke for searchCheapGasStationsByLocationQuery('서울역', ...); npm run ci; LSP diagnostics on affected cheap-gas-nearby files
Not-tested: Live Kakao/Opinet network path without a non-placeholder OPINET_API_KEY

* Keep Blue Ribbon premium-gate remapping explicitly bounded

The issue #63 fix already remaps PREMIUM_REQUIRED for nearby lookups. This follow-up adds a regression that proves non-premium /restaurants/map failures still surface as generic request errors, and clarifies that boundary in the package and feature docs.

Constraint: PR #68 already carries the shipped behavior change and must stay aligned with current live premium-gated upstream behavior
Rejected: Broaden domain remapping to all 403 nearby errors | would hide distinct upstream failure modes
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep only PREMIUM_REQUIRED on /restaurants/map mapped to premium_required unless a new verified upstream contract is documented
Tested: npm test --workspace blue-ribbon-nearby; npm run lint --workspace blue-ribbon-nearby; npm run ci; live node repro for location+coordinate nearby lookups on 2026-04-06; LSP diagnostics on src/test
Not-tested: Alternative non-premium upstream status codes beyond the mocked 403 ACCESS_DENIED path

* Keep Olive Young clone fallback docs runnable from a fresh clone

Round 3 review found that the inline fallback shorthand skipped `cd daiso-mcp`,
which made `npm install` run outside the cloned upstream repo even though the
fenced examples were already correct. This tightens the two published shorthand
snippets and adds regression coverage so both docs surfaces reject the broken
chain in future edits.

Constraint: Existing node dist/bin.js clone fallback examples were already verified and had to stay aligned
Constraint: The follow-up had to stay scoped to docs/test coverage without touching the existing daiso-product-search skill
Rejected: Convert the shorthand to prose only | the review specifically requested a runnable inline form or an explicit removal
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep inline clone fallback snippets synchronized with the fenced node dist/bin.js examples and require `cd daiso-mcp` before install/build
Tested: node --test scripts/skill-docs.test.js; npm run ci; cd /tmp/daiso-mcp && npm install && npm run build; cd /tmp/daiso-mcp && node dist/bin.js health; cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/stores --keyword 명동 --limit 3 --json; cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/products --keyword 선크림 --size 3 --json; cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/inventory --keyword 선크림 --storeKeyword 명동 --size 2 --json
Not-tested: Public npx path in this follow-up round (previous PR verification already covered it)
Related: PR #71

* Bundle korean-spell-check script inside skill directory for packageless installs

The Python helper lived only in the repo-root scripts/ folder, so
`skills add` never shipped it. Move the real implementation into
korean-spell-check/scripts/ (mirroring joseon-sillok-search) and
replace the root copy with a thin re-export wrapper so lint/test
still resolve from the repo root.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Exclude .claude directory from skill validation to fix CI in worktrees

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* real-estate-search: MCP self-host → k-skill-proxy HTTP 전환

사용자가 직접 real-estate-mcp를 clone/self-host하는 대신
k-skill-proxy에 MOLIT 실거래가 API route를 추가해서
다른 스킬(한강수위, 미세먼지 등)과 동일한 패턴으로 사용 가능하게 함.

- GET /v1/real-estate/region-code — 지역코드 검색
- GET /v1/real-estate/:assetType/:dealType — 9개 거래 유형 조회
- molit.js: XML 파싱, 필드 정규화, 취소거래 필터링, 요약통계
- region-lookup.js: 284개 법정동 5자리 코드 토큰 매칭
- SKILL.md/docs를 HTTP proxy 기반으로 전면 재작성
- DATA_GO_KR_API_KEY를 사용자 필수항목에서 프록시 운영자 전용으로 이동

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* cheap-gas-nearby: proxy 경유로 전환 + 프로덕션 배포 구조 문서화

- cheap-gas-nearby: OPINET_API_KEY 없이 k-skill-proxy 경유로 동작하도록 변경
  - fetchAroundStations/fetchDetailById에 proxy fallback 추가
  - SKILL.md, feature doc에서 사용자 API key 요구 제거
- AGENTS.md, CLAUDE.md: proxy 개발/배포 워크플로우 문서화
- docs/features/k-skill-proxy.md: opinet route 추가, 프로덕션 자동 배포 구조 설명

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* blue-ribbon-nearby: proxy 경유로 전환 + 프리미엄 세션 프록시 라우트

Blue Ribbon /restaurants/map이 프리미엄 전용으로 전환되어,
k-skill-proxy에 BLUE_RIBBON_SESSION_ID 기반 프록시 라우트를 추가하고
blue-ribbon-nearby 패키지가 기본적으로 프록시를 경유하도록 변경.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Document a bunjang-cli workflow for Bunjang marketplace lookups

Issue #73 adds a new root skill plus matching repository docs/tests so agents can guide 번개장터 searches, detail reads, optional login-gated favorites/chats, and bulk export flows without vendoring upstream code.

Constraint: Reuse upstream bunjang-cli instead of adding a new crawler or package wrapper
Constraint: favorite/chat flows require a headful TTY login and cannot be fully smoke-tested non-interactively
Rejected: Add a local workspace/package wrapper | docs-only skill scope does not justify new abstraction
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the skill CLI-first and treat favorite/chat/purchase as optional login flows unless a live authenticated session is intentionally available
Tested: npm run ci
Tested: npx --yes bunjang-cli --help
Tested: npx --yes bunjang-cli --json auth status
Tested: npx --yes bunjang-cli --json search "아이폰" --max-items 1
Tested: npx --yes bunjang-cli --json item get 354957625
Tested: bunjang-cli JSON/AI export smoke runs with --with-detail/--output and --ai --output
Not-tested: Authenticated favorite/chat round-trip in a logged-in interactive browser session

* Prevent Bunjang agents from trusting noisy search summaries

Live bunjang-cli verification showed that search results are safe for title/price triage, but not for reliable description/status/location decisions. This update tightens the skill and feature docs around that boundary and locks it with a regression assertion so future edits do not drift back into summary-field assumptions.

Constraint: bunjang-cli search summary fields are transport-dependent and can omit or mangle description/status/location
Rejected: Keep region/status-first shortlist guidance in search docs | live verification returned malformed location and missing description/status
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Do not reintroduce description/status/location-based search triage without re-verifying the live search payload contract
Tested: node --test scripts/skill-docs.test.js; npm run ci; npx --yes bunjang-cli --help; npx --yes bunjang-cli --json auth status; npx --yes bunjang-cli --json search "아이폰" --max-items 1; npx --yes bunjang-cli --json item get 354957625; npx --yes bunjang-cli search "아이폰" --max-items 2 --with-detail --output /tmp/bunjang-73-export-XXXXXX.json; npx --yes bunjang-cli search "아이폰" --max-items 2 --with-detail --ai --output /tmp/bunjang-73-ai-Xp2bu0
Not-tested: Authenticated favorite/chat round-trip still requires an interactive TTY login session

* Shield Korean stock lookups behind the proxy so users avoid KRX key setup

Use the local k-skill proxy as the public integration surface for Korea Exchange stock data. The new skill/docs flow starts with stock search, then narrows into base-info and trade-info lookups, while the proxy injects KRX credentials server-side only.

Constraint: KRX_API_KEY must remain server-side under the repo's free read-only proxy policy
Rejected: Expose upstream korea-stock-mcp setup to end users | would force user-issued KRX keys and a heavier install path
Confidence: high
Scope-risk: moderate
Directive: Keep Korean stock access proxy-first and read-only; do not move KRX_API_KEY into client setup docs
Tested: node --test packages/k-skill-proxy/test/server.test.js scripts/skill-docs.test.js; npm run ci; local proxy smoke on port 4120 (/health 200, /v1/korean-stock/search 503 without KRX_API_KEY)
Not-tested: Live upstream success path with a real local KRX_API_KEY

* Protect KRX proxy responses from insecure or mismatched upstream data

Review follow-up switches KRX endpoints to HTTPS and removes the single-row trade fallback that could mislabel an unrelated stock as the requested code. Regression tests now assert HTTPS transport for search/base/trade flows and 404 behavior for unmatched single-row trade responses.

Constraint: KRX key must never leave the proxy over plaintext transport
Constraint: No local KRX_API_KEY is available for live upstream success verification
Rejected: Keep the single-row trade fallback with a synthetic code rewrite | can fabricate mismatched stock responses
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Do not reintroduce inferred short-code fallbacks without validating them against matching base-info first
Tested: node --test packages/k-skill-proxy/test/server.test.js scripts/skill-docs.test.js; npm run ci; local proxy smoke on 127.0.0.1:4120 (/health 200, /v1/korean-stock/search?q=삼성전자&bas_dd=20260404 503 without KRX_API_KEY); lsp diagnostics on changed files
Not-tested: live KRX upstream success path with a real KRX_API_KEY

* Close the remaining Korean stock proxy gaps before public rollout

The KRX search route trusted spoofable direct-request IP headers and
failed whole-search fanout when one market snapshot errored. This
change keys rate limits off Fastify's resolved request.ip, reuses
per-market base snapshots across same-date queries, and keeps healthy
search results available when one market fails while new regressions
lock the behavior.

Constraint: Public proxy routes stay unauthenticated, so abuse controls must not depend on untrusted client headers
Constraint: Search must still fail loudly when every market fetch fails
Rejected: Keep trusting cf-connecting-ip on direct requests | direct clients can rotate the header to reset the bucket
Rejected: Promise.all all-or-nothing market fanout | one failing market hid healthy KOSPI results
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep Korean stock rate limiting keyed to Fastify-resolved client IP unless trust-proxy behavior is explicitly audited end-to-end
Tested: node --test packages/k-skill-proxy/test/server.test.js scripts/skill-docs.test.js; npm run ci; local proxy smoke on 127.0.0.1:4120; targeted inject script for spoofed-header rate limiting and partial-market failure
Not-tested: Live KRX success with a real KRX_API_KEY

* Feature/#57 (#69)

* Hide KMA forecast keys behind the public proxy

Add a Korea weather skill plus a new k-skill-proxy route for the
KMA short-term forecast API. The route accepts grid coordinates or
lat/lon, defaults to the latest safe base time, and keeps the user
workflow keyless while the proxy owns the upstream credential.

Constraint: KMA short-term forecast requires a service key and 5km grid parameters
Constraint: Must keep the client flow dependency-free and free of user API issuance
Rejected: Direct client-side KMA calls | would force end users to issue and store their own keys
Rejected: Add a third-party geocoder dependency | unnecessary for v1 when lat/lon-to-grid conversion is enough
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep future KMA integrations on explicit allowlisted proxy routes and avoid documenting upstream keys for clients
Tested: npm run ci; local HTTP smoke test for /v1/korea-weather/forecast with mocked upstream fetch; npx tsc --noEmit --pretty false --project /Users/jeffrey/Projects/k-skill/tsconfig.json
Not-tested: Live KMA upstream call with a production service key

* Reject invalid weather coordinates before proxying

The korea-weather forecast route already normalized valid lat/lon requests, but
out-of-range inputs could still project to NaN grid values and consume upstream
quota. This change rejects invalid latitude/longitude pairs at the boundary and
locks the behavior with a regression that proves invalid coordinates never
reach fetch.

Constraint: Public proxy endpoints must reject malformed caller input before upstream calls
Rejected: Allow upstream KMA failures to handle invalid coordinates | wastes quota and returns opaque errors
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep coordinate validation local to query normalization so cache keys and upstream URLs only see finite grid values
Tested: node --test packages/k-skill-proxy/test/server.test.js; local app.inject smoke for /v1/korea-weather/forecast with mocked fetch; npx tsc --noEmit --pretty false --project /Users/jeffrey/Projects/k-skill/tsconfig.json; npm run ci
Not-tested: Live KMA upstream behavior for invalid coordinates (intentionally blocked before proxying)

* Protect proxied public-data API keys with HTTPS transport

The new korea-weather route already hid the KMA service key behind the proxy, but
it was still forwarding that key over plaintext HTTP. Switching the shared
data.go.kr proxy base URL to HTTPS keeps the existing request contract intact
while closing the transport exposure. Regression coverage now locks the weather
route and direct KMA proxy helper to HTTPS so the downgrade does not regress.

Constraint: The follow-up had to stay minimal on the existing PR branch
Rejected: Add a KMA-only base URL constant | unnecessary divergence from the shared data.go.kr transport setting
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep proxied upstreams on HTTPS whenever the provider supports it, especially when operator-managed keys are injected server-side
Tested: node --test packages/k-skill-proxy/test/server.test.js; local buildServer().inject smoke with mocked fetch; npx tsc --noEmit --pretty false --project /Users/jeffrey/Projects/k-skill/tsconfig.json; npm run ci
Not-tested: Live network call to KMA upstream with a real service key

* Merge dev into main: new skills & proxy enhancements (#72)

* Remove client-side Seoul subway key setup

Route Seoul subway arrival lookups through k-skill-proxy so the
hosted proxy owns the Seoul Open Data upstream key and end users
only need the proxy base URL. Add proxy route coverage, update
skill/docs guidance, and align setup materials with the hosted
proxy flow used for fine dust.

Constraint: Must keep the proxy public, read-only, and dependency-free
Constraint: Must satisfy TDD-first verification and ship on feature/#35 targeting dev
Rejected: Add a separate client helper package | unnecessary extra layer for a single proxy route
Rejected: Keep SEOUL_OPEN_API_KEY as an end-user requirement | defeats the issue goal
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep the Seoul subway proxy surface limited to station-arrival passthrough unless tests/docs expand the contract
Tested: npm run ci; local proxy runtime on 127.0.0.1:4120 for /health and /v1/seoul-subway/arrival on 2026-03-31 with an invalid upstream key
Not-tested: Live success response with a valid Seoul Open API key
Related: #35

* Prevent broken Seoul subway proxy defaults before hosted rollout

The Seoul subway proxy endpoint code is present locally, but the hosted public route is not live yet. This change turns the user-facing subway docs back into an explicit proxy configuration flow, replaces the misleading hosted default in setup examples, and keeps subway proxy examples on self-host/local URLs until rollout is verified.

Constraint: Hosted k-skill-proxy.nomadamas.org/v1/seoul-subway/arrival is not live yet
Rejected: Keep the hosted Seoul subway URL as the default path | would send default users to a 404 route
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Do not restore a hosted Seoul subway default until the public proxy route is deployed and smoke-verified
Tested: node --test scripts/skill-docs.test.js; npm run ci; local proxy smoke on 127.0.0.1:4120 with stubbed Seoul upstream (GET /health, GET /v1/seoul-subway/arrival?stationName=강남)
Not-tested: Live hosted proxy smoke after deployment

* Reduce Seoul subway proxy upstream pressure with request caching

The public subway arrival route already normalized caller input but still re-fetched the Seoul upstream for every identical poll. This change adds a short-TTL cache keyed from the normalized subway query, annotates JSON responses with cache metadata, and locks the repeated-read collapse with a regression that proves alias/default normalization still reuses the cached result.

Constraint: The endpoint must stay public/no-auth while protecting a shared server-side SEOUL_OPEN_API_KEY from unnecessary repeated upstream hits
Rejected: Add a new shared cache abstraction for proxy metadata | unnecessary for this narrow fix and would enlarge the diff
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep subway cache keys tied to normalizeSeoulSubwayQuery output so alias/default query forms continue collapsing to one upstream request
Tested: node --test packages/k-skill-proxy/test/server.test.js
Tested: npm run ci
Tested: Local proxy runtime on 127.0.0.1:4120 with SEOUL_OPEN_API_KEY=test-seoul-key and stubbed fetch for /health plus repeated /v1/seoul-subway/arrival requests
Not-tested: Live hosted proxy rollout state

* Document OpenClaw support in the public compatibility list

The approved scope for issue #29 was narrowed to README support messaging,
so this change adds OpenClaw/ClawHub to the supported-client line and locks
that wording with a regression test in the docs suite.

Constraint: Issue #29 was approved as a README-only compatibility/docs change
Rejected: Broader install-doc updates | out of approved scope for this issue
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep OpenClaw support wording aligned with the supported-client README line and its docs regression test
Tested: Focused Node docs regression, npm run lint, npm run typecheck, npm run build, npm test
Not-tested: Live OpenClaw or ClawHub install flow (issue scope was documentation-only)

* Protect README client-support claims from ClawHub regressions

The README already advertises OpenClaw/ClawHub, but the docs
regression only matched OpenClaw. Tighten the assertion to the
exact supported-client fragment so a future edit cannot silently
remove ClawHub while keeping the issue verification command stable.

Constraint: PR #39 already publishes a verification command keyed to the current test name
Rejected: Rename the test to mention ClawHub explicitly | would drift from the published verification command
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep this regression synchronized with the README supported-client line whenever that copy changes
Tested: node --test scripts/skill-docs.test.js --test-name-pattern 'README advertises OpenClaw among the supported coding agents'
Tested: npm run lint
Tested: npm run typecheck
Tested: npm run build
Tested: npm test
Tested: lsp diagnostics for scripts/skill-docs.test.js (0 errors)
Not-tested: N/A

* Make Coupang shopper research usable without pretending scraping is stable

Coupang blocks unattended shopper queries in this environment, so the new package focuses on the durable pieces we can ship honestly: official URL builders, browser-captured HTML parsers, and explicit automation probes. The docs and skill now explain the seller-API limitation, the verified anti-bot behavior, and the browser-capture fallback expected by callers.

Constraint: No general shopper Open API surfaced in Coupang's official developer docs

Constraint: Headless/direct retrieval is anti-bot blocked in verified local probes

Rejected: Bundle a scraping bypass or hidden browser dependency | violates repo policy and would over-claim reliability

Confidence: high

Scope-risk: moderate

Reversibility: clean

Directive: Keep probe/docs behavior aligned with fresh live verification before claiming unattended Coupang access works

Tested: npm run ci; live probeAutomation(query=생수) with direct fetch + Playwright-core browserFetchHtml; LSP diagnostics on changed JS/test files

Not-tested: Headed/manual browser sessions with a human-authenticated Coupang context

* Keep the Coupang workspace installable and its entrypoint honest

Review follow-up found two contract gaps in the first issue #36 rollout: fresh installs were missing the new workspace link in package-lock, and the package README documented parser helpers that were not exported from the package entrypoint. Refreshing the lockfile and re-exporting the parsers keeps npm ci and consumer imports aligned with the published API.

Constraint: npm ci must succeed from a clean checkout
Rejected: Narrow the README API list to only client helpers | would hide useful parsers that the package already ships and tests
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: When adding a new workspace, refresh package-lock and verify the package entrypoint matches README public API claims
Tested: npm run ci; workspace coupang-product-search tests; LSP diagnostics on coupang-product-search JS/test files
Not-tested: published npm install from registry (local pack dry-run only)

* Keep Coupang anti-bot docs honest as probe results drift

A same-day PR rerun showed the published mobile probe snapshot was already stale, so the follow-up locks the 403/access-denied mobile path with regression coverage and softens the docs/skill contract to describe blocked outcome classes instead of one frozen signature. The published guidance now also explains that browser results only appear when browserFetchHtml is injected, matching what a clean checkout can actually reproduce.

Constraint: Coupang anti-bot responses vary by edge/challenge between reruns on the same day
Rejected: Freeze one exact mobile probe snapshot | same-day reruns already diverged
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Refresh Coupang anti-bot docs only with same-day live probe evidence, and prefer blocked outcome classes over a single exact signature when reruns disagree
Tested: npm run lint; npm run typecheck; npm test; npm run ci; live probeAutomation("생수") with direct fetch + Playwright-core browser fetch
Not-tested: Non-Chrome Playwright-core executables

* Keep the Coupang skill honest about clean-checkout browser probes

The approved follow-up already documented that clean-checkout probeAutomation runs leave browser unset unless a browser fetcher is injected, but the skill text itself had not made that null contract explicit. This commit adds a regression test that locks the browser-null wording across the published docs surfaces and updates the skill so the stated behavior matches the package implementation and clean-checkout reality.

Constraint: probeAutomation() only populates browser when browserFetchHtml is supplied by the caller
Rejected: Leave the skill wording implicit and rely on README examples alone | the skill could drift away from the shipped package contract again
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the README, feature doc, and skill aligned whenever the probeAutomation browser contract changes
Tested: node --test packages/coupang-product-search/test/index.test.js; npm run lint; npm run typecheck; npm test; npm run ci; live probeAutomation("생수") with direct fetch + Playwright-core browser fetch; codex exec review --uncommitted
Not-tested: Alternative browser engines beyond local Google Chrome for the manual browserFetchHtml probe

* Keep Coupang browser probe docs aligned with manual verification paths

The approved PR #40 follow-up still had one wording gap: the skill said when browser results appear, but it did not explicitly say those populated results come from an injected manual/external browserFetchHtml path. This change locks that contract with a failing regression first, then updates the skill text to match the already-shipped README/feature-doc guidance and runtime behavior.

Constraint: clean-checkout probeAutomation() must keep browser null unless browserFetchHtml is supplied
Rejected: change runtime browser probe behavior | approved follow-up was docs/test only and runtime contract is already correct
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: keep Coupang probe docs aligned with the shipped browserFetchHtml injection contract; do not imply built-in browser probing in clean checkouts
Tested: node --test packages/coupang-product-search/test/index.test.js; npm run lint; npm run typecheck; npm test; npm run ci; live probeAutomation("생수") with direct fetch + Playwright-core browser fetch; lsp diagnostics on changed files; architect review approved
Not-tested: alternate browser engines beyond local Google Chrome + playwright-core manual runner

* Route Korean law lookups through korean-law-mcp

Add a documentation-first korean-law-search skill and lock the repo docs around the rule that Korean law queries must go through korean-law-mcp rather than a new in-repo package.

The change updates install/setup/security guidance, publishes the new feature doc, and adds regression tests so future edits keep the LAW_OC + korean-law-mcp contract intact.

Constraint: Issue #41 requires korean-law-mcp for every Korean law lookup
Constraint: Must not add a new npm or python package in this repository
Rejected: Add a repo-local law package | violates the no-new-package requirement and duplicates upstream MCP work
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep Korean law lookup guidance pinned to korean-law-mcp unless issue requirements explicitly change
Tested: node --test scripts/skill-docs.test.js
Tested: npm install -g korean-law-mcp && korean-law list && korean-law help search_law
Tested: npm run ci
Tested: npx tsc --noEmit --pretty false --project /Users/jeffrey/Projects/k-skill/tsconfig.json
Not-tested: live search_law/get_law_text against a real LAW_OC credential

* Clarify Korean law setup modes to avoid credential confusion

A review found the new korean-law-search docs treated LAW_OC as an unconditional prerequisite even though the upstream remote MCP endpoint is configured separately from the local CLI/server path. This update makes the docs and regression tests mode-specific: local CLI/MCP uses LAW_OC, while the remote endpoint stays a korean-law-mcp-only url fallback without a user-supplied credential.

Constraint: Upstream korean-law-mcp uses LAW_OC on the local CLI/server path while the documented remote MCP endpoint is configured with url only
Rejected: Keep LAW_OC mandatory for every korean-law-mcp mode | contradicts upstream docs and reviewer evidence
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep README/setup/skill docs and regression tests aligned on the local-vs-remote korean-law-mcp contract
Tested: node --test scripts/skill-docs.test.js; npm install -g korean-law-mcp && korean-law list && korean-law help search_law; npx tsc --noEmit --pretty false --project /Users/jeffrey/Projects/k-skill/tsconfig.json; npm run ci; lsp_diagnostics scripts/skill-docs.test.js
Not-tested: Live remote MCP endpoint connection against https://korean-law-mcp.fly.dev/mcp

* Record issue #41 follow-up after approved verification

The approved korean-law-search change was already present on feature/#41, so this follow-up records a fresh verification pass and updates the PR without introducing unnecessary code churn.

Constraint: Existing branch already contained the approved implementation
Rejected: Touch repo files without need | would create noise without improving behavior
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the korean-law-mcp-only + mode-specific LAW_OC contract aligned with upstream docs before changing these surfaces again
Tested: node --test scripts/skill-docs.test.js; npx tsc --noEmit --pretty false --project /Users/jeffrey/Projects/k-skill/tsconfig.json; npm install -g korean-law-mcp && korean-law list && korean-law help search_law; npm run ci
Not-tested: Live korean-law queries that require a real LAW_OC or remote endpoint session

* Keep the Korean law feature diff merge-ready

Verification uncovered trailing whitespace in the new korean-law feature guide when checking the branch diff. I added a regression to the skill docs suite and removed the whitespace so the approved documentation contract stays clean and reviewable.

Constraint: Preserve the existing korean-law-mcp + mode-specific LAW_OC contract without widening scope
Rejected: Leave the whitespace issue as-is | git diff --check stayed dirty on the branch
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep docs/features/korean-law-search.md free of trailing whitespace so diff hygiene stays enforced by the regression
Tested: node --test scripts/skill-docs.test.js; npx tsc --noEmit --pretty false --project /Users/jeffrey/Projects/k-skill/tsconfig.json; npm install -g korean-law-mcp && korean-law list && korean-law help search_law; npm run ci; git diff --check
Not-tested: Live credentialed LAW_OC search execution against the upstream API

* Keep Korean law skill guidance aligned with supported lookups

The approved issue #41 feature already worked, but the shipped skill text still under-described ordinance and interpretation lookups compared with the documented capability set. This follow-up tightens the skill copy and locks that contract with a doc regression so the branch stays merge-ready without changing the underlying korean-law-mcp routing rules.

Constraint: Must preserve the existing korean-law-mcp-only and mode-specific LAW_OC setup contract
Rejected: Leave the skill wording as-is | drift from the documented lookup surface would remain
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the skill examples and doc regression aligned whenever supported korean-law-mcp search surfaces change
Tested: node --test scripts/skill-docs.test.js
Not-tested: live LAW_OC-backed upstream API queries

* Keep Korean law completion guidance in sync with enforced lookups

The previous follow-up aligned the main skill examples, but the done-criteria text still left interpretation and ordinance routing implicit. This commit makes the completion checklist explicit and extends the doc regression so future edits cannot silently drop those lookup paths.

Constraint: Must stay within the existing issue #41 korean-law-mcp-only contract
Rejected: Leave the done checklist implicit | reviewers and future edits could drift from the enforced lookup set
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: When the supported Korean-law lookup set changes, update the done checklist and regression together
Tested: node --test scripts/skill-docs.test.js
Not-tested: full CI before commit

* Enable repeatable used-car price lookups from a rental-company source

Issue #46 required surveying major Korean rental companies before implementation and then choosing the easiest stable provider. SK렌터카 다이렉트 exposes 타고BUY inventory in public Next.js page data, so the feature stays dependency-free while still supporting live repeated lookups and documented provider rationale.

Constraint: Must compare major Korean rental companies before implementation
Constraint: Must verify 10+ live lookups against a real provider surface
Rejected: 롯데오토옥션 as v1 provider | public list contract was unstable and legacy .do flows returned inconsistent or 404 pages
Rejected: 레드캡렌터카 as v1 provider | no public used-car inventory or API surface was found
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep the v1 provider read-only and inventory-snapshot-based unless a stable documented public API is confirmed
Tested: npm run ci
Tested: Live 10-query run against https://www.skdirect.co.kr/tb at 2026-04-02T07:22:46Z
Tested: LSP diagnostics on affected files
Not-tested: Seller-specific detail drilldowns or non-SK providers
Related: #46

* Keep used-car-price-search releasable after merge

Review feedback found that the new used-car workspace would not ship because it lacked a changeset, and the fallback install docs still omitted the runtime package. This follow-up adds regression coverage first, then restores both release and install-path coverage with the smallest possible diff.

Constraint: New publishable workspaces must be wired through Changesets to reach npm release automation
Constraint: Fallback install docs must list runtime packages users need when skill files are present but global packages are missing
Rejected: Fix only the changeset gap | would leave the documented fallback install path incomplete
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep used-car-price-search in both the fallback global install example and a Changesets entry whenever release wiring changes
Tested: node --test scripts/skill-docs.test.js; npx changeset status; live 10-query used-car run against SK direct at 2026-04-02T07:38:44.949Z; npm run ci; LSP diagnostics on used-car package files and scripts/skill-docs.test.js; architect verification
Not-tested: No additional live provider permutations beyond the verified 10-query smoke run

* Keep used-car verification docs resilient to live inventory churn

A fresh rerun showed the SK direct inventory total continues to move during the day, so the feature doc now records the verified smoke-run timestamp without freezing a brittle exact count. The regression suite was updated first so the docs stay variability-aware and diff-clean in future follow-ups.

Constraint: Live SK direct inventory totals change over time even when the parsing contract stays stable
Rejected: Keep documenting a fixed total from one smoke run | it immediately drifted and made the docs stale
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the used-car live verification note timestamped and variability-aware instead of pinning an exact inventory total
Tested: node --test scripts/skill-docs.test.js; git diff --check; npm run ci; npx changeset status; live 10-query run against https://www.skdirect.co.kr/tb at 2026-04-02T07:59:41.391Z; LSP diagnostics on scripts/skill-docs.test.js; architect verification
Not-tested: No additional content changes outside the used-car feature doc

* Keep used-car query summaries honest when results are limited

The review found that query-level stats were being computed from the
post-limit slice, so common searches like 아반떼 under-reported both
match counts and price ranges. This change locks the regression first,
then computes the full filtered set before slicing only the returned
items.

Constraint: PR #48 must preserve the existing API shape while fixing the review-blocking stats bug
Rejected: Expanding the response with separate limited/full summary fields | unnecessary API churn for a targeted bug fix
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Query-level summary and matchedCount must stay derived from the full filtered set, not the display limit slice
Tested: npm test --workspace used-car-price-search; npm run ci; git diff --check; npx changeset status; live SK direct smoke run on 2026-04-02T08:23:59Z; LSP diagnostics on used-car-price-search source and test files; architect verification APPROVED
Not-tested: limit=0 semantics remain unchanged and still coerce to the default limit
Related: PR #48

* Keep korean-law-search available during upstream outages (#45)

Issue #44 adds Beopmang as the documented fallback when the primary korean-law-mcp path is unavailable, and locks the new routing into the doc regression suite so future edits do not silently revert the policy.

Constraint: Existing guidance must still prefer korean-law-mcp and keep LAW_OC scoped to the local CLI/MCP path
Rejected: Add a repo-local Beopmang client package | issue only requires fallback registration, not a new implementation surface
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the primary-first rule intact; Beopmang is an outage fallback, not the default path
Tested: node --test scripts/skill-docs.test.js; npm run ci; korean-law list; python3 live Beopmang search smoke for 관세법
Not-tested: Live Beopmang MCP handshake from a GUI MCP client

* Replace coupang scraping package with coupang-mcp server integration

Drop the browser-based scraping package (packages/coupang-product-search)
and switch to the uju777/coupang-mcp MCP server for all Coupang product
searches. This removes the anti-bot workaround complexity and provides
8 ready-to-use tools via MCP Streamable HTTP with no API key required.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Add disclaimer to used-car-price-search README

Clarify that the package queries SK렌터카 타고BUY public data with
no affiliation or sponsorship, and that ad/partnership inquiries
are welcome.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Refactor used-car-price-search into provider-based architecture

SK 타고BUY 전용 로직을 src/providers/sk-tagobuy.js로 분리하고,
공급자를 수평 확장할 수 있는 registry 구조로 전환한다.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Ship LCK analytics inside k-skill's managed release flow

Adapt jerjangmin's upstream lck-analytics skill/package into a new
workspace and skill pack, wire docs/install/release surfaces, and add
regression fixtures/tests plus script smoke coverage so the feature is
verifiable before publish.

Constraint: Upstream package is not published to npm yet
Constraint: Must preserve attribution to original source and author in shipped docs
Rejected: Keep the upstream lck-results install wording in k-skill | conflicts with repo workspace/package naming
Rejected: Ship only the npm package without the local skill scripts | issue explicitly requested the skill as well
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep Changesets as the only version-bump path for lck-analytics; do not hand-edit versions for release
Tested: node --test packages/lck-analytics/test/index.test.js scripts/skill-docs.test.js
Tested: npm run lint --workspace lck-analytics
Tested: npm test --workspace lck-analytics
Tested: live getLckSummary('2026-04-01', { team: '한화', includeStandings: true })
Tested: node lck-analytics/scripts/sync-oracle.js --csv lck-analytics/samples/oracle-lck-sample.csv --cache .tmp/lck-cache && node lck-analytics/scripts/build-match-report.js --date 2026-04-01 --team 한화 --cache .tmp/lck-cache && node lck-analytics/scripts/analyze-live-game.js --game game-1 --window packages/lck-analytics/test/fixtures/live-window-game-1.json --details packages/lck-analytics/test/fixtures/live-details-game-1.json --cache .tmp/lck-cache
Tested: npm run ci
Tested: npx tsc --noEmit --project /Users/jeffrey/Projects/k-skill/tsconfig.json
Not-tested: Riot live feed behavior for arbitrary future game ids outside the fixture-backed smoke path

* Enable policy-aware Korean spell checking from the official Nara surface

Add a skill guide and Python helper that use the approved old_speller HTML flow, chunk long text conservatively, and report original/suggestion/reason deltas. The docs also record the public-site limits, Cloudflare behavior, and non-commercial usage policy so agents do not overreach the free surface.

Constraint: Public site is HTML-only and may return 403 to non-browser clients
Constraint: Must not add new dependencies or high-volume crawling behavior
Rejected: Node fetch client | Cloudflare returned 403 in this environment
Rejected: Paid API integration | no public contract or credentials were available for this task
Confidence: medium
Scope-risk: moderate
Reversibility: clean
Directive: Keep usage low-rate and non-commercial unless supplier-approved API terms are added
Tested: npm run lint
Tested: npm run typecheck
Tested: npm test
Tested: npm run build
Tested: python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json
Tested: python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json
Not-tested: Paid API/order flow
Not-tested: High-volume commercial workloads
Not-tested: Non-UTF-8 file inputs

* Preserve korean spell-check layout in corrected output

The Nara payload can collapse paragraph separators into normalized page text, so the helper now maps corrections back onto the original chunk before rebuilding corrected_text. The CLI also rejects non-positive --max-chars values, and regression tests cover both the layout-preservation path and invalid argument handling.

Constraint: Nara result pages can normalize blank lines and sentence spacing before exposing errInfo offsets
Rejected: Narrow docs away from file/Markdown proofreading | preserving original chunk separators keeps the documented workflow intact
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep multiline separator preservation tied to the original chunk whenever a suggestion only changes whitespace across collapsed boundaries
Tested: python3 -m unittest scripts.test_korean_spell_check; npm run lint; npm run typecheck; npm test; npm run build; python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json; python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json; python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n왠지 않되요.' --format json; python3 scripts/korean_spell_check.py --text 테스트 --max-chars 0 --format json
Not-tested: Live multi-page Nara payloads with separator-sensitive corrections across multiple returned pages

* Preserve file-proofreading layout in Korean spell check output

The spell-check helper now keeps exact blank-line runs and paragraph
indentation when chunking and reassembling file-style input, while still
allowing the official service's spacing corrections to flow through.
Regression coverage now locks the collapsed-layout, triple-blank-line,
and cross-boundary spacing cases that triggered the PR review.

Constraint: Official Nara/PNU payloads can collapse layout and sometimes merge corrections across preserved paragraph boundaries
Rejected: Narrow docs away from file-level proofreading | the existing feature scope explicitly supports file and Markdown checks
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Preserve exact layout tokens only at original newline boundaries; do not reintroduce global strip/join normalization without real file-mode verification
Tested: npm run lint; npm run typecheck; npm test; npm run build; python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json; python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json; python3 scripts/korean_spell_check.py --file <tmpfile> --format json; python3 scripts/korean_spell_check.py --text 테스트 --max-chars 0 --format json
Not-tested: Live upstream behavior for extremely long leading/trailing-whitespace-only files
Related: PR #60

* Keep layout-preserving chunk splits from tripping on separator-boundary fixes

The follow-up layout preservation pass renamed the working unit variable, but one separator-length guard still referenced the old paragraph name. This commit finishes the refactor so exact-boundary paragraph chunks keep the preserved-separator logic reachable and the new regression coverage stays green.

Constraint: Must preserve the existing feature/#47 branch and PR #60 flow while fixing the approved review follow-up
Rejected: Revert the layout-preserving chunking refactor | would discard the verified file-layout fix and its regressions
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep chunk reassembly lossless; new chunking changes should continue to round-trip original separators byte-for-byte
Tested: npm run lint; npm run typecheck; npm test; npm run build; python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json; python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json; python3 scripts/korean_spell_check.py --file <tempfile-with-triple-blank-lines> --format json; python3 scripts/korean_spell_check.py --text 테스트 --max-chars 0 --format json (expected argument error)
Not-tested: Live multi-page service responses beyond the local smoke cases

* Preserve spell-check chunk separators when units overflow

The file-layout follow-up already preserved blank runs and indentation,
but the overlong-unit path still checked the wrong variable when
extracting a trailing separator. That could strand separators in
a standalone chunk and break exact reassembly for tight max-char
limits.

This commit fixes the separator extraction guard and locks the
behavior with a regression that proves chunk concatenation still
matches the original text when an overlong paragraph is followed
by a blank-line separator.

Constraint: File-mode reconstruction must preserve exact layout while chunking long input
Rejected: Broader chunking rewrite | existing structure only needed the overflow guard corrected
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep split_text_into_chunks chunk concatenation identical to the original input for layout-sensitive file checks
Tested: npm run lint; npm run typecheck; npm test; npm run build; python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json; python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json; python3 scripts/korean_spell_check.py --text 테스트 --max-chars 0 --format json; manual --file smoke with triple blank lines and indentation
Not-tested: Live-service failure modes such as Cloudflare/browser challenges

* Prevent clean spell-check chunks from crashing mixed file runs

The Nara/PNU surface can return a plain 'no issues found' HTML page
without the embedded result payload when a chunk is already clean.
Chunked file and markdown runs could hit that response on earlier
chunks, raise a ValueError, and never reach later chunks that still
needed corrections. Treat the no-issues page as an empty result set
and lock the behavior with narrow regression coverage.

Constraint: Upstream no-issue responses omit the JavaScript payload entirely and can split the status message across HTML whitespace
Rejected: Fabricate a synthetic result page | empty-page handling already preserves the original clean chunk
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep empty-result detection aligned with the upstream no-issues message unless the service publishes a structured empty payload contract
Tested: python3 -m unittest scripts.test_korean_spell_check
Tested: npm run lint
Tested: npm run typecheck
Tested: npm test
Tested: npm run build
Tested: python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json
Tested: python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json
Tested: python3 scripts/korean_spell_check.py --text 테스트 --max-chars 0 --format json
Tested: python3 scripts/korean_spell_check.py --file <tmpfile> --format json
Tested: python3 scripts/korean_spell_check.py --file <tmpfile> --max-chars 18 --format json
Not-tested: Other upstream empty-result templates beyond the current no-issues HTML wording

* Make Joseon Sillok lookups reproducible from the official site

Add a joseon-sillok-search skill and a Python helper that scrape the
official Joseon Annals search/detail pages. The helper normalizes
king/year metadata, fetches detail excerpts, and locks the repository
docs plus regression coverage around the shipped workflow.

Constraint: v1 must stay on official public HTML surfaces only
Constraint: Must avoid adding new dependencies for a simple scraping helper
Constraint: Shell connectivity to sillok.history.go.kr became intermittent during final live reruns
Rejected: Ship a new npm workspace | repo skill/docs pattern is enough for v1
Rejected: Add BeautifulSoup or another parser dependency | unnecessary for the bounded HTML patterns
Confidence: medium
Scope-risk: narrow
Reversibility: clean
Directive: Keep year filtering Gregorian and derived from official regnal metadata unless the upstream site exposes a better structured contract
Tested: npm run ci
Tested: Earlier live POST/detail probes against search/searchResultList.do and /id/kda_12512030_002 during implementation
Tested: Live official article inspection for kda_12512030_002 via the public site
Not-tested: Final end-to-end CLI live run after the last refactor, because the shell hit transient TCP timeouts to sillok.history.go.kr
Related: #59

* Prevent sillok follow-up fixes from missing filtered results or weakening trust

This follow-up addresses the blocking PR review items on the Joseon Sillok helper. The search loop now derives total pages from the first live page size so king/year filtering can reach later pages, TLS verification stays enabled on both requests and urllib paths, detail parsing accepts the live classification brackets, and repo docs stop linking to missing Korean spell-check assets on this branch.

Constraint: Must preserve the existing joseon-sillok-search surface and avoid new dependencies
Rejected: Keep verify=False behind an implicit fallback | still weakens authenticity for the default path
Rejected: Infer pagination from the hardcoded 50-row default | misses valid later-page matches when the site serves smaller pages
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If the official site changes page size or pagination markup again, update the first-page pagination regression before altering search_sillok pagination math
Tested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_sillok_search
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: python3 scripts/sillok_search.py --query '훈민정음' --king 세종 --year 1443 --limit 1 --timeout 20
Tested: python3 - <<'PY' ... fetch_detail_page(..., article_id='kda_12512030_002', timeout=20) ... PY
Not-tested: Successful live sillok.history.go.kr responses in this environment (POST and detail GET both timed out at 20s)
Related: PR #62

* Make joseon-sillok-search installs work outside the repo

The skill installer only ships the skill directory, so the sillok helper has to live inside that payload. This moves the authoritative helper into joseon-sillok-search/scripts/, keeps the repo-root script as a thin shim for tests and docs, and trims footer metadata from parsed article bodies so excerpts match the published examples.

Constraint: skills add exposes only the installed skill payload, not repo-root helpers

Rejected: Keep the helper only under scripts/ | installed skill commands fail after copy-only installs

Confidence: high

Scope-risk: narrow

Directive: Keep the repo-root sillok wrapper thin and update the bundled helper first when behavior changes

Tested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_sillok_search

Tested: node --test scripts/skill-docs.test.js

Tested: temp installed-skill smoke run of python3 scripts/sillok_search.py --help plus footer-cleaning parse_detail_page check

Tested: npm run ci

Not-tested: live sillok.history.go.kr shell requests still hit connect timeouts in this environment

* Restore the sillok CLI's verified stdlib transport fallback

The helper already shipped a stdlib urllib opener that can reach the
official search endpoint in environments where requests/urllib3 aborts.
This change keeps that opener available even when requests imports
successfully and falls back to it on retryable requests transport
failures. Added regression coverage for opener availability and the
requests-to-urllib fallback so the default CLI path matches the live
verified behavior.

Constraint: Official sillok detail GETs can still time out transiently in this environment
Constraint: Keep TLS verification enabled and preserve the documented CLI entrypoints
Rejected: Force urllib for every request | keep the existing requests fast path when it succeeds
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Preserve the stdlib fallback tests whenever the transport layer changes
Tested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_sillok_search
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: live forced-fallback probe against searchResultList.do with requests.post patched to OSError(22)
Not-tested: full live CLI completion through the detail GET in this environment

* Document the approved real-estate MCP skill without vendoring upstream

Issue #53 is intentionally doc-only, so this change adds the
real-estate-search skill, feature guide, setup/security notes,
and regression coverage around the upstream real-estate-mcp
integration instead of importing server code.

The new docs keep the original MCP link, cover Codex/Claude
registration, and spell out the self-host + Cloudflare Tunnel +
launchd path for environments where no fixed hosted endpoint is
available.

Constraint: Must use tae0y/real-estate-mcp without copying its source into this repository
Constraint: Must include the original MCP link and a stable self-host fallback when no hosted endpoint is available
Rejected: Vendor the upstream MCP source | issue explicitly requires skill docs only
Rejected: Assume a public hosted MCP endpoint exists | upstream docs did not publish one
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep this integration doc-only; do not add a workspace or vendored server without revisiting issue #53 constraints
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: upstream bootstrap smoke (`uv sync`, `uv run real-estate-mcp --help`, HTTP initialize on 127.0.0.1:8017)
Not-tested: live property data queries with a valid DATA_GO_KR_API_KEY

* Prevent misleading real-estate self-host instructions

Tighten the real-estate skill docs so the launchd fallback stays operational
and the Onbid bid-result tools are described with the same WIP caveat the
upstream project still publishes.

Constraint: Upstream Docker compose already uses restart: unless-stopped while `docker compose ... up -d` daemonizes immediately
Rejected: Keep a separate server LaunchAgent with RunAtLoad + KeepAlive | launchd would restart-loop on the exiting compose command
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Mirror upstream capability caveats in k-skill docs and do not wrap daemonized server commands in launchd KeepAlive jobs
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: uv sync
Tested: uv run real-estate-mcp --help
Tested: DATA_GO_KR_API_KEY=dummy uv run real-estate-mcp --transport http --host 127.0.0.1 --port 8017
Tested: curl initialize on http://127.0.0.1:8017/mcp returned protocolVersion 2024-11-05
Not-tested: Live 거래 조회 with a real DATA_GO_KR_API_KEY

* Keep install docs from reintroducing broken launchd guidance

The install guide had drifted from the real-estate skill and feature docs and still implied that macOS launchd should auto-run both the server and tunnel. This narrows the guidance back to tunnel-only launchd ownership and extends regression coverage so docs/install.md cannot silently reintroduce the server-side loop wording.

Constraint: Upstream docker compose already uses restart: unless-stopped
Constraint: Review-round-2 scope is limited to docs and regression coverage
Rejected: Leave docs/install.md under a generic launchd presence check | it missed the exact server/터널 regression
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep docs/install.md aligned with the detailed real-estate feature guide when self-host guidance changes
Tested: node --test scripts/skill-docs.test.js; npm run ci; fresh-clone upstream smoke with uv sync, uv run real-estate-mcp --help, and HTTP initialize on 127.0.0.1:8017/mcp
Not-tested: None

* Allow Han River water-level lookups without user API keys

The proxy now resolves HRFCO water-level stations via waterlevel/info and
fetches the latest 10-minute measurement via waterlevel/list, exposing a
public summary route plus a hosted skill/docs path.

Constraint: HRFCO requires a ServiceKey and station-code-based latest lookup
Rejected: Raw passthrough only | forces users to manage upstream details and misses the approved no-key UX
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep this route summary-only and public; expand rainfall/dam/bo/fldfct in separate issues
Tested: npm run ci; local server smoke test with HRFCO sample key against /v1/han-river/water-level; tsc diagnostics on packages/k-skill-proxy/src/server.js and src/hrfco.js
Not-tested: Hosted production proxy rollout

* Expose official nearby fuel prices for location-based gas station lookups

Add the first cheap-gas-nearby skill/package pair so nearby gas-price
queries can resolve a user-supplied location, translate it into Opinet's
KATEC search contract, and return the cheapest nearby stations with
address and facility detail. The docs and setup surfaces now advertise
the new skill and its Opinet API key requirement.

Constraint: Nearby fuel prices must come from the official KNOC Opinet API when available
Constraint: No new external dependencies were allowed for coordinate conversion or location resolution
Rejected: Map-only gas price scraping | official Opinet Open API exists and is the preferred source
Rejected: Require lat/lng input only | poorer UX than supporting landmark/station queries through anchor resolution
Confidence: medium
Scope-risk: moderate
Reversibility: clean
Directive: Keep `OPINET_API_KEY` as the only supported official-price credential unless the repo adopts an Opinet proxy later
Tested: npm run ci; node --test packages/cheap-gas-nearby/test/index.test.js; offline fixture smoke via searchCheapGasStationsByLocationQuery('서울역', ...)
Not-tested: Live Opinet API call with a real `OPINET_API_KEY` (no non-placeholder key was configured locally)
Related: #54

* Explain Blue Ribbon premium gating instead of failing opaquely

Blue Ribbon's nearby endpoint now returns PREMIUM_REQUIRED for public
requests, so the package now upgrades that response into a stable domain
error and updates user-facing docs to describe the degraded nearby state.
Regression tests cover both location-query and coordinate-query entrypoints
while keeping the existing happy path intact.

Constraint: Must not attempt to bypass Blue Ribbon premium access controls
Constraint: Package releases must use Changesets metadata
Rejected: Silently return empty results on PREMIUM_REQUIRED | would hide the upstream contract change from callers
Rejected: Keep generic 403 error and docs unchanged | leaves the main failure mode opaque and misleading
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If Blue Ribbon reopens a public nearby surface later, update the docs and replace the premium_required remap only after fresh live verification
Tested: npm test --workspace blue-ribbon-nearby; npm run lint --workspace blue-ribbon-nearby; npm run ci; live node repro now returns premium_required metadata
Not-tested: Official premium-authenticated nearby flow, because no approved premium credentials are available

* Reduce duplicate premium-required test assertions

The implementation diff was already verified, so this follow-up keeps the
new regression coverage easier to read by sharing one assertion helper
instead of repeating the same predicate twice.

Constraint: Must preserve the verified PREMIUM_REQUIRED regression coverage exactly
Rejected: Leave duplicated inline predicates in both tests | repeats the same contract and adds noise to future edits
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep location-query and coordinate-query regressions aligned when the premium_required error contract changes
Tested: npm test --workspace blue-ribbon-nearby; npm run lint --workspace blue-ribbon-nearby; npm run ci
Not-tested: Additional live Blue Ribbon requests beyond the previously verified premium_required repro

* Document an Olive Young search skill around the upstream daiso CLI

Issue #61 asked for an Olive Young lookup workflow without vendoring the upstream daiso-mcp server into k-skill, so this change adds a docs-only skill, threads it through repository docs, and locks the guidance with regression assertions.

The new guidance prefers CLI-first verification (`npx daiso`) and a clone fallback (`git clone https://github.com/hmmhmmhm/daiso-mcp.git`) instead of requiring direct Claude Code MCP installation. The existing daiso-product-search skill remains untouched.

Constraint: Must mention the original https://github.com/hmmhmmhm/daiso-mcp repo
Constraint: Must avoid changing the existing daiso-product-search skill
Constraint: Must prefer upstream CLI/package usage over direct Claude Code MCP installation
Rejected: Vendor upstream Olive Young code into k-skill | contradicts the minimal-addition design request
Rejected: Make Claude Code MCP setup the default path | issue explicitly asked for CLI-first usage
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the Olive Young docs aligned with upstream `daiso` CLI behavior and note public endpoint instability when live verification shows it
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: 2026-04-05 live /tmp/daiso-mcp CLI checks for health, /api/oliveyoung/stores, /api/oliveyoung/products, /api/oliveyoung/inventory
Not-tested: Authenticated/private Olive Young flows or ordering/payment paths

* Keep Han River docs honest until hosted rollout lands

The local HRFCO proxy route already works, but the deployed public proxy still
lacks /v1/han-river/water-level as of 2026-04-05. This follow-up changes the
user-facing docs to require a self-hosted or deployment-verified proxy URL,
keeps the intended hosted path documented as rollout-pending, and locks that
caveat with a regression test.

Constraint: Hosted k-skill-proxy deployment still returns 404 for /v1/han-river/water-level on 2026-04-05
Rejected: Keep advertising the hosted route as the default live path | current deployment is not live yet
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Restore hosted-default wording only after the public proxy route and HRFCO configuration are verified live
Tested: node --test scripts/skill-docs.test.js; npm run ci; local mocked smoke via node packages/k-skill-proxy/src/server.js + GET /v1/han-river/water-level?stationName=한강대교
Not-tested: Live hosted k-skill-proxy behavior after deployment

* Keep cheap gas lookups from failing on recoverable Kakao and input issues

The lookup now walks ranked Kakao anchor candidates until one returns usable coordinates, and it rejects invalid limit inputs instead of silently collapsing non-empty results into an empty list. The regression suite now locks both review repros plus the null-coordinate normalization edge case that surfaced while implementing the fallback.

Constraint: Kakao place panels can be partially populated or return 404 for otherwise valid search candidates
Rejected: Default invalid limit/detailLimit values silently | still masks caller bugs and can look like no results nearby
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep anchor resolution tolerant of unusable Kakao panels before failing the whole lookup
Tested: node --test packages/cheap-gas-nearby/test/index.test.js; npm run ci; offline fixture smoke for searchCheapGasStationsByLocationQuery('서울역', ...)
Not-tested: Live Opinet/Kakao network path without a real OPINET_API_KEY

* Keep Olive Young install guidance aligned with live retry behavior

Issue #61's initial branch already shipped the olive-young-search skill/docs set, but the install guide did not have a regression lock for the public endpoint instability note captured during live verification. This follow-up adds that test first and updates the install quickstart so retry-or-clone fallback guidance stays in sync with the verified daiso CLI workflow.

Constraint: Must keep the follow-up scoped to the approved issue #61 docs behavior
Constraint: Public olive-young endpoint can intermittently return 5xx/503 during live verification
Rejected: Re-open the broader feature docs set | the existing branch already covered the main feature scope
Rejected: Leave the retry guidance untested in install docs | risk of future doc drift across surfaces
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If live olive-young verification changes again, update install docs and the regression together so retry/fallback guidance stays honest
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: cd /tmp/daiso-mcp && npx daiso health
Tested: cd /tmp/daiso-mcp && npx daiso get /api/oliveyoung/stores --keyword 명동 --limit 3 --json
Tested: cd /tmp/daiso-mcp && npx daiso get /api/oliveyoung/products --keyword 선크림 --size 3 --json
Tested: cd /tmp/daiso-mcp && npx daiso get /api/oliveyoung/inventory --keyword 선크림 --storeKeyword 명동 --size 2 --json
Not-tested: Authenticated Olive Young flows or sustained-rate retry behavior beyond the documented smoke checks

* Preserve ranked Kakao anchor fallbacks after panel failures

The resolver already retried later Kakao panels, but after the best panel failed it
walked the remaining candidates in raw HTML order. Centralizing the full
anchor ranking in parse.js keeps fallback iteration aligned with the
existing scoring rules and locks the review repro with a regression test
for 강남역 ordering.

Constraint: Kakao place panels can 404 or omit coordinates even when later ranked candidates are usable
Rejected: Re-rank only after a failed panel | duplicates scoring logic and drifts from selectAnchorCandidate
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep resolveAnchor fallback order tied to the shared anchor scoring logic rather than raw Kakao result order
Tested: node --test packages/cheap-gas-nearby/test/index.test.js; npm run ci; offline fixture smoke for searchCheapGasStationsByLocationQuery('서울역', ...); lsp diagnostics on affected files
Not-tested: Live Kakao/Opinet network calls with a non-placeholder OPINET_API_KEY

* Clarify that Blue Ribbon coordinate lookups fail the same way

The premium gate now affects both location-query and coordinate-entry nearby flows. Record that parity in the skill and docs so the live 2026-04-05 repro evidence stays aligned with the shipped public contract.

Constraint: /restaurants/map remains premium-gated for public requests as of 2026-04-05
Rejected: Add more code changes without a behavior gap | would add churn after the approved fix already landed
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the docs/examples aligned with live repro evidence whenever Blue Ribbon changes nearby access behavior
Tested: npm test --workspace blue-ribbon-nearby; npm run lint --workspace blue-ribbon-nearby; npm run ci; live node repro for location + coordinates premium_required contract
Not-tested: New upstream success-path live nearby payloads, because public access is still premium-gated

* Document a runnable Olive Young clone fallback

The follow-up review found that clone-local `npx daiso` commands do not run from a built `hmmhmmhm/daiso-mcp` checkout because the generated bin file is not executable. This updates the olive-young skill and docs to use the verified `node dist/bin.js ...` path for clone fallback, and locks that behavior with regression tests while keeping the public CLI-first path unchanged.\n\nConstraint: Upstream clone checkouts can fail with `Permission denied` when invoked through clone-local `npx daiso`\nConstraint: Keep the existing daiso-product-search skill untouched\nRejected: Leave install docs unchanged | PR body and docs would keep a broken clone fallback path\nRejected: Vendor or patch upstream daiso-mcp inside k-skill | issue explicitly requires documenting upstream flow instead\nConfidence: high\nScope-risk: narrow\nReversibility: clean\nDirective: Keep clone-fallback docs and PR verification commands aligned with the actually runnable local invocation\nTested: node --test scripts/skill-docs.test.js\nTested: npm run ci\nTested: cd /tmp/daiso-mcp && npm install && npm run build && node dist/bin.js health\nTested: cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/stores --keyword 명동 --limit 3 --json\nTested: cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/products --keyword 선크림 --size 3 --json\nTested: cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/inventory --keyword 선크림 --storeKeyword 명동 --size 2 --json\nNot-tested: Public npx endpoint stability beyond the verified smoke-test window

* Record fresh verification for the approved cheap-gas follow-up

The ranked Kakao fallback-order and invalid-limit fixes are already present on feature/#54, so no further code edits were necessary. This empty follow-up commit records the requested rerun verification for PR #67 before posting the implementation update.\n\nConstraint: Existing branch head already contains the approved cheap-gas-nearby follow-up\nRejected: Invent another code/doc change just to force a non-empty diff | unnecessary risk after approval\nConfidence: high\nScope-risk: narrow\nReversibility: clean\nDirective: Keep Kakao anchor fallback iteration tied to the shared ranking helper and preserve finite-number validation for limit inputs\nTested: node --test packages/cheap-gas-nearby/test/index.test.js; npm run ci; offline fixture smoke for searchCheapGasStationsByLocationQuery('서울역', ...); lsp diagnostics on affected files\nNot-tested: Live Kakao/Opinet network calls with a non-placeholder OPINET_API_KEY

* Record verified delivery for approved cheap-gas-nearby rollout

The approved Issue #54 implementation was already present on feature/#54 when this follow-up began, so this checkpoint records fresh verification evidence and keeps PR #67 moving without introducing extra code churn.

Constraint: Existing approved fixes were already on the branch and the follow-up still required a pushed update plus implementation comment
Rejected: Add additional code or docs churn | approved scope was already satisfied and green locally
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Preserve the Kakao fallback-order and invalid-count regression coverage unless equivalent runtime repros replace it
Tested: node --test packages/cheap-gas-nearby/test/index.test.js; offline fixture smoke for searchCheapGasStationsByLocationQuery('서울역', ...); npm run ci; LSP diagnostics on affected cheap-gas-nearby files
Not-tested: Live Kakao/Opinet network path without a non-placeholder OPINET_API_KEY

* Keep Blue Ribbon premium-gate remapping explicitly bounded

The issue #63 fix already remaps PREMIUM_REQUIRED for nearby lookups. This follow-up adds a regression that proves non-premium /restaurants/map failures still surface as generic request errors, and clarifies that boundary in the package and feature docs.

Constraint: PR #68 already carries the shipped behavior change and must stay aligned with current live premium-gated upstream behavior
Rejected: Broaden domain remapping to all 403 nearby errors | would hide distinct upstream failure modes
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep only PREMIUM_REQUIRED on /restaurants/map mapped to premium_required unless a new verified upstream contract is documented
Tested: npm test --workspace blue-ribbon-nearby; npm run lint --workspace blue-ribbon-nearby; npm run ci; live node repro for location+coordinate nearby lookups on 2026-04-06; LSP diagnostics on src/test
Not-tested: Alternative non-premium upstream status codes beyond the mocked 403 ACCESS_DENIED path

* Keep Olive Young clone fallback docs runnable from a fresh clone

Round 3 review found that the inline fallback shorthand skipped `cd daiso-mcp`,
which made `npm install` run outside the cloned upstream repo even though the
fenced examples were already correct. This tightens the two published shorthand
snippets and adds regression coverage so both docs surfaces reject the broken
chain in future edits.

Constraint: Existing node dist/bin.js clone fallback examples were already verified and had to stay aligned
Constraint: The follow-up had to stay scoped to docs/test coverage without touching the existing daiso-product-search skill
Rejected: Convert the shorthand to prose only | the review specifically requested a runnable inline form or an explicit removal
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep inline clone fallback snippets synchronized with the fenced node dist/bin.js examples and require `cd daiso-mcp` before install/build
Tested: node --test scripts/skill-docs.test.js; npm run ci; cd /tmp/daiso-mcp && npm install && npm run build; cd /tmp/daiso-mcp && node dist/bin.js health; cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/stores --keyword 명동 --limit 3 --json; cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/products --keyword 선크림 --size 3 --json; cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/inventory --keyword 선크림 --storeKeyword 명동 --size 2 --json
Not-tested: Public npx path in this follow-up round (previous PR verification already covered it)
Related: PR #71

* Bundle korean-spell-check script inside skill directory for packageless installs

The Python helper lived only in the repo-root scripts/ folder, so
`skills add` never shipped it. Move the real implementation into
korean-spell-check/scripts/ (mirroring joseon-sillok-search) and
replace the root copy with a thin re-export wrapper so lint/test
still resolve from the repo root.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Exclude .claude directory from skill validation to fix CI in worktrees

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* real-estate-search: MCP self-host → k-skill-proxy HTTP 전환

사용자가 직접 real-estate-mcp를 clone/self-host하는 대신
k-skill-proxy에 MOLIT 실거래가 API route를 추가해서
다른 스킬(한강수위, 미세먼지 등)과 동일한 패턴으로 사용 가능하게 함.

- GET /v1/real-estate/region-code — 지역코드 검색
- GET /v1/real-estate/:assetType/:dealType — 9개 거래 유형 조회
- molit.js: XML 파싱, 필드 정규화, 취소거래 필터링, 요약통계
- region-lookup.js: 284개 법정동 5자리 코드 토큰 매칭
- SKILL.md/docs를 HTTP proxy 기반으로 전면 재작성
- DATA_GO_KR_API_KEY를 사용자 필수항목에서 프록시 운영자 전용으로 이동

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* cheap-gas-nearby: proxy 경유로 전환 + 프로덕션 배포 구조 문서화

- cheap-gas-nearby: OPINET_API_KEY 없이 k-skill-proxy 경유로 동작하도록 변경
  - fetchAroundStations/fetchDetailById에 proxy fallback 추가
  - SKILL.md, feature doc에서 사용자 API key 요구 제거
- AGENTS.md, CLAUDE.md: proxy 개발/배포 워크플로우 문서화
- docs/features/k-skill-proxy.md: opinet route 추가, 프로덕션 자동 배포 구조 설명

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic…

* Feature/#58 (#70)

* Add an official KIPRIS patent-search skill for Korean IP lookups

Issue #58 adds a bundled stdlib Python helper plus install/setup/docs coverage
for KIPRIS Plus keyword search and application-number detail lookup. The
implementation keeps auth explicit via KIPRIS_PLUS_API_KEY -> ServiceKey and
locks the repo contract with doc/install and regression tests.

Constraint: KIPRIS Plus requires a per-user ServiceKey and no valid key was available for live success-path runs
Constraint: No new dependencies allowed for bundled skill helpers
Rejected: Add a new npm/python workspace | docs+helper pattern already fits repo and keeps install payload lighter
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep the helper aligned with official KIPRIS Plus XML fields and ServiceKey naming before widening the skill surface
Tested: python3 scripts/patent_search.py --help; python3 scripts/patent_search.py --query '배터리' --service-key dummy; python3 scripts/patent_search.py --application-number 1020240001234 --service-key dummy; PYTHONPATH=.:scripts python3 -m unittest scripts.test_patent_search; node --test scripts/skill-docs.test.js; npm run lint; npm run typecheck; npm test
Not-tested: Successful live KIPRIS search with a valid production ServiceKey
Related: #58

* Accept copied KIPRIS portal keys without request corruption

KIPRIS Plus users commonly paste the percent-encoded ServiceKey shown by the
portal. The helper now normalizes that key once before query serialization,
adds regression coverage for explicit and env-driven inputs, and clarifies the
documentation so copied portal keys remain valid instead of being double-
encoded on the wire.

Constraint: KIPRIS Plus still expects the standard ServiceKey query parameter contract
Rejected: Preserve raw percent signs during urlencode only for ServiceKey | more brittle than normalizing once at the boundary
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep ServiceKey normalization at the input boundary so future request-building changes do not reintroduce double-encoding
Tested: python3 scripts/patent_search.py --help; python3 scripts/patent_search.py --query '배터리' --service-key dummy; python3 scripts/patent_search.py --application-number 1020240001234 --service-key dummy; PYTHONPATH=.:scripts python3 -m unittest scripts.test_patent_search; node --test scripts/skill-docs.test.js; npm run lint; npm run typecheck; npm test
Not-tested: Live KIPRIS Plus lookup with a real KIPRIS_PLUS_API_KEY

* Record fresh verification for the approved KIPRIS key fix

The approved ServiceKey normalization fix is already present on feature/#58, so no further code edits were necessary. This empty follow-up commit records the requested rerun verification and keeps PR #70 moving without reopening a settled implementation path.

Constraint: Existing branch head already contains the approved code and docs fix
Rejected: Invent an extra code/doc change just to produce a non-empty diff | unnecessary risk after approval
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Do not change ServiceKey handling again without reproducing the percent-encoded portal-key path
Tested: python3 scripts/patent_search.py --help; python3 scripts/patent_search.py --query '배터리' --service-key dummy; python3 scripts/patent_search.py --application-number 1020240001234 --service-key dummy; PYTHONPATH=.:scripts python3 -m unittest scripts.test_patent_search; node --test scripts/skill-docs.test.js; npm run lint; npm run typecheck; npm test
Not-tested: Live KIPRIS request with a real KIPRIS_PLUS_API_KEY

* Record fresh Issue #58 verification without reopening the approved fix

The approved KIPRIS ServiceKey normalization change was already present on
feature/#58, so this follow-up records a fresh verification point for the
existing implementation instead of reopening the code path.

Constraint: User requested commit/push + PR follow-up on the existing issue branch
Rejected: Reopen the already-approved implementation | no new blocker or code defect remained
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If this helper changes again, preserve support for percent-encoded portal keys and keep the URL serialization regression coverage intact
Tested: patent helper smoke commands, patent/doc regression tests, lint, typecheck, full npm test suite, encoded-key reproduction, architect review
Not-tested: Live KIPRIS success path with a real KIPRIS_PLUS_API_KEY

* fix: decode percent-encoded ServiceKey in build_search_params and build_detail_params

The low-level helper functions still double-encoded percent-encoded
KIPRIS portal keys when callers bypassed resolve_service_key(). Apply
unquote() at the param-builder boundary so copied portal keys work
regardless of call path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: trigger GitHub merge-status recalculation

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Enable session-bound Hi-Pass receipt workflows without promising unattended login

Issue #79 was approved specifically as a logged-in-session-only automation surface, so the change adds a new hipass-receipt skill/package that focuses on query building, usage-history parsing, receipt payload assembly, and session-expiry detection. The docs now make manual login, session expiry, and official flow limits explicit while wiring the new skill into install/setup/source surfaces.

Constraint: Hi-Pass uses short-lived server sessions and multi-step login flows that are not safe to reimplement as raw HTTP automation
Rejected: Unattended cookie/session reuse bot | server session expires and redirects back to login
Rejected: Docs-only skill with no helper package | would not provide runnable, testable behavior for payload parsing and session detection
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep this feature scoped to logged-in browser sessions; do not promise credential automation or long-lived session recovery without new evidence from the official site
Tested: npm run ci
Tested: CLI fixture-demo and chrome-command smoke runs
Tested: Live inspectHipassPage smoke against /comm/lginpg.do and /usepculr/InitUsePculrTabSearch.do
Not-tested: Authenticated live receipt popup opening with a real Hi-Pass account/session

* Keep hipass-receipt's published workflow runnable from a clean install

Reviewer verification found three blocking gaps in the follow-up: the browser workflow still navigated to an undefined endpoint, the published package did not carry the runtime CDP client or fixture smoke assets, and the documented encrypted-card-number flag was silently ignored. This update bundles the CDP client + published fixtures, fixes the usage-history init route, and normalizes the alias in code and docs while locking the behavior with mocked-CDP and pack dry-run regressions.

Constraint: Hi-Pass support remains logged-in-browser-session-only; no automated credential entry or long-lived unattended login
Constraint: The published npm package must support the documented fixture smoke path from an unpacked tarball
Rejected: Document a separate manual playwright install only | reviewer found the advertised clean-install flow should work as shipped
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep CLI aliases, published files, and README smoke commands aligned whenever hipass-receipt packaging changes
Tested: npm run ci; node packages/hipass-receipt/src/cli.js fixture-demo --fixture packages/hipass-receipt/test/fixtures/usage-history-list.html --start-date 2026-04-01 --end-date 2026-04-07; node packages/hipass-receipt/src/cli.js chrome-command --profile-dir "/Users/jeffrey/.cache/k-skill/hipass-chrome" --debugging-port 9222; live unauthenticated inspectHipassPage smoke for /comm/lginpg.do and /usepculr/InitUsePculrTabSearch.do; packed tarball install smoke with npm ls playwright-core + fixture-demo; LSP diagnostics 0 on hipass-receipt JS files
Not-tested: authenticated live list/receipt against a real logged-in Hi-Pass browser session

* Keep Hi-Pass live receipt automation runnable end-to-end

Regression tests now lock the Playwright navigation target and browser lifecycle so live list/receipt sessions use an absolute URL and close their CDP connection before exit. The implementation switches the init navigation to the exported absolute endpoint and closes the browser in finally blocks for both workflows, matching the blocked review findings without widening scope.

Constraint: Hi-Pass live automation depends on an existing logged-in Chrome CDP session
Constraint: Review blockers required fixing both real Playwright navigation and clean CLI exit behavior without changing the manual-login contract
Rejected: Keep the relative path and relax mocks | would miss the real invalid-URL failure mode
Rejected: Leave browser lifecycle ownership to CLI callers | library users would still hang on list/receipt
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep usage-history navigation on absolute URLs and preserve explicit browser.close() regression coverage for both list and receipt flows
Tested: node --test packages/hipass-receipt/test/index.test.js; node packages/hipass-receipt/src/cli.js fixture-demo --fixture packages/hipass-receipt/test/fixtures/usage-history-list.html --start-date 2026-04-01 --end-date 2026-04-07; node packages/hipass-receipt/src/cli.js chrome-command --profile-dir "/Users/jeffrey/.cache/k-skill/hipass-chrome" --debugging-port 9222; live unauthenticated inspectHipassPage smoke for /comm/lginpg.do and /usepculr/InitUsePculrTabSearch.do; mocked CLI list/receipt exit smoke; npm run ci
Not-tested: Real authenticated Hi-Pass CDP session with a manually logged-in user profile

* 📝 docs: 블루리본 맛집 스킬 지원 중단 안내

bluer.co.kr 측이 자동화 접근을 전면 차단해 스킬이 더 이상
동작하지 않는다. 표에 취소선·경고 태그를 달고 표 아래에
상세 사유 blockquote를 추가한다.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

*  test: 블루리본 deprecated 테이블 행 패턴 반영

README에 취소선·지원중단 태그가 붙으면서 기존 regex가
플레인 `| 근처 블루리본 맛집 |` 를 요구해 CI가 깨졌다.
새 deprecated 행 형태와 상세 안내 blockquote 존재를
assertion 으로 확정해 문서가 다시 편집돼 경고가 사라지는
회귀를 잡는다.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-04-08 21:13:55 +09:00
Jeffrey (Dongkyu) Kim
52ca59327e test: 블루리본 deprecated 테이블 행 패턴 반영
README에 취소선·지원중단 태그가 붙으면서 기존 regex가
플레인 `| 근처 블루리본 맛집 |` 를 요구해 CI가 깨졌다.
새 deprecated 행 형태와 상세 안내 blockquote 존재를
assertion 으로 확정해 문서가 다시 편집돼 경고가 사라지는
회귀를 잡는다.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 19:56:08 +09:00
Jeffrey (Dongkyu) Kim
2b811f0149 📝 docs: 블루리본 맛집 스킬 지원 중단 안내
bluer.co.kr 측이 자동화 접근을 전면 차단해 스킬이 더 이상
동작하지 않는다. 표에 취소선·경고 태그를 달고 표 아래에
상세 사유 blockquote를 추가한다.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 19:51:41 +09:00
Jeffrey (Dongkyu) Kim
7710281ade Merge main into dev: sync household-waste-info and README trim/auth-collapse from PR #82 2026-04-08 16:18:23 +09:00
hon2be
73f995b343
feat: 생활쓰레기 조회 스킬 및 프록시 라우트 구현 (#82)
*  feat: 생활쓰레기 조회 스킬 및 프록시 라우트 구현

생활쓰레기 조회 스킬 문서와 기능 가이드를 추가하고, 프록시 라우트를 구현해 조회 흐름을 완성했다. 설치/설정 문서도 스킬 사용 흐름에 맞게 정리했다.

Made-with: Cursor

* fix(household-waste-info): force returnType=json, add proxy tests, fix SKILL.md newline

- Drop user-supplied returnType and force "json" upstream so the cache key
  (which omits returnType) cannot be poisoned by alternate response shapes.
- Add server tests covering: missing SGG_NM (400), missing API key (503),
  serviceKey injection + cache hit on second call, returnType=xml override
  ignored, upstream non-200 surfaced as 502.
- Add trailing newline to household-waste-info/SKILL.md.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(household-waste-info): align skill contract with proxy reality

vkehfdl1's review on PR #82: skill/docs claimed support for
cond[DAT_CRTR_YMD::*] / cond[DAT_UPDT_PNT::*] filters and an optional
returnType, but the proxy only forwards pageNo, numOfRows, and
cond[SGG_NM::LIKE], and forces returnType=json. Typical user queries
("강남구 쓰레기 배출 요일") only need 시군구 검색, so shrink the
documented contract to match the proxy instead of widening pass-through.

- household-waste-info/SKILL.md: list only proxy-supported params, note
  returnType is server-forced, fix failure modes.
- docs/features/household-waste-info.md: switch base example to the
  proxy route, drop the bare upstream curl, call out unsupported
  filters explicitly.
- docs/install.md, docs/security-and-secrets.md, k-skill-setup/SKILL.md:
  describe the skill as calling the proxy /v1/household-waste/info
  route rather than the raw upstream endpoint.

* docs(readme): collapse auth column to user-login binary

The 인증/시크릿 column mixed user-side credentials, proxy URL hints,
and "use this fallback" notes — confusing for end users who only need
to know "do I have to log in or not?". Operator-managed secrets that
ship in k-skill-proxy are not the user's problem.

- Rename column to "사용자 로그인" with a one-line preface explaining
  the new contract.
- Reclassify proxy-fronted skills (서울 지하철, 한강 수위, 부동산,
  생활쓰레기, 가장 싼 주유소, 한국 법령 remote endpoint) to 불필요.
- Only SRT, KTX, 토스증권 keep 필요 (real per-user account login).
- Tighten the household-waste-info row to use the proxy-route phrasing
  consistent with the rest of the docs in this PR.
- Update skill-docs tests to assert the new binary classification for
  서울 지하철 and 한국 법령 rows.

* docs(readme): trim feature table descriptions to user-facing capability

The 설명 column was leaking implementation details — k-skill-proxy
routing notes, upstream package names, anti-bot helper mentions —
that don't help a user decide whether the skill does what they want.

Rewrite each row to state only "what this skill does for the user",
dropping references to k-skill-proxy, upstream library names
(real-estate-mcp, kakaocli, daiso-mcp, coupang-mcp, tossctl,
korean-law-mcp, Dynapath helper, Kakao Map anchor, Opinet, etc.) and
proxy route paths. The 사용자 로그인 column already captures the
"do I need credentials?" question, so the description is free to
focus on the capability itself.

---------

Co-authored-by: hyeongr <honeybee@hyeongrui-MacBookPro.local>
Co-authored-by: Jeffrey (Dongkyu) Kim <vkehfdl1@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:08:24 +09:00
Jeffrey (Dongkyu) Kim
2b381bd2b5 docs(readme): trim feature table descriptions to user-facing capability
The 설명 column was leaking implementation details — k-skill-proxy
routing notes, upstream package names, anti-bot helper mentions —
that don't help a user decide whether the skill does what they want.

Rewrite each row to state only "what this skill does for the user",
dropping references to k-skill-proxy, upstream library names
(real-estate-mcp, kakaocli, daiso-mcp, coupang-mcp, tossctl,
korean-law-mcp, Dynapath helper, Kakao Map anchor, Opinet, etc.) and
proxy route paths. The 사용자 로그인 column already captures the
"do I need credentials?" question, so the description is free to
focus on the capability itself.
2026-04-08 16:04:08 +09:00
Jeffrey (Dongkyu) Kim
a3062895ee docs(readme): collapse auth column to user-login binary
The 인증/시크릿 column mixed user-side credentials, proxy URL hints,
and "use this fallback" notes — confusing for end users who only need
to know "do I have to log in or not?". Operator-managed secrets that
ship in k-skill-proxy are not the user's problem.

- Rename column to "사용자 로그인" with a one-line preface explaining
  the new contract.
- Reclassify proxy-fronted skills (서울 지하철, 한강 수위, 부동산,
  생활쓰레기, 가장 싼 주유소, 한국 법령 remote endpoint) to 불필요.
- Only SRT, KTX, 토스증권 keep 필요 (real per-user account login).
- Tighten the household-waste-info row to use the proxy-route phrasing
  consistent with the rest of the docs in this PR.
- Update skill-docs tests to assert the new binary classification for
  서울 지하철 and 한국 법령 rows.
2026-04-08 15:59:28 +09:00
Jeffrey (Dongkyu) Kim
df70ce7a09 docs(household-waste-info): align skill contract with proxy reality
vkehfdl1's review on PR #82: skill/docs claimed support for
cond[DAT_CRTR_YMD::*] / cond[DAT_UPDT_PNT::*] filters and an optional
returnType, but the proxy only forwards pageNo, numOfRows, and
cond[SGG_NM::LIKE], and forces returnType=json. Typical user queries
("강남구 쓰레기 배출 요일") only need 시군구 검색, so shrink the
documented contract to match the proxy instead of widening pass-through.

- household-waste-info/SKILL.md: list only proxy-supported params, note
  returnType is server-forced, fix failure modes.
- docs/features/household-waste-info.md: switch base example to the
  proxy route, drop the bare upstream curl, call out unsupported
  filters explicitly.
- docs/install.md, docs/security-and-secrets.md, k-skill-setup/SKILL.md:
  describe the skill as calling the proxy /v1/household-waste/info
  route rather than the raw upstream endpoint.
2026-04-08 15:45:25 +09:00
Jeffrey (Dongkyu) Kim
baea3f2c24 fix(household-waste-info): force returnType=json, add proxy tests, fix SKILL.md newline
- Drop user-supplied returnType and force "json" upstream so the cache key
  (which omits returnType) cannot be poisoned by alternate response shapes.
- Add server tests covering: missing SGG_NM (400), missing API key (503),
  serviceKey injection + cache hit on second call, returnType=xml override
  ignored, upstream non-200 surfaced as 502.
- Add trailing newline to household-waste-info/SKILL.md.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 15:22:48 +09:00
Jeffrey (Dongkyu) Kim
ed3cbb8c92
Merge pull request #81 from NomaDamas/feature/#79
Feature/#79
2026-04-07 20:42:50 +09:00
Jeffrey (Dongkyu) Kim
ce162ddb11 Keep Hi-Pass live receipt automation runnable end-to-end
Regression tests now lock the Playwright navigation target and browser lifecycle so live list/receipt sessions use an absolute URL and close their CDP connection before exit. The implementation switches the init navigation to the exported absolute endpoint and closes the browser in finally blocks for both workflows, matching the blocked review findings without widening scope.

Constraint: Hi-Pass live automation depends on an existing logged-in Chrome CDP session
Constraint: Review blockers required fixing both real Playwright navigation and clean CLI exit behavior without changing the manual-login contract
Rejected: Keep the relative path and relax mocks | would miss the real invalid-URL failure mode
Rejected: Leave browser lifecycle ownership to CLI callers | library users would still hang on list/receipt
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep usage-history navigation on absolute URLs and preserve explicit browser.close() regression coverage for both list and receipt flows
Tested: node --test packages/hipass-receipt/test/index.test.js; node packages/hipass-receipt/src/cli.js fixture-demo --fixture packages/hipass-receipt/test/fixtures/usage-history-list.html --start-date 2026-04-01 --end-date 2026-04-07; node packages/hipass-receipt/src/cli.js chrome-command --profile-dir "/Users/jeffrey/.cache/k-skill/hipass-chrome" --debugging-port 9222; live unauthenticated inspectHipassPage smoke for /comm/lginpg.do and /usepculr/InitUsePculrTabSearch.do; mocked CLI list/receipt exit smoke; npm run ci
Not-tested: Real authenticated Hi-Pass CDP session with a manually logged-in user profile
2026-04-07 20:17:45 +09:00
Jeffrey (Dongkyu) Kim
77edc3ad28 Keep hipass-receipt's published workflow runnable from a clean install
Reviewer verification found three blocking gaps in the follow-up: the browser workflow still navigated to an undefined endpoint, the published package did not carry the runtime CDP client or fixture smoke assets, and the documented encrypted-card-number flag was silently ignored. This update bundles the CDP client + published fixtures, fixes the usage-history init route, and normalizes the alias in code and docs while locking the behavior with mocked-CDP and pack dry-run regressions.

Constraint: Hi-Pass support remains logged-in-browser-session-only; no automated credential entry or long-lived unattended login
Constraint: The published npm package must support the documented fixture smoke path from an unpacked tarball
Rejected: Document a separate manual playwright install only | reviewer found the advertised clean-install flow should work as shipped
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep CLI aliases, published files, and README smoke commands aligned whenever hipass-receipt packaging changes
Tested: npm run ci; node packages/hipass-receipt/src/cli.js fixture-demo --fixture packages/hipass-receipt/test/fixtures/usage-history-list.html --start-date 2026-04-01 --end-date 2026-04-07; node packages/hipass-receipt/src/cli.js chrome-command --profile-dir "/Users/jeffrey/.cache/k-skill/hipass-chrome" --debugging-port 9222; live unauthenticated inspectHipassPage smoke for /comm/lginpg.do and /usepculr/InitUsePculrTabSearch.do; packed tarball install smoke with npm ls playwright-core + fixture-demo; LSP diagnostics 0 on hipass-receipt JS files
Not-tested: authenticated live list/receipt against a real logged-in Hi-Pass browser session
2026-04-07 20:03:59 +09:00
Jeffrey (Dongkyu) Kim
bb79fa5813 Enable session-bound Hi-Pass receipt workflows without promising unattended login
Issue #79 was approved specifically as a logged-in-session-only automation surface, so the change adds a new hipass-receipt skill/package that focuses on query building, usage-history parsing, receipt payload assembly, and session-expiry detection. The docs now make manual login, session expiry, and official flow limits explicit while wiring the new skill into install/setup/source surfaces.

Constraint: Hi-Pass uses short-lived server sessions and multi-step login flows that are not safe to reimplement as raw HTTP automation
Rejected: Unattended cookie/session reuse bot | server session expires and redirects back to login
Rejected: Docs-only skill with no helper package | would not provide runnable, testable behavior for payload parsing and session detection
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep this feature scoped to logged-in browser sessions; do not promise credential automation or long-lived session recovery without new evidence from the official site
Tested: npm run ci
Tested: CLI fixture-demo and chrome-command smoke runs
Tested: Live inspectHipassPage smoke against /comm/lginpg.do and /usepculr/InitUsePculrTabSearch.do
Not-tested: Authenticated live receipt popup opening with a real Hi-Pass account/session
2026-04-07 19:44:43 +09:00
hyeongr
bcd32ac7cc feat: 생활쓰레기 조회 스킬 및 프록시 라우트 구현
생활쓰레기 조회 스킬 문서와 기능 가이드를 추가하고, 프록시 라우트를 구현해 조회 흐름을 완성했다. 설치/설정 문서도 스킬 사용 흐름에 맞게 정리했다.

Made-with: Cursor
2026-04-07 19:35:01 +09:00
Jeffrey (Dongkyu) Kim
b8ea339446
Feature/#58 (#70)
* Add an official KIPRIS patent-search skill for Korean IP lookups

Issue #58 adds a bundled stdlib Python helper plus install/setup/docs coverage
for KIPRIS Plus keyword search and application-number detail lookup. The
implementation keeps auth explicit via KIPRIS_PLUS_API_KEY -> ServiceKey and
locks the repo contract with doc/install and regression tests.

Constraint: KIPRIS Plus requires a per-user ServiceKey and no valid key was available for live success-path runs
Constraint: No new dependencies allowed for bundled skill helpers
Rejected: Add a new npm/python workspace | docs+helper pattern already fits repo and keeps install payload lighter
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep the helper aligned with official KIPRIS Plus XML fields and ServiceKey naming before widening the skill surface
Tested: python3 scripts/patent_search.py --help; python3 scripts/patent_search.py --query '배터리' --service-key dummy; python3 scripts/patent_search.py --application-number 1020240001234 --service-key dummy; PYTHONPATH=.:scripts python3 -m unittest scripts.test_patent_search; node --test scripts/skill-docs.test.js; npm run lint; npm run typecheck; npm test
Not-tested: Successful live KIPRIS search with a valid production ServiceKey
Related: #58

* Accept copied KIPRIS portal keys without request corruption

KIPRIS Plus users commonly paste the percent-encoded ServiceKey shown by the
portal. The helper now normalizes that key once before query serialization,
adds regression coverage for explicit and env-driven inputs, and clarifies the
documentation so copied portal keys remain valid instead of being double-
encoded on the wire.

Constraint: KIPRIS Plus still expects the standard ServiceKey query parameter contract
Rejected: Preserve raw percent signs during urlencode only for ServiceKey | more brittle than normalizing once at the boundary
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep ServiceKey normalization at the input boundary so future request-building changes do not reintroduce double-encoding
Tested: python3 scripts/patent_search.py --help; python3 scripts/patent_search.py --query '배터리' --service-key dummy; python3 scripts/patent_search.py --application-number 1020240001234 --service-key dummy; PYTHONPATH=.:scripts python3 -m unittest scripts.test_patent_search; node --test scripts/skill-docs.test.js; npm run lint; npm run typecheck; npm test
Not-tested: Live KIPRIS Plus lookup with a real KIPRIS_PLUS_API_KEY

* Record fresh verification for the approved KIPRIS key fix

The approved ServiceKey normalization fix is already present on feature/#58, so no further code edits were necessary. This empty follow-up commit records the requested rerun verification and keeps PR #70 moving without reopening a settled implementation path.

Constraint: Existing branch head already contains the approved code and docs fix
Rejected: Invent an extra code/doc change just to produce a non-empty diff | unnecessary risk after approval
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Do not change ServiceKey handling again without reproducing the percent-encoded portal-key path
Tested: python3 scripts/patent_search.py --help; python3 scripts/patent_search.py --query '배터리' --service-key dummy; python3 scripts/patent_search.py --application-number 1020240001234 --service-key dummy; PYTHONPATH=.:scripts python3 -m unittest scripts.test_patent_search; node --test scripts/skill-docs.test.js; npm run lint; npm run typecheck; npm test
Not-tested: Live KIPRIS request with a real KIPRIS_PLUS_API_KEY

* Record fresh Issue #58 verification without reopening the approved fix

The approved KIPRIS ServiceKey normalization change was already present on
feature/#58, so this follow-up records a fresh verification point for the
existing implementation instead of reopening the code path.

Constraint: User requested commit/push + PR follow-up on the existing issue branch
Rejected: Reopen the already-approved implementation | no new blocker or code defect remained
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If this helper changes again, preserve support for percent-encoded portal keys and keep the URL serialization regression coverage intact
Tested: patent helper smoke commands, patent/doc regression tests, lint, typecheck, full npm test suite, encoded-key reproduction, architect review
Not-tested: Live KIPRIS success path with a real KIPRIS_PLUS_API_KEY

* fix: decode percent-encoded ServiceKey in build_search_params and build_detail_params

The low-level helper functions still double-encoded percent-encoded
KIPRIS portal keys when callers bypassed resolve_service_key(). Apply
unquote() at the param-builder boundary so copied portal keys work
regardless of call path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: trigger GitHub merge-status recalculation

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 18:30:03 +09:00
Jeffrey (Dongkyu) Kim
d5034daaaf
Feature/#57 (#69)
* Hide KMA forecast keys behind the public proxy

Add a Korea weather skill plus a new k-skill-proxy route for the
KMA short-term forecast API. The route accepts grid coordinates or
lat/lon, defaults to the latest safe base time, and keeps the user
workflow keyless while the proxy owns the upstream credential.

Constraint: KMA short-term forecast requires a service key and 5km grid parameters
Constraint: Must keep the client flow dependency-free and free of user API issuance
Rejected: Direct client-side KMA calls | would force end users to issue and store their own keys
Rejected: Add a third-party geocoder dependency | unnecessary for v1 when lat/lon-to-grid conversion is enough
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep future KMA integrations on explicit allowlisted proxy routes and avoid documenting upstream keys for clients
Tested: npm run ci; local HTTP smoke test for /v1/korea-weather/forecast with mocked upstream fetch; npx tsc --noEmit --pretty false --project /Users/jeffrey/Projects/k-skill/tsconfig.json
Not-tested: Live KMA upstream call with a production service key

* Reject invalid weather coordinates before proxying

The korea-weather forecast route already normalized valid lat/lon requests, but
out-of-range inputs could still project to NaN grid values and consume upstream
quota. This change rejects invalid latitude/longitude pairs at the boundary and
locks the behavior with a regression that proves invalid coordinates never
reach fetch.

Constraint: Public proxy endpoints must reject malformed caller input before upstream calls
Rejected: Allow upstream KMA failures to handle invalid coordinates | wastes quota and returns opaque errors
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep coordinate validation local to query normalization so cache keys and upstream URLs only see finite grid values
Tested: node --test packages/k-skill-proxy/test/server.test.js; local app.inject smoke for /v1/korea-weather/forecast with mocked fetch; npx tsc --noEmit --pretty false --project /Users/jeffrey/Projects/k-skill/tsconfig.json; npm run ci
Not-tested: Live KMA upstream behavior for invalid coordinates (intentionally blocked before proxying)

* Protect proxied public-data API keys with HTTPS transport

The new korea-weather route already hid the KMA service key behind the proxy, but
it was still forwarding that key over plaintext HTTP. Switching the shared
data.go.kr proxy base URL to HTTPS keeps the existing request contract intact
while closing the transport exposure. Regression coverage now locks the weather
route and direct KMA proxy helper to HTTPS so the downgrade does not regress.

Constraint: The follow-up had to stay minimal on the existing PR branch
Rejected: Add a KMA-only base URL constant | unnecessary divergence from the shared data.go.kr transport setting
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep proxied upstreams on HTTPS whenever the provider supports it, especially when operator-managed keys are injected server-side
Tested: node --test packages/k-skill-proxy/test/server.test.js; local buildServer().inject smoke with mocked fetch; npx tsc --noEmit --pretty false --project /Users/jeffrey/Projects/k-skill/tsconfig.json; npm run ci
Not-tested: Live network call to KMA upstream with a real service key

* Merge dev into main: new skills & proxy enhancements (#72)

* Remove client-side Seoul subway key setup

Route Seoul subway arrival lookups through k-skill-proxy so the
hosted proxy owns the Seoul Open Data upstream key and end users
only need the proxy base URL. Add proxy route coverage, update
skill/docs guidance, and align setup materials with the hosted
proxy flow used for fine dust.

Constraint: Must keep the proxy public, read-only, and dependency-free
Constraint: Must satisfy TDD-first verification and ship on feature/#35 targeting dev
Rejected: Add a separate client helper package | unnecessary extra layer for a single proxy route
Rejected: Keep SEOUL_OPEN_API_KEY as an end-user requirement | defeats the issue goal
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep the Seoul subway proxy surface limited to station-arrival passthrough unless tests/docs expand the contract
Tested: npm run ci; local proxy runtime on 127.0.0.1:4120 for /health and /v1/seoul-subway/arrival on 2026-03-31 with an invalid upstream key
Not-tested: Live success response with a valid Seoul Open API key
Related: #35

* Prevent broken Seoul subway proxy defaults before hosted rollout

The Seoul subway proxy endpoint code is present locally, but the hosted public route is not live yet. This change turns the user-facing subway docs back into an explicit proxy configuration flow, replaces the misleading hosted default in setup examples, and keeps subway proxy examples on self-host/local URLs until rollout is verified.

Constraint: Hosted k-skill-proxy.nomadamas.org/v1/seoul-subway/arrival is not live yet
Rejected: Keep the hosted Seoul subway URL as the default path | would send default users to a 404 route
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Do not restore a hosted Seoul subway default until the public proxy route is deployed and smoke-verified
Tested: node --test scripts/skill-docs.test.js; npm run ci; local proxy smoke on 127.0.0.1:4120 with stubbed Seoul upstream (GET /health, GET /v1/seoul-subway/arrival?stationName=강남)
Not-tested: Live hosted proxy smoke after deployment

* Reduce Seoul subway proxy upstream pressure with request caching

The public subway arrival route already normalized caller input but still re-fetched the Seoul upstream for every identical poll. This change adds a short-TTL cache keyed from the normalized subway query, annotates JSON responses with cache metadata, and locks the repeated-read collapse with a regression that proves alias/default normalization still reuses the cached result.

Constraint: The endpoint must stay public/no-auth while protecting a shared server-side SEOUL_OPEN_API_KEY from unnecessary repeated upstream hits
Rejected: Add a new shared cache abstraction for proxy metadata | unnecessary for this narrow fix and would enlarge the diff
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep subway cache keys tied to normalizeSeoulSubwayQuery output so alias/default query forms continue collapsing to one upstream request
Tested: node --test packages/k-skill-proxy/test/server.test.js
Tested: npm run ci
Tested: Local proxy runtime on 127.0.0.1:4120 with SEOUL_OPEN_API_KEY=test-seoul-key and stubbed fetch for /health plus repeated /v1/seoul-subway/arrival requests
Not-tested: Live hosted proxy rollout state

* Document OpenClaw support in the public compatibility list

The approved scope for issue #29 was narrowed to README support messaging,
so this change adds OpenClaw/ClawHub to the supported-client line and locks
that wording with a regression test in the docs suite.

Constraint: Issue #29 was approved as a README-only compatibility/docs change
Rejected: Broader install-doc updates | out of approved scope for this issue
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep OpenClaw support wording aligned with the supported-client README line and its docs regression test
Tested: Focused Node docs regression, npm run lint, npm run typecheck, npm run build, npm test
Not-tested: Live OpenClaw or ClawHub install flow (issue scope was documentation-only)

* Protect README client-support claims from ClawHub regressions

The README already advertises OpenClaw/ClawHub, but the docs
regression only matched OpenClaw. Tighten the assertion to the
exact supported-client fragment so a future edit cannot silently
remove ClawHub while keeping the issue verification command stable.

Constraint: PR #39 already publishes a verification command keyed to the current test name
Rejected: Rename the test to mention ClawHub explicitly | would drift from the published verification command
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep this regression synchronized with the README supported-client line whenever that copy changes
Tested: node --test scripts/skill-docs.test.js --test-name-pattern 'README advertises OpenClaw among the supported coding agents'
Tested: npm run lint
Tested: npm run typecheck
Tested: npm run build
Tested: npm test
Tested: lsp diagnostics for scripts/skill-docs.test.js (0 errors)
Not-tested: N/A

* Make Coupang shopper research usable without pretending scraping is stable

Coupang blocks unattended shopper queries in this environment, so the new package focuses on the durable pieces we can ship honestly: official URL builders, browser-captured HTML parsers, and explicit automation probes. The docs and skill now explain the seller-API limitation, the verified anti-bot behavior, and the browser-capture fallback expected by callers.

Constraint: No general shopper Open API surfaced in Coupang's official developer docs

Constraint: Headless/direct retrieval is anti-bot blocked in verified local probes

Rejected: Bundle a scraping bypass or hidden browser dependency | violates repo policy and would over-claim reliability

Confidence: high

Scope-risk: moderate

Reversibility: clean

Directive: Keep probe/docs behavior aligned with fresh live verification before claiming unattended Coupang access works

Tested: npm run ci; live probeAutomation(query=생수) with direct fetch + Playwright-core browserFetchHtml; LSP diagnostics on changed JS/test files

Not-tested: Headed/manual browser sessions with a human-authenticated Coupang context

* Keep the Coupang workspace installable and its entrypoint honest

Review follow-up found two contract gaps in the first issue #36 rollout: fresh installs were missing the new workspace link in package-lock, and the package README documented parser helpers that were not exported from the package entrypoint. Refreshing the lockfile and re-exporting the parsers keeps npm ci and consumer imports aligned with the published API.

Constraint: npm ci must succeed from a clean checkout
Rejected: Narrow the README API list to only client helpers | would hide useful parsers that the package already ships and tests
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: When adding a new workspace, refresh package-lock and verify the package entrypoint matches README public API claims
Tested: npm run ci; workspace coupang-product-search tests; LSP diagnostics on coupang-product-search JS/test files
Not-tested: published npm install from registry (local pack dry-run only)

* Keep Coupang anti-bot docs honest as probe results drift

A same-day PR rerun showed the published mobile probe snapshot was already stale, so the follow-up locks the 403/access-denied mobile path with regression coverage and softens the docs/skill contract to describe blocked outcome classes instead of one frozen signature. The published guidance now also explains that browser results only appear when browserFetchHtml is injected, matching what a clean checkout can actually reproduce.

Constraint: Coupang anti-bot responses vary by edge/challenge between reruns on the same day
Rejected: Freeze one exact mobile probe snapshot | same-day reruns already diverged
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Refresh Coupang anti-bot docs only with same-day live probe evidence, and prefer blocked outcome classes over a single exact signature when reruns disagree
Tested: npm run lint; npm run typecheck; npm test; npm run ci; live probeAutomation("생수") with direct fetch + Playwright-core browser fetch
Not-tested: Non-Chrome Playwright-core executables

* Keep the Coupang skill honest about clean-checkout browser probes

The approved follow-up already documented that clean-checkout probeAutomation runs leave browser unset unless a browser fetcher is injected, but the skill text itself had not made that null contract explicit. This commit adds a regression test that locks the browser-null wording across the published docs surfaces and updates the skill so the stated behavior matches the package implementation and clean-checkout reality.

Constraint: probeAutomation() only populates browser when browserFetchHtml is supplied by the caller
Rejected: Leave the skill wording implicit and rely on README examples alone | the skill could drift away from the shipped package contract again
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the README, feature doc, and skill aligned whenever the probeAutomation browser contract changes
Tested: node --test packages/coupang-product-search/test/index.test.js; npm run lint; npm run typecheck; npm test; npm run ci; live probeAutomation("생수") with direct fetch + Playwright-core browser fetch; codex exec review --uncommitted
Not-tested: Alternative browser engines beyond local Google Chrome for the manual browserFetchHtml probe

* Keep Coupang browser probe docs aligned with manual verification paths

The approved PR #40 follow-up still had one wording gap: the skill said when browser results appear, but it did not explicitly say those populated results come from an injected manual/external browserFetchHtml path. This change locks that contract with a failing regression first, then updates the skill text to match the already-shipped README/feature-doc guidance and runtime behavior.

Constraint: clean-checkout probeAutomation() must keep browser null unless browserFetchHtml is supplied
Rejected: change runtime browser probe behavior | approved follow-up was docs/test only and runtime contract is already correct
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: keep Coupang probe docs aligned with the shipped browserFetchHtml injection contract; do not imply built-in browser probing in clean checkouts
Tested: node --test packages/coupang-product-search/test/index.test.js; npm run lint; npm run typecheck; npm test; npm run ci; live probeAutomation("생수") with direct fetch + Playwright-core browser fetch; lsp diagnostics on changed files; architect review approved
Not-tested: alternate browser engines beyond local Google Chrome + playwright-core manual runner

* Route Korean law lookups through korean-law-mcp

Add a documentation-first korean-law-search skill and lock the repo docs around the rule that Korean law queries must go through korean-law-mcp rather than a new in-repo package.

The change updates install/setup/security guidance, publishes the new feature doc, and adds regression tests so future edits keep the LAW_OC + korean-law-mcp contract intact.

Constraint: Issue #41 requires korean-law-mcp for every Korean law lookup
Constraint: Must not add a new npm or python package in this repository
Rejected: Add a repo-local law package | violates the no-new-package requirement and duplicates upstream MCP work
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep Korean law lookup guidance pinned to korean-law-mcp unless issue requirements explicitly change
Tested: node --test scripts/skill-docs.test.js
Tested: npm install -g korean-law-mcp && korean-law list && korean-law help search_law
Tested: npm run ci
Tested: npx tsc --noEmit --pretty false --project /Users/jeffrey/Projects/k-skill/tsconfig.json
Not-tested: live search_law/get_law_text against a real LAW_OC credential

* Clarify Korean law setup modes to avoid credential confusion

A review found the new korean-law-search docs treated LAW_OC as an unconditional prerequisite even though the upstream remote MCP endpoint is configured separately from the local CLI/server path. This update makes the docs and regression tests mode-specific: local CLI/MCP uses LAW_OC, while the remote endpoint stays a korean-law-mcp-only url fallback without a user-supplied credential.

Constraint: Upstream korean-law-mcp uses LAW_OC on the local CLI/server path while the documented remote MCP endpoint is configured with url only
Rejected: Keep LAW_OC mandatory for every korean-law-mcp mode | contradicts upstream docs and reviewer evidence
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep README/setup/skill docs and regression tests aligned on the local-vs-remote korean-law-mcp contract
Tested: node --test scripts/skill-docs.test.js; npm install -g korean-law-mcp && korean-law list && korean-law help search_law; npx tsc --noEmit --pretty false --project /Users/jeffrey/Projects/k-skill/tsconfig.json; npm run ci; lsp_diagnostics scripts/skill-docs.test.js
Not-tested: Live remote MCP endpoint connection against https://korean-law-mcp.fly.dev/mcp

* Record issue #41 follow-up after approved verification

The approved korean-law-search change was already present on feature/#41, so this follow-up records a fresh verification pass and updates the PR without introducing unnecessary code churn.

Constraint: Existing branch already contained the approved implementation
Rejected: Touch repo files without need | would create noise without improving behavior
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the korean-law-mcp-only + mode-specific LAW_OC contract aligned with upstream docs before changing these surfaces again
Tested: node --test scripts/skill-docs.test.js; npx tsc --noEmit --pretty false --project /Users/jeffrey/Projects/k-skill/tsconfig.json; npm install -g korean-law-mcp && korean-law list && korean-law help search_law; npm run ci
Not-tested: Live korean-law queries that require a real LAW_OC or remote endpoint session

* Keep the Korean law feature diff merge-ready

Verification uncovered trailing whitespace in the new korean-law feature guide when checking the branch diff. I added a regression to the skill docs suite and removed the whitespace so the approved documentation contract stays clean and reviewable.

Constraint: Preserve the existing korean-law-mcp + mode-specific LAW_OC contract without widening scope
Rejected: Leave the whitespace issue as-is | git diff --check stayed dirty on the branch
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep docs/features/korean-law-search.md free of trailing whitespace so diff hygiene stays enforced by the regression
Tested: node --test scripts/skill-docs.test.js; npx tsc --noEmit --pretty false --project /Users/jeffrey/Projects/k-skill/tsconfig.json; npm install -g korean-law-mcp && korean-law list && korean-law help search_law; npm run ci; git diff --check
Not-tested: Live credentialed LAW_OC search execution against the upstream API

* Keep Korean law skill guidance aligned with supported lookups

The approved issue #41 feature already worked, but the shipped skill text still under-described ordinance and interpretation lookups compared with the documented capability set. This follow-up tightens the skill copy and locks that contract with a doc regression so the branch stays merge-ready without changing the underlying korean-law-mcp routing rules.

Constraint: Must preserve the existing korean-law-mcp-only and mode-specific LAW_OC setup contract
Rejected: Leave the skill wording as-is | drift from the documented lookup surface would remain
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the skill examples and doc regression aligned whenever supported korean-law-mcp search surfaces change
Tested: node --test scripts/skill-docs.test.js
Not-tested: live LAW_OC-backed upstream API queries

* Keep Korean law completion guidance in sync with enforced lookups

The previous follow-up aligned the main skill examples, but the done-criteria text still left interpretation and ordinance routing implicit. This commit makes the completion checklist explicit and extends the doc regression so future edits cannot silently drop those lookup paths.

Constraint: Must stay within the existing issue #41 korean-law-mcp-only contract
Rejected: Leave the done checklist implicit | reviewers and future edits could drift from the enforced lookup set
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: When the supported Korean-law lookup set changes, update the done checklist and regression together
Tested: node --test scripts/skill-docs.test.js
Not-tested: full CI before commit

* Enable repeatable used-car price lookups from a rental-company source

Issue #46 required surveying major Korean rental companies before implementation and then choosing the easiest stable provider. SK렌터카 다이렉트 exposes 타고BUY inventory in public Next.js page data, so the feature stays dependency-free while still supporting live repeated lookups and documented provider rationale.

Constraint: Must compare major Korean rental companies before implementation
Constraint: Must verify 10+ live lookups against a real provider surface
Rejected: 롯데오토옥션 as v1 provider | public list contract was unstable and legacy .do flows returned inconsistent or 404 pages
Rejected: 레드캡렌터카 as v1 provider | no public used-car inventory or API surface was found
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep the v1 provider read-only and inventory-snapshot-based unless a stable documented public API is confirmed
Tested: npm run ci
Tested: Live 10-query run against https://www.skdirect.co.kr/tb at 2026-04-02T07:22:46Z
Tested: LSP diagnostics on affected files
Not-tested: Seller-specific detail drilldowns or non-SK providers
Related: #46

* Keep used-car-price-search releasable after merge

Review feedback found that the new used-car workspace would not ship because it lacked a changeset, and the fallback install docs still omitted the runtime package. This follow-up adds regression coverage first, then restores both release and install-path coverage with the smallest possible diff.

Constraint: New publishable workspaces must be wired through Changesets to reach npm release automation
Constraint: Fallback install docs must list runtime packages users need when skill files are present but global packages are missing
Rejected: Fix only the changeset gap | would leave the documented fallback install path incomplete
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep used-car-price-search in both the fallback global install example and a Changesets entry whenever release wiring changes
Tested: node --test scripts/skill-docs.test.js; npx changeset status; live 10-query used-car run against SK direct at 2026-04-02T07:38:44.949Z; npm run ci; LSP diagnostics on used-car package files and scripts/skill-docs.test.js; architect verification
Not-tested: No additional live provider permutations beyond the verified 10-query smoke run

* Keep used-car verification docs resilient to live inventory churn

A fresh rerun showed the SK direct inventory total continues to move during the day, so the feature doc now records the verified smoke-run timestamp without freezing a brittle exact count. The regression suite was updated first so the docs stay variability-aware and diff-clean in future follow-ups.

Constraint: Live SK direct inventory totals change over time even when the parsing contract stays stable
Rejected: Keep documenting a fixed total from one smoke run | it immediately drifted and made the docs stale
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the used-car live verification note timestamped and variability-aware instead of pinning an exact inventory total
Tested: node --test scripts/skill-docs.test.js; git diff --check; npm run ci; npx changeset status; live 10-query run against https://www.skdirect.co.kr/tb at 2026-04-02T07:59:41.391Z; LSP diagnostics on scripts/skill-docs.test.js; architect verification
Not-tested: No additional content changes outside the used-car feature doc

* Keep used-car query summaries honest when results are limited

The review found that query-level stats were being computed from the
post-limit slice, so common searches like 아반떼 under-reported both
match counts and price ranges. This change locks the regression first,
then computes the full filtered set before slicing only the returned
items.

Constraint: PR #48 must preserve the existing API shape while fixing the review-blocking stats bug
Rejected: Expanding the response with separate limited/full summary fields | unnecessary API churn for a targeted bug fix
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Query-level summary and matchedCount must stay derived from the full filtered set, not the display limit slice
Tested: npm test --workspace used-car-price-search; npm run ci; git diff --check; npx changeset status; live SK direct smoke run on 2026-04-02T08:23:59Z; LSP diagnostics on used-car-price-search source and test files; architect verification APPROVED
Not-tested: limit=0 semantics remain unchanged and still coerce to the default limit
Related: PR #48

* Keep korean-law-search available during upstream outages (#45)

Issue #44 adds Beopmang as the documented fallback when the primary korean-law-mcp path is unavailable, and locks the new routing into the doc regression suite so future edits do not silently revert the policy.

Constraint: Existing guidance must still prefer korean-law-mcp and keep LAW_OC scoped to the local CLI/MCP path
Rejected: Add a repo-local Beopmang client package | issue only requires fallback registration, not a new implementation surface
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the primary-first rule intact; Beopmang is an outage fallback, not the default path
Tested: node --test scripts/skill-docs.test.js; npm run ci; korean-law list; python3 live Beopmang search smoke for 관세법
Not-tested: Live Beopmang MCP handshake from a GUI MCP client

* Replace coupang scraping package with coupang-mcp server integration

Drop the browser-based scraping package (packages/coupang-product-search)
and switch to the uju777/coupang-mcp MCP server for all Coupang product
searches. This removes the anti-bot workaround complexity and provides
8 ready-to-use tools via MCP Streamable HTTP with no API key required.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Add disclaimer to used-car-price-search README

Clarify that the package queries SK렌터카 타고BUY public data with
no affiliation or sponsorship, and that ad/partnership inquiries
are welcome.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Refactor used-car-price-search into provider-based architecture

SK 타고BUY 전용 로직을 src/providers/sk-tagobuy.js로 분리하고,
공급자를 수평 확장할 수 있는 registry 구조로 전환한다.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Ship LCK analytics inside k-skill's managed release flow

Adapt jerjangmin's upstream lck-analytics skill/package into a new
workspace and skill pack, wire docs/install/release surfaces, and add
regression fixtures/tests plus script smoke coverage so the feature is
verifiable before publish.

Constraint: Upstream package is not published to npm yet
Constraint: Must preserve attribution to original source and author in shipped docs
Rejected: Keep the upstream lck-results install wording in k-skill | conflicts with repo workspace/package naming
Rejected: Ship only the npm package without the local skill scripts | issue explicitly requested the skill as well
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep Changesets as the only version-bump path for lck-analytics; do not hand-edit versions for release
Tested: node --test packages/lck-analytics/test/index.test.js scripts/skill-docs.test.js
Tested: npm run lint --workspace lck-analytics
Tested: npm test --workspace lck-analytics
Tested: live getLckSummary('2026-04-01', { team: '한화', includeStandings: true })
Tested: node lck-analytics/scripts/sync-oracle.js --csv lck-analytics/samples/oracle-lck-sample.csv --cache .tmp/lck-cache && node lck-analytics/scripts/build-match-report.js --date 2026-04-01 --team 한화 --cache .tmp/lck-cache && node lck-analytics/scripts/analyze-live-game.js --game game-1 --window packages/lck-analytics/test/fixtures/live-window-game-1.json --details packages/lck-analytics/test/fixtures/live-details-game-1.json --cache .tmp/lck-cache
Tested: npm run ci
Tested: npx tsc --noEmit --project /Users/jeffrey/Projects/k-skill/tsconfig.json
Not-tested: Riot live feed behavior for arbitrary future game ids outside the fixture-backed smoke path

* Enable policy-aware Korean spell checking from the official Nara surface

Add a skill guide and Python helper that use the approved old_speller HTML flow, chunk long text conservatively, and report original/suggestion/reason deltas. The docs also record the public-site limits, Cloudflare behavior, and non-commercial usage policy so agents do not overreach the free surface.

Constraint: Public site is HTML-only and may return 403 to non-browser clients
Constraint: Must not add new dependencies or high-volume crawling behavior
Rejected: Node fetch client | Cloudflare returned 403 in this environment
Rejected: Paid API integration | no public contract or credentials were available for this task
Confidence: medium
Scope-risk: moderate
Reversibility: clean
Directive: Keep usage low-rate and non-commercial unless supplier-approved API terms are added
Tested: npm run lint
Tested: npm run typecheck
Tested: npm test
Tested: npm run build
Tested: python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json
Tested: python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json
Not-tested: Paid API/order flow
Not-tested: High-volume commercial workloads
Not-tested: Non-UTF-8 file inputs

* Preserve korean spell-check layout in corrected output

The Nara payload can collapse paragraph separators into normalized page text, so the helper now maps corrections back onto the original chunk before rebuilding corrected_text. The CLI also rejects non-positive --max-chars values, and regression tests cover both the layout-preservation path and invalid argument handling.

Constraint: Nara result pages can normalize blank lines and sentence spacing before exposing errInfo offsets
Rejected: Narrow docs away from file/Markdown proofreading | preserving original chunk separators keeps the documented workflow intact
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep multiline separator preservation tied to the original chunk whenever a suggestion only changes whitespace across collapsed boundaries
Tested: python3 -m unittest scripts.test_korean_spell_check; npm run lint; npm run typecheck; npm test; npm run build; python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json; python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json; python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n왠지 않되요.' --format json; python3 scripts/korean_spell_check.py --text 테스트 --max-chars 0 --format json
Not-tested: Live multi-page Nara payloads with separator-sensitive corrections across multiple returned pages

* Preserve file-proofreading layout in Korean spell check output

The spell-check helper now keeps exact blank-line runs and paragraph
indentation when chunking and reassembling file-style input, while still
allowing the official service's spacing corrections to flow through.
Regression coverage now locks the collapsed-layout, triple-blank-line,
and cross-boundary spacing cases that triggered the PR review.

Constraint: Official Nara/PNU payloads can collapse layout and sometimes merge corrections across preserved paragraph boundaries
Rejected: Narrow docs away from file-level proofreading | the existing feature scope explicitly supports file and Markdown checks
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Preserve exact layout tokens only at original newline boundaries; do not reintroduce global strip/join normalization without real file-mode verification
Tested: npm run lint; npm run typecheck; npm test; npm run build; python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json; python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json; python3 scripts/korean_spell_check.py --file <tmpfile> --format json; python3 scripts/korean_spell_check.py --text 테스트 --max-chars 0 --format json
Not-tested: Live upstream behavior for extremely long leading/trailing-whitespace-only files
Related: PR #60

* Keep layout-preserving chunk splits from tripping on separator-boundary fixes

The follow-up layout preservation pass renamed the working unit variable, but one separator-length guard still referenced the old paragraph name. This commit finishes the refactor so exact-boundary paragraph chunks keep the preserved-separator logic reachable and the new regression coverage stays green.

Constraint: Must preserve the existing feature/#47 branch and PR #60 flow while fixing the approved review follow-up
Rejected: Revert the layout-preserving chunking refactor | would discard the verified file-layout fix and its regressions
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep chunk reassembly lossless; new chunking changes should continue to round-trip original separators byte-for-byte
Tested: npm run lint; npm run typecheck; npm test; npm run build; python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json; python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json; python3 scripts/korean_spell_check.py --file <tempfile-with-triple-blank-lines> --format json; python3 scripts/korean_spell_check.py --text 테스트 --max-chars 0 --format json (expected argument error)
Not-tested: Live multi-page service responses beyond the local smoke cases

* Preserve spell-check chunk separators when units overflow

The file-layout follow-up already preserved blank runs and indentation,
but the overlong-unit path still checked the wrong variable when
extracting a trailing separator. That could strand separators in
a standalone chunk and break exact reassembly for tight max-char
limits.

This commit fixes the separator extraction guard and locks the
behavior with a regression that proves chunk concatenation still
matches the original text when an overlong paragraph is followed
by a blank-line separator.

Constraint: File-mode reconstruction must preserve exact layout while chunking long input
Rejected: Broader chunking rewrite | existing structure only needed the overflow guard corrected
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep split_text_into_chunks chunk concatenation identical to the original input for layout-sensitive file checks
Tested: npm run lint; npm run typecheck; npm test; npm run build; python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json; python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json; python3 scripts/korean_spell_check.py --text 테스트 --max-chars 0 --format json; manual --file smoke with triple blank lines and indentation
Not-tested: Live-service failure modes such as Cloudflare/browser challenges

* Prevent clean spell-check chunks from crashing mixed file runs

The Nara/PNU surface can return a plain 'no issues found' HTML page
without the embedded result payload when a chunk is already clean.
Chunked file and markdown runs could hit that response on earlier
chunks, raise a ValueError, and never reach later chunks that still
needed corrections. Treat the no-issues page as an empty result set
and lock the behavior with narrow regression coverage.

Constraint: Upstream no-issue responses omit the JavaScript payload entirely and can split the status message across HTML whitespace
Rejected: Fabricate a synthetic result page | empty-page handling already preserves the original clean chunk
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep empty-result detection aligned with the upstream no-issues message unless the service publishes a structured empty payload contract
Tested: python3 -m unittest scripts.test_korean_spell_check
Tested: npm run lint
Tested: npm run typecheck
Tested: npm test
Tested: npm run build
Tested: python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json
Tested: python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json
Tested: python3 scripts/korean_spell_check.py --text 테스트 --max-chars 0 --format json
Tested: python3 scripts/korean_spell_check.py --file <tmpfile> --format json
Tested: python3 scripts/korean_spell_check.py --file <tmpfile> --max-chars 18 --format json
Not-tested: Other upstream empty-result templates beyond the current no-issues HTML wording

* Make Joseon Sillok lookups reproducible from the official site

Add a joseon-sillok-search skill and a Python helper that scrape the
official Joseon Annals search/detail pages. The helper normalizes
king/year metadata, fetches detail excerpts, and locks the repository
docs plus regression coverage around the shipped workflow.

Constraint: v1 must stay on official public HTML surfaces only
Constraint: Must avoid adding new dependencies for a simple scraping helper
Constraint: Shell connectivity to sillok.history.go.kr became intermittent during final live reruns
Rejected: Ship a new npm workspace | repo skill/docs pattern is enough for v1
Rejected: Add BeautifulSoup or another parser dependency | unnecessary for the bounded HTML patterns
Confidence: medium
Scope-risk: narrow
Reversibility: clean
Directive: Keep year filtering Gregorian and derived from official regnal metadata unless the upstream site exposes a better structured contract
Tested: npm run ci
Tested: Earlier live POST/detail probes against search/searchResultList.do and /id/kda_12512030_002 during implementation
Tested: Live official article inspection for kda_12512030_002 via the public site
Not-tested: Final end-to-end CLI live run after the last refactor, because the shell hit transient TCP timeouts to sillok.history.go.kr
Related: #59

* Prevent sillok follow-up fixes from missing filtered results or weakening trust

This follow-up addresses the blocking PR review items on the Joseon Sillok helper. The search loop now derives total pages from the first live page size so king/year filtering can reach later pages, TLS verification stays enabled on both requests and urllib paths, detail parsing accepts the live classification brackets, and repo docs stop linking to missing Korean spell-check assets on this branch.

Constraint: Must preserve the existing joseon-sillok-search surface and avoid new dependencies
Rejected: Keep verify=False behind an implicit fallback | still weakens authenticity for the default path
Rejected: Infer pagination from the hardcoded 50-row default | misses valid later-page matches when the site serves smaller pages
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If the official site changes page size or pagination markup again, update the first-page pagination regression before altering search_sillok pagination math
Tested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_sillok_search
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: python3 scripts/sillok_search.py --query '훈민정음' --king 세종 --year 1443 --limit 1 --timeout 20
Tested: python3 - <<'PY' ... fetch_detail_page(..., article_id='kda_12512030_002', timeout=20) ... PY
Not-tested: Successful live sillok.history.go.kr responses in this environment (POST and detail GET both timed out at 20s)
Related: PR #62

* Make joseon-sillok-search installs work outside the repo

The skill installer only ships the skill directory, so the sillok helper has to live inside that payload. This moves the authoritative helper into joseon-sillok-search/scripts/, keeps the repo-root script as a thin shim for tests and docs, and trims footer metadata from parsed article bodies so excerpts match the published examples.

Constraint: skills add exposes only the installed skill payload, not repo-root helpers

Rejected: Keep the helper only under scripts/ | installed skill commands fail after copy-only installs

Confidence: high

Scope-risk: narrow

Directive: Keep the repo-root sillok wrapper thin and update the bundled helper first when behavior changes

Tested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_sillok_search

Tested: node --test scripts/skill-docs.test.js

Tested: temp installed-skill smoke run of python3 scripts/sillok_search.py --help plus footer-cleaning parse_detail_page check

Tested: npm run ci

Not-tested: live sillok.history.go.kr shell requests still hit connect timeouts in this environment

* Restore the sillok CLI's verified stdlib transport fallback

The helper already shipped a stdlib urllib opener that can reach the
official search endpoint in environments where requests/urllib3 aborts.
This change keeps that opener available even when requests imports
successfully and falls back to it on retryable requests transport
failures. Added regression coverage for opener availability and the
requests-to-urllib fallback so the default CLI path matches the live
verified behavior.

Constraint: Official sillok detail GETs can still time out transiently in this environment
Constraint: Keep TLS verification enabled and preserve the documented CLI entrypoints
Rejected: Force urllib for every request | keep the existing requests fast path when it succeeds
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Preserve the stdlib fallback tests whenever the transport layer changes
Tested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_sillok_search
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: live forced-fallback probe against searchResultList.do with requests.post patched to OSError(22)
Not-tested: full live CLI completion through the detail GET in this environment

* Document the approved real-estate MCP skill without vendoring upstream

Issue #53 is intentionally doc-only, so this change adds the
real-estate-search skill, feature guide, setup/security notes,
and regression coverage around the upstream real-estate-mcp
integration instead of importing server code.

The new docs keep the original MCP link, cover Codex/Claude
registration, and spell out the self-host + Cloudflare Tunnel +
launchd path for environments where no fixed hosted endpoint is
available.

Constraint: Must use tae0y/real-estate-mcp without copying its source into this repository
Constraint: Must include the original MCP link and a stable self-host fallback when no hosted endpoint is available
Rejected: Vendor the upstream MCP source | issue explicitly requires skill docs only
Rejected: Assume a public hosted MCP endpoint exists | upstream docs did not publish one
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep this integration doc-only; do not add a workspace or vendored server without revisiting issue #53 constraints
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: upstream bootstrap smoke (`uv sync`, `uv run real-estate-mcp --help`, HTTP initialize on 127.0.0.1:8017)
Not-tested: live property data queries with a valid DATA_GO_KR_API_KEY

* Prevent misleading real-estate self-host instructions

Tighten the real-estate skill docs so the launchd fallback stays operational
and the Onbid bid-result tools are described with the same WIP caveat the
upstream project still publishes.

Constraint: Upstream Docker compose already uses restart: unless-stopped while `docker compose ... up -d` daemonizes immediately
Rejected: Keep a separate server LaunchAgent with RunAtLoad + KeepAlive | launchd would restart-loop on the exiting compose command
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Mirror upstream capability caveats in k-skill docs and do not wrap daemonized server commands in launchd KeepAlive jobs
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: uv sync
Tested: uv run real-estate-mcp --help
Tested: DATA_GO_KR_API_KEY=dummy uv run real-estate-mcp --transport http --host 127.0.0.1 --port 8017
Tested: curl initialize on http://127.0.0.1:8017/mcp returned protocolVersion 2024-11-05
Not-tested: Live 거래 조회 with a real DATA_GO_KR_API_KEY

* Keep install docs from reintroducing broken launchd guidance

The install guide had drifted from the real-estate skill and feature docs and still implied that macOS launchd should auto-run both the server and tunnel. This narrows the guidance back to tunnel-only launchd ownership and extends regression coverage so docs/install.md cannot silently reintroduce the server-side loop wording.

Constraint: Upstream docker compose already uses restart: unless-stopped
Constraint: Review-round-2 scope is limited to docs and regression coverage
Rejected: Leave docs/install.md under a generic launchd presence check | it missed the exact server/터널 regression
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep docs/install.md aligned with the detailed real-estate feature guide when self-host guidance changes
Tested: node --test scripts/skill-docs.test.js; npm run ci; fresh-clone upstream smoke with uv sync, uv run real-estate-mcp --help, and HTTP initialize on 127.0.0.1:8017/mcp
Not-tested: None

* Allow Han River water-level lookups without user API keys

The proxy now resolves HRFCO water-level stations via waterlevel/info and
fetches the latest 10-minute measurement via waterlevel/list, exposing a
public summary route plus a hosted skill/docs path.

Constraint: HRFCO requires a ServiceKey and station-code-based latest lookup
Rejected: Raw passthrough only | forces users to manage upstream details and misses the approved no-key UX
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep this route summary-only and public; expand rainfall/dam/bo/fldfct in separate issues
Tested: npm run ci; local server smoke test with HRFCO sample key against /v1/han-river/water-level; tsc diagnostics on packages/k-skill-proxy/src/server.js and src/hrfco.js
Not-tested: Hosted production proxy rollout

* Expose official nearby fuel prices for location-based gas station lookups

Add the first cheap-gas-nearby skill/package pair so nearby gas-price
queries can resolve a user-supplied location, translate it into Opinet's
KATEC search contract, and return the cheapest nearby stations with
address and facility detail. The docs and setup surfaces now advertise
the new skill and its Opinet API key requirement.

Constraint: Nearby fuel prices must come from the official KNOC Opinet API when available
Constraint: No new external dependencies were allowed for coordinate conversion or location resolution
Rejected: Map-only gas price scraping | official Opinet Open API exists and is the preferred source
Rejected: Require lat/lng input only | poorer UX than supporting landmark/station queries through anchor resolution
Confidence: medium
Scope-risk: moderate
Reversibility: clean
Directive: Keep `OPINET_API_KEY` as the only supported official-price credential unless the repo adopts an Opinet proxy later
Tested: npm run ci; node --test packages/cheap-gas-nearby/test/index.test.js; offline fixture smoke via searchCheapGasStationsByLocationQuery('서울역', ...)
Not-tested: Live Opinet API call with a real `OPINET_API_KEY` (no non-placeholder key was configured locally)
Related: #54

* Explain Blue Ribbon premium gating instead of failing opaquely

Blue Ribbon's nearby endpoint now returns PREMIUM_REQUIRED for public
requests, so the package now upgrades that response into a stable domain
error and updates user-facing docs to describe the degraded nearby state.
Regression tests cover both location-query and coordinate-query entrypoints
while keeping the existing happy path intact.

Constraint: Must not attempt to bypass Blue Ribbon premium access controls
Constraint: Package releases must use Changesets metadata
Rejected: Silently return empty results on PREMIUM_REQUIRED | would hide the upstream contract change from callers
Rejected: Keep generic 403 error and docs unchanged | leaves the main failure mode opaque and misleading
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If Blue Ribbon reopens a public nearby surface later, update the docs and replace the premium_required remap only after fresh live verification
Tested: npm test --workspace blue-ribbon-nearby; npm run lint --workspace blue-ribbon-nearby; npm run ci; live node repro now returns premium_required metadata
Not-tested: Official premium-authenticated nearby flow, because no approved premium credentials are available

* Reduce duplicate premium-required test assertions

The implementation diff was already verified, so this follow-up keeps the
new regression coverage easier to read by sharing one assertion helper
instead of repeating the same predicate twice.

Constraint: Must preserve the verified PREMIUM_REQUIRED regression coverage exactly
Rejected: Leave duplicated inline predicates in both tests | repeats the same contract and adds noise to future edits
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep location-query and coordinate-query regressions aligned when the premium_required error contract changes
Tested: npm test --workspace blue-ribbon-nearby; npm run lint --workspace blue-ribbon-nearby; npm run ci
Not-tested: Additional live Blue Ribbon requests beyond the previously verified premium_required repro

* Document an Olive Young search skill around the upstream daiso CLI

Issue #61 asked for an Olive Young lookup workflow without vendoring the upstream daiso-mcp server into k-skill, so this change adds a docs-only skill, threads it through repository docs, and locks the guidance with regression assertions.

The new guidance prefers CLI-first verification (`npx daiso`) and a clone fallback (`git clone https://github.com/hmmhmmhm/daiso-mcp.git`) instead of requiring direct Claude Code MCP installation. The existing daiso-product-search skill remains untouched.

Constraint: Must mention the original https://github.com/hmmhmmhm/daiso-mcp repo
Constraint: Must avoid changing the existing daiso-product-search skill
Constraint: Must prefer upstream CLI/package usage over direct Claude Code MCP installation
Rejected: Vendor upstream Olive Young code into k-skill | contradicts the minimal-addition design request
Rejected: Make Claude Code MCP setup the default path | issue explicitly asked for CLI-first usage
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the Olive Young docs aligned with upstream `daiso` CLI behavior and note public endpoint instability when live verification shows it
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: 2026-04-05 live /tmp/daiso-mcp CLI checks for health, /api/oliveyoung/stores, /api/oliveyoung/products, /api/oliveyoung/inventory
Not-tested: Authenticated/private Olive Young flows or ordering/payment paths

* Keep Han River docs honest until hosted rollout lands

The local HRFCO proxy route already works, but the deployed public proxy still
lacks /v1/han-river/water-level as of 2026-04-05. This follow-up changes the
user-facing docs to require a self-hosted or deployment-verified proxy URL,
keeps the intended hosted path documented as rollout-pending, and locks that
caveat with a regression test.

Constraint: Hosted k-skill-proxy deployment still returns 404 for /v1/han-river/water-level on 2026-04-05
Rejected: Keep advertising the hosted route as the default live path | current deployment is not live yet
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Restore hosted-default wording only after the public proxy route and HRFCO configuration are verified live
Tested: node --test scripts/skill-docs.test.js; npm run ci; local mocked smoke via node packages/k-skill-proxy/src/server.js + GET /v1/han-river/water-level?stationName=한강대교
Not-tested: Live hosted k-skill-proxy behavior after deployment

* Keep cheap gas lookups from failing on recoverable Kakao and input issues

The lookup now walks ranked Kakao anchor candidates until one returns usable coordinates, and it rejects invalid limit inputs instead of silently collapsing non-empty results into an empty list. The regression suite now locks both review repros plus the null-coordinate normalization edge case that surfaced while implementing the fallback.

Constraint: Kakao place panels can be partially populated or return 404 for otherwise valid search candidates
Rejected: Default invalid limit/detailLimit values silently | still masks caller bugs and can look like no results nearby
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep anchor resolution tolerant of unusable Kakao panels before failing the whole lookup
Tested: node --test packages/cheap-gas-nearby/test/index.test.js; npm run ci; offline fixture smoke for searchCheapGasStationsByLocationQuery('서울역', ...)
Not-tested: Live Opinet/Kakao network path without a real OPINET_API_KEY

* Keep Olive Young install guidance aligned with live retry behavior

Issue #61's initial branch already shipped the olive-young-search skill/docs set, but the install guide did not have a regression lock for the public endpoint instability note captured during live verification. This follow-up adds that test first and updates the install quickstart so retry-or-clone fallback guidance stays in sync with the verified daiso CLI workflow.

Constraint: Must keep the follow-up scoped to the approved issue #61 docs behavior
Constraint: Public olive-young endpoint can intermittently return 5xx/503 during live verification
Rejected: Re-open the broader feature docs set | the existing branch already covered the main feature scope
Rejected: Leave the retry guidance untested in install docs | risk of future doc drift across surfaces
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If live olive-young verification changes again, update install docs and the regression together so retry/fallback guidance stays honest
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: cd /tmp/daiso-mcp && npx daiso health
Tested: cd /tmp/daiso-mcp && npx daiso get /api/oliveyoung/stores --keyword 명동 --limit 3 --json
Tested: cd /tmp/daiso-mcp && npx daiso get /api/oliveyoung/products --keyword 선크림 --size 3 --json
Tested: cd /tmp/daiso-mcp && npx daiso get /api/oliveyoung/inventory --keyword 선크림 --storeKeyword 명동 --size 2 --json
Not-tested: Authenticated Olive Young flows or sustained-rate retry behavior beyond the documented smoke checks

* Preserve ranked Kakao anchor fallbacks after panel failures

The resolver already retried later Kakao panels, but after the best panel failed it
walked the remaining candidates in raw HTML order. Centralizing the full
anchor ranking in parse.js keeps fallback iteration aligned with the
existing scoring rules and locks the review repro with a regression test
for 강남역 ordering.

Constraint: Kakao place panels can 404 or omit coordinates even when later ranked candidates are usable
Rejected: Re-rank only after a failed panel | duplicates scoring logic and drifts from selectAnchorCandidate
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep resolveAnchor fallback order tied to the shared anchor scoring logic rather than raw Kakao result order
Tested: node --test packages/cheap-gas-nearby/test/index.test.js; npm run ci; offline fixture smoke for searchCheapGasStationsByLocationQuery('서울역', ...); lsp diagnostics on affected files
Not-tested: Live Kakao/Opinet network calls with a non-placeholder OPINET_API_KEY

* Clarify that Blue Ribbon coordinate lookups fail the same way

The premium gate now affects both location-query and coordinate-entry nearby flows. Record that parity in the skill and docs so the live 2026-04-05 repro evidence stays aligned with the shipped public contract.

Constraint: /restaurants/map remains premium-gated for public requests as of 2026-04-05
Rejected: Add more code changes without a behavior gap | would add churn after the approved fix already landed
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the docs/examples aligned with live repro evidence whenever Blue Ribbon changes nearby access behavior
Tested: npm test --workspace blue-ribbon-nearby; npm run lint --workspace blue-ribbon-nearby; npm run ci; live node repro for location + coordinates premium_required contract
Not-tested: New upstream success-path live nearby payloads, because public access is still premium-gated

* Document a runnable Olive Young clone fallback

The follow-up review found that clone-local `npx daiso` commands do not run from a built `hmmhmmhm/daiso-mcp` checkout because the generated bin file is not executable. This updates the olive-young skill and docs to use the verified `node dist/bin.js ...` path for clone fallback, and locks that behavior with regression tests while keeping the public CLI-first path unchanged.\n\nConstraint: Upstream clone checkouts can fail with `Permission denied` when invoked through clone-local `npx daiso`\nConstraint: Keep the existing daiso-product-search skill untouched\nRejected: Leave install docs unchanged | PR body and docs would keep a broken clone fallback path\nRejected: Vendor or patch upstream daiso-mcp inside k-skill | issue explicitly requires documenting upstream flow instead\nConfidence: high\nScope-risk: narrow\nReversibility: clean\nDirective: Keep clone-fallback docs and PR verification commands aligned with the actually runnable local invocation\nTested: node --test scripts/skill-docs.test.js\nTested: npm run ci\nTested: cd /tmp/daiso-mcp && npm install && npm run build && node dist/bin.js health\nTested: cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/stores --keyword 명동 --limit 3 --json\nTested: cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/products --keyword 선크림 --size 3 --json\nTested: cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/inventory --keyword 선크림 --storeKeyword 명동 --size 2 --json\nNot-tested: Public npx endpoint stability beyond the verified smoke-test window

* Record fresh verification for the approved cheap-gas follow-up

The ranked Kakao fallback-order and invalid-limit fixes are already present on feature/#54, so no further code edits were necessary. This empty follow-up commit records the requested rerun verification for PR #67 before posting the implementation update.\n\nConstraint: Existing branch head already contains the approved cheap-gas-nearby follow-up\nRejected: Invent another code/doc change just to force a non-empty diff | unnecessary risk after approval\nConfidence: high\nScope-risk: narrow\nReversibility: clean\nDirective: Keep Kakao anchor fallback iteration tied to the shared ranking helper and preserve finite-number validation for limit inputs\nTested: node --test packages/cheap-gas-nearby/test/index.test.js; npm run ci; offline fixture smoke for searchCheapGasStationsByLocationQuery('서울역', ...); lsp diagnostics on affected files\nNot-tested: Live Kakao/Opinet network calls with a non-placeholder OPINET_API_KEY

* Record verified delivery for approved cheap-gas-nearby rollout

The approved Issue #54 implementation was already present on feature/#54 when this follow-up began, so this checkpoint records fresh verification evidence and keeps PR #67 moving without introducing extra code churn.

Constraint: Existing approved fixes were already on the branch and the follow-up still required a pushed update plus implementation comment
Rejected: Add additional code or docs churn | approved scope was already satisfied and green locally
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Preserve the Kakao fallback-order and invalid-count regression coverage unless equivalent runtime repros replace it
Tested: node --test packages/cheap-gas-nearby/test/index.test.js; offline fixture smoke for searchCheapGasStationsByLocationQuery('서울역', ...); npm run ci; LSP diagnostics on affected cheap-gas-nearby files
Not-tested: Live Kakao/Opinet network path without a non-placeholder OPINET_API_KEY

* Keep Blue Ribbon premium-gate remapping explicitly bounded

The issue #63 fix already remaps PREMIUM_REQUIRED for nearby lookups. This follow-up adds a regression that proves non-premium /restaurants/map failures still surface as generic request errors, and clarifies that boundary in the package and feature docs.

Constraint: PR #68 already carries the shipped behavior change and must stay aligned with current live premium-gated upstream behavior
Rejected: Broaden domain remapping to all 403 nearby errors | would hide distinct upstream failure modes
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep only PREMIUM_REQUIRED on /restaurants/map mapped to premium_required unless a new verified upstream contract is documented
Tested: npm test --workspace blue-ribbon-nearby; npm run lint --workspace blue-ribbon-nearby; npm run ci; live node repro for location+coordinate nearby lookups on 2026-04-06; LSP diagnostics on src/test
Not-tested: Alternative non-premium upstream status codes beyond the mocked 403 ACCESS_DENIED path

* Keep Olive Young clone fallback docs runnable from a fresh clone

Round 3 review found that the inline fallback shorthand skipped `cd daiso-mcp`,
which made `npm install` run outside the cloned upstream repo even though the
fenced examples were already correct. This tightens the two published shorthand
snippets and adds regression coverage so both docs surfaces reject the broken
chain in future edits.

Constraint: Existing node dist/bin.js clone fallback examples were already verified and had to stay aligned
Constraint: The follow-up had to stay scoped to docs/test coverage without touching the existing daiso-product-search skill
Rejected: Convert the shorthand to prose only | the review specifically requested a runnable inline form or an explicit removal
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep inline clone fallback snippets synchronized with the fenced node dist/bin.js examples and require `cd daiso-mcp` before install/build
Tested: node --test scripts/skill-docs.test.js; npm run ci; cd /tmp/daiso-mcp && npm install && npm run build; cd /tmp/daiso-mcp && node dist/bin.js health; cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/stores --keyword 명동 --limit 3 --json; cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/products --keyword 선크림 --size 3 --json; cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/inventory --keyword 선크림 --storeKeyword 명동 --size 2 --json
Not-tested: Public npx path in this follow-up round (previous PR verification already covered it)
Related: PR #71

* Bundle korean-spell-check script inside skill directory for packageless installs

The Python helper lived only in the repo-root scripts/ folder, so
`skills add` never shipped it. Move the real implementation into
korean-spell-check/scripts/ (mirroring joseon-sillok-search) and
replace the root copy with a thin re-export wrapper so lint/test
still resolve from the repo root.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Exclude .claude directory from skill validation to fix CI in worktrees

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* real-estate-search: MCP self-host → k-skill-proxy HTTP 전환

사용자가 직접 real-estate-mcp를 clone/self-host하는 대신
k-skill-proxy에 MOLIT 실거래가 API route를 추가해서
다른 스킬(한강수위, 미세먼지 등)과 동일한 패턴으로 사용 가능하게 함.

- GET /v1/real-estate/region-code — 지역코드 검색
- GET /v1/real-estate/:assetType/:dealType — 9개 거래 유형 조회
- molit.js: XML 파싱, 필드 정규화, 취소거래 필터링, 요약통계
- region-lookup.js: 284개 법정동 5자리 코드 토큰 매칭
- SKILL.md/docs를 HTTP proxy 기반으로 전면 재작성
- DATA_GO_KR_API_KEY를 사용자 필수항목에서 프록시 운영자 전용으로 이동

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* cheap-gas-nearby: proxy 경유로 전환 + 프로덕션 배포 구조 문서화

- cheap-gas-nearby: OPINET_API_KEY 없이 k-skill-proxy 경유로 동작하도록 변경
  - fetchAroundStations/fetchDetailById에 proxy fallback 추가
  - SKILL.md, feature doc에서 사용자 API key 요구 제거
- AGENTS.md, CLAUDE.md: proxy 개발/배포 워크플로우 문서화
- docs/features/k-skill-proxy.md: opinet route 추가, 프로덕션 자동 배포 구조 설명

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* blue-ribbon-nearby: proxy 경유로 전환 + 프리미엄 세션 프록시 라우트

Blue Ribbon /restaurants/map이 프리미엄 전용으로 전환되어,
k-skill-proxy에 BLUE_RIBBON_SESSION_ID 기반 프록시 라우트를 추가하고
blue-ribbon-nearby 패키지가 기본적으로 프록시를 경유하도록 변경.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: version packages (#74)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix(ci): remove changeset assertion that breaks CI after version bump (#78)

The used-car-price-search changeset assertion fails after `changeset version`
consumes .changeset/*.md files, breaking both CI and Release workflows on main.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-04-07 04:35:50 +09:00
Jeffrey (Dongkyu) Kim
1e72d5d06d Merge main into dev: sync version bumps and changeset fix
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 04:27:42 +09:00
Jeffrey (Dongkyu) Kim
79dacba05a
fix(ci): remove changeset assertion that breaks CI after version bump (#78)
The used-car-price-search changeset assertion fails after `changeset version`
consumes .changeset/*.md files, breaking both CI and Release workflows on main.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 00:46:09 +09:00
Jeffrey (Dongkyu) Kim
8169d1d443
Merge pull request #77 from NomaDamas/feature/#76
Feature/#76
2026-04-06 21:09:02 +09:00
Jeffrey (Dongkyu) Kim
7699a7bdbf Close the remaining Korean stock proxy gaps before public rollout
The KRX search route trusted spoofable direct-request IP headers and
failed whole-search fanout when one market snapshot errored. This
change keys rate limits off Fastify's resolved request.ip, reuses
per-market base snapshots across same-date queries, and keeps healthy
search results available when one market fails while new regressions
lock the behavior.

Constraint: Public proxy routes stay unauthenticated, so abuse controls must not depend on untrusted client headers
Constraint: Search must still fail loudly when every market fetch fails
Rejected: Keep trusting cf-connecting-ip on direct requests | direct clients can rotate the header to reset the bucket
Rejected: Promise.all all-or-nothing market fanout | one failing market hid healthy KOSPI results
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep Korean stock rate limiting keyed to Fastify-resolved client IP unless trust-proxy behavior is explicitly audited end-to-end
Tested: node --test packages/k-skill-proxy/test/server.test.js scripts/skill-docs.test.js; npm run ci; local proxy smoke on 127.0.0.1:4120; targeted inject script for spoofed-header rate limiting and partial-market failure
Not-tested: Live KRX success with a real KRX_API_KEY
2026-04-06 21:02:16 +09:00
Jeffrey (Dongkyu) Kim
e4c43d2c7f Protect KRX proxy responses from insecure or mismatched upstream data
Review follow-up switches KRX endpoints to HTTPS and removes the single-row trade fallback that could mislabel an unrelated stock as the requested code. Regression tests now assert HTTPS transport for search/base/trade flows and 404 behavior for unmatched single-row trade responses.

Constraint: KRX key must never leave the proxy over plaintext transport
Constraint: No local KRX_API_KEY is available for live upstream success verification
Rejected: Keep the single-row trade fallback with a synthetic code rewrite | can fabricate mismatched stock responses
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Do not reintroduce inferred short-code fallbacks without validating them against matching base-info first
Tested: node --test packages/k-skill-proxy/test/server.test.js scripts/skill-docs.test.js; npm run ci; local proxy smoke on 127.0.0.1:4120 (/health 200, /v1/korean-stock/search?q=삼성전자&bas_dd=20260404 503 without KRX_API_KEY); lsp diagnostics on changed files
Not-tested: live KRX upstream success path with a real KRX_API_KEY
2026-04-06 20:31:58 +09:00
Jeffrey (Dongkyu) Kim
4ddbc2e8b1
Merge pull request #75 from NomaDamas/feature/#73
Feature/#73
2026-04-06 20:26:03 +09:00
Jeffrey (Dongkyu) Kim
6270ec64b3 Shield Korean stock lookups behind the proxy so users avoid KRX key setup
Use the local k-skill proxy as the public integration surface for Korea Exchange stock data. The new skill/docs flow starts with stock search, then narrows into base-info and trade-info lookups, while the proxy injects KRX credentials server-side only.

Constraint: KRX_API_KEY must remain server-side under the repo's free read-only proxy policy
Rejected: Expose upstream korea-stock-mcp setup to end users | would force user-issued KRX keys and a heavier install path
Confidence: high
Scope-risk: moderate
Directive: Keep Korean stock access proxy-first and read-only; do not move KRX_API_KEY into client setup docs
Tested: node --test packages/k-skill-proxy/test/server.test.js scripts/skill-docs.test.js; npm run ci; local proxy smoke on port 4120 (/health 200, /v1/korean-stock/search 503 without KRX_API_KEY)
Not-tested: Live upstream success path with a real local KRX_API_KEY
2026-04-06 20:06:08 +09:00
Jeffrey (Dongkyu) Kim
a4588442db Prevent Bunjang agents from trusting noisy search summaries
Live bunjang-cli verification showed that search results are safe for title/price triage, but not for reliable description/status/location decisions. This update tightens the skill and feature docs around that boundary and locks it with a regression assertion so future edits do not drift back into summary-field assumptions.

Constraint: bunjang-cli search summary fields are transport-dependent and can omit or mangle description/status/location
Rejected: Keep region/status-first shortlist guidance in search docs | live verification returned malformed location and missing description/status
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Do not reintroduce description/status/location-based search triage without re-verifying the live search payload contract
Tested: node --test scripts/skill-docs.test.js; npm run ci; npx --yes bunjang-cli --help; npx --yes bunjang-cli --json auth status; npx --yes bunjang-cli --json search "아이폰" --max-items 1; npx --yes bunjang-cli --json item get 354957625; npx --yes bunjang-cli search "아이폰" --max-items 2 --with-detail --output /tmp/bunjang-73-export-XXXXXX.json; npx --yes bunjang-cli search "아이폰" --max-items 2 --with-detail --ai --output /tmp/bunjang-73-ai-Xp2bu0
Not-tested: Authenticated favorite/chat round-trip still requires an interactive TTY login session
2026-04-06 19:29:16 +09:00
Jeffrey (Dongkyu) Kim
afe5d33885 Document a bunjang-cli workflow for Bunjang marketplace lookups
Issue #73 adds a new root skill plus matching repository docs/tests so agents can guide 번개장터 searches, detail reads, optional login-gated favorites/chats, and bulk export flows without vendoring upstream code.

Constraint: Reuse upstream bunjang-cli instead of adding a new crawler or package wrapper
Constraint: favorite/chat flows require a headful TTY login and cannot be fully smoke-tested non-interactively
Rejected: Add a local workspace/package wrapper | docs-only skill scope does not justify new abstraction
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the skill CLI-first and treat favorite/chat/purchase as optional login flows unless a live authenticated session is intentionally available
Tested: npm run ci
Tested: npx --yes bunjang-cli --help
Tested: npx --yes bunjang-cli --json auth status
Tested: npx --yes bunjang-cli --json search "아이폰" --max-items 1
Tested: npx --yes bunjang-cli --json item get 354957625
Tested: bunjang-cli JSON/AI export smoke runs with --with-detail/--output and --ai --output
Not-tested: Authenticated favorite/chat round-trip in a logged-in interactive browser session
2026-04-06 19:13:31 +09:00
github-actions[bot]
7301a90e00
chore: version packages (#74)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-04-06 19:03:43 +09:00
Jeffrey (Dongkyu) Kim
9f29ed5714
Merge dev into main: new skills & proxy enhancements (#72)
* Remove client-side Seoul subway key setup

Route Seoul subway arrival lookups through k-skill-proxy so the
hosted proxy owns the Seoul Open Data upstream key and end users
only need the proxy base URL. Add proxy route coverage, update
skill/docs guidance, and align setup materials with the hosted
proxy flow used for fine dust.

Constraint: Must keep the proxy public, read-only, and dependency-free
Constraint: Must satisfy TDD-first verification and ship on feature/#35 targeting dev
Rejected: Add a separate client helper package | unnecessary extra layer for a single proxy route
Rejected: Keep SEOUL_OPEN_API_KEY as an end-user requirement | defeats the issue goal
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep the Seoul subway proxy surface limited to station-arrival passthrough unless tests/docs expand the contract
Tested: npm run ci; local proxy runtime on 127.0.0.1:4120 for /health and /v1/seoul-subway/arrival on 2026-03-31 with an invalid upstream key
Not-tested: Live success response with a valid Seoul Open API key
Related: #35

* Prevent broken Seoul subway proxy defaults before hosted rollout

The Seoul subway proxy endpoint code is present locally, but the hosted public route is not live yet. This change turns the user-facing subway docs back into an explicit proxy configuration flow, replaces the misleading hosted default in setup examples, and keeps subway proxy examples on self-host/local URLs until rollout is verified.

Constraint: Hosted k-skill-proxy.nomadamas.org/v1/seoul-subway/arrival is not live yet
Rejected: Keep the hosted Seoul subway URL as the default path | would send default users to a 404 route
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Do not restore a hosted Seoul subway default until the public proxy route is deployed and smoke-verified
Tested: node --test scripts/skill-docs.test.js; npm run ci; local proxy smoke on 127.0.0.1:4120 with stubbed Seoul upstream (GET /health, GET /v1/seoul-subway/arrival?stationName=강남)
Not-tested: Live hosted proxy smoke after deployment

* Reduce Seoul subway proxy upstream pressure with request caching

The public subway arrival route already normalized caller input but still re-fetched the Seoul upstream for every identical poll. This change adds a short-TTL cache keyed from the normalized subway query, annotates JSON responses with cache metadata, and locks the repeated-read collapse with a regression that proves alias/default normalization still reuses the cached result.

Constraint: The endpoint must stay public/no-auth while protecting a shared server-side SEOUL_OPEN_API_KEY from unnecessary repeated upstream hits
Rejected: Add a new shared cache abstraction for proxy metadata | unnecessary for this narrow fix and would enlarge the diff
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep subway cache keys tied to normalizeSeoulSubwayQuery output so alias/default query forms continue collapsing to one upstream request
Tested: node --test packages/k-skill-proxy/test/server.test.js
Tested: npm run ci
Tested: Local proxy runtime on 127.0.0.1:4120 with SEOUL_OPEN_API_KEY=test-seoul-key and stubbed fetch for /health plus repeated /v1/seoul-subway/arrival requests
Not-tested: Live hosted proxy rollout state

* Document OpenClaw support in the public compatibility list

The approved scope for issue #29 was narrowed to README support messaging,
so this change adds OpenClaw/ClawHub to the supported-client line and locks
that wording with a regression test in the docs suite.

Constraint: Issue #29 was approved as a README-only compatibility/docs change
Rejected: Broader install-doc updates | out of approved scope for this issue
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep OpenClaw support wording aligned with the supported-client README line and its docs regression test
Tested: Focused Node docs regression, npm run lint, npm run typecheck, npm run build, npm test
Not-tested: Live OpenClaw or ClawHub install flow (issue scope was documentation-only)

* Protect README client-support claims from ClawHub regressions

The README already advertises OpenClaw/ClawHub, but the docs
regression only matched OpenClaw. Tighten the assertion to the
exact supported-client fragment so a future edit cannot silently
remove ClawHub while keeping the issue verification command stable.

Constraint: PR #39 already publishes a verification command keyed to the current test name
Rejected: Rename the test to mention ClawHub explicitly | would drift from the published verification command
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep this regression synchronized with the README supported-client line whenever that copy changes
Tested: node --test scripts/skill-docs.test.js --test-name-pattern 'README advertises OpenClaw among the supported coding agents'
Tested: npm run lint
Tested: npm run typecheck
Tested: npm run build
Tested: npm test
Tested: lsp diagnostics for scripts/skill-docs.test.js (0 errors)
Not-tested: N/A

* Make Coupang shopper research usable without pretending scraping is stable

Coupang blocks unattended shopper queries in this environment, so the new package focuses on the durable pieces we can ship honestly: official URL builders, browser-captured HTML parsers, and explicit automation probes. The docs and skill now explain the seller-API limitation, the verified anti-bot behavior, and the browser-capture fallback expected by callers.

Constraint: No general shopper Open API surfaced in Coupang's official developer docs

Constraint: Headless/direct retrieval is anti-bot blocked in verified local probes

Rejected: Bundle a scraping bypass or hidden browser dependency | violates repo policy and would over-claim reliability

Confidence: high

Scope-risk: moderate

Reversibility: clean

Directive: Keep probe/docs behavior aligned with fresh live verification before claiming unattended Coupang access works

Tested: npm run ci; live probeAutomation(query=생수) with direct fetch + Playwright-core browserFetchHtml; LSP diagnostics on changed JS/test files

Not-tested: Headed/manual browser sessions with a human-authenticated Coupang context

* Keep the Coupang workspace installable and its entrypoint honest

Review follow-up found two contract gaps in the first issue #36 rollout: fresh installs were missing the new workspace link in package-lock, and the package README documented parser helpers that were not exported from the package entrypoint. Refreshing the lockfile and re-exporting the parsers keeps npm ci and consumer imports aligned with the published API.

Constraint: npm ci must succeed from a clean checkout
Rejected: Narrow the README API list to only client helpers | would hide useful parsers that the package already ships and tests
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: When adding a new workspace, refresh package-lock and verify the package entrypoint matches README public API claims
Tested: npm run ci; workspace coupang-product-search tests; LSP diagnostics on coupang-product-search JS/test files
Not-tested: published npm install from registry (local pack dry-run only)

* Keep Coupang anti-bot docs honest as probe results drift

A same-day PR rerun showed the published mobile probe snapshot was already stale, so the follow-up locks the 403/access-denied mobile path with regression coverage and softens the docs/skill contract to describe blocked outcome classes instead of one frozen signature. The published guidance now also explains that browser results only appear when browserFetchHtml is injected, matching what a clean checkout can actually reproduce.

Constraint: Coupang anti-bot responses vary by edge/challenge between reruns on the same day
Rejected: Freeze one exact mobile probe snapshot | same-day reruns already diverged
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Refresh Coupang anti-bot docs only with same-day live probe evidence, and prefer blocked outcome classes over a single exact signature when reruns disagree
Tested: npm run lint; npm run typecheck; npm test; npm run ci; live probeAutomation("생수") with direct fetch + Playwright-core browser fetch
Not-tested: Non-Chrome Playwright-core executables

* Keep the Coupang skill honest about clean-checkout browser probes

The approved follow-up already documented that clean-checkout probeAutomation runs leave browser unset unless a browser fetcher is injected, but the skill text itself had not made that null contract explicit. This commit adds a regression test that locks the browser-null wording across the published docs surfaces and updates the skill so the stated behavior matches the package implementation and clean-checkout reality.

Constraint: probeAutomation() only populates browser when browserFetchHtml is supplied by the caller
Rejected: Leave the skill wording implicit and rely on README examples alone | the skill could drift away from the shipped package contract again
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the README, feature doc, and skill aligned whenever the probeAutomation browser contract changes
Tested: node --test packages/coupang-product-search/test/index.test.js; npm run lint; npm run typecheck; npm test; npm run ci; live probeAutomation("생수") with direct fetch + Playwright-core browser fetch; codex exec review --uncommitted
Not-tested: Alternative browser engines beyond local Google Chrome for the manual browserFetchHtml probe

* Keep Coupang browser probe docs aligned with manual verification paths

The approved PR #40 follow-up still had one wording gap: the skill said when browser results appear, but it did not explicitly say those populated results come from an injected manual/external browserFetchHtml path. This change locks that contract with a failing regression first, then updates the skill text to match the already-shipped README/feature-doc guidance and runtime behavior.

Constraint: clean-checkout probeAutomation() must keep browser null unless browserFetchHtml is supplied
Rejected: change runtime browser probe behavior | approved follow-up was docs/test only and runtime contract is already correct
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: keep Coupang probe docs aligned with the shipped browserFetchHtml injection contract; do not imply built-in browser probing in clean checkouts
Tested: node --test packages/coupang-product-search/test/index.test.js; npm run lint; npm run typecheck; npm test; npm run ci; live probeAutomation("생수") with direct fetch + Playwright-core browser fetch; lsp diagnostics on changed files; architect review approved
Not-tested: alternate browser engines beyond local Google Chrome + playwright-core manual runner

* Route Korean law lookups through korean-law-mcp

Add a documentation-first korean-law-search skill and lock the repo docs around the rule that Korean law queries must go through korean-law-mcp rather than a new in-repo package.

The change updates install/setup/security guidance, publishes the new feature doc, and adds regression tests so future edits keep the LAW_OC + korean-law-mcp contract intact.

Constraint: Issue #41 requires korean-law-mcp for every Korean law lookup
Constraint: Must not add a new npm or python package in this repository
Rejected: Add a repo-local law package | violates the no-new-package requirement and duplicates upstream MCP work
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep Korean law lookup guidance pinned to korean-law-mcp unless issue requirements explicitly change
Tested: node --test scripts/skill-docs.test.js
Tested: npm install -g korean-law-mcp && korean-law list && korean-law help search_law
Tested: npm run ci
Tested: npx tsc --noEmit --pretty false --project /Users/jeffrey/Projects/k-skill/tsconfig.json
Not-tested: live search_law/get_law_text against a real LAW_OC credential

* Clarify Korean law setup modes to avoid credential confusion

A review found the new korean-law-search docs treated LAW_OC as an unconditional prerequisite even though the upstream remote MCP endpoint is configured separately from the local CLI/server path. This update makes the docs and regression tests mode-specific: local CLI/MCP uses LAW_OC, while the remote endpoint stays a korean-law-mcp-only url fallback without a user-supplied credential.

Constraint: Upstream korean-law-mcp uses LAW_OC on the local CLI/server path while the documented remote MCP endpoint is configured with url only
Rejected: Keep LAW_OC mandatory for every korean-law-mcp mode | contradicts upstream docs and reviewer evidence
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep README/setup/skill docs and regression tests aligned on the local-vs-remote korean-law-mcp contract
Tested: node --test scripts/skill-docs.test.js; npm install -g korean-law-mcp && korean-law list && korean-law help search_law; npx tsc --noEmit --pretty false --project /Users/jeffrey/Projects/k-skill/tsconfig.json; npm run ci; lsp_diagnostics scripts/skill-docs.test.js
Not-tested: Live remote MCP endpoint connection against https://korean-law-mcp.fly.dev/mcp

* Record issue #41 follow-up after approved verification

The approved korean-law-search change was already present on feature/#41, so this follow-up records a fresh verification pass and updates the PR without introducing unnecessary code churn.

Constraint: Existing branch already contained the approved implementation
Rejected: Touch repo files without need | would create noise without improving behavior
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the korean-law-mcp-only + mode-specific LAW_OC contract aligned with upstream docs before changing these surfaces again
Tested: node --test scripts/skill-docs.test.js; npx tsc --noEmit --pretty false --project /Users/jeffrey/Projects/k-skill/tsconfig.json; npm install -g korean-law-mcp && korean-law list && korean-law help search_law; npm run ci
Not-tested: Live korean-law queries that require a real LAW_OC or remote endpoint session

* Keep the Korean law feature diff merge-ready

Verification uncovered trailing whitespace in the new korean-law feature guide when checking the branch diff. I added a regression to the skill docs suite and removed the whitespace so the approved documentation contract stays clean and reviewable.

Constraint: Preserve the existing korean-law-mcp + mode-specific LAW_OC contract without widening scope
Rejected: Leave the whitespace issue as-is | git diff --check stayed dirty on the branch
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep docs/features/korean-law-search.md free of trailing whitespace so diff hygiene stays enforced by the regression
Tested: node --test scripts/skill-docs.test.js; npx tsc --noEmit --pretty false --project /Users/jeffrey/Projects/k-skill/tsconfig.json; npm install -g korean-law-mcp && korean-law list && korean-law help search_law; npm run ci; git diff --check
Not-tested: Live credentialed LAW_OC search execution against the upstream API

* Keep Korean law skill guidance aligned with supported lookups

The approved issue #41 feature already worked, but the shipped skill text still under-described ordinance and interpretation lookups compared with the documented capability set. This follow-up tightens the skill copy and locks that contract with a doc regression so the branch stays merge-ready without changing the underlying korean-law-mcp routing rules.

Constraint: Must preserve the existing korean-law-mcp-only and mode-specific LAW_OC setup contract
Rejected: Leave the skill wording as-is | drift from the documented lookup surface would remain
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the skill examples and doc regression aligned whenever supported korean-law-mcp search surfaces change
Tested: node --test scripts/skill-docs.test.js
Not-tested: live LAW_OC-backed upstream API queries

* Keep Korean law completion guidance in sync with enforced lookups

The previous follow-up aligned the main skill examples, but the done-criteria text still left interpretation and ordinance routing implicit. This commit makes the completion checklist explicit and extends the doc regression so future edits cannot silently drop those lookup paths.

Constraint: Must stay within the existing issue #41 korean-law-mcp-only contract
Rejected: Leave the done checklist implicit | reviewers and future edits could drift from the enforced lookup set
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: When the supported Korean-law lookup set changes, update the done checklist and regression together
Tested: node --test scripts/skill-docs.test.js
Not-tested: full CI before commit

* Enable repeatable used-car price lookups from a rental-company source

Issue #46 required surveying major Korean rental companies before implementation and then choosing the easiest stable provider. SK렌터카 다이렉트 exposes 타고BUY inventory in public Next.js page data, so the feature stays dependency-free while still supporting live repeated lookups and documented provider rationale.

Constraint: Must compare major Korean rental companies before implementation
Constraint: Must verify 10+ live lookups against a real provider surface
Rejected: 롯데오토옥션 as v1 provider | public list contract was unstable and legacy .do flows returned inconsistent or 404 pages
Rejected: 레드캡렌터카 as v1 provider | no public used-car inventory or API surface was found
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep the v1 provider read-only and inventory-snapshot-based unless a stable documented public API is confirmed
Tested: npm run ci
Tested: Live 10-query run against https://www.skdirect.co.kr/tb at 2026-04-02T07:22:46Z
Tested: LSP diagnostics on affected files
Not-tested: Seller-specific detail drilldowns or non-SK providers
Related: #46

* Keep used-car-price-search releasable after merge

Review feedback found that the new used-car workspace would not ship because it lacked a changeset, and the fallback install docs still omitted the runtime package. This follow-up adds regression coverage first, then restores both release and install-path coverage with the smallest possible diff.

Constraint: New publishable workspaces must be wired through Changesets to reach npm release automation
Constraint: Fallback install docs must list runtime packages users need when skill files are present but global packages are missing
Rejected: Fix only the changeset gap | would leave the documented fallback install path incomplete
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep used-car-price-search in both the fallback global install example and a Changesets entry whenever release wiring changes
Tested: node --test scripts/skill-docs.test.js; npx changeset status; live 10-query used-car run against SK direct at 2026-04-02T07:38:44.949Z; npm run ci; LSP diagnostics on used-car package files and scripts/skill-docs.test.js; architect verification
Not-tested: No additional live provider permutations beyond the verified 10-query smoke run

* Keep used-car verification docs resilient to live inventory churn

A fresh rerun showed the SK direct inventory total continues to move during the day, so the feature doc now records the verified smoke-run timestamp without freezing a brittle exact count. The regression suite was updated first so the docs stay variability-aware and diff-clean in future follow-ups.

Constraint: Live SK direct inventory totals change over time even when the parsing contract stays stable
Rejected: Keep documenting a fixed total from one smoke run | it immediately drifted and made the docs stale
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the used-car live verification note timestamped and variability-aware instead of pinning an exact inventory total
Tested: node --test scripts/skill-docs.test.js; git diff --check; npm run ci; npx changeset status; live 10-query run against https://www.skdirect.co.kr/tb at 2026-04-02T07:59:41.391Z; LSP diagnostics on scripts/skill-docs.test.js; architect verification
Not-tested: No additional content changes outside the used-car feature doc

* Keep used-car query summaries honest when results are limited

The review found that query-level stats were being computed from the
post-limit slice, so common searches like 아반떼 under-reported both
match counts and price ranges. This change locks the regression first,
then computes the full filtered set before slicing only the returned
items.

Constraint: PR #48 must preserve the existing API shape while fixing the review-blocking stats bug
Rejected: Expanding the response with separate limited/full summary fields | unnecessary API churn for a targeted bug fix
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Query-level summary and matchedCount must stay derived from the full filtered set, not the display limit slice
Tested: npm test --workspace used-car-price-search; npm run ci; git diff --check; npx changeset status; live SK direct smoke run on 2026-04-02T08:23:59Z; LSP diagnostics on used-car-price-search source and test files; architect verification APPROVED
Not-tested: limit=0 semantics remain unchanged and still coerce to the default limit
Related: PR #48

* Keep korean-law-search available during upstream outages (#45)

Issue #44 adds Beopmang as the documented fallback when the primary korean-law-mcp path is unavailable, and locks the new routing into the doc regression suite so future edits do not silently revert the policy.

Constraint: Existing guidance must still prefer korean-law-mcp and keep LAW_OC scoped to the local CLI/MCP path
Rejected: Add a repo-local Beopmang client package | issue only requires fallback registration, not a new implementation surface
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the primary-first rule intact; Beopmang is an outage fallback, not the default path
Tested: node --test scripts/skill-docs.test.js; npm run ci; korean-law list; python3 live Beopmang search smoke for 관세법
Not-tested: Live Beopmang MCP handshake from a GUI MCP client

* Replace coupang scraping package with coupang-mcp server integration

Drop the browser-based scraping package (packages/coupang-product-search)
and switch to the uju777/coupang-mcp MCP server for all Coupang product
searches. This removes the anti-bot workaround complexity and provides
8 ready-to-use tools via MCP Streamable HTTP with no API key required.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Add disclaimer to used-car-price-search README

Clarify that the package queries SK렌터카 타고BUY public data with
no affiliation or sponsorship, and that ad/partnership inquiries
are welcome.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Refactor used-car-price-search into provider-based architecture

SK 타고BUY 전용 로직을 src/providers/sk-tagobuy.js로 분리하고,
공급자를 수평 확장할 수 있는 registry 구조로 전환한다.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Ship LCK analytics inside k-skill's managed release flow

Adapt jerjangmin's upstream lck-analytics skill/package into a new
workspace and skill pack, wire docs/install/release surfaces, and add
regression fixtures/tests plus script smoke coverage so the feature is
verifiable before publish.

Constraint: Upstream package is not published to npm yet
Constraint: Must preserve attribution to original source and author in shipped docs
Rejected: Keep the upstream lck-results install wording in k-skill | conflicts with repo workspace/package naming
Rejected: Ship only the npm package without the local skill scripts | issue explicitly requested the skill as well
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep Changesets as the only version-bump path for lck-analytics; do not hand-edit versions for release
Tested: node --test packages/lck-analytics/test/index.test.js scripts/skill-docs.test.js
Tested: npm run lint --workspace lck-analytics
Tested: npm test --workspace lck-analytics
Tested: live getLckSummary('2026-04-01', { team: '한화', includeStandings: true })
Tested: node lck-analytics/scripts/sync-oracle.js --csv lck-analytics/samples/oracle-lck-sample.csv --cache .tmp/lck-cache && node lck-analytics/scripts/build-match-report.js --date 2026-04-01 --team 한화 --cache .tmp/lck-cache && node lck-analytics/scripts/analyze-live-game.js --game game-1 --window packages/lck-analytics/test/fixtures/live-window-game-1.json --details packages/lck-analytics/test/fixtures/live-details-game-1.json --cache .tmp/lck-cache
Tested: npm run ci
Tested: npx tsc --noEmit --project /Users/jeffrey/Projects/k-skill/tsconfig.json
Not-tested: Riot live feed behavior for arbitrary future game ids outside the fixture-backed smoke path

* Enable policy-aware Korean spell checking from the official Nara surface

Add a skill guide and Python helper that use the approved old_speller HTML flow, chunk long text conservatively, and report original/suggestion/reason deltas. The docs also record the public-site limits, Cloudflare behavior, and non-commercial usage policy so agents do not overreach the free surface.

Constraint: Public site is HTML-only and may return 403 to non-browser clients
Constraint: Must not add new dependencies or high-volume crawling behavior
Rejected: Node fetch client | Cloudflare returned 403 in this environment
Rejected: Paid API integration | no public contract or credentials were available for this task
Confidence: medium
Scope-risk: moderate
Reversibility: clean
Directive: Keep usage low-rate and non-commercial unless supplier-approved API terms are added
Tested: npm run lint
Tested: npm run typecheck
Tested: npm test
Tested: npm run build
Tested: python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json
Tested: python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json
Not-tested: Paid API/order flow
Not-tested: High-volume commercial workloads
Not-tested: Non-UTF-8 file inputs

* Preserve korean spell-check layout in corrected output

The Nara payload can collapse paragraph separators into normalized page text, so the helper now maps corrections back onto the original chunk before rebuilding corrected_text. The CLI also rejects non-positive --max-chars values, and regression tests cover both the layout-preservation path and invalid argument handling.

Constraint: Nara result pages can normalize blank lines and sentence spacing before exposing errInfo offsets
Rejected: Narrow docs away from file/Markdown proofreading | preserving original chunk separators keeps the documented workflow intact
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep multiline separator preservation tied to the original chunk whenever a suggestion only changes whitespace across collapsed boundaries
Tested: python3 -m unittest scripts.test_korean_spell_check; npm run lint; npm run typecheck; npm test; npm run build; python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json; python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json; python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n왠지 않되요.' --format json; python3 scripts/korean_spell_check.py --text 테스트 --max-chars 0 --format json
Not-tested: Live multi-page Nara payloads with separator-sensitive corrections across multiple returned pages

* Preserve file-proofreading layout in Korean spell check output

The spell-check helper now keeps exact blank-line runs and paragraph
indentation when chunking and reassembling file-style input, while still
allowing the official service's spacing corrections to flow through.
Regression coverage now locks the collapsed-layout, triple-blank-line,
and cross-boundary spacing cases that triggered the PR review.

Constraint: Official Nara/PNU payloads can collapse layout and sometimes merge corrections across preserved paragraph boundaries
Rejected: Narrow docs away from file-level proofreading | the existing feature scope explicitly supports file and Markdown checks
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Preserve exact layout tokens only at original newline boundaries; do not reintroduce global strip/join normalization without real file-mode verification
Tested: npm run lint; npm run typecheck; npm test; npm run build; python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json; python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json; python3 scripts/korean_spell_check.py --file <tmpfile> --format json; python3 scripts/korean_spell_check.py --text 테스트 --max-chars 0 --format json
Not-tested: Live upstream behavior for extremely long leading/trailing-whitespace-only files
Related: PR #60

* Keep layout-preserving chunk splits from tripping on separator-boundary fixes

The follow-up layout preservation pass renamed the working unit variable, but one separator-length guard still referenced the old paragraph name. This commit finishes the refactor so exact-boundary paragraph chunks keep the preserved-separator logic reachable and the new regression coverage stays green.

Constraint: Must preserve the existing feature/#47 branch and PR #60 flow while fixing the approved review follow-up
Rejected: Revert the layout-preserving chunking refactor | would discard the verified file-layout fix and its regressions
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep chunk reassembly lossless; new chunking changes should continue to round-trip original separators byte-for-byte
Tested: npm run lint; npm run typecheck; npm test; npm run build; python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json; python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json; python3 scripts/korean_spell_check.py --file <tempfile-with-triple-blank-lines> --format json; python3 scripts/korean_spell_check.py --text 테스트 --max-chars 0 --format json (expected argument error)
Not-tested: Live multi-page service responses beyond the local smoke cases

* Preserve spell-check chunk separators when units overflow

The file-layout follow-up already preserved blank runs and indentation,
but the overlong-unit path still checked the wrong variable when
extracting a trailing separator. That could strand separators in
a standalone chunk and break exact reassembly for tight max-char
limits.

This commit fixes the separator extraction guard and locks the
behavior with a regression that proves chunk concatenation still
matches the original text when an overlong paragraph is followed
by a blank-line separator.

Constraint: File-mode reconstruction must preserve exact layout while chunking long input
Rejected: Broader chunking rewrite | existing structure only needed the overflow guard corrected
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep split_text_into_chunks chunk concatenation identical to the original input for layout-sensitive file checks
Tested: npm run lint; npm run typecheck; npm test; npm run build; python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json; python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json; python3 scripts/korean_spell_check.py --text 테스트 --max-chars 0 --format json; manual --file smoke with triple blank lines and indentation
Not-tested: Live-service failure modes such as Cloudflare/browser challenges

* Prevent clean spell-check chunks from crashing mixed file runs

The Nara/PNU surface can return a plain 'no issues found' HTML page
without the embedded result payload when a chunk is already clean.
Chunked file and markdown runs could hit that response on earlier
chunks, raise a ValueError, and never reach later chunks that still
needed corrections. Treat the no-issues page as an empty result set
and lock the behavior with narrow regression coverage.

Constraint: Upstream no-issue responses omit the JavaScript payload entirely and can split the status message across HTML whitespace
Rejected: Fabricate a synthetic result page | empty-page handling already preserves the original clean chunk
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep empty-result detection aligned with the upstream no-issues message unless the service publishes a structured empty payload contract
Tested: python3 -m unittest scripts.test_korean_spell_check
Tested: npm run lint
Tested: npm run typecheck
Tested: npm test
Tested: npm run build
Tested: python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json
Tested: python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json
Tested: python3 scripts/korean_spell_check.py --text 테스트 --max-chars 0 --format json
Tested: python3 scripts/korean_spell_check.py --file <tmpfile> --format json
Tested: python3 scripts/korean_spell_check.py --file <tmpfile> --max-chars 18 --format json
Not-tested: Other upstream empty-result templates beyond the current no-issues HTML wording

* Make Joseon Sillok lookups reproducible from the official site

Add a joseon-sillok-search skill and a Python helper that scrape the
official Joseon Annals search/detail pages. The helper normalizes
king/year metadata, fetches detail excerpts, and locks the repository
docs plus regression coverage around the shipped workflow.

Constraint: v1 must stay on official public HTML surfaces only
Constraint: Must avoid adding new dependencies for a simple scraping helper
Constraint: Shell connectivity to sillok.history.go.kr became intermittent during final live reruns
Rejected: Ship a new npm workspace | repo skill/docs pattern is enough for v1
Rejected: Add BeautifulSoup or another parser dependency | unnecessary for the bounded HTML patterns
Confidence: medium
Scope-risk: narrow
Reversibility: clean
Directive: Keep year filtering Gregorian and derived from official regnal metadata unless the upstream site exposes a better structured contract
Tested: npm run ci
Tested: Earlier live POST/detail probes against search/searchResultList.do and /id/kda_12512030_002 during implementation
Tested: Live official article inspection for kda_12512030_002 via the public site
Not-tested: Final end-to-end CLI live run after the last refactor, because the shell hit transient TCP timeouts to sillok.history.go.kr
Related: #59

* Prevent sillok follow-up fixes from missing filtered results or weakening trust

This follow-up addresses the blocking PR review items on the Joseon Sillok helper. The search loop now derives total pages from the first live page size so king/year filtering can reach later pages, TLS verification stays enabled on both requests and urllib paths, detail parsing accepts the live classification brackets, and repo docs stop linking to missing Korean spell-check assets on this branch.

Constraint: Must preserve the existing joseon-sillok-search surface and avoid new dependencies
Rejected: Keep verify=False behind an implicit fallback | still weakens authenticity for the default path
Rejected: Infer pagination from the hardcoded 50-row default | misses valid later-page matches when the site serves smaller pages
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If the official site changes page size or pagination markup again, update the first-page pagination regression before altering search_sillok pagination math
Tested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_sillok_search
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: python3 scripts/sillok_search.py --query '훈민정음' --king 세종 --year 1443 --limit 1 --timeout 20
Tested: python3 - <<'PY' ... fetch_detail_page(..., article_id='kda_12512030_002', timeout=20) ... PY
Not-tested: Successful live sillok.history.go.kr responses in this environment (POST and detail GET both timed out at 20s)
Related: PR #62

* Make joseon-sillok-search installs work outside the repo

The skill installer only ships the skill directory, so the sillok helper has to live inside that payload. This moves the authoritative helper into joseon-sillok-search/scripts/, keeps the repo-root script as a thin shim for tests and docs, and trims footer metadata from parsed article bodies so excerpts match the published examples.

Constraint: skills add exposes only the installed skill payload, not repo-root helpers

Rejected: Keep the helper only under scripts/ | installed skill commands fail after copy-only installs

Confidence: high

Scope-risk: narrow

Directive: Keep the repo-root sillok wrapper thin and update the bundled helper first when behavior changes

Tested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_sillok_search

Tested: node --test scripts/skill-docs.test.js

Tested: temp installed-skill smoke run of python3 scripts/sillok_search.py --help plus footer-cleaning parse_detail_page check

Tested: npm run ci

Not-tested: live sillok.history.go.kr shell requests still hit connect timeouts in this environment

* Restore the sillok CLI's verified stdlib transport fallback

The helper already shipped a stdlib urllib opener that can reach the
official search endpoint in environments where requests/urllib3 aborts.
This change keeps that opener available even when requests imports
successfully and falls back to it on retryable requests transport
failures. Added regression coverage for opener availability and the
requests-to-urllib fallback so the default CLI path matches the live
verified behavior.

Constraint: Official sillok detail GETs can still time out transiently in this environment
Constraint: Keep TLS verification enabled and preserve the documented CLI entrypoints
Rejected: Force urllib for every request | keep the existing requests fast path when it succeeds
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Preserve the stdlib fallback tests whenever the transport layer changes
Tested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_sillok_search
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: live forced-fallback probe against searchResultList.do with requests.post patched to OSError(22)
Not-tested: full live CLI completion through the detail GET in this environment

* Document the approved real-estate MCP skill without vendoring upstream

Issue #53 is intentionally doc-only, so this change adds the
real-estate-search skill, feature guide, setup/security notes,
and regression coverage around the upstream real-estate-mcp
integration instead of importing server code.

The new docs keep the original MCP link, cover Codex/Claude
registration, and spell out the self-host + Cloudflare Tunnel +
launchd path for environments where no fixed hosted endpoint is
available.

Constraint: Must use tae0y/real-estate-mcp without copying its source into this repository
Constraint: Must include the original MCP link and a stable self-host fallback when no hosted endpoint is available
Rejected: Vendor the upstream MCP source | issue explicitly requires skill docs only
Rejected: Assume a public hosted MCP endpoint exists | upstream docs did not publish one
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep this integration doc-only; do not add a workspace or vendored server without revisiting issue #53 constraints
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: upstream bootstrap smoke (`uv sync`, `uv run real-estate-mcp --help`, HTTP initialize on 127.0.0.1:8017)
Not-tested: live property data queries with a valid DATA_GO_KR_API_KEY

* Prevent misleading real-estate self-host instructions

Tighten the real-estate skill docs so the launchd fallback stays operational
and the Onbid bid-result tools are described with the same WIP caveat the
upstream project still publishes.

Constraint: Upstream Docker compose already uses restart: unless-stopped while `docker compose ... up -d` daemonizes immediately
Rejected: Keep a separate server LaunchAgent with RunAtLoad + KeepAlive | launchd would restart-loop on the exiting compose command
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Mirror upstream capability caveats in k-skill docs and do not wrap daemonized server commands in launchd KeepAlive jobs
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: uv sync
Tested: uv run real-estate-mcp --help
Tested: DATA_GO_KR_API_KEY=dummy uv run real-estate-mcp --transport http --host 127.0.0.1 --port 8017
Tested: curl initialize on http://127.0.0.1:8017/mcp returned protocolVersion 2024-11-05
Not-tested: Live 거래 조회 with a real DATA_GO_KR_API_KEY

* Keep install docs from reintroducing broken launchd guidance

The install guide had drifted from the real-estate skill and feature docs and still implied that macOS launchd should auto-run both the server and tunnel. This narrows the guidance back to tunnel-only launchd ownership and extends regression coverage so docs/install.md cannot silently reintroduce the server-side loop wording.

Constraint: Upstream docker compose already uses restart: unless-stopped
Constraint: Review-round-2 scope is limited to docs and regression coverage
Rejected: Leave docs/install.md under a generic launchd presence check | it missed the exact server/터널 regression
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep docs/install.md aligned with the detailed real-estate feature guide when self-host guidance changes
Tested: node --test scripts/skill-docs.test.js; npm run ci; fresh-clone upstream smoke with uv sync, uv run real-estate-mcp --help, and HTTP initialize on 127.0.0.1:8017/mcp
Not-tested: None

* Allow Han River water-level lookups without user API keys

The proxy now resolves HRFCO water-level stations via waterlevel/info and
fetches the latest 10-minute measurement via waterlevel/list, exposing a
public summary route plus a hosted skill/docs path.

Constraint: HRFCO requires a ServiceKey and station-code-based latest lookup
Rejected: Raw passthrough only | forces users to manage upstream details and misses the approved no-key UX
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep this route summary-only and public; expand rainfall/dam/bo/fldfct in separate issues
Tested: npm run ci; local server smoke test with HRFCO sample key against /v1/han-river/water-level; tsc diagnostics on packages/k-skill-proxy/src/server.js and src/hrfco.js
Not-tested: Hosted production proxy rollout

* Expose official nearby fuel prices for location-based gas station lookups

Add the first cheap-gas-nearby skill/package pair so nearby gas-price
queries can resolve a user-supplied location, translate it into Opinet's
KATEC search contract, and return the cheapest nearby stations with
address and facility detail. The docs and setup surfaces now advertise
the new skill and its Opinet API key requirement.

Constraint: Nearby fuel prices must come from the official KNOC Opinet API when available
Constraint: No new external dependencies were allowed for coordinate conversion or location resolution
Rejected: Map-only gas price scraping | official Opinet Open API exists and is the preferred source
Rejected: Require lat/lng input only | poorer UX than supporting landmark/station queries through anchor resolution
Confidence: medium
Scope-risk: moderate
Reversibility: clean
Directive: Keep `OPINET_API_KEY` as the only supported official-price credential unless the repo adopts an Opinet proxy later
Tested: npm run ci; node --test packages/cheap-gas-nearby/test/index.test.js; offline fixture smoke via searchCheapGasStationsByLocationQuery('서울역', ...)
Not-tested: Live Opinet API call with a real `OPINET_API_KEY` (no non-placeholder key was configured locally)
Related: #54

* Explain Blue Ribbon premium gating instead of failing opaquely

Blue Ribbon's nearby endpoint now returns PREMIUM_REQUIRED for public
requests, so the package now upgrades that response into a stable domain
error and updates user-facing docs to describe the degraded nearby state.
Regression tests cover both location-query and coordinate-query entrypoints
while keeping the existing happy path intact.

Constraint: Must not attempt to bypass Blue Ribbon premium access controls
Constraint: Package releases must use Changesets metadata
Rejected: Silently return empty results on PREMIUM_REQUIRED | would hide the upstream contract change from callers
Rejected: Keep generic 403 error and docs unchanged | leaves the main failure mode opaque and misleading
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If Blue Ribbon reopens a public nearby surface later, update the docs and replace the premium_required remap only after fresh live verification
Tested: npm test --workspace blue-ribbon-nearby; npm run lint --workspace blue-ribbon-nearby; npm run ci; live node repro now returns premium_required metadata
Not-tested: Official premium-authenticated nearby flow, because no approved premium credentials are available

* Reduce duplicate premium-required test assertions

The implementation diff was already verified, so this follow-up keeps the
new regression coverage easier to read by sharing one assertion helper
instead of repeating the same predicate twice.

Constraint: Must preserve the verified PREMIUM_REQUIRED regression coverage exactly
Rejected: Leave duplicated inline predicates in both tests | repeats the same contract and adds noise to future edits
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep location-query and coordinate-query regressions aligned when the premium_required error contract changes
Tested: npm test --workspace blue-ribbon-nearby; npm run lint --workspace blue-ribbon-nearby; npm run ci
Not-tested: Additional live Blue Ribbon requests beyond the previously verified premium_required repro

* Document an Olive Young search skill around the upstream daiso CLI

Issue #61 asked for an Olive Young lookup workflow without vendoring the upstream daiso-mcp server into k-skill, so this change adds a docs-only skill, threads it through repository docs, and locks the guidance with regression assertions.

The new guidance prefers CLI-first verification (`npx daiso`) and a clone fallback (`git clone https://github.com/hmmhmmhm/daiso-mcp.git`) instead of requiring direct Claude Code MCP installation. The existing daiso-product-search skill remains untouched.

Constraint: Must mention the original https://github.com/hmmhmmhm/daiso-mcp repo
Constraint: Must avoid changing the existing daiso-product-search skill
Constraint: Must prefer upstream CLI/package usage over direct Claude Code MCP installation
Rejected: Vendor upstream Olive Young code into k-skill | contradicts the minimal-addition design request
Rejected: Make Claude Code MCP setup the default path | issue explicitly asked for CLI-first usage
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the Olive Young docs aligned with upstream `daiso` CLI behavior and note public endpoint instability when live verification shows it
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: 2026-04-05 live /tmp/daiso-mcp CLI checks for health, /api/oliveyoung/stores, /api/oliveyoung/products, /api/oliveyoung/inventory
Not-tested: Authenticated/private Olive Young flows or ordering/payment paths

* Keep Han River docs honest until hosted rollout lands

The local HRFCO proxy route already works, but the deployed public proxy still
lacks /v1/han-river/water-level as of 2026-04-05. This follow-up changes the
user-facing docs to require a self-hosted or deployment-verified proxy URL,
keeps the intended hosted path documented as rollout-pending, and locks that
caveat with a regression test.

Constraint: Hosted k-skill-proxy deployment still returns 404 for /v1/han-river/water-level on 2026-04-05
Rejected: Keep advertising the hosted route as the default live path | current deployment is not live yet
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Restore hosted-default wording only after the public proxy route and HRFCO configuration are verified live
Tested: node --test scripts/skill-docs.test.js; npm run ci; local mocked smoke via node packages/k-skill-proxy/src/server.js + GET /v1/han-river/water-level?stationName=한강대교
Not-tested: Live hosted k-skill-proxy behavior after deployment

* Keep cheap gas lookups from failing on recoverable Kakao and input issues

The lookup now walks ranked Kakao anchor candidates until one returns usable coordinates, and it rejects invalid limit inputs instead of silently collapsing non-empty results into an empty list. The regression suite now locks both review repros plus the null-coordinate normalization edge case that surfaced while implementing the fallback.

Constraint: Kakao place panels can be partially populated or return 404 for otherwise valid search candidates
Rejected: Default invalid limit/detailLimit values silently | still masks caller bugs and can look like no results nearby
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep anchor resolution tolerant of unusable Kakao panels before failing the whole lookup
Tested: node --test packages/cheap-gas-nearby/test/index.test.js; npm run ci; offline fixture smoke for searchCheapGasStationsByLocationQuery('서울역', ...)
Not-tested: Live Opinet/Kakao network path without a real OPINET_API_KEY

* Keep Olive Young install guidance aligned with live retry behavior

Issue #61's initial branch already shipped the olive-young-search skill/docs set, but the install guide did not have a regression lock for the public endpoint instability note captured during live verification. This follow-up adds that test first and updates the install quickstart so retry-or-clone fallback guidance stays in sync with the verified daiso CLI workflow.

Constraint: Must keep the follow-up scoped to the approved issue #61 docs behavior
Constraint: Public olive-young endpoint can intermittently return 5xx/503 during live verification
Rejected: Re-open the broader feature docs set | the existing branch already covered the main feature scope
Rejected: Leave the retry guidance untested in install docs | risk of future doc drift across surfaces
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If live olive-young verification changes again, update install docs and the regression together so retry/fallback guidance stays honest
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: cd /tmp/daiso-mcp && npx daiso health
Tested: cd /tmp/daiso-mcp && npx daiso get /api/oliveyoung/stores --keyword 명동 --limit 3 --json
Tested: cd /tmp/daiso-mcp && npx daiso get /api/oliveyoung/products --keyword 선크림 --size 3 --json
Tested: cd /tmp/daiso-mcp && npx daiso get /api/oliveyoung/inventory --keyword 선크림 --storeKeyword 명동 --size 2 --json
Not-tested: Authenticated Olive Young flows or sustained-rate retry behavior beyond the documented smoke checks

* Preserve ranked Kakao anchor fallbacks after panel failures

The resolver already retried later Kakao panels, but after the best panel failed it
walked the remaining candidates in raw HTML order. Centralizing the full
anchor ranking in parse.js keeps fallback iteration aligned with the
existing scoring rules and locks the review repro with a regression test
for 강남역 ordering.

Constraint: Kakao place panels can 404 or omit coordinates even when later ranked candidates are usable
Rejected: Re-rank only after a failed panel | duplicates scoring logic and drifts from selectAnchorCandidate
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep resolveAnchor fallback order tied to the shared anchor scoring logic rather than raw Kakao result order
Tested: node --test packages/cheap-gas-nearby/test/index.test.js; npm run ci; offline fixture smoke for searchCheapGasStationsByLocationQuery('서울역', ...); lsp diagnostics on affected files
Not-tested: Live Kakao/Opinet network calls with a non-placeholder OPINET_API_KEY

* Clarify that Blue Ribbon coordinate lookups fail the same way

The premium gate now affects both location-query and coordinate-entry nearby flows. Record that parity in the skill and docs so the live 2026-04-05 repro evidence stays aligned with the shipped public contract.

Constraint: /restaurants/map remains premium-gated for public requests as of 2026-04-05
Rejected: Add more code changes without a behavior gap | would add churn after the approved fix already landed
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the docs/examples aligned with live repro evidence whenever Blue Ribbon changes nearby access behavior
Tested: npm test --workspace blue-ribbon-nearby; npm run lint --workspace blue-ribbon-nearby; npm run ci; live node repro for location + coordinates premium_required contract
Not-tested: New upstream success-path live nearby payloads, because public access is still premium-gated

* Document a runnable Olive Young clone fallback

The follow-up review found that clone-local `npx daiso` commands do not run from a built `hmmhmmhm/daiso-mcp` checkout because the generated bin file is not executable. This updates the olive-young skill and docs to use the verified `node dist/bin.js ...` path for clone fallback, and locks that behavior with regression tests while keeping the public CLI-first path unchanged.\n\nConstraint: Upstream clone checkouts can fail with `Permission denied` when invoked through clone-local `npx daiso`\nConstraint: Keep the existing daiso-product-search skill untouched\nRejected: Leave install docs unchanged | PR body and docs would keep a broken clone fallback path\nRejected: Vendor or patch upstream daiso-mcp inside k-skill | issue explicitly requires documenting upstream flow instead\nConfidence: high\nScope-risk: narrow\nReversibility: clean\nDirective: Keep clone-fallback docs and PR verification commands aligned with the actually runnable local invocation\nTested: node --test scripts/skill-docs.test.js\nTested: npm run ci\nTested: cd /tmp/daiso-mcp && npm install && npm run build && node dist/bin.js health\nTested: cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/stores --keyword 명동 --limit 3 --json\nTested: cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/products --keyword 선크림 --size 3 --json\nTested: cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/inventory --keyword 선크림 --storeKeyword 명동 --size 2 --json\nNot-tested: Public npx endpoint stability beyond the verified smoke-test window

* Record fresh verification for the approved cheap-gas follow-up

The ranked Kakao fallback-order and invalid-limit fixes are already present on feature/#54, so no further code edits were necessary. This empty follow-up commit records the requested rerun verification for PR #67 before posting the implementation update.\n\nConstraint: Existing branch head already contains the approved cheap-gas-nearby follow-up\nRejected: Invent another code/doc change just to force a non-empty diff | unnecessary risk after approval\nConfidence: high\nScope-risk: narrow\nReversibility: clean\nDirective: Keep Kakao anchor fallback iteration tied to the shared ranking helper and preserve finite-number validation for limit inputs\nTested: node --test packages/cheap-gas-nearby/test/index.test.js; npm run ci; offline fixture smoke for searchCheapGasStationsByLocationQuery('서울역', ...); lsp diagnostics on affected files\nNot-tested: Live Kakao/Opinet network calls with a non-placeholder OPINET_API_KEY

* Record verified delivery for approved cheap-gas-nearby rollout

The approved Issue #54 implementation was already present on feature/#54 when this follow-up began, so this checkpoint records fresh verification evidence and keeps PR #67 moving without introducing extra code churn.

Constraint: Existing approved fixes were already on the branch and the follow-up still required a pushed update plus implementation comment
Rejected: Add additional code or docs churn | approved scope was already satisfied and green locally
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Preserve the Kakao fallback-order and invalid-count regression coverage unless equivalent runtime repros replace it
Tested: node --test packages/cheap-gas-nearby/test/index.test.js; offline fixture smoke for searchCheapGasStationsByLocationQuery('서울역', ...); npm run ci; LSP diagnostics on affected cheap-gas-nearby files
Not-tested: Live Kakao/Opinet network path without a non-placeholder OPINET_API_KEY

* Keep Blue Ribbon premium-gate remapping explicitly bounded

The issue #63 fix already remaps PREMIUM_REQUIRED for nearby lookups. This follow-up adds a regression that proves non-premium /restaurants/map failures still surface as generic request errors, and clarifies that boundary in the package and feature docs.

Constraint: PR #68 already carries the shipped behavior change and must stay aligned with current live premium-gated upstream behavior
Rejected: Broaden domain remapping to all 403 nearby errors | would hide distinct upstream failure modes
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep only PREMIUM_REQUIRED on /restaurants/map mapped to premium_required unless a new verified upstream contract is documented
Tested: npm test --workspace blue-ribbon-nearby; npm run lint --workspace blue-ribbon-nearby; npm run ci; live node repro for location+coordinate nearby lookups on 2026-04-06; LSP diagnostics on src/test
Not-tested: Alternative non-premium upstream status codes beyond the mocked 403 ACCESS_DENIED path

* Keep Olive Young clone fallback docs runnable from a fresh clone

Round 3 review found that the inline fallback shorthand skipped `cd daiso-mcp`,
which made `npm install` run outside the cloned upstream repo even though the
fenced examples were already correct. This tightens the two published shorthand
snippets and adds regression coverage so both docs surfaces reject the broken
chain in future edits.

Constraint: Existing node dist/bin.js clone fallback examples were already verified and had to stay aligned
Constraint: The follow-up had to stay scoped to docs/test coverage without touching the existing daiso-product-search skill
Rejected: Convert the shorthand to prose only | the review specifically requested a runnable inline form or an explicit removal
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep inline clone fallback snippets synchronized with the fenced node dist/bin.js examples and require `cd daiso-mcp` before install/build
Tested: node --test scripts/skill-docs.test.js; npm run ci; cd /tmp/daiso-mcp && npm install && npm run build; cd /tmp/daiso-mcp && node dist/bin.js health; cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/stores --keyword 명동 --limit 3 --json; cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/products --keyword 선크림 --size 3 --json; cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/inventory --keyword 선크림 --storeKeyword 명동 --size 2 --json
Not-tested: Public npx path in this follow-up round (previous PR verification already covered it)
Related: PR #71

* Bundle korean-spell-check script inside skill directory for packageless installs

The Python helper lived only in the repo-root scripts/ folder, so
`skills add` never shipped it. Move the real implementation into
korean-spell-check/scripts/ (mirroring joseon-sillok-search) and
replace the root copy with a thin re-export wrapper so lint/test
still resolve from the repo root.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Exclude .claude directory from skill validation to fix CI in worktrees

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* real-estate-search: MCP self-host → k-skill-proxy HTTP 전환

사용자가 직접 real-estate-mcp를 clone/self-host하는 대신
k-skill-proxy에 MOLIT 실거래가 API route를 추가해서
다른 스킬(한강수위, 미세먼지 등)과 동일한 패턴으로 사용 가능하게 함.

- GET /v1/real-estate/region-code — 지역코드 검색
- GET /v1/real-estate/:assetType/:dealType — 9개 거래 유형 조회
- molit.js: XML 파싱, 필드 정규화, 취소거래 필터링, 요약통계
- region-lookup.js: 284개 법정동 5자리 코드 토큰 매칭
- SKILL.md/docs를 HTTP proxy 기반으로 전면 재작성
- DATA_GO_KR_API_KEY를 사용자 필수항목에서 프록시 운영자 전용으로 이동

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* cheap-gas-nearby: proxy 경유로 전환 + 프로덕션 배포 구조 문서화

- cheap-gas-nearby: OPINET_API_KEY 없이 k-skill-proxy 경유로 동작하도록 변경
  - fetchAroundStations/fetchDetailById에 proxy fallback 추가
  - SKILL.md, feature doc에서 사용자 API key 요구 제거
- AGENTS.md, CLAUDE.md: proxy 개발/배포 워크플로우 문서화
- docs/features/k-skill-proxy.md: opinet route 추가, 프로덕션 자동 배포 구조 설명

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* blue-ribbon-nearby: proxy 경유로 전환 + 프리미엄 세션 프록시 라우트

Blue Ribbon /restaurants/map이 프리미엄 전용으로 전환되어,
k-skill-proxy에 BLUE_RIBBON_SESSION_ID 기반 프록시 라우트를 추가하고
blue-ribbon-nearby 패키지가 기본적으로 프록시를 경유하도록 변경.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 17:44:23 +09:00
Jeffrey (Dongkyu) Kim
8dc6c9ea50 blue-ribbon-nearby: proxy 경유로 전환 + 프리미엄 세션 프록시 라우트
Blue Ribbon /restaurants/map이 프리미엄 전용으로 전환되어,
k-skill-proxy에 BLUE_RIBBON_SESSION_ID 기반 프록시 라우트를 추가하고
blue-ribbon-nearby 패키지가 기본적으로 프록시를 경유하도록 변경.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 17:36:26 +09:00
Jeffrey (Dongkyu) Kim
98d83fd5a7 cheap-gas-nearby: proxy 경유로 전환 + 프로덕션 배포 구조 문서화
- cheap-gas-nearby: OPINET_API_KEY 없이 k-skill-proxy 경유로 동작하도록 변경
  - fetchAroundStations/fetchDetailById에 proxy fallback 추가
  - SKILL.md, feature doc에서 사용자 API key 요구 제거
- AGENTS.md, CLAUDE.md: proxy 개발/배포 워크플로우 문서화
- docs/features/k-skill-proxy.md: opinet route 추가, 프로덕션 자동 배포 구조 설명

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 16:51:27 +09:00
Jeffrey (Dongkyu) Kim
e81388f0d5 real-estate-search: MCP self-host → k-skill-proxy HTTP 전환
사용자가 직접 real-estate-mcp를 clone/self-host하는 대신
k-skill-proxy에 MOLIT 실거래가 API route를 추가해서
다른 스킬(한강수위, 미세먼지 등)과 동일한 패턴으로 사용 가능하게 함.

- GET /v1/real-estate/region-code — 지역코드 검색
- GET /v1/real-estate/:assetType/:dealType — 9개 거래 유형 조회
- molit.js: XML 파싱, 필드 정규화, 취소거래 필터링, 요약통계
- region-lookup.js: 284개 법정동 5자리 코드 토큰 매칭
- SKILL.md/docs를 HTTP proxy 기반으로 전면 재작성
- DATA_GO_KR_API_KEY를 사용자 필수항목에서 프록시 운영자 전용으로 이동

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 16:50:27 +09:00
Jeffrey (Dongkyu) Kim
cdae5a93ac Exclude .claude directory from skill validation to fix CI in worktrees
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 16:05:56 +09:00
Jeffrey (Dongkyu) Kim
35602d0d7b Merge remote-tracking branch 'origin/dev' into feature/#52
# Conflicts:
#	docs/install.md
#	package.json
#	scripts/skill-docs.test.js
2026-04-06 16:04:22 +09:00
Jeffrey (Dongkyu) Kim
d7a7a6de79 Merge main into dev and resolve 16-file conflicts
main accumulated release/hotfix PRs (#34, #43, #49, #50, kipris,
LCK, KMA forecast) while dev grew new skills (cheap-gas, han-river,
olive-young, joseon-sillok, korean-spell-check, real-estate, blue-
ribbon premium gate).  Both sides are fully preserved.

Also bump toss-securities test assertion to match the 0.2.0 version
that main already shipped.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 15:25:49 +09:00
Jeffrey (Dongkyu) Kim
50e4bdd769 Bundle korean-spell-check script inside skill directory for packageless installs
The Python helper lived only in the repo-root scripts/ folder, so
`skills add` never shipped it. Move the real implementation into
korean-spell-check/scripts/ (mirroring joseon-sillok-search) and
replace the root copy with a thin re-export wrapper so lint/test
still resolve from the repo root.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 15:11:24 +09:00
Jeffrey (Dongkyu) Kim
f043b5724b
Merge pull request #67 from NomaDamas/feature/#54
Feature/#54
2026-04-06 01:20:34 +09:00
Jeffrey (Dongkyu) Kim
22ff61b714 Restore PR #67 reviewability after syncing feature/#54 with dev
Merged origin/dev into feature/#54 and resolved the documentation/test conflicts by keeping both the cheap-gas-nearby Opinet guidance and the newer han-river/olive-young rollout docs from dev. The merged docs and regression suite now cover both feature lanes so the branch can be re-reviewed without dropping either set of release notes or setup requirements.

Constraint: PR head had to absorb dev without merging the PR itself
Rejected: Favor one side of the conflicted docs/tests | would silently drop shipped guidance from the other branch
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep shared setup/install/security docs additive when multiple feature branches introduce new skills concurrently
Tested: npm run ci; node --test packages/cheap-gas-nearby/test/index.test.js; offline fixture smoke for searchCheapGasStationsByLocationQuery('서울역')
Not-tested: Live Opinet/API-key-backed network verification
2026-04-06 01:07:47 +09:00
Jeffrey (Dongkyu) Kim
58ee85d1f3
Merge pull request #71 from NomaDamas/feature/#61
Feature/#61
2026-04-06 01:03:28 +09:00
Jeffrey (Dongkyu) Kim
8be93184b8
Merge pull request #68 from NomaDamas/feature/#63
Feature/#63
2026-04-06 00:52:15 +09:00
Jeffrey (Dongkyu) Kim
8ebc131e37 Keep Olive Young clone fallback docs runnable from a fresh clone
Round 3 review found that the inline fallback shorthand skipped `cd daiso-mcp`,
which made `npm install` run outside the cloned upstream repo even though the
fenced examples were already correct. This tightens the two published shorthand
snippets and adds regression coverage so both docs surfaces reject the broken
chain in future edits.

Constraint: Existing node dist/bin.js clone fallback examples were already verified and had to stay aligned
Constraint: The follow-up had to stay scoped to docs/test coverage without touching the existing daiso-product-search skill
Rejected: Convert the shorthand to prose only | the review specifically requested a runnable inline form or an explicit removal
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep inline clone fallback snippets synchronized with the fenced node dist/bin.js examples and require `cd daiso-mcp` before install/build
Tested: node --test scripts/skill-docs.test.js; npm run ci; cd /tmp/daiso-mcp && npm install && npm run build; cd /tmp/daiso-mcp && node dist/bin.js health; cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/stores --keyword 명동 --limit 3 --json; cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/products --keyword 선크림 --size 3 --json; cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/inventory --keyword 선크림 --storeKeyword 명동 --size 2 --json
Not-tested: Public npx path in this follow-up round (previous PR verification already covered it)
Related: PR #71
2026-04-06 00:44:33 +09:00
Jeffrey (Dongkyu) Kim
8a1b88f56a
Merge pull request #66 from NomaDamas/feature/#56
Feature/#56
2026-04-06 00:24:04 +09:00
Jeffrey (Dongkyu) Kim
1246bf976d Keep Blue Ribbon premium-gate remapping explicitly bounded
The issue #63 fix already remaps PREMIUM_REQUIRED for nearby lookups. This follow-up adds a regression that proves non-premium /restaurants/map failures still surface as generic request errors, and clarifies that boundary in the package and feature docs.

Constraint: PR #68 already carries the shipped behavior change and must stay aligned with current live premium-gated upstream behavior
Rejected: Broaden domain remapping to all 403 nearby errors | would hide distinct upstream failure modes
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep only PREMIUM_REQUIRED on /restaurants/map mapped to premium_required unless a new verified upstream contract is documented
Tested: npm test --workspace blue-ribbon-nearby; npm run lint --workspace blue-ribbon-nearby; npm run ci; live node repro for location+coordinate nearby lookups on 2026-04-06; LSP diagnostics on src/test
Not-tested: Alternative non-premium upstream status codes beyond the mocked 403 ACCESS_DENIED path
2026-04-06 00:18:42 +09:00
Jeffrey (Dongkyu) Kim
994efd1c7f Record verified delivery for approved cheap-gas-nearby rollout
The approved Issue #54 implementation was already present on feature/#54 when this follow-up began, so this checkpoint records fresh verification evidence and keeps PR #67 moving without introducing extra code churn.

Constraint: Existing approved fixes were already on the branch and the follow-up still required a pushed update plus implementation comment
Rejected: Add additional code or docs churn | approved scope was already satisfied and green locally
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Preserve the Kakao fallback-order and invalid-count regression coverage unless equivalent runtime repros replace it
Tested: node --test packages/cheap-gas-nearby/test/index.test.js; offline fixture smoke for searchCheapGasStationsByLocationQuery('서울역', ...); npm run ci; LSP diagnostics on affected cheap-gas-nearby files
Not-tested: Live Kakao/Opinet network path without a non-placeholder OPINET_API_KEY
2026-04-06 00:10:55 +09:00
Jeffrey (Dongkyu) Kim
b89f91802a Record fresh verification for the approved cheap-gas follow-up
The ranked Kakao fallback-order and invalid-limit fixes are already present on feature/#54, so no further code edits were necessary. This empty follow-up commit records the requested rerun verification for PR #67 before posting the implementation update.\n\nConstraint: Existing branch head already contains the approved cheap-gas-nearby follow-up\nRejected: Invent another code/doc change just to force a non-empty diff | unnecessary risk after approval\nConfidence: high\nScope-risk: narrow\nReversibility: clean\nDirective: Keep Kakao anchor fallback iteration tied to the shared ranking helper and preserve finite-number validation for limit inputs\nTested: node --test packages/cheap-gas-nearby/test/index.test.js; npm run ci; offline fixture smoke for searchCheapGasStationsByLocationQuery('서울역', ...); lsp diagnostics on affected files\nNot-tested: Live Kakao/Opinet network calls with a non-placeholder OPINET_API_KEY
2026-04-06 00:09:12 +09:00
Jeffrey (Dongkyu) Kim
62c0007d2a Document a runnable Olive Young clone fallback
The follow-up review found that clone-local `npx daiso` commands do not run from a built `hmmhmmhm/daiso-mcp` checkout because the generated bin file is not executable. This updates the olive-young skill and docs to use the verified `node dist/bin.js ...` path for clone fallback, and locks that behavior with regression tests while keeping the public CLI-first path unchanged.\n\nConstraint: Upstream clone checkouts can fail with `Permission denied` when invoked through clone-local `npx daiso`\nConstraint: Keep the existing daiso-product-search skill untouched\nRejected: Leave install docs unchanged | PR body and docs would keep a broken clone fallback path\nRejected: Vendor or patch upstream daiso-mcp inside k-skill | issue explicitly requires documenting upstream flow instead\nConfidence: high\nScope-risk: narrow\nReversibility: clean\nDirective: Keep clone-fallback docs and PR verification commands aligned with the actually runnable local invocation\nTested: node --test scripts/skill-docs.test.js\nTested: npm run ci\nTested: cd /tmp/daiso-mcp && npm install && npm run build && node dist/bin.js health\nTested: cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/stores --keyword 명동 --limit 3 --json\nTested: cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/products --keyword 선크림 --size 3 --json\nTested: cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/inventory --keyword 선크림 --storeKeyword 명동 --size 2 --json\nNot-tested: Public npx endpoint stability beyond the verified smoke-test window
2026-04-05 23:34:21 +09:00
Jeffrey (Dongkyu) Kim
21f5ad9b3a Clarify that Blue Ribbon coordinate lookups fail the same way
The premium gate now affects both location-query and coordinate-entry nearby flows. Record that parity in the skill and docs so the live 2026-04-05 repro evidence stays aligned with the shipped public contract.

Constraint: /restaurants/map remains premium-gated for public requests as of 2026-04-05
Rejected: Add more code changes without a behavior gap | would add churn after the approved fix already landed
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the docs/examples aligned with live repro evidence whenever Blue Ribbon changes nearby access behavior
Tested: npm test --workspace blue-ribbon-nearby; npm run lint --workspace blue-ribbon-nearby; npm run ci; live node repro for location + coordinates premium_required contract
Not-tested: New upstream success-path live nearby payloads, because public access is still premium-gated
2026-04-05 23:03:26 +09:00
Jeffrey (Dongkyu) Kim
128540c0dc Preserve ranked Kakao anchor fallbacks after panel failures
The resolver already retried later Kakao panels, but after the best panel failed it
walked the remaining candidates in raw HTML order. Centralizing the full
anchor ranking in parse.js keeps fallback iteration aligned with the
existing scoring rules and locks the review repro with a regression test
for 강남역 ordering.

Constraint: Kakao place panels can 404 or omit coordinates even when later ranked candidates are usable
Rejected: Re-rank only after a failed panel | duplicates scoring logic and drifts from selectAnchorCandidate
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep resolveAnchor fallback order tied to the shared anchor scoring logic rather than raw Kakao result order
Tested: node --test packages/cheap-gas-nearby/test/index.test.js; npm run ci; offline fixture smoke for searchCheapGasStationsByLocationQuery('서울역', ...); lsp diagnostics on affected files
Not-tested: Live Kakao/Opinet network calls with a non-placeholder OPINET_API_KEY
2026-04-05 22:51:56 +09:00
Jeffrey (Dongkyu) Kim
6d0164b383 Keep Olive Young install guidance aligned with live retry behavior
Issue #61's initial branch already shipped the olive-young-search skill/docs set, but the install guide did not have a regression lock for the public endpoint instability note captured during live verification. This follow-up adds that test first and updates the install quickstart so retry-or-clone fallback guidance stays in sync with the verified daiso CLI workflow.

Constraint: Must keep the follow-up scoped to the approved issue #61 docs behavior
Constraint: Public olive-young endpoint can intermittently return 5xx/503 during live verification
Rejected: Re-open the broader feature docs set | the existing branch already covered the main feature scope
Rejected: Leave the retry guidance untested in install docs | risk of future doc drift across surfaces
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If live olive-young verification changes again, update install docs and the regression together so retry/fallback guidance stays honest
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: cd /tmp/daiso-mcp && npx daiso health
Tested: cd /tmp/daiso-mcp && npx daiso get /api/oliveyoung/stores --keyword 명동 --limit 3 --json
Tested: cd /tmp/daiso-mcp && npx daiso get /api/oliveyoung/products --keyword 선크림 --size 3 --json
Tested: cd /tmp/daiso-mcp && npx daiso get /api/oliveyoung/inventory --keyword 선크림 --storeKeyword 명동 --size 2 --json
Not-tested: Authenticated Olive Young flows or sustained-rate retry behavior beyond the documented smoke checks
2026-04-05 22:15:57 +09:00
Jeffrey (Dongkyu) Kim
f4237469f1 Keep cheap gas lookups from failing on recoverable Kakao and input issues
The lookup now walks ranked Kakao anchor candidates until one returns usable coordinates, and it rejects invalid limit inputs instead of silently collapsing non-empty results into an empty list. The regression suite now locks both review repros plus the null-coordinate normalization edge case that surfaced while implementing the fallback.

Constraint: Kakao place panels can be partially populated or return 404 for otherwise valid search candidates
Rejected: Default invalid limit/detailLimit values silently | still masks caller bugs and can look like no results nearby
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep anchor resolution tolerant of unusable Kakao panels before failing the whole lookup
Tested: node --test packages/cheap-gas-nearby/test/index.test.js; npm run ci; offline fixture smoke for searchCheapGasStationsByLocationQuery('서울역', ...)
Not-tested: Live Opinet/Kakao network path without a real OPINET_API_KEY
2026-04-05 21:37:58 +09:00
Jeffrey (Dongkyu) Kim
653c62523a Keep Han River docs honest until hosted rollout lands
The local HRFCO proxy route already works, but the deployed public proxy still
lacks /v1/han-river/water-level as of 2026-04-05. This follow-up changes the
user-facing docs to require a self-hosted or deployment-verified proxy URL,
keeps the intended hosted path documented as rollout-pending, and locks that
caveat with a regression test.

Constraint: Hosted k-skill-proxy deployment still returns 404 for /v1/han-river/water-level on 2026-04-05
Rejected: Keep advertising the hosted route as the default live path | current deployment is not live yet
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Restore hosted-default wording only after the public proxy route and HRFCO configuration are verified live
Tested: node --test scripts/skill-docs.test.js; npm run ci; local mocked smoke via node packages/k-skill-proxy/src/server.js + GET /v1/han-river/water-level?stationName=한강대교
Not-tested: Live hosted k-skill-proxy behavior after deployment
2026-04-05 21:18:05 +09:00
Jeffrey (Dongkyu) Kim
f4eafae78a Document an Olive Young search skill around the upstream daiso CLI
Issue #61 asked for an Olive Young lookup workflow without vendoring the upstream daiso-mcp server into k-skill, so this change adds a docs-only skill, threads it through repository docs, and locks the guidance with regression assertions.

The new guidance prefers CLI-first verification (`npx daiso`) and a clone fallback (`git clone https://github.com/hmmhmmhm/daiso-mcp.git`) instead of requiring direct Claude Code MCP installation. The existing daiso-product-search skill remains untouched.

Constraint: Must mention the original https://github.com/hmmhmmhm/daiso-mcp repo
Constraint: Must avoid changing the existing daiso-product-search skill
Constraint: Must prefer upstream CLI/package usage over direct Claude Code MCP installation
Rejected: Vendor upstream Olive Young code into k-skill | contradicts the minimal-addition design request
Rejected: Make Claude Code MCP setup the default path | issue explicitly asked for CLI-first usage
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the Olive Young docs aligned with upstream `daiso` CLI behavior and note public endpoint instability when live verification shows it
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: 2026-04-05 live /tmp/daiso-mcp CLI checks for health, /api/oliveyoung/stores, /api/oliveyoung/products, /api/oliveyoung/inventory
Not-tested: Authenticated/private Olive Young flows or ordering/payment paths
2026-04-05 20:58:36 +09:00
Jeffrey (Dongkyu) Kim
0314e129eb Reduce duplicate premium-required test assertions
The implementation diff was already verified, so this follow-up keeps the
new regression coverage easier to read by sharing one assertion helper
instead of repeating the same predicate twice.

Constraint: Must preserve the verified PREMIUM_REQUIRED regression coverage exactly
Rejected: Leave duplicated inline predicates in both tests | repeats the same contract and adds noise to future edits
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep location-query and coordinate-query regressions aligned when the premium_required error contract changes
Tested: npm test --workspace blue-ribbon-nearby; npm run lint --workspace blue-ribbon-nearby; npm run ci
Not-tested: Additional live Blue Ribbon requests beyond the previously verified premium_required repro
2026-04-05 20:01:17 +09:00
Jeffrey (Dongkyu) Kim
6be102bd78 Explain Blue Ribbon premium gating instead of failing opaquely
Blue Ribbon's nearby endpoint now returns PREMIUM_REQUIRED for public
requests, so the package now upgrades that response into a stable domain
error and updates user-facing docs to describe the degraded nearby state.
Regression tests cover both location-query and coordinate-query entrypoints
while keeping the existing happy path intact.

Constraint: Must not attempt to bypass Blue Ribbon premium access controls
Constraint: Package releases must use Changesets metadata
Rejected: Silently return empty results on PREMIUM_REQUIRED | would hide the upstream contract change from callers
Rejected: Keep generic 403 error and docs unchanged | leaves the main failure mode opaque and misleading
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If Blue Ribbon reopens a public nearby surface later, update the docs and replace the premium_required remap only after fresh live verification
Tested: npm test --workspace blue-ribbon-nearby; npm run lint --workspace blue-ribbon-nearby; npm run ci; live node repro now returns premium_required metadata
Not-tested: Official premium-authenticated nearby flow, because no approved premium credentials are available
2026-04-05 19:58:48 +09:00
Jeffrey (Dongkyu) Kim
397d0eea84 Expose official nearby fuel prices for location-based gas station lookups
Add the first cheap-gas-nearby skill/package pair so nearby gas-price
queries can resolve a user-supplied location, translate it into Opinet's
KATEC search contract, and return the cheapest nearby stations with
address and facility detail. The docs and setup surfaces now advertise
the new skill and its Opinet API key requirement.

Constraint: Nearby fuel prices must come from the official KNOC Opinet API when available
Constraint: No new external dependencies were allowed for coordinate conversion or location resolution
Rejected: Map-only gas price scraping | official Opinet Open API exists and is the preferred source
Rejected: Require lat/lng input only | poorer UX than supporting landmark/station queries through anchor resolution
Confidence: medium
Scope-risk: moderate
Reversibility: clean
Directive: Keep `OPINET_API_KEY` as the only supported official-price credential unless the repo adopts an Opinet proxy later
Tested: npm run ci; node --test packages/cheap-gas-nearby/test/index.test.js; offline fixture smoke via searchCheapGasStationsByLocationQuery('서울역', ...)
Not-tested: Live Opinet API call with a real `OPINET_API_KEY` (no non-placeholder key was configured locally)
Related: #54
2026-04-05 19:46:09 +09:00
Jeffrey (Dongkyu) Kim
afd7c6a996 Allow Han River water-level lookups without user API keys
The proxy now resolves HRFCO water-level stations via waterlevel/info and
fetches the latest 10-minute measurement via waterlevel/list, exposing a
public summary route plus a hosted skill/docs path.

Constraint: HRFCO requires a ServiceKey and station-code-based latest lookup
Rejected: Raw passthrough only | forces users to manage upstream details and misses the approved no-key UX
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep this route summary-only and public; expand rainfall/dam/bo/fldfct in separate issues
Tested: npm run ci; local server smoke test with HRFCO sample key against /v1/han-river/water-level; tsc diagnostics on packages/k-skill-proxy/src/server.js and src/hrfco.js
Not-tested: Hosted production proxy rollout
2026-04-05 19:26:06 +09:00
Jeffrey (Dongkyu) Kim
ffb1cce97e
Merge pull request #64 from NomaDamas/feature/#53
Feature/#53
2026-04-05 14:54:50 +09:00
Jeffrey (Dongkyu) Kim
a5c9f83a0d Keep install docs from reintroducing broken launchd guidance
The install guide had drifted from the real-estate skill and feature docs and still implied that macOS launchd should auto-run both the server and tunnel. This narrows the guidance back to tunnel-only launchd ownership and extends regression coverage so docs/install.md cannot silently reintroduce the server-side loop wording.

Constraint: Upstream docker compose already uses restart: unless-stopped
Constraint: Review-round-2 scope is limited to docs and regression coverage
Rejected: Leave docs/install.md under a generic launchd presence check | it missed the exact server/터널 regression
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep docs/install.md aligned with the detailed real-estate feature guide when self-host guidance changes
Tested: node --test scripts/skill-docs.test.js; npm run ci; fresh-clone upstream smoke with uv sync, uv run real-estate-mcp --help, and HTTP initialize on 127.0.0.1:8017/mcp
Not-tested: None
2026-04-05 14:39:26 +09:00
Jeffrey (Dongkyu) Kim
d656d26412 Prevent misleading real-estate self-host instructions
Tighten the real-estate skill docs so the launchd fallback stays operational
and the Onbid bid-result tools are described with the same WIP caveat the
upstream project still publishes.

Constraint: Upstream Docker compose already uses restart: unless-stopped while `docker compose ... up -d` daemonizes immediately
Rejected: Keep a separate server LaunchAgent with RunAtLoad + KeepAlive | launchd would restart-loop on the exiting compose command
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Mirror upstream capability caveats in k-skill docs and do not wrap daemonized server commands in launchd KeepAlive jobs
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: uv sync
Tested: uv run real-estate-mcp --help
Tested: DATA_GO_KR_API_KEY=dummy uv run real-estate-mcp --transport http --host 127.0.0.1 --port 8017
Tested: curl initialize on http://127.0.0.1:8017/mcp returned protocolVersion 2024-11-05
Not-tested: Live 거래 조회 with a real DATA_GO_KR_API_KEY
2026-04-05 14:25:56 +09:00
Jeffrey (Dongkyu) Kim
81c81cb5da Document the approved real-estate MCP skill without vendoring upstream
Issue #53 is intentionally doc-only, so this change adds the
real-estate-search skill, feature guide, setup/security notes,
and regression coverage around the upstream real-estate-mcp
integration instead of importing server code.

The new docs keep the original MCP link, cover Codex/Claude
registration, and spell out the self-host + Cloudflare Tunnel +
launchd path for environments where no fixed hosted endpoint is
available.

Constraint: Must use tae0y/real-estate-mcp without copying its source into this repository
Constraint: Must include the original MCP link and a stable self-host fallback when no hosted endpoint is available
Rejected: Vendor the upstream MCP source | issue explicitly requires skill docs only
Rejected: Assume a public hosted MCP endpoint exists | upstream docs did not publish one
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep this integration doc-only; do not add a workspace or vendored server without revisiting issue #53 constraints
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: upstream bootstrap smoke (`uv sync`, `uv run real-estate-mcp --help`, HTTP initialize on 127.0.0.1:8017)
Not-tested: live property data queries with a valid DATA_GO_KR_API_KEY
2026-04-05 14:10:45 +09:00
Jeffrey (Dongkyu) Kim
4f0dd0366c
Merge pull request #62 from NomaDamas/feature/#59
Feature/#59
2026-04-04 00:41:58 +09:00
Jeffrey (Dongkyu) Kim
109dbc70ca Keep feature/#59 mergeable on top of current dev
Merged dev into feature/#59 and resolved the overlap between the Joseon Sillok and Korean spell-check additions. The resolution keeps both helper/documentation surfaces, extends the root lint/test wiring to cover both Python helpers, and updates the stale negative docs regression so the merged branch verifies the shipped Korean spell-check assets instead of rejecting them.

Constraint: PR #62 must remain unmerged while restoring a clean merge against dev
Rejected: Drop the korean-spell-check changes from dev | would discard already-merged functionality on the base branch
Rejected: Keep the stale negative docs regression | contradicted the merged branch and broke ci
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: When merging feature branches that add repo-level docs regressions, re-check for contradictory assertions before rerunning ci
Tested: npm run ci
Not-tested: Live upstream joseon-sillok or Nara spell-check HTTP probes during merge resolution
2026-04-04 00:36:40 +09:00
Jeffrey (Dongkyu) Kim
f55154e543 Restore the sillok CLI's verified stdlib transport fallback
The helper already shipped a stdlib urllib opener that can reach the
official search endpoint in environments where requests/urllib3 aborts.
This change keeps that opener available even when requests imports
successfully and falls back to it on retryable requests transport
failures. Added regression coverage for opener availability and the
requests-to-urllib fallback so the default CLI path matches the live
verified behavior.

Constraint: Official sillok detail GETs can still time out transiently in this environment
Constraint: Keep TLS verification enabled and preserve the documented CLI entrypoints
Rejected: Force urllib for every request | keep the existing requests fast path when it succeeds
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Preserve the stdlib fallback tests whenever the transport layer changes
Tested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_sillok_search
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: live forced-fallback probe against searchResultList.do with requests.post patched to OSError(22)
Not-tested: full live CLI completion through the detail GET in this environment
2026-04-04 00:28:58 +09:00
Jeffrey (Dongkyu) Kim
cecb18a51d Make joseon-sillok-search installs work outside the repo
The skill installer only ships the skill directory, so the sillok helper has to live inside that payload. This moves the authoritative helper into joseon-sillok-search/scripts/, keeps the repo-root script as a thin shim for tests and docs, and trims footer metadata from parsed article bodies so excerpts match the published examples.

Constraint: skills add exposes only the installed skill payload, not repo-root helpers

Rejected: Keep the helper only under scripts/ | installed skill commands fail after copy-only installs

Confidence: high

Scope-risk: narrow

Directive: Keep the repo-root sillok wrapper thin and update the bundled helper first when behavior changes

Tested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_sillok_search

Tested: node --test scripts/skill-docs.test.js

Tested: temp installed-skill smoke run of python3 scripts/sillok_search.py --help plus footer-cleaning parse_detail_page check

Tested: npm run ci

Not-tested: live sillok.history.go.kr shell requests still hit connect timeouts in this environment
2026-04-04 00:15:50 +09:00
Jeffrey (Dongkyu) Kim
fc8d4c3c16 Prevent sillok follow-up fixes from missing filtered results or weakening trust
This follow-up addresses the blocking PR review items on the Joseon Sillok helper. The search loop now derives total pages from the first live page size so king/year filtering can reach later pages, TLS verification stays enabled on both requests and urllib paths, detail parsing accepts the live classification brackets, and repo docs stop linking to missing Korean spell-check assets on this branch.

Constraint: Must preserve the existing joseon-sillok-search surface and avoid new dependencies
Rejected: Keep verify=False behind an implicit fallback | still weakens authenticity for the default path
Rejected: Infer pagination from the hardcoded 50-row default | misses valid later-page matches when the site serves smaller pages
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If the official site changes page size or pagination markup again, update the first-page pagination regression before altering search_sillok pagination math
Tested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_sillok_search
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: python3 scripts/sillok_search.py --query '훈민정음' --king 세종 --year 1443 --limit 1 --timeout 20
Tested: python3 - <<'PY' ... fetch_detail_page(..., article_id='kda_12512030_002', timeout=20) ... PY
Not-tested: Successful live sillok.history.go.kr responses in this environment (POST and detail GET both timed out at 20s)
Related: PR #62
2026-04-03 23:55:55 +09:00
Jeffrey (Dongkyu) Kim
d09b41a12f Make Joseon Sillok lookups reproducible from the official site
Add a joseon-sillok-search skill and a Python helper that scrape the
official Joseon Annals search/detail pages. The helper normalizes
king/year metadata, fetches detail excerpts, and locks the repository
docs plus regression coverage around the shipped workflow.

Constraint: v1 must stay on official public HTML surfaces only
Constraint: Must avoid adding new dependencies for a simple scraping helper
Constraint: Shell connectivity to sillok.history.go.kr became intermittent during final live reruns
Rejected: Ship a new npm workspace | repo skill/docs pattern is enough for v1
Rejected: Add BeautifulSoup or another parser dependency | unnecessary for the bounded HTML patterns
Confidence: medium
Scope-risk: narrow
Reversibility: clean
Directive: Keep year filtering Gregorian and derived from official regnal metadata unless the upstream site exposes a better structured contract
Tested: npm run ci
Tested: Earlier live POST/detail probes against search/searchResultList.do and /id/kda_12512030_002 during implementation
Tested: Live official article inspection for kda_12512030_002 via the public site
Not-tested: Final end-to-end CLI live run after the last refactor, because the shell hit transient TCP timeouts to sillok.history.go.kr
Related: #59
2026-04-03 23:34:43 +09:00
Jeffrey (Dongkyu) Kim
391e68ae34
Merge pull request #60 from NomaDamas/feature/#47
Feature/#47
2026-04-03 22:36:12 +09:00
Jeffrey (Dongkyu) Kim
273b4767d2 Prevent clean spell-check chunks from crashing mixed file runs
The Nara/PNU surface can return a plain 'no issues found' HTML page
without the embedded result payload when a chunk is already clean.
Chunked file and markdown runs could hit that response on earlier
chunks, raise a ValueError, and never reach later chunks that still
needed corrections. Treat the no-issues page as an empty result set
and lock the behavior with narrow regression coverage.

Constraint: Upstream no-issue responses omit the JavaScript payload entirely and can split the status message across HTML whitespace
Rejected: Fabricate a synthetic result page | empty-page handling already preserves the original clean chunk
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep empty-result detection aligned with the upstream no-issues message unless the service publishes a structured empty payload contract
Tested: python3 -m unittest scripts.test_korean_spell_check
Tested: npm run lint
Tested: npm run typecheck
Tested: npm test
Tested: npm run build
Tested: python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json
Tested: python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json
Tested: python3 scripts/korean_spell_check.py --text 테스트 --max-chars 0 --format json
Tested: python3 scripts/korean_spell_check.py --file <tmpfile> --format json
Tested: python3 scripts/korean_spell_check.py --file <tmpfile> --max-chars 18 --format json
Not-tested: Other upstream empty-result templates beyond the current no-issues HTML wording
2026-04-03 22:29:50 +09:00
Jeffrey (Dongkyu) Kim
4475adfbf7 Preserve spell-check chunk separators when units overflow
The file-layout follow-up already preserved blank runs and indentation,
but the overlong-unit path still checked the wrong variable when
extracting a trailing separator. That could strand separators in
a standalone chunk and break exact reassembly for tight max-char
limits.

This commit fixes the separator extraction guard and locks the
behavior with a regression that proves chunk concatenation still
matches the original text when an overlong paragraph is followed
by a blank-line separator.

Constraint: File-mode reconstruction must preserve exact layout while chunking long input
Rejected: Broader chunking rewrite | existing structure only needed the overflow guard corrected
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep split_text_into_chunks chunk concatenation identical to the original input for layout-sensitive file checks
Tested: npm run lint; npm run typecheck; npm test; npm run build; python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json; python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json; python3 scripts/korean_spell_check.py --text 테스트 --max-chars 0 --format json; manual --file smoke with triple blank lines and indentation
Not-tested: Live-service failure modes such as Cloudflare/browser challenges
2026-04-03 22:15:04 +09:00
Jeffrey (Dongkyu) Kim
41d04be878 Keep layout-preserving chunk splits from tripping on separator-boundary fixes
The follow-up layout preservation pass renamed the working unit variable, but one separator-length guard still referenced the old paragraph name. This commit finishes the refactor so exact-boundary paragraph chunks keep the preserved-separator logic reachable and the new regression coverage stays green.

Constraint: Must preserve the existing feature/#47 branch and PR #60 flow while fixing the approved review follow-up
Rejected: Revert the layout-preserving chunking refactor | would discard the verified file-layout fix and its regressions
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep chunk reassembly lossless; new chunking changes should continue to round-trip original separators byte-for-byte
Tested: npm run lint; npm run typecheck; npm test; npm run build; python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json; python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json; python3 scripts/korean_spell_check.py --file <tempfile-with-triple-blank-lines> --format json; python3 scripts/korean_spell_check.py --text 테스트 --max-chars 0 --format json (expected argument error)
Not-tested: Live multi-page service responses beyond the local smoke cases
2026-04-03 22:14:53 +09:00
Jeffrey (Dongkyu) Kim
43f7ac9a56 Preserve file-proofreading layout in Korean spell check output
The spell-check helper now keeps exact blank-line runs and paragraph
indentation when chunking and reassembling file-style input, while still
allowing the official service's spacing corrections to flow through.
Regression coverage now locks the collapsed-layout, triple-blank-line,
and cross-boundary spacing cases that triggered the PR review.

Constraint: Official Nara/PNU payloads can collapse layout and sometimes merge corrections across preserved paragraph boundaries
Rejected: Narrow docs away from file-level proofreading | the existing feature scope explicitly supports file and Markdown checks
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Preserve exact layout tokens only at original newline boundaries; do not reintroduce global strip/join normalization without real file-mode verification
Tested: npm run lint; npm run typecheck; npm test; npm run build; python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json; python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json; python3 scripts/korean_spell_check.py --file <tmpfile> --format json; python3 scripts/korean_spell_check.py --text 테스트 --max-chars 0 --format json
Not-tested: Live upstream behavior for extremely long leading/trailing-whitespace-only files
Related: PR #60
2026-04-03 22:13:07 +09:00
Jeffrey (Dongkyu) Kim
614aa68075 Preserve korean spell-check layout in corrected output
The Nara payload can collapse paragraph separators into normalized page text, so the helper now maps corrections back onto the original chunk before rebuilding corrected_text. The CLI also rejects non-positive --max-chars values, and regression tests cover both the layout-preservation path and invalid argument handling.

Constraint: Nara result pages can normalize blank lines and sentence spacing before exposing errInfo offsets
Rejected: Narrow docs away from file/Markdown proofreading | preserving original chunk separators keeps the documented workflow intact
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep multiline separator preservation tied to the original chunk whenever a suggestion only changes whitespace across collapsed boundaries
Tested: python3 -m unittest scripts.test_korean_spell_check; npm run lint; npm run typecheck; npm test; npm run build; python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json; python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json; python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n왠지 않되요.' --format json; python3 scripts/korean_spell_check.py --text 테스트 --max-chars 0 --format json
Not-tested: Live multi-page Nara payloads with separator-sensitive corrections across multiple returned pages
2026-04-03 21:58:23 +09:00
Jeffrey (Dongkyu) Kim
d05a7cc63d Enable policy-aware Korean spell checking from the official Nara surface
Add a skill guide and Python helper that use the approved old_speller HTML flow, chunk long text conservatively, and report original/suggestion/reason deltas. The docs also record the public-site limits, Cloudflare behavior, and non-commercial usage policy so agents do not overreach the free surface.

Constraint: Public site is HTML-only and may return 403 to non-browser clients
Constraint: Must not add new dependencies or high-volume crawling behavior
Rejected: Node fetch client | Cloudflare returned 403 in this environment
Rejected: Paid API integration | no public contract or credentials were available for this task
Confidence: medium
Scope-risk: moderate
Reversibility: clean
Directive: Keep usage low-rate and non-commercial unless supplier-approved API terms are added
Tested: npm run lint
Tested: npm run typecheck
Tested: npm test
Tested: npm run build
Tested: python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json
Tested: python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json
Not-tested: Paid API/order flow
Not-tested: High-volume commercial workloads
Not-tested: Non-UTF-8 file inputs
2026-04-03 21:39:11 +09:00
Jeffrey (Dongkyu) Kim
9fad8ae045 Ship LCK analytics inside k-skill's managed release flow
Adapt jerjangmin's upstream lck-analytics skill/package into a new
workspace and skill pack, wire docs/install/release surfaces, and add
regression fixtures/tests plus script smoke coverage so the feature is
verifiable before publish.

Constraint: Upstream package is not published to npm yet
Constraint: Must preserve attribution to original source and author in shipped docs
Rejected: Keep the upstream lck-results install wording in k-skill | conflicts with repo workspace/package naming
Rejected: Ship only the npm package without the local skill scripts | issue explicitly requested the skill as well
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep Changesets as the only version-bump path for lck-analytics; do not hand-edit versions for release
Tested: node --test packages/lck-analytics/test/index.test.js scripts/skill-docs.test.js
Tested: npm run lint --workspace lck-analytics
Tested: npm test --workspace lck-analytics
Tested: live getLckSummary('2026-04-01', { team: '한화', includeStandings: true })
Tested: node lck-analytics/scripts/sync-oracle.js --csv lck-analytics/samples/oracle-lck-sample.csv --cache .tmp/lck-cache && node lck-analytics/scripts/build-match-report.js --date 2026-04-01 --team 한화 --cache .tmp/lck-cache && node lck-analytics/scripts/analyze-live-game.js --game game-1 --window packages/lck-analytics/test/fixtures/live-window-game-1.json --details packages/lck-analytics/test/fixtures/live-details-game-1.json --cache .tmp/lck-cache
Tested: npm run ci
Tested: npx tsc --noEmit --project /Users/jeffrey/Projects/k-skill/tsconfig.json
Not-tested: Riot live feed behavior for arbitrary future game ids outside the fixture-backed smoke path
2026-04-03 18:05:14 +09:00
Jeffrey (Dongkyu) Kim
f8390ff95e
Add workflow_dispatch to release workflow and document changeset test anti-pattern (#51)
- Add workflow_dispatch trigger to release-npm.yml so releases can be
  manually re-triggered when the automatic path-based trigger misses.
- Document in CLAUDE.md and AGENTS.md that tests must never assert
  .changeset file existence, since changeset version consumes them.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 11:13:31 +09:00
Jeffrey (Dongkyu) Kim
99a761dfee
Remove consumed changeset assertion that breaks CI after version bump (#50)
The test asserted a changeset file exists for used-car-price-search,
but changesets consumes these files during `changeset version`. Since
the package has already been published at 0.1.0, the guard is no
longer needed.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 10:57:17 +09:00
github-actions[bot]
e825df715e
chore: version packages (#49)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-04-03 00:10:09 +09:00
Jeffrey (Dongkyu) Kim
51464d1806
Release: coupang, korean-law, subway proxy improvements (#43)
* Remove client-side Seoul subway key setup

Route Seoul subway arrival lookups through k-skill-proxy so the
hosted proxy owns the Seoul Open Data upstream key and end users
only need the proxy base URL. Add proxy route coverage, update
skill/docs guidance, and align setup materials with the hosted
proxy flow used for fine dust.

Constraint: Must keep the proxy public, read-only, and dependency-free
Constraint: Must satisfy TDD-first verification and ship on feature/#35 targeting dev
Rejected: Add a separate client helper package | unnecessary extra layer for a single proxy route
Rejected: Keep SEOUL_OPEN_API_KEY as an end-user requirement | defeats the issue goal
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep the Seoul subway proxy surface limited to station-arrival passthrough unless tests/docs expand the contract
Tested: npm run ci; local proxy runtime on 127.0.0.1:4120 for /health and /v1/seoul-subway/arrival on 2026-03-31 with an invalid upstream key
Not-tested: Live success response with a valid Seoul Open API key
Related: #35

* Prevent broken Seoul subway proxy defaults before hosted rollout

The Seoul subway proxy endpoint code is present locally, but the hosted public route is not live yet. This change turns the user-facing subway docs back into an explicit proxy configuration flow, replaces the misleading hosted default in setup examples, and keeps subway proxy examples on self-host/local URLs until rollout is verified.

Constraint: Hosted k-skill-proxy.nomadamas.org/v1/seoul-subway/arrival is not live yet
Rejected: Keep the hosted Seoul subway URL as the default path | would send default users to a 404 route
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Do not restore a hosted Seoul subway default until the public proxy route is deployed and smoke-verified
Tested: node --test scripts/skill-docs.test.js; npm run ci; local proxy smoke on 127.0.0.1:4120 with stubbed Seoul upstream (GET /health, GET /v1/seoul-subway/arrival?stationName=강남)
Not-tested: Live hosted proxy smoke after deployment

* Reduce Seoul subway proxy upstream pressure with request caching

The public subway arrival route already normalized caller input but still re-fetched the Seoul upstream for every identical poll. This change adds a short-TTL cache keyed from the normalized subway query, annotates JSON responses with cache metadata, and locks the repeated-read collapse with a regression that proves alias/default normalization still reuses the cached result.

Constraint: The endpoint must stay public/no-auth while protecting a shared server-side SEOUL_OPEN_API_KEY from unnecessary repeated upstream hits
Rejected: Add a new shared cache abstraction for proxy metadata | unnecessary for this narrow fix and would enlarge the diff
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep subway cache keys tied to normalizeSeoulSubwayQuery output so alias/default query forms continue collapsing to one upstream request
Tested: node --test packages/k-skill-proxy/test/server.test.js
Tested: npm run ci
Tested: Local proxy runtime on 127.0.0.1:4120 with SEOUL_OPEN_API_KEY=test-seoul-key and stubbed fetch for /health plus repeated /v1/seoul-subway/arrival requests
Not-tested: Live hosted proxy rollout state

* Document OpenClaw support in the public compatibility list

The approved scope for issue #29 was narrowed to README support messaging,
so this change adds OpenClaw/ClawHub to the supported-client line and locks
that wording with a regression test in the docs suite.

Constraint: Issue #29 was approved as a README-only compatibility/docs change
Rejected: Broader install-doc updates | out of approved scope for this issue
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep OpenClaw support wording aligned with the supported-client README line and its docs regression test
Tested: Focused Node docs regression, npm run lint, npm run typecheck, npm run build, npm test
Not-tested: Live OpenClaw or ClawHub install flow (issue scope was documentation-only)

* Protect README client-support claims from ClawHub regressions

The README already advertises OpenClaw/ClawHub, but the docs
regression only matched OpenClaw. Tighten the assertion to the
exact supported-client fragment so a future edit cannot silently
remove ClawHub while keeping the issue verification command stable.

Constraint: PR #39 already publishes a verification command keyed to the current test name
Rejected: Rename the test to mention ClawHub explicitly | would drift from the published verification command
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep this regression synchronized with the README supported-client line whenever that copy changes
Tested: node --test scripts/skill-docs.test.js --test-name-pattern 'README advertises OpenClaw among the supported coding agents'
Tested: npm run lint
Tested: npm run typecheck
Tested: npm run build
Tested: npm test
Tested: lsp diagnostics for scripts/skill-docs.test.js (0 errors)
Not-tested: N/A

* Make Coupang shopper research usable without pretending scraping is stable

Coupang blocks unattended shopper queries in this environment, so the new package focuses on the durable pieces we can ship honestly: official URL builders, browser-captured HTML parsers, and explicit automation probes. The docs and skill now explain the seller-API limitation, the verified anti-bot behavior, and the browser-capture fallback expected by callers.

Constraint: No general shopper Open API surfaced in Coupang's official developer docs

Constraint: Headless/direct retrieval is anti-bot blocked in verified local probes

Rejected: Bundle a scraping bypass or hidden browser dependency | violates repo policy and would over-claim reliability

Confidence: high

Scope-risk: moderate

Reversibility: clean

Directive: Keep probe/docs behavior aligned with fresh live verification before claiming unattended Coupang access works

Tested: npm run ci; live probeAutomation(query=생수) with direct fetch + Playwright-core browserFetchHtml; LSP diagnostics on changed JS/test files

Not-tested: Headed/manual browser sessions with a human-authenticated Coupang context

* Keep the Coupang workspace installable and its entrypoint honest

Review follow-up found two contract gaps in the first issue #36 rollout: fresh installs were missing the new workspace link in package-lock, and the package README documented parser helpers that were not exported from the package entrypoint. Refreshing the lockfile and re-exporting the parsers keeps npm ci and consumer imports aligned with the published API.

Constraint: npm ci must succeed from a clean checkout
Rejected: Narrow the README API list to only client helpers | would hide useful parsers that the package already ships and tests
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: When adding a new workspace, refresh package-lock and verify the package entrypoint matches README public API claims
Tested: npm run ci; workspace coupang-product-search tests; LSP diagnostics on coupang-product-search JS/test files
Not-tested: published npm install from registry (local pack dry-run only)

* Keep Coupang anti-bot docs honest as probe results drift

A same-day PR rerun showed the published mobile probe snapshot was already stale, so the follow-up locks the 403/access-denied mobile path with regression coverage and softens the docs/skill contract to describe blocked outcome classes instead of one frozen signature. The published guidance now also explains that browser results only appear when browserFetchHtml is injected, matching what a clean checkout can actually reproduce.

Constraint: Coupang anti-bot responses vary by edge/challenge between reruns on the same day
Rejected: Freeze one exact mobile probe snapshot | same-day reruns already diverged
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Refresh Coupang anti-bot docs only with same-day live probe evidence, and prefer blocked outcome classes over a single exact signature when reruns disagree
Tested: npm run lint; npm run typecheck; npm test; npm run ci; live probeAutomation("생수") with direct fetch + Playwright-core browser fetch
Not-tested: Non-Chrome Playwright-core executables

* Keep the Coupang skill honest about clean-checkout browser probes

The approved follow-up already documented that clean-checkout probeAutomation runs leave browser unset unless a browser fetcher is injected, but the skill text itself had not made that null contract explicit. This commit adds a regression test that locks the browser-null wording across the published docs surfaces and updates the skill so the stated behavior matches the package implementation and clean-checkout reality.

Constraint: probeAutomation() only populates browser when browserFetchHtml is supplied by the caller
Rejected: Leave the skill wording implicit and rely on README examples alone | the skill could drift away from the shipped package contract again
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the README, feature doc, and skill aligned whenever the probeAutomation browser contract changes
Tested: node --test packages/coupang-product-search/test/index.test.js; npm run lint; npm run typecheck; npm test; npm run ci; live probeAutomation("생수") with direct fetch + Playwright-core browser fetch; codex exec review --uncommitted
Not-tested: Alternative browser engines beyond local Google Chrome for the manual browserFetchHtml probe

* Keep Coupang browser probe docs aligned with manual verification paths

The approved PR #40 follow-up still had one wording gap: the skill said when browser results appear, but it did not explicitly say those populated results come from an injected manual/external browserFetchHtml path. This change locks that contract with a failing regression first, then updates the skill text to match the already-shipped README/feature-doc guidance and runtime behavior.

Constraint: clean-checkout probeAutomation() must keep browser null unless browserFetchHtml is supplied
Rejected: change runtime browser probe behavior | approved follow-up was docs/test only and runtime contract is already correct
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: keep Coupang probe docs aligned with the shipped browserFetchHtml injection contract; do not imply built-in browser probing in clean checkouts
Tested: node --test packages/coupang-product-search/test/index.test.js; npm run lint; npm run typecheck; npm test; npm run ci; live probeAutomation("생수") with direct fetch + Playwright-core browser fetch; lsp diagnostics on changed files; architect review approved
Not-tested: alternate browser engines beyond local Google Chrome + playwright-core manual runner

* Route Korean law lookups through korean-law-mcp

Add a documentation-first korean-law-search skill and lock the repo docs around the rule that Korean law queries must go through korean-law-mcp rather than a new in-repo package.

The change updates install/setup/security guidance, publishes the new feature doc, and adds regression tests so future edits keep the LAW_OC + korean-law-mcp contract intact.

Constraint: Issue #41 requires korean-law-mcp for every Korean law lookup
Constraint: Must not add a new npm or python package in this repository
Rejected: Add a repo-local law package | violates the no-new-package requirement and duplicates upstream MCP work
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep Korean law lookup guidance pinned to korean-law-mcp unless issue requirements explicitly change
Tested: node --test scripts/skill-docs.test.js
Tested: npm install -g korean-law-mcp && korean-law list && korean-law help search_law
Tested: npm run ci
Tested: npx tsc --noEmit --pretty false --project /Users/jeffrey/Projects/k-skill/tsconfig.json
Not-tested: live search_law/get_law_text against a real LAW_OC credential

* Clarify Korean law setup modes to avoid credential confusion

A review found the new korean-law-search docs treated LAW_OC as an unconditional prerequisite even though the upstream remote MCP endpoint is configured separately from the local CLI/server path. This update makes the docs and regression tests mode-specific: local CLI/MCP uses LAW_OC, while the remote endpoint stays a korean-law-mcp-only url fallback without a user-supplied credential.

Constraint: Upstream korean-law-mcp uses LAW_OC on the local CLI/server path while the documented remote MCP endpoint is configured with url only
Rejected: Keep LAW_OC mandatory for every korean-law-mcp mode | contradicts upstream docs and reviewer evidence
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep README/setup/skill docs and regression tests aligned on the local-vs-remote korean-law-mcp contract
Tested: node --test scripts/skill-docs.test.js; npm install -g korean-law-mcp && korean-law list && korean-law help search_law; npx tsc --noEmit --pretty false --project /Users/jeffrey/Projects/k-skill/tsconfig.json; npm run ci; lsp_diagnostics scripts/skill-docs.test.js
Not-tested: Live remote MCP endpoint connection against https://korean-law-mcp.fly.dev/mcp

* Record issue #41 follow-up after approved verification

The approved korean-law-search change was already present on feature/#41, so this follow-up records a fresh verification pass and updates the PR without introducing unnecessary code churn.

Constraint: Existing branch already contained the approved implementation
Rejected: Touch repo files without need | would create noise without improving behavior
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the korean-law-mcp-only + mode-specific LAW_OC contract aligned with upstream docs before changing these surfaces again
Tested: node --test scripts/skill-docs.test.js; npx tsc --noEmit --pretty false --project /Users/jeffrey/Projects/k-skill/tsconfig.json; npm install -g korean-law-mcp && korean-law list && korean-law help search_law; npm run ci
Not-tested: Live korean-law queries that require a real LAW_OC or remote endpoint session

* Keep the Korean law feature diff merge-ready

Verification uncovered trailing whitespace in the new korean-law feature guide when checking the branch diff. I added a regression to the skill docs suite and removed the whitespace so the approved documentation contract stays clean and reviewable.

Constraint: Preserve the existing korean-law-mcp + mode-specific LAW_OC contract without widening scope
Rejected: Leave the whitespace issue as-is | git diff --check stayed dirty on the branch
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep docs/features/korean-law-search.md free of trailing whitespace so diff hygiene stays enforced by the regression
Tested: node --test scripts/skill-docs.test.js; npx tsc --noEmit --pretty false --project /Users/jeffrey/Projects/k-skill/tsconfig.json; npm install -g korean-law-mcp && korean-law list && korean-law help search_law; npm run ci; git diff --check
Not-tested: Live credentialed LAW_OC search execution against the upstream API

* Keep Korean law skill guidance aligned with supported lookups

The approved issue #41 feature already worked, but the shipped skill text still under-described ordinance and interpretation lookups compared with the documented capability set. This follow-up tightens the skill copy and locks that contract with a doc regression so the branch stays merge-ready without changing the underlying korean-law-mcp routing rules.

Constraint: Must preserve the existing korean-law-mcp-only and mode-specific LAW_OC setup contract
Rejected: Leave the skill wording as-is | drift from the documented lookup surface would remain
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the skill examples and doc regression aligned whenever supported korean-law-mcp search surfaces change
Tested: node --test scripts/skill-docs.test.js
Not-tested: live LAW_OC-backed upstream API queries

* Keep Korean law completion guidance in sync with enforced lookups

The previous follow-up aligned the main skill examples, but the done-criteria text still left interpretation and ordinance routing implicit. This commit makes the completion checklist explicit and extends the doc regression so future edits cannot silently drop those lookup paths.

Constraint: Must stay within the existing issue #41 korean-law-mcp-only contract
Rejected: Leave the done checklist implicit | reviewers and future edits could drift from the enforced lookup set
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: When the supported Korean-law lookup set changes, update the done checklist and regression together
Tested: node --test scripts/skill-docs.test.js
Not-tested: full CI before commit

* Enable repeatable used-car price lookups from a rental-company source

Issue #46 required surveying major Korean rental companies before implementation and then choosing the easiest stable provider. SK렌터카 다이렉트 exposes 타고BUY inventory in public Next.js page data, so the feature stays dependency-free while still supporting live repeated lookups and documented provider rationale.

Constraint: Must compare major Korean rental companies before implementation
Constraint: Must verify 10+ live lookups against a real provider surface
Rejected: 롯데오토옥션 as v1 provider | public list contract was unstable and legacy .do flows returned inconsistent or 404 pages
Rejected: 레드캡렌터카 as v1 provider | no public used-car inventory or API surface was found
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep the v1 provider read-only and inventory-snapshot-based unless a stable documented public API is confirmed
Tested: npm run ci
Tested: Live 10-query run against https://www.skdirect.co.kr/tb at 2026-04-02T07:22:46Z
Tested: LSP diagnostics on affected files
Not-tested: Seller-specific detail drilldowns or non-SK providers
Related: #46

* Keep used-car-price-search releasable after merge

Review feedback found that the new used-car workspace would not ship because it lacked a changeset, and the fallback install docs still omitted the runtime package. This follow-up adds regression coverage first, then restores both release and install-path coverage with the smallest possible diff.

Constraint: New publishable workspaces must be wired through Changesets to reach npm release automation
Constraint: Fallback install docs must list runtime packages users need when skill files are present but global packages are missing
Rejected: Fix only the changeset gap | would leave the documented fallback install path incomplete
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep used-car-price-search in both the fallback global install example and a Changesets entry whenever release wiring changes
Tested: node --test scripts/skill-docs.test.js; npx changeset status; live 10-query used-car run against SK direct at 2026-04-02T07:38:44.949Z; npm run ci; LSP diagnostics on used-car package files and scripts/skill-docs.test.js; architect verification
Not-tested: No additional live provider permutations beyond the verified 10-query smoke run

* Keep used-car verification docs resilient to live inventory churn

A fresh rerun showed the SK direct inventory total continues to move during the day, so the feature doc now records the verified smoke-run timestamp without freezing a brittle exact count. The regression suite was updated first so the docs stay variability-aware and diff-clean in future follow-ups.

Constraint: Live SK direct inventory totals change over time even when the parsing contract stays stable
Rejected: Keep documenting a fixed total from one smoke run | it immediately drifted and made the docs stale
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the used-car live verification note timestamped and variability-aware instead of pinning an exact inventory total
Tested: node --test scripts/skill-docs.test.js; git diff --check; npm run ci; npx changeset status; live 10-query run against https://www.skdirect.co.kr/tb at 2026-04-02T07:59:41.391Z; LSP diagnostics on scripts/skill-docs.test.js; architect verification
Not-tested: No additional content changes outside the used-car feature doc

* Keep used-car query summaries honest when results are limited

The review found that query-level stats were being computed from the
post-limit slice, so common searches like 아반떼 under-reported both
match counts and price ranges. This change locks the regression first,
then computes the full filtered set before slicing only the returned
items.

Constraint: PR #48 must preserve the existing API shape while fixing the review-blocking stats bug
Rejected: Expanding the response with separate limited/full summary fields | unnecessary API churn for a targeted bug fix
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Query-level summary and matchedCount must stay derived from the full filtered set, not the display limit slice
Tested: npm test --workspace used-car-price-search; npm run ci; git diff --check; npx changeset status; live SK direct smoke run on 2026-04-02T08:23:59Z; LSP diagnostics on used-car-price-search source and test files; architect verification APPROVED
Not-tested: limit=0 semantics remain unchanged and still coerce to the default limit
Related: PR #48

* Keep korean-law-search available during upstream outages (#45)

Issue #44 adds Beopmang as the documented fallback when the primary korean-law-mcp path is unavailable, and locks the new routing into the doc regression suite so future edits do not silently revert the policy.

Constraint: Existing guidance must still prefer korean-law-mcp and keep LAW_OC scoped to the local CLI/MCP path
Rejected: Add a repo-local Beopmang client package | issue only requires fallback registration, not a new implementation surface
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the primary-first rule intact; Beopmang is an outage fallback, not the default path
Tested: node --test scripts/skill-docs.test.js; npm run ci; korean-law list; python3 live Beopmang search smoke for 관세법
Not-tested: Live Beopmang MCP handshake from a GUI MCP client

* Replace coupang scraping package with coupang-mcp server integration

Drop the browser-based scraping package (packages/coupang-product-search)
and switch to the uju777/coupang-mcp MCP server for all Coupang product
searches. This removes the anti-bot workaround complexity and provides
8 ready-to-use tools via MCP Streamable HTTP with no API key required.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Add disclaimer to used-car-price-search README

Clarify that the package queries SK렌터카 타고BUY public data with
no affiliation or sponsorship, and that ad/partnership inquiries
are welcome.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Refactor used-car-price-search into provider-based architecture

SK 타고BUY 전용 로직을 src/providers/sk-tagobuy.js로 분리하고,
공급자를 수평 확장할 수 있는 registry 구조로 전환한다.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 23:59:38 +09:00
Jeffrey (Dongkyu) Kim
66000d92f2 Refactor used-car-price-search into provider-based architecture
SK 타고BUY 전용 로직을 src/providers/sk-tagobuy.js로 분리하고,
공급자를 수평 확장할 수 있는 registry 구조로 전환한다.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 23:58:30 +09:00
Jeffrey (Dongkyu) Kim
d0c102ebc6 Add disclaimer to used-car-price-search README
Clarify that the package queries SK렌터카 타고BUY public data with
no affiliation or sponsorship, and that ad/partnership inquiries
are welcome.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 23:44:16 +09:00
Jeffrey (Dongkyu) Kim
af58195abc Replace coupang scraping package with coupang-mcp server integration
Drop the browser-based scraping package (packages/coupang-product-search)
and switch to the uju777/coupang-mcp MCP server for all Coupang product
searches. This removes the anti-bot workaround complexity and provides
8 ready-to-use tools via MCP Streamable HTTP with no API key required.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 23:34:55 +09:00
Jeffrey (Dongkyu) Kim
8a2a1565fc
Keep korean-law-search available during upstream outages (#45)
Issue #44 adds Beopmang as the documented fallback when the primary korean-law-mcp path is unavailable, and locks the new routing into the doc regression suite so future edits do not silently revert the policy.

Constraint: Existing guidance must still prefer korean-law-mcp and keep LAW_OC scoped to the local CLI/MCP path
Rejected: Add a repo-local Beopmang client package | issue only requires fallback registration, not a new implementation surface
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the primary-first rule intact; Beopmang is an outage fallback, not the default path
Tested: node --test scripts/skill-docs.test.js; npm run ci; korean-law list; python3 live Beopmang search smoke for 관세법
Not-tested: Live Beopmang MCP handshake from a GUI MCP client
2026-04-02 23:33:12 +09:00
Jeffrey (Dongkyu) Kim
07c498669d
Merge pull request #48 from NomaDamas/feature/#46
Feature/#46
2026-04-02 17:39:25 +09:00
Jeffrey (Dongkyu) Kim
d0226eedc1 Keep used-car query summaries honest when results are limited
The review found that query-level stats were being computed from the
post-limit slice, so common searches like 아반떼 under-reported both
match counts and price ranges. This change locks the regression first,
then computes the full filtered set before slicing only the returned
items.

Constraint: PR #48 must preserve the existing API shape while fixing the review-blocking stats bug
Rejected: Expanding the response with separate limited/full summary fields | unnecessary API churn for a targeted bug fix
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Query-level summary and matchedCount must stay derived from the full filtered set, not the display limit slice
Tested: npm test --workspace used-car-price-search; npm run ci; git diff --check; npx changeset status; live SK direct smoke run on 2026-04-02T08:23:59Z; LSP diagnostics on used-car-price-search source and test files; architect verification APPROVED
Not-tested: limit=0 semantics remain unchanged and still coerce to the default limit
Related: PR #48
2026-04-02 17:29:56 +09:00
Jeffrey (Dongkyu) Kim
2ad51c30d4 Keep used-car verification docs resilient to live inventory churn
A fresh rerun showed the SK direct inventory total continues to move during the day, so the feature doc now records the verified smoke-run timestamp without freezing a brittle exact count. The regression suite was updated first so the docs stay variability-aware and diff-clean in future follow-ups.

Constraint: Live SK direct inventory totals change over time even when the parsing contract stays stable
Rejected: Keep documenting a fixed total from one smoke run | it immediately drifted and made the docs stale
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the used-car live verification note timestamped and variability-aware instead of pinning an exact inventory total
Tested: node --test scripts/skill-docs.test.js; git diff --check; npm run ci; npx changeset status; live 10-query run against https://www.skdirect.co.kr/tb at 2026-04-02T07:59:41.391Z; LSP diagnostics on scripts/skill-docs.test.js; architect verification
Not-tested: No additional content changes outside the used-car feature doc
2026-04-02 17:02:31 +09:00
Jeffrey (Dongkyu) Kim
89a016a922 Keep used-car-price-search releasable after merge
Review feedback found that the new used-car workspace would not ship because it lacked a changeset, and the fallback install docs still omitted the runtime package. This follow-up adds regression coverage first, then restores both release and install-path coverage with the smallest possible diff.

Constraint: New publishable workspaces must be wired through Changesets to reach npm release automation
Constraint: Fallback install docs must list runtime packages users need when skill files are present but global packages are missing
Rejected: Fix only the changeset gap | would leave the documented fallback install path incomplete
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep used-car-price-search in both the fallback global install example and a Changesets entry whenever release wiring changes
Tested: node --test scripts/skill-docs.test.js; npx changeset status; live 10-query used-car run against SK direct at 2026-04-02T07:38:44.949Z; npm run ci; LSP diagnostics on used-car package files and scripts/skill-docs.test.js; architect verification
Not-tested: No additional live provider permutations beyond the verified 10-query smoke run
2026-04-02 16:42:01 +09:00
Jeffrey (Dongkyu) Kim
3bc3762195 Enable repeatable used-car price lookups from a rental-company source
Issue #46 required surveying major Korean rental companies before implementation and then choosing the easiest stable provider. SK렌터카 다이렉트 exposes 타고BUY inventory in public Next.js page data, so the feature stays dependency-free while still supporting live repeated lookups and documented provider rationale.

Constraint: Must compare major Korean rental companies before implementation
Constraint: Must verify 10+ live lookups against a real provider surface
Rejected: 롯데오토옥션 as v1 provider | public list contract was unstable and legacy .do flows returned inconsistent or 404 pages
Rejected: 레드캡렌터카 as v1 provider | no public used-car inventory or API surface was found
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep the v1 provider read-only and inventory-snapshot-based unless a stable documented public API is confirmed
Tested: npm run ci
Tested: Live 10-query run against https://www.skdirect.co.kr/tb at 2026-04-02T07:22:46Z
Tested: LSP diagnostics on affected files
Not-tested: Seller-specific detail drilldowns or non-SK providers
Related: #46
2026-04-02 16:26:04 +09:00
Jeffrey (Dongkyu) Kim
bd6997d133
Merge pull request #34 from NomaDamas/changeset-release/main
chore: version packages
2026-03-30 22:41:07 +09:00
github-actions[bot]
fa2e1bada8 chore: version packages 2026-03-30 13:40:28 +00:00
Jeffrey (Dongkyu) Kim
3255b236f0
Merge pull request #33 from NomaDamas/hotfix/changesets-changelog-release
Fix Changesets release PR generation by enabling package changelogs
2026-03-30 22:31:14 +09:00
Jeffrey (Dongkyu) Kim
da8eca15c2 Align Changesets release flow with generated package changelogs
The release workflow uses changesets/action to build the Version Packages PR, and that action reads each changed package's CHANGELOG.md after running changeset version. The repo had changelog generation disabled, so versioning succeeded but the action crashed with ENOENT when it tried to read missing package changelogs.

This enables the default Changesets changelog generator so release PRs can be assembled without manual stub files, while keeping GitHub Releases disabled for npm publishes. The release docs now match that behavior.

Constraint: Keep createGithubReleases false because npm package GitHub releases are still out of scope
Rejected: Add empty CHANGELOG.md files to each package | quick hotfix but leaves PR notes weak and requires manual upkeep for new packages
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If changelog generation is disabled again, replace changesets/action PR generation or restore package changelog files before merging
Tested: npm run version-packages in local verification after enabling changelog generation
Tested: npm run ci
Not-tested: Live GitHub Actions release run on main after merge
2026-03-30 22:27:07 +09:00
Jeffrey (Dongkyu) Kim
13f39d14ff
Merge pull request #31 from NomaDamas/dev
Release dev into main with Kakao bar, K League, and Toss Securities
2026-03-30 22:17:24 +09:00
Jeffrey (Dongkyu) Kim
91ec9615fc
Merge pull request #22 from NomaDamas/dev
Release dev into main with the current Korea skill set
2026-03-28 23:32:54 +09:00
Jeffrey (Dongkyu) Kim
efbaf079db
Merge pull request #8 from NomaDamas/dev
hwp / zipcode / kakaotalk skills
2026-03-27 00:18:52 +09:00
673 changed files with 104178 additions and 2884 deletions

View file

@ -1,5 +0,0 @@
---
"blue-ribbon-nearby": minor
---
Add the first reusable Blue Ribbon nearby restaurant client and skill docs.

View file

@ -1,5 +0,0 @@
---
"kleague-results": minor
---
Add the first official K League results and standings client package.

View file

@ -1,5 +0,0 @@
---
"k-lotto": minor
---
Add the initial official dhlottery-backed k-lotto package.

View file

@ -1,6 +1,6 @@
{
"$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
"changelog": false,
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],

View file

@ -1,5 +0,0 @@
---
"coupang-product-search": minor
---
Add the first Coupang shopper URL/parser package with anti-bot probe guidance and skill docs.

View file

@ -1,5 +0,0 @@
---
"toss-securities": minor
---
Add the first safe read-only Toss Securities wrapper package and skill docs.

View file

@ -1,5 +0,0 @@
---
"kakao-bar-nearby": minor
---
Add the first Kakao Map nearby bar lookup package and skill docs.

View file

@ -1,5 +0,0 @@
---
"daiso-product-search": minor
---
Publish the official Daiso Mall store and pickup-stock lookup package.

View file

@ -0,0 +1,13 @@
{
"name": "k-skill",
"owner": {
"name": "NomaDamas"
},
"plugins": [
{
"name": "k-skill",
"source": "./",
"description": "한국인을 위한 90+ Agent Skill 번들 — SRT/KTX/당근/쿠팡/카톡/정부24 등 한국 일상·업무 자동화"
}
]
}

110
.claude-plugin/plugin.json Normal file
View file

@ -0,0 +1,110 @@
{
"name": "k-skill",
"description": "한국인을 위한 90+ Agent Skill 모음 — SRT/KTX/당근/쿠팡/카톡/정부24 등 한국 일상·업무 자동화",
"version": "1.0.0",
"author": {
"name": "NomaDamas"
},
"homepage": "https://github.com/NomaDamas/k-skill",
"repository": "https://github.com/NomaDamas/k-skill",
"license": "MIT",
"skills": [
"./biz-health-check",
"./bunjang-search",
"./catchtable-sniper",
"./cheap-gas-nearby",
"./corporate-registration-consulting",
"./coupang-product-search",
"./court-auction-notice-search",
"./daangn-cars-search",
"./daangn-jobs-search",
"./daangn-realty-search",
"./daangn-used-goods-search",
"./daishin-report-search",
"./daiso-product-search",
"./danawa-price-search",
"./delivery-tracking",
"./donation-place-search",
"./emergency-room-beds",
"./express-bus-booking",
"./fine-dust-location",
"./flight-ticket-search",
"./foresttrip-vacancy",
"./fsc-corporate-info",
"./g2b-sanctioned-supplier",
"./gangnamunni-clinic-search",
"./geeknews-search",
"./gongsijiga-search",
"./han-river-water-level",
"./hipass-receipt",
"./hola-poke-yeoksam",
"./household-waste-info",
"./hwp",
"./intercity-bus-booking",
"./iros-registry-automation",
"./jobkorea-talent-search",
"./joseon-sillok-search",
"./k-dart",
"./k-schoollunch-menu",
"./k-skill-cleaner",
"./k-skill-setup",
"./kakao-bar-nearby",
"./kakao-map",
"./kakaotalk-mac",
"./kbl-results",
"./kbo-results",
"./kleague-results",
"./korea-weather",
"./korean-character-count",
"./korean-cinema-search",
"./korean-humanizer",
"./korean-jangbu-for",
"./korean-law-search",
"./korean-marathon-schedule",
"./korean-middle-korean",
"./korean-patent-search",
"./korean-privacy-terms",
"./korean-scholarship-search",
"./korean-slang-writing",
"./korean-spell-check",
"./korean-stock-search",
"./korean-transit-route",
"./kosis-stats",
"./kstartup-search",
"./ktx-booking",
"./lck-analytics",
"./lh-notice-search",
"./library-book-search",
"./local-election-candidate-search",
"./localdata-business-status",
"./lotto-results",
"./market-kurly-search",
"./mfds-drug-safety",
"./mfds-food-safety",
"./myrealtrip-search",
"./national-pension-workplace",
"./naver-blog-research",
"./naver-news-search",
"./naver-shopping-search",
"./nts-business-registration",
"./nts-tax-delinquency",
"./ohou-today-deal",
"./olive-young-search",
"./parking-lot-search",
"./public-restroom-nearby",
"./real-estate-search",
"./rhwp-advanced",
"./rhwp-edit",
"./saramin-talent-search",
"./seoul-bike",
"./seoul-density",
"./seoul-subway-arrival",
"./sh-notice-search",
"./srt-booking",
"./subway-lost-property",
"./ticket-availability",
"./toss-securities",
"./used-car-price-search",
"./zipcode-search"
]
}

38
.dockerignore Normal file
View file

@ -0,0 +1,38 @@
.git
.github
.gitignore
.DS_Store
.omx
.sisyphus
.venv
.env
.env.*
*.dec
*.plaintext
__pycache__
**/__pycache__
**/node_modules
**/dist
**/.next
**/.cache
docs
tests
test
**/test
**/tests
**/*.test.js
**/*.test.py
**/*.spec.js
scripts/build-manus-bundle.js
*.md
LICENSE
CONTRIBUTING.md
CHANGELOG.md
**/CHANGELOG.md
**/README.md
**/*.test.js
.changeset
.claude
.agents
.cursor
.kiro

View file

@ -10,9 +10,9 @@ jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: actions/setup-node@v4
- uses: actions/setup-node@v5
with:
node-version: 20
cache: npm

View file

@ -0,0 +1,147 @@
name: Deploy k-skill-proxy to Cloud Run
# Live: https://k-skill-proxy.nomadamas.org
# GCP project: k-skill-proxy, region: asia-northeast1
# Auth: Workload Identity Federation. Setup: docs/deploy-k-skill-proxy.md
on:
push:
branches: [main]
workflow_dispatch: {}
permissions:
contents: read
id-token: write
concurrency:
group: deploy-k-skill-proxy
cancel-in-progress: false
env:
GCP_PROJECT_ID: k-skill-proxy
GCP_REGION: asia-northeast1
AR_REPO: k-skill
SERVICE_NAME: k-skill-proxy
IMAGE_NAME: k-skill-proxy
jobs:
deploy:
name: Build and deploy
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Authenticate to Google Cloud (Workload Identity Federation)
id: auth
uses: google-github-actions/auth@v3
with:
project_id: ${{ env.GCP_PROJECT_ID }}
workload_identity_provider: ${{ secrets.GCP_WIF_PROVIDER }}
service_account: ${{ secrets.GCP_DEPLOY_SERVICE_ACCOUNT }}
token_format: access_token
- name: Set up gcloud CLI
uses: google-github-actions/setup-gcloud@v3
with:
project_id: ${{ env.GCP_PROJECT_ID }}
- name: Configure Docker for Artifact Registry
run: gcloud auth configure-docker ${{ env.GCP_REGION }}-docker.pkg.dev --quiet
- name: Resolve image URI
id: image
run: |
IMAGE_URI="${GCP_REGION}-docker.pkg.dev/${GCP_PROJECT_ID}/${AR_REPO}/${IMAGE_NAME}:${GITHUB_SHA}"
echo "uri=${IMAGE_URI}" >> "$GITHUB_OUTPUT"
echo "Image: ${IMAGE_URI}"
- name: Build container image
run: |
docker build \
--tag "${{ steps.image.outputs.uri }}" \
--file packages/k-skill-proxy/Dockerfile \
.
- name: Push image to Artifact Registry
run: docker push "${{ steps.image.outputs.uri }}"
- name: Deploy to Cloud Run
id: deploy
uses: google-github-actions/deploy-cloudrun@v3
with:
service: ${{ env.SERVICE_NAME }}
region: ${{ env.GCP_REGION }}
image: ${{ steps.image.outputs.uri }}
secrets: |-
AIR_KOREA_OPEN_API_KEY=AIR_KOREA_OPEN_API_KEY:latest
KMA_OPEN_API_KEY=KMA_OPEN_API_KEY:latest
SEOUL_OPEN_API_KEY=SEOUL_OPEN_API_KEY:latest
HRFCO_OPEN_API_KEY=HRFCO_OPEN_API_KEY:latest
OPINET_API_KEY=OPINET_API_KEY:latest
DATA_GO_KR_API_KEY=DATA_GO_KR_API_KEY:latest
KEDU_INFO_KEY=KEDU_INFO_KEY:latest
DATA4LIBRARY_AUTH_KEY=DATA4LIBRARY_AUTH_KEY:latest
FOODSAFETYKOREA_API_KEY=FOODSAFETYKOREA_API_KEY:latest
KAKAO_REST_API_KEY=KAKAO_REST_API_KEY:latest
KRX_API_KEY=KRX_API_KEY:latest
KOSIS_API_KEY=KOSIS_API_KEY:latest
NAVER_SEARCH_CLIENT_ID=NAVER_SEARCH_CLIENT_ID:latest
NAVER_SEARCH_CLIENT_SECRET=NAVER_SEARCH_CLIENT_SECRET:latest
LAW_OC=LAW_OC:latest
env_vars: |-
KSKILL_PROXY_HOST=0.0.0.0
KSKILL_PROXY_NAME=k-skill-proxy
KSKILL_PROXY_CACHE_TTL_MS=300000
KSKILL_PROXY_RATE_LIMIT_WINDOW_MS=60000
KSKILL_PROXY_RATE_LIMIT_MAX=60
flags: >-
--platform=managed
--allow-unauthenticated
--cpu=1
--memory=512Mi
--min-instances=0
--max-instances=3
--concurrency=80
--timeout=60
--execution-environment=gen2
--cpu-boost
- name: Smoke test /health on the new revision
env:
SERVICE_URL: ${{ steps.deploy.outputs.url }}
run: |
set -euo pipefail
echo "Service URL: ${SERVICE_URL}"
for attempt in 1 2 3 4 5; do
if curl -fsS --max-time 15 "${SERVICE_URL}/health" >/tmp/health.json; then
break
fi
echo "Health probe attempt ${attempt} failed, retrying in 5s..."
sleep 5
done
python3 -c "
import json, sys
data = json.load(open('/tmp/health.json'))
if not data.get('ok'):
print('Health response is not ok:', data)
sys.exit(1)
missing = [k for k, v in data.get('upstreams', {}).items() if k.endswith('Configured') and v is not True]
if missing:
print('Upstreams not configured:', missing)
sys.exit(1)
print('Health OK. All upstreams configured.')
"
- name: Smoke test custom domain (k-skill-proxy.nomadamas.org)
run: |
set -euo pipefail
if curl -fsS --max-time 15 https://k-skill-proxy.nomadamas.org/health >/tmp/prod-health.json; then
python3 -c "
import json
data = json.load(open('/tmp/prod-health.json'))
print('Prod /health ok:', data.get('ok'))
"
else
echo "::warning::Custom domain /health probe failed; revision may need traffic split or DNS warm-up."
fi

91
.github/workflows/manus-bundle.yml vendored Normal file
View file

@ -0,0 +1,91 @@
name: Publish Manus bundle
on:
workflow_dispatch:
push:
branches:
- main
paths:
- "*/SKILL.md"
- "*/scripts/**"
- "*/references/**"
- "*/templates/**"
- "scripts/build-manus-bundle.js"
- "scripts/validate-skills.sh"
- ".github/workflows/manus-bundle.yml"
permissions:
contents: write
concurrency:
group: manus-bundle-latest
cancel-in-progress: true
jobs:
publish:
runs-on: ubuntu-latest
env:
RELEASE_TAG: manus-bundle-latest
RELEASE_TITLE: "Manus bundle (rolling)"
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version: 20
cache: npm
- run: npm ci
- name: Build .skill bundle
run: npm run build:manus-bundle
- name: Verify build output
run: |
set -euo pipefail
test -f dist/manus/k-skill-manus-all.zip
test -f dist/manus/INDEX.md
count=$(ls dist/manus/*.skill | wc -l)
if [ "$count" -lt 1 ]; then
echo "no .skill files produced" >&2
exit 1
fi
echo "built $count .skill files"
- name: Publish rolling release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
notes=$(cat <<EOF
Auto-built Manus.ai-compatible \`.skill\` bundle for every skill in k-skill.
- **\`k-skill-manus-all.zip\`** — combined download containing every \`.skill\` file plus \`INDEX.md\`. Unzip, then drag-drop individual \`.skill\` files into Manus.
- **\`INDEX.md\`** — human-readable listing of every bundled skill.
Manus accepts one skill per upload (\`.skill\`/\`.zip\`/folder). See [\`docs/install-manus.md\`](https://github.com/${GITHUB_REPOSITORY}/blob/main/docs/install-manus.md) for the full flow.
This is a rolling pre-release that is overwritten on every push to \`main\`. Built from commit \`${GITHUB_SHA}\`.
EOF
)
if gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
gh release edit "$RELEASE_TAG" \
--repo "$GITHUB_REPOSITORY" \
--title "$RELEASE_TITLE" \
--notes "$notes" \
--prerelease
else
gh release create "$RELEASE_TAG" \
--repo "$GITHUB_REPOSITORY" \
--title "$RELEASE_TITLE" \
--notes "$notes" \
--prerelease \
--target "$GITHUB_SHA"
fi
gh release upload "$RELEASE_TAG" \
--repo "$GITHUB_REPOSITORY" \
--clobber \
dist/manus/k-skill-manus-all.zip \
dist/manus/INDEX.md

View file

@ -1,6 +1,7 @@
name: Release npm packages
on:
workflow_dispatch:
push:
branches:
- main
@ -23,11 +24,11 @@ jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: actions/setup-node@v4
- uses: actions/setup-node@v5
with:
node-version: 24
cache: npm
@ -36,6 +37,41 @@ jobs:
- run: npm ci
- run: npm run ci
- name: Preflight verify npm auth
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
set -euo pipefail
echo "::group::npm whoami"
NPM_USER=$(npm whoami 2>&1) || {
echo "::error::npm whoami failed NPM_TOKEN is invalid or expired. Rotate the token and update the repository secret."
exit 1
}
echo "Authenticated as: ${NPM_USER}"
echo "::endgroup::"
- name: Preflight list unpublished packages (diagnostic)
run: |
set -euo pipefail
echo "Packages that will be published:"
FOUND=0
for pkg_json in packages/*/package.json; do
PRIVATE=$(node -e "console.log(require('./${pkg_json}').private || false)")
[ "$PRIVATE" = "true" ] && continue
PKG=$(node -e "console.log(require('./${pkg_json}').name)")
LOCAL_VER=$(node -e "console.log(require('./${pkg_json}').version)")
REMOTE_VER=$(npm view "${PKG}" version 2>/dev/null || echo "")
if [ "${LOCAL_VER}" != "${REMOTE_VER}" ]; then
echo " → ${PKG}@${LOCAL_VER} (npm: ${REMOTE_VER:-not yet published})"
FOUND=1
fi
done
if [ "$FOUND" -eq 0 ]; then
echo " (none all versions are already on npm)"
fi
- name: Create npm release PR or publish changed packages
uses: changesets/action@v1
with:

View file

@ -16,20 +16,37 @@ permissions:
id-token: write
jobs:
detect_python_packages:
runs-on: ubuntu-latest
outputs:
has_python_packages: ${{ steps.detect.outputs.has_python_packages }}
steps:
- uses: actions/checkout@v5
- id: detect
shell: bash
run: |
if find python-packages -mindepth 2 -maxdepth 2 -name pyproject.toml -print -quit | grep -q .; then
echo "has_python_packages=true" >> "$GITHUB_OUTPUT"
else
echo "has_python_packages=false" >> "$GITHUB_OUTPUT"
fi
scaffold-only:
if: ${{ hashFiles('python-packages/**/pyproject.toml') == '' }}
needs: detect_python_packages
if: ${{ needs.detect_python_packages.outputs.has_python_packages != 'true' }}
runs-on: ubuntu-latest
steps:
- run: echo "No Python package exists yet. release-please remains scaffold-only."
release:
if: ${{ hashFiles('python-packages/**/pyproject.toml') != '' }}
needs: detect_python_packages
if: ${{ needs.detect_python_packages.outputs.has_python_packages == 'true' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- id: release
uses: googleapis/release-please-action@v4
uses: googleapis/release-please-action@v5
with:
config-file: .github/release-please/python-config.json
manifest-file: .github/release-please/python-manifest.json

6
.gitignore vendored
View file

@ -7,3 +7,9 @@ node_modules/
*.plaintext
.venv/
__pycache__/
dist/
.sisyphus/
.omo/
.gjc/
.agents/

View file

@ -17,6 +17,11 @@ These rules are repo-specific and apply to everything under this directory.
- For release or packaging changes, run `npm run ci`.
- Keep release docs, workflow files, and package metadata aligned in the same change.
## Testing anti-patterns
- **Never write tests that assert `.changeset/*.md` files exist.** Changesets are consumed (deleted) by `changeset version` during the release flow. Any test guarding changeset file presence will break CI on the version-bump commit and block the release pipeline.
- **Never write tests that pin a workspace package's `version` field** (in `package.json` or `package-lock.json`). `changeset version` bumps these on every release, so any hardcoded version assertion will fail the next release commit and block the npm publish pipeline. Stable invariants like `name`, `license`, `engines.node`, or workspace link metadata are fine to assert; the `version` is not.
## Development skill install rules
- When testing or developing skills from this repository, install or sync the current skill directories into the user's home-directory global skill locations first.
@ -24,9 +29,30 @@ These rules are repo-specific and apply to everything under this directory.
- Respect existing home-directory indirection such as symlinks when syncing `~/.agents/skills`.
- Do **not** create repo-local `.claude` or `.agents` directories for skill installation unless the user explicitly asks for a repository-local test fixture.
## Crawling/search skill authoring
- For any k-skill that crawls or searches a website, the expected output is a site-dependent recipe packaged into that skill.
- Before fixing that recipe, use an insane-search-style, site-agnostic discovery pass: identify public entry points, observe browser-visible data flows when needed, prefer stable public/data endpoints over brittle screen scraping, and classify login/CAPTCHA/empty/blocked responses as explicit failure modes.
- Record the discovered site-dependent access path, fallback order, inputs/outputs, and failure modes in `SKILL.md` and any helper package code. See `docs/adding-a-skill.md` for the canonical checklist.
- Do not add crawling dependencies by default; first prefer existing runtime capabilities, public endpoints, or narrow allowlisted proxy routes.
## Free API proxy policy
- The built-in `k-skill-proxy` is for **free APIs only**.
- **k-skill-proxy inclusion rule**: A skill should be served through `k-skill-proxy` **only when the upstream requires an API key** (e.g., data.go.kr, KRX, Naver Search Open API, NEIS, Data4Library). Fully public endpoints that work without any authentication (e.g., realtyprice.kr) should be called directly from the user's machine, not routed through the proxy.
- Default posture: public read-only endpoint, **no proxy auth by default**.
- Keep free-API proxy surfaces narrow, allowlisted, cache-backed, and rate-limited.
- If abuse or operational issues appear later, add stricter controls then instead of preemptively requiring auth.
## Proxy server development
- 개발 repo (`dev` 브랜치)에서 proxy 코드를 수정하고, main에 merge하면 프로덕션에 반영된다.
- 프로덕션 배포 대상은 **Google Cloud Run** (`asia-northeast1`, GCP project `k-skill-proxy`)이며, 커스텀 도메인 `k-skill-proxy.nomadamas.org`로 노출된다.
- `main` 브랜치에 merge되면 `.github/workflows/deploy-k-skill-proxy.yml`이 Workload Identity Federation으로 GCP 인증 → Artifact Registry로 image build/push → Cloud Run 재배포 → `/health` smoke test까지 자동으로 수행한다.
- 따라서 **dev에서 route를 추가/수정한 뒤 main에 merge되기 전까지는 프로덕션 proxy에 반영되지 않는다.**
- proxy 서버 코드: `packages/k-skill-proxy/src/server.js`
- 컨테이너 이미지 빌드 정의: `packages/k-skill-proxy/Dockerfile`
- proxy 서버 테스트: `packages/k-skill-proxy/test/server.test.js`
- 로컬 테스트: `node packages/k-skill-proxy/src/server.js` (환경변수는 `~/.config/k-skill/secrets.env` 등에서 직접 export해서 띄운다)
- 프로덕션 시크릿은 GCP Secret Manager에 보관되고 Cloud Run runtime에 주입된다.
- **운영 관련 모든 절차는 [`docs/deploy-k-skill-proxy.md`](docs/deploy-k-skill-proxy.md)에 정리되어 있다.** 새 maintainer 인계를 위한 1회성 GCP/WIF 셋업, GitHub repository secrets 등록, upstream API 키 회전(rotation), 자동 배포 상태/로그/이미지 태그 확인, Cloud Run 트래픽 롤백, GitHub Actions 장애 시 로컬에서 동일한 배포를 수동으로 돌리는 비상 명령까지 전부 거기서 본다. proxy 운영 관련 어떤 질문이 들어와도 먼저 그 문서를 확인한다.

22
CLAUDE.md Normal file
View file

@ -0,0 +1,22 @@
# k-skill
## Testing anti-patterns
- **Never write tests that assert `.changeset/*.md` files exist.** Changesets are consumed (deleted) by `changeset version` during the release flow. Any test guarding changeset file presence will break CI on the version-bump commit and block the release pipeline.
- **Never write tests that pin a workspace package's `version` field** (in `package.json` or `package-lock.json`). `changeset version` bumps these on every release, so any hardcoded version assertion will fail the next release commit and block the npm publish pipeline. Stable invariants like `name`, `license`, `engines.node`, or workspace link metadata are fine to assert; the `version` is not.
## Crawling/search skill authoring
- 크롤링/검색 k-skill의 목표는 최종적으로 대상 사이트에 맞는 site-dependent 접근 방법을 스킬에 패키징하는 것이다.
- 다만 방법을 고정하기 전에 `insane-search`식 site-agnostic discovery를 먼저 수행한다: 공개 입구, 브라우저에서 보이는 데이터 흐름, RSS/sitemap/정적 JSON/모바일 페이지, 차단·빈 응답·로그인벽 실패 모드를 확인한다.
- 발견한 검색 URL, 필수 입력값, 결과 해석 규칙, fallback 순서, 실패 모드는 `SKILL.md`와 helper 코드에 명확히 남긴다. 자세한 체크리스트는 `docs/adding-a-skill.md`를 따른다.
- 새 크롤링 dependency는 기본값으로 추가하지 말고 기존 기능, 공개 endpoint, 좁은 proxy route로 해결 가능한지 먼저 확인한다.
## Proxy server development
- 개발 repo: 이 디렉토리, `dev` 브랜치
- 프로덕션 배포 대상: **Google Cloud Run** (project `k-skill-proxy`, region `asia-northeast1`, custom domain `k-skill-proxy.nomadamas.org`)
- `main` 브랜치에 merge되면 `.github/workflows/deploy-k-skill-proxy.yml`이 자동으로 Cloud Run 재배포를 수행한다. 인증은 Workload Identity Federation, 이미지 빌드 정의는 `packages/k-skill-proxy/Dockerfile`, 시크릿은 GCP Secret Manager에서 주입된다. WIF/Secret Manager 셋업은 `docs/deploy-k-skill-proxy.md` 참고.
- 따라서 proxy route 변경은 **main에 merge되어야 프로덕션에 반영**된다. dev에서 코드를 바꿔도 프로덕션 proxy에는 영향 없음.
- 로컬 테스트는 `node packages/k-skill-proxy/src/server.js` 로 직접 실행하거나 `node --test packages/k-skill-proxy/test/server.test.js` 로 확인.
- **Proxy 편입 규칙**: k-skill-proxy에 route를 추가하려면 upstream이 API 키를 필요로 해야 한다. 공개 엔드포인트(키 불필요)는 skill 코드에서 직접 호출하고 프록시를 거치지 않는다.

76
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,76 @@
# 기여 가이드
외부 기여자는 이 문서를 기준으로 이슈, PR, 스킬, 패키지, 프록시 변경을 준비해 주세요. 이 레포의 세부 운영 규칙은 `AGENTS.md``CLAUDE.md`에도 있으며, 충돌할 때는 더 구체적인 최신 지침을 우선합니다.
## 소통 언어
- PR 코멘트, 이슈, 리뷰 등 모든 소통은 한국어로 진행합니다.
- 외부 문서나 로그를 인용해야 할 때는 원문을 함께 둘 수 있지만, 결정 사항과 요청 사항은 한국어로 요약해 주세요.
## 브랜치와 PR 대상
- 기능/수정 브랜치는 가능한 한 `feature/<issue-number>` 또는 `feature/#<issue-number>`처럼 추적 가능한 이름을 사용합니다.
- PR의 대상 브랜치는 반드시 `dev` 브랜치여야 합니다.
- `main` 브랜치로 PR을 만들 수 있는 사람은 `@vkehfdl1`뿐입니다. 그 외 기여자는 `main` 대상 PR을 만들지 않습니다.
- 프록시 서버 변경도 개발 레포의 `dev` 브랜치에서 작업하고, `main`에 머지된 뒤에만 프로덕션에 반영됩니다.
## 스킬 추가 또는 변경
스킬을 추가하거나 변경할 때는 관련 기능 문서와 `README.md`의 표를 포함해 코드와 문서를 함께 갱신합니다.
- 관련 기능 문서(`docs/features/<skill-name>.md`)를 추가하거나 업데이트합니다.
- `README.md`의 "어떤 걸 할 수 있나" 표에 스킬 이름, 설명, 사용자 로그인 필요 여부, 문서 링크를 업데이트합니다.
- 설치 흐름이 바뀌면 `docs/install.md`, `docs/setup.md`, `docs/security-and-secrets.md` 등 관련 문서도 함께 맞춥니다.
- 출처나 공식 표면이 바뀌면 `docs/sources.md`에 반영합니다.
- 스킬 개발/테스트 시에는 현재 스킬 디렉터리를 먼저 홈 디렉터리 전역 스킬 위치에 동기화합니다.
- Claude Code: `~/.claude/skills/<skill-name>`
- agents 호환 런타임: `~/.agents/skills/<skill-name>`
- `~/.agents/skills`가 symlink 등으로 우회되어 있으면 기존 indirection을 존중합니다.
- 사용자가 명시적으로 요청하지 않는 한 레포 내부에 `.claude` 또는 `.agents` 설치 테스트 디렉터리를 만들지 않습니다.
## npm 패키지와 릴리스
- Node 패키지는 `packages/*` 아래 npm workspaces로 관리합니다.
- npm 패키지를 수정할 때는 Changesets를 조사하고, 자동 CD가 올바르게 트리거되도록 `.changeset/*.md` 변경이 필요한지 신중히 판단합니다.
- 패키지 릴리스 목적의 버전 변경은 `package.json`만 직접 수정하지 말고 Changesets 흐름을 사용합니다.
- npm publish는 GitHub Actions가 생성하는 **Version Packages** PR이 `main`에 머지된 뒤 자동으로 수행되는 것을 전제로 합니다.
- Changeset 파일의 존재 여부를 테스트로 검증하지 않는다. Changesets는 `changeset version` 단계에서 소비되어 삭제될 수 있으므로, 그런 테스트는 버전 bump 커밋의 CI를 막습니다.
- `package.json``package-lock.json``version` 필드를 테스트에서 고정하지 않는다. Changesets 릴리스 흐름에서 매번 바뀔 수 있으므로, 테스트는 `name`, `license`, `engines.node`, workspace link metadata처럼 안정적인 invariant를 검증합니다.
- 현재 구현이 registry token 기반인 경우에도 신규 또는 재설계 흐름은 trusted publishing/OIDC를 우선합니다. 기존 token 기반 경로를 고칠 때는 현재 구현 예외와 목표 원칙을 PR 설명에 분리해 적습니다.
## Python 패키지와 PyPI
- Python 패키지는 `python-packages/*` 아래에 둡니다.
- Python 릴리스는 release-please 기반입니다.
- 실제 Python 패키지가 생기기 전까지 Python release workflow는 scaffold-only로 유지합니다.
- PyPI publish는 release-please가 구체적인 패키지 경로에 대해 `release_created=true`를 보고할 때만 실행되도록 설계합니다.
- PyPI도 가능하면 trusted publishing/OIDC를 우선합니다.
## API와 k-skill-proxy 정책
- `k-skill-proxy`는 무료 API 전용입니다.
- 신규 proxy route는 upstream이 API key를 요구하는 무료 API인 경우에만 `k-skill-proxy` 경유를 검토합니다. 기존 승인 예외를 넓히려면 근거와 운영 경계를 문서화합니다.
- 인증 없이 동작하는 공개 read-only endpoint는 기본적으로 사용자 머신에서 직접 호출하고, 불필요하게 프록시 운영 표면을 넓히지 않습니다.
- 유료 API, 사용자별 과금 API, 개인 계정 권한이 필요한 API는 `k-skill-proxy`를 타지 않도록 설계합니다.
- 기본 자세는 공개 read-only endpoint, proxy auth 없음입니다.
- 프록시 표면은 좁게 유지하고 allowlist, cache, rate limit를 적용합니다.
- 남용이나 운영 문제가 실제로 나타나면 그때 더 강한 제어를 추가합니다.
## 프록시 서버 개발과 배포
- 프록시 서버 코드: `packages/k-skill-proxy/src/server.js`
- 프록시 서버 테스트: `packages/k-skill-proxy/test/server.test.js`
- 컨테이너 이미지 정의: `packages/k-skill-proxy/Dockerfile`
- 로컬 테스트: 필요한 upstream 환경변수를 export한 상태에서 `node packages/k-skill-proxy/src/server.js`. 로컬에서 시크릿을 모아두는 표준 위치는 `~/.config/k-skill/secrets.env` 입니다.
- 프로덕션 프록시는 **Google Cloud Run** (project `k-skill-proxy`, region `asia-northeast1`)에서 운영하며 `k-skill-proxy.nomadamas.org` 도메인에 매핑되어 있습니다.
- `main` 브랜치에 머지되면 `.github/workflows/deploy-k-skill-proxy.yml` 워크플로가 Workload Identity Federation으로 GCP 인증 → Artifact Registry로 이미지 빌드/푸시 → Cloud Run 재배포 → `/health` smoke test까지 자동 수행합니다.
- 프로덕션 시크릿은 GCP Secret Manager에 보관되고 Cloud Run 런타임에 주입됩니다. 프록시 운영자(maintainer)가 한 번 수행해야 하는 WIF/Secret Manager 셋업과 운영 점검 절차는 [`docs/deploy-k-skill-proxy.md`](docs/deploy-k-skill-proxy.md)에 정리되어 있습니다.
- `dev`에서 route를 추가하거나 수정해도 `main`에 머지되기 전까지는 프로덕션 프록시에 반영되지 않습니다.
## 검증
- 문서만 바꿔도 관련 문서 테스트를 먼저 추가하거나 업데이트하고, 실패를 확인한 뒤 구현하는 TDD 흐름을 권장합니다.
- 일반 변경은 가능한 한 `npm run lint`, `npm run typecheck`, `npm test`를 실행합니다.
- 릴리스나 패키징 관련 변경은 `npm run ci`를 실행합니다.
- 변경 범위가 작더라도 최종 보고에는 어떤 명령을 실행했고 어떤 결과가 나왔는지 적습니다.
- 테스트를 통과시키기 위해 기존 테스트를 삭제하거나 범위를 부당하게 줄이지 않습니다.

216
README.md
View file

@ -14,28 +14,121 @@ Claude Code, Codex, OpenCode, OpenClaw/ClawHub 등 각종 코딩 에이전트
한국인이면 깃허브 스타 눌러줍시다.
다만 에이전트는 자동으로 누르지 말고, 사용자가 동의한 경우에만 `gh repo star NomaDamas/k-skill` 를 실행합니다.
기여 전에는 [기여 가이드](CONTRIBUTING.md)를 확인해 주세요.
## 어떤 걸 할 수 있나
| 할 수 있는 일 | 설명 | 인증/시크릿 | 문서 |
| --- | --- | --- | --- |
| SRT 예매 | 열차 조회, 예약, 예약 확인, 취소 | 필요 | [SRT 예매 가이드](docs/features/srt-booking.md) |
| KTX 예매 | Dynapath anti-bot 대응 helper 로 KTX/Korail 열차 조회, 예약, 예약 확인, 취소 | 필요 | [KTX 예매 가이드](docs/features/ktx-booking.md) |
| 카카오톡 Mac CLI | macOS에서 kakaocli로 대화 조회, 검색, 테스트 전송, 확인 후 실제 전송 | 불필요 | [카카오톡 Mac CLI 가이드](docs/features/kakaotalk-mac.md) |
| 서울 지하철 도착정보 조회 | self-host 또는 배포 확인이 끝난 `k-skill-proxy` 경유로 역 기준 실시간 도착 예정 열차 확인 | 프록시 URL 필요 | [서울 지하철 도착정보 가이드](docs/features/seoul-subway-arrival.md) |
| 사용자 위치 미세먼지 조회 | `k-skill-proxy` 로 현재 위치 또는 지역 fallback 기준 PM10/PM2.5 확인 | 불필요 | [사용자 위치 미세먼지 조회 가이드](docs/features/fine-dust-location.md) |
| 한국 법령 검색 | `korean-law-mcp` 우선 + 장애 시 `법망` fallback으로 법령/조문/판례/유권해석 조회 | 로컬 CLI/MCP면 `LAW_OC` 필요, remote endpoint/법망 fallback은 불필요 | [한국 법령 검색 가이드](docs/features/korean-law-search.md) |
| KBO 경기 결과 조회 | 날짜별 경기 일정, 결과, 팀별 필터링 | 불필요 | [KBO 결과 가이드](docs/features/kbo-results.md) |
| K리그 경기 결과 조회 | 날짜별 K리그1/K리그2 경기 결과, 팀별 필터링, 현재 순위 확인 | 불필요 | [K리그 결과 가이드](docs/features/kleague-results.md) |
| 토스증권 조회 | `tossctl` 기반 계좌 요약, 포트폴리오, 시세, 주문내역, 관심종목 조회 | 필요 | [토스증권 조회 가이드](docs/features/toss-securities.md) |
| 로또 당첨 확인 | 최신 회차, 특정 회차, 번호 대조 | 불필요 | [로또 결과 가이드](docs/features/lotto-results.md) |
| HWP 문서 처리 | `.hwp` → JSON/Markdown/HTML 변환, 이미지 추출, 배치 처리, Windows 직접 제어 선택 | 불필요 | [HWP 문서 처리 가이드](docs/features/hwp.md) |
| 근처 블루리본 맛집 | 현재 위치를 먼저 확인한 뒤 블루리본 서베이 공식 표면으로 근처 블루리본 맛집 검색 | 불필요 | [근처 블루리본 맛집 가이드](docs/features/blue-ribbon-nearby.md) |
| 근처 술집 조회 | 현재 위치(서울역/강남/사당 등)를 먼저 확인한 뒤 카카오맵 기준으로 영업 상태·메뉴·좌석·전화번호가 포함된 술집 조회 | 불필요 | [근처 술집 조회 가이드](docs/features/kakao-bar-nearby.md) |
| 우편번호 검색 | 주소 키워드로 공식 우체국 우편번호 조회 | 불필요 | [우편번호 검색 가이드](docs/features/zipcode-search.md) |
| 다이소 상품 조회 | 다이소몰 공식 매장/상품/재고 표면으로 특정 매장의 상품 재고 확인 | 불필요 | [다이소 상품 조회 가이드](docs/features/daiso-product-search.md) |
| 택배 배송조회 | CJ대한통운·우체국 공식 표면으로 배송 상태를 조회하고, carrier adapter 규칙으로 추가 택배사 확장을 준비 | 불필요 | [택배 배송조회 가이드](docs/features/delivery-tracking.md) |
| 쿠팡 상품 가격 조회 | 공식 쿠팡 URL + 브라우저 캡처 HTML 기준으로 상품 후보/가격/리뷰를 정리하고 anti-bot 차단 여부를 probe | 브라우저 세션/HTML 캡처 권장 | [쿠팡 상품 가격 조회 가이드](docs/features/coupang-product-search.md) |
"사용자 로그인" 컬럼은 **사용자 본인이 직접 로그인/시크릿을 들고 있어야 하는지** 만 표시합니다. `k-skill-proxy` 등 운영자가 관리하는 키는 사용자 입장에서는 **불필요**로 분류합니다. **선택사항**은 사용자가 운영자 키를 직접 들고 있으면 더 풍부한 경로가 켜지고, 없으면 기본 경로(보통 운영자가 관리하는 hosted fallback)로 그대로 동작하는 경우를 말합니다.
| 할 수 있는 일 | 스킬 이름 | 설명 | 사용자 로그인 | 문서 |
| --- | --- | --- | --- | --- |
| SRT 예매 | `srt-booking` | SRT 열차 조회, 예약, 예약 확인, 취소 | 필요 | [SRT 예매 가이드](docs/features/srt-booking.md) |
| KTX 예매 | `ktx-booking` | KTX/Korail 열차 조회, 호차별 좌석번호·콘센트 좌석 확인, 예약, 예약 확인, 취소 | 필요 | [KTX 예매 가이드](docs/features/ktx-booking.md) |
| 고속버스 예매 | `express-bus-booking` | KOBUS 고속버스 배차·좌석·요금 조회와 결제 직전 handoff(결제는 수동) | 불필요 | [고속버스 예매 가이드](docs/features/express-bus-booking.md) |
| 시외버스 예매 | `intercity-bus-booking` | 티머니 시외버스 배차·좌석·요금 조회와 결제 직전 handoff(결제는 수동) | 불필요 | [시외버스 예매 가이드](docs/features/intercity-bus-booking.md) |
| 자연휴양림 빈 객실 조회 | `foresttrip-vacancy` | 공식 숲나들e 자연휴양림 예약 가능 객실 조회 자동화 (예약/결제 제외) | 필요 | [자연휴양림 빈 객실 조회 가이드](docs/features/foresttrip-vacancy.md) |
| 카카오톡 Mac 아카이브 검색 | `kakaotalk-mac` | `katok`으로 macOS 카카오톡 로컬 아카이브를 동기화하고 keyword/BM25/semantic 검색 | 불필요(로컬 앱/권한 필요) | [카카오톡 Mac 아카이브 검색](docs/features/kakaotalk-mac.md) |
| 서울 지하철 도착정보 조회 | `seoul-subway-arrival` | 서울 지하철 역 기준 실시간 도착 예정 열차 확인 | 불필요 | [서울 지하철 도착정보 가이드](docs/features/seoul-subway-arrival.md) |
| 서울 실시간 혼잡도 조회 | `seoul-density` | 서울 주요 121개 핫스팟의 실시간 혼잡도 단계와 추정 인구 조회 | 불필요 | [서울 실시간 혼잡도 가이드](docs/features/seoul-density.md) |
| 서울 따릉이 실시간 대여소 조회 | `seoul-bike` | 현재 좌표 주변 또는 대여소 이름 기준 따릉이 대여 가능 자전거와 빈 거치대 조회 | 불필요 | [서울 따릉이 실시간 대여소 가이드](docs/features/seoul-bike.md) |
| 한국 대중교통 길찾기 | `korean-transit-route` | ODsay LIVE API + Kakao geocoding 기반 출발지→도착지 지하철+버스+도보 경로 및 환승 정보 조회 | 필요 | [한국 대중교통 길찾기 가이드](docs/features/korean-transit-route.md) |
| 카카오맵 장소·자동차 길찾기 | `kakao-map` | Kakao Local 키워드/카테고리/좌표↔주소 변환 + Kakao Mobility 자동차 길찾기(거리·소요시간·통행료·예상 택시요금) | 불필요 | [카카오맵 가이드](docs/features/kakao-map.md) |
| 지하철 분실물 조회 | `subway-lost-property` | 지하철 역/물품명 기준 공식 LOST112 분실물 검색 조건과 유실물센터 진입점 안내 | 불필요 | [지하철 분실물 조회 가이드](docs/features/subway-lost-property.md) |
| 긱뉴스 조회 | `geeknews-search` | GeekNews 공개 RSS/Atom 피드 기반 최신 글 목록, 검색, 상세 확인 | 불필요 | [긱뉴스 조회 가이드](docs/features/geeknews-search.md) |
| 한국 날씨 조회 | `korea-weather` | 기상청 단기예보 기반 한국 날씨 조회 | 불필요 | [한국 날씨 조회 가이드](docs/features/korea-weather.md) |
| 사용자 위치 미세먼지 조회 | `fine-dust-location` | 현재 위치 또는 지역 기준 PM10/PM2.5 미세먼지 조회 | 불필요 | [사용자 위치 미세먼지 조회 가이드](docs/features/fine-dust-location.md) |
| 한강 수위 정보 조회 | `han-river-water-level` | 한강 관측소 기준 현재 수위·유량·기준수위 확인 | 불필요 | [한강 수위 정보 가이드](docs/features/han-river-water-level.md) |
| 한국 법령 검색 | `korean-law-search` | 한국 법령/조문/판례/유권해석 검색 | 불필요 | [한국 법령 검색 가이드](docs/features/korean-law-search.md) |
| 등기부등본 자동화 | `iros-registry-automation` | 인터넷등기소(IROS)에서 법인/부동산 등기부등본 장바구니, 수동 결제 후 열람·저장 흐름을 보조 | 필요(수동 로그인·결제/TouchEn) | [등기부등본 자동화 가이드](docs/features/iros-registry-automation.md) |
| 법인등기 신청 컨설팅 | `corporate-registration-consulting` | 일반 영리 주식회사 발기설립 기준으로 법인명·이사·주소 등 사용자 결정사항을 받아 표준 정관, 설립등기 첨부서류, 등록면허세·과밀억제권역 중과 체크, rhwp 기반 HWP 양식 순차 작성 흐름을 참고용으로 안내 | 불필요 | [법인등기 신청 컨설팅 가이드](docs/features/corporate-registration-consulting.md) |
| 사업자등록정보 확인 | `nts-business-registration` | 국세청 사업자등록번호 상태조회와 사업자등록정보 진위확인(공공데이터포털 API, 프록시 경유) | 불필요 | [사업자등록정보 확인 가이드](docs/features/nts-business-registration.md) |
| 사업자 실사 종합 | `biz-health-check` | 사업자등록번호를 중심으로, 상호·지역을 함께 주면 국세청 상태·국민연금·체납 명단·금융위 법인개요·부정당제재·인허가 영업상태를 교차 조회한 실사 리포트(점수·판정 없이 사실만) | 불필요 | [사업자 실사 종합 가이드](docs/features/biz-health-check.md) |
| 국민연금 가입 사업장 조회 | `national-pension-workplace` | 사업장명으로 국민연금 가입자수·당월 고지금액·월별 추이 조회(공공데이터포털 API, 프록시 경유) | 불필요 | [국민연금 가입 사업장 조회 가이드](docs/features/national-pension-workplace.md) |
| 국세 체납 명단공개 검색 | `nts-tax-delinquency` | 상호·법인명으로 국세청 고액·상습체납자 명단공개 대조(무인증 공개 검색) | 불필요 | [국세 체납 명단공개 검색 가이드](docs/features/nts-tax-delinquency.md) |
| 금융위 기업기본정보 조회 | `fsc-corporate-info` | 법인명으로 대표자·설립일·업종 등 법인 개요 조회와 사업자번호 교차검증(공공데이터포털 API, 프록시 경유) | 불필요 | [금융위 기업기본정보 조회 가이드](docs/features/fsc-corporate-info.md) |
| 부정당제재업체 조회 | `g2b-sanctioned-supplier` | 사업자번호로 나라장터 부정당제재(조회시점 유효 제재) 조회(공공데이터포털 API, 프록시 경유) | 불필요 | [부정당제재업체 조회 가이드](docs/features/g2b-sanctioned-supplier.md) |
| 인허가 영업상태 조회 | `localdata-business-status` | 상호+시군구로 동네 사업장(208업종)의 영업/휴업/폐업·업력·주소 조회(LOCALDATA 무인증) | 불필요 | [인허가 영업상태 조회 가이드](docs/features/localdata-business-status.md) |
| 창업진흥원 K-Startup 조회 | `kstartup-search` | 창업진흥원 K-Startup 통합공고 사업·지원사업 공고·창업 콘텐츠·통계보고서 조회 (공공데이터포털 15125364, 프록시 경유) | 불필요 | [창업진흥원 K-Startup 조회 가이드](docs/features/kstartup-search.md) |
| 지방선거 후보자 조회 | `local-election-candidate-search` | 중앙선거관리위원회 선거통계시스템 공개 통합검색으로 지방선거 후보자 이력·선거종류·정당·지역·득표 정보를 이름 기준으로 조회 | 불필요 | [지방선거 후보자 조회 가이드](docs/features/local-election-candidate-search.md) |
| 한국 사업자 장부 자동화 | `korean-jangbu-for` | `kimlawtech/korean-jangbu-for` 기반 카드·은행·영수증·세금계산서 입력 → 표준 거래내역·계정과목·세무사 전달 CSV·경영 리포트 생성 thin wrapper | 선택사항(CODEF BYOK 자동 수집 시 필요) | [한국 사업자 장부 자동화 가이드](docs/features/korean-jangbu-for.md) |
| 한국 개인정보처리방침·이용약관 자동 생성 | `korean-privacy-terms` | Next.js 프로젝트에 개인정보보호법·약관규제법·전자상거래법 기반 개인정보처리방침/이용약관/쿠키 배너/동의 모달을 생성하는 `kimlawtech/korean-privacy-terms` (Apache-2.0) thin wrapper | 불필요 | [한국 개인정보처리방침·이용약관 자동 생성 가이드](docs/features/korean-privacy-terms.md) |
| 한국 부동산 실거래가 조회 | `real-estate-search` | 아파트/오피스텔/빌라/단독주택 실거래가·전월세·지역코드 조회 | 불필요 | [한국 부동산 실거래가 조회 가이드](docs/features/real-estate-search.md) |
| 개별공시지가 조회 | `gongsijiga-search` | realtyprice.kr 공개 API에서 지번 단위 개별공시지가(원/㎡) 다년도 추이·전년 대비 변동률 조회 | 불필요 | [개별공시지가 조회 가이드](docs/features/gongsijiga-search.md) |
| SH 청약·주택 공고문 조회 | `sh-notice-search` | 서울주택도시개발공사(SH) 공개 공고/공지 게시판을 직접 조회해 키워드·공고 종류별 목록, 상세 본문, 첨부 미리보기 메타데이터 확인 | 불필요 | [SH 청약·주택 공고문 조회 가이드](docs/features/sh-notice-search.md) |
| LH 청약 공고문 조회 | `lh-notice-search` | 한국토지주택공사(LH) 임대/분양/주거복지(신혼희망타운)/토지/상가 공고를 지역·상태·공고유형·키워드로 조회하고 마감 여부를 KST 기준으로 표시 | 불필요 | [LH 청약 공고문 조회 가이드](docs/features/lh-notice-search.md) |
| 법원 경매 부동산 매각공고 조회 | `court-auction-notice-search` | 대법원경매정보(courtauction.go.kr) 부동산 매각공고를 매각기일·법원·기일/기간 입찰 조건으로 검색해 사건번호·용도·주소·감정평가액·최저매각가격을 펼치고, 사건번호로 직접 사건정보·물건내역·매각기일이력을 조회 | 불필요 | [법원 경매 부동산 매각공고 조회 가이드](docs/features/court-auction-notice-search.md) |
| 기부처 조회 | `donation-place-search` | 지역·관심 분야 기준 기부처 후보와 공식 페이지/1365 확인용 검색 링크 안내 (기부·결제 자동화 제외) | 불필요 | [기부처 조회 가이드](docs/features/donation-place-search.md) |
| 장학금 검색 및 조회 | `korean-scholarship-search` | 한국장학재단·전국 대학교·재단·기업 장학 공고를 검색해 금액·자격·지원구간·링크를 정리하고 KST 기준 현재 날짜 마감 상태와 조건별 필터링까지 제공 | 불필요 | [장학금 검색 및 조회 가이드](docs/features/korean-scholarship-search.md) |
| 생활쓰레기 배출정보 조회 | `household-waste-info` | 시군구 기준 생활쓰레기·음식물·재활용 배출요일·시간·장소·관리부서 확인 | 불필요 | [생활쓰레기 배출정보 조회 가이드](docs/features/household-waste-info.md) |
| 학교 급식 식단 조회 | `k-schoollunch-menu` | 교육청·학교명으로 NEIS 학교 검색·급식 식단 조회 | 불필요 | [학교 급식 식단 조회 가이드](docs/features/k-schoollunch-menu.md) |
| 도서관 도서 조회 | `library-book-search` | 도서관 정보나루 기반 도서 검색, 상세, 소장 도서관, 도서관별 소장 여부 조회 | 불필요 | [도서관 도서 조회 가이드](docs/features/library-book-search.md) |
| 의약품 안전 체크 | `mfds-drug-safety` | 식약처 e약은요·안전상비의약품 정보를 인터뷰-first 흐름으로 프록시 조회 | 불필요 | [의약품 안전 체크 가이드](docs/features/mfds-drug-safety.md) |
| 식품 안전 체크 | `mfds-food-safety` | 식약처 부적합 식품·식품안전나라 회수 정보를 인터뷰-first 흐름으로 프록시 조회 | 불필요 | [식품 안전 체크 가이드](docs/features/mfds-food-safety.md) |
| 한국 주식 정보 조회 | `korean-stock-search` | KRX 상장 종목 검색, 기본정보, 일별 시세 조회 | 불필요 | [한국 주식 정보 조회 가이드](docs/features/korean-stock-search.md) |
| 금감원 DART 전자공시 조회 | `k-dart` | 공시검색, 기업개황, 재무제표, 배당, 증자/감자, 감사의견, 주요사항보고서 등 14개 endpoint | 필요 | [금감원 DART 전자공시 조회 가이드](docs/features/k-dart.md) |
| 잡코리아 인재검색 | `jobkorea-talent-search` | 잡코리아 기업회원 로그인 세션에서 마스킹 후보 정보를 읽고 유료 열람 전 shortlist 작성 | 필요 | [잡코리아 인재검색 가이드](docs/features/jobkorea-talent-search.md) |
| 사람인 인재풀 검색 | `saramin-talent-search` | 사람인 기업회원 인재풀에서 마스킹 후보 정보를 읽고 유료 열람 전 shortlist 작성 | 필요 | [사람인 인재풀 검색 가이드](docs/features/saramin-talent-search.md) |
| 대신증권 리포트 조회 | `daishin-report-search` | GitHub Pages에 공개된 대신증권 리포트 HTML 미러에서 최신 리포트 목록, 원문, 설명 페이지, Rating/Target 표를 조회 | 불필요 | [대신증권 리포트 조회 가이드](docs/features/daishin-report-search.md) |
| 국가데이터처 KOSIS 통계 조회 | `kosis-stats` | 국가데이터처가 운영하는 KOSIS(국가통계포털) Open API로 통계표 검색·메타·데이터·대용량 자료 조회 (조회 전용) | 일반 조회 불필요 (`bigdata`/`--direct` 필요) | [국가데이터처 KOSIS 통계 조회 가이드](docs/features/kosis-stats.md) |
| 조선왕조실록 검색 | `joseon-sillok-search` | 조선왕조실록 키워드 검색과 왕별/연도별 필터, 기사 발췌 조회 | 불필요 | [조선왕조실록 검색 가이드](docs/features/joseon-sillok-search.md) |
| 한국 특허 정보 검색 | `korean-patent-search` | 한국 특허/실용신안 키워드 검색 및 출원번호 상세 조회 | 필요 | [한국 특허 정보 검색 가이드](docs/features/korean-patent-search.md) |
| 근처 가장 싼 주유소 찾기 | `cheap-gas-nearby` | 현재 위치 기준 근처 최저가 주유소 조회 | 불필요 | [근처 가장 싼 주유소 찾기 가이드](docs/features/cheap-gas-nearby.md) |
| 근처 공중화장실 찾기 | `public-restroom-nearby` | 현재 위치 기준 근처 공중화장실/개방화장실 조회 | 불필요 | [근처 공중화장실 찾기 가이드](docs/features/public-restroom-nearby.md) |
| 근처 공영주차장 찾기 | `parking-lot-search` | 현재 위치 기준 근처 공영주차장 위치·요금·운영시간 조회 | 불필요 | [근처 공영주차장 찾기 가이드](docs/features/parking-lot-search.md) |
| 근처 응급실 병상 상태 확인 | `emergency-room-beds` | 현재 위치 기준 가까운 응급실 운영·입원실/병상 운영 플래그와 갱신시각 조회 (정확한 잔여 병상 수/가동률은 공개 E-Gen nearby 목록에 없음) | 불필요 | [근처 응급실 병상 상태 확인 가이드](docs/features/emergency-room-beds.md) |
| 한국 마라톤 일정 조회 | `korean-marathon-schedule` | 고러닝 공개 페이지와 대한철인3종협회 일정에서 마라톤·철인3종 대회 일정, 장소, 신청 마감일, 종목 조회 | 불필요 | [한국 마라톤 일정 조회 가이드](docs/features/korean-marathon-schedule.md) |
| KBO 경기 결과 조회 | `kbo-results` | 날짜별 KBO 경기 일정, 결과, 팀별 필터링 | 불필요 | [KBO 결과 가이드](docs/features/kbo-results.md) |
| KBL 경기 결과 조회 | `kbl-results` | 날짜별 KBL 경기 일정, 결과, 팀별 필터링, 현재 순위 확인 | 불필요 | [KBL 경기 결과 가이드](docs/features/kbl-results.md) |
| K리그 경기 결과 조회 | `kleague-results` | 날짜별 K리그1/K리그2 경기 결과, 팀별 필터링, 현재 순위 확인 | 불필요 | [K리그 결과 가이드](docs/features/kleague-results.md) |
| LCK 경기 분석 | `lck-analytics` | LCK 경기 결과, 현재 순위, live turning point, 밴픽, 패치 메타, 팀 파워 레이팅 | 불필요 | [LCK 경기 분석 가이드](docs/features/lck-analytics.md) |
| 토스증권 조회 | `toss-securities` | 토스증권 공식 Open API(OAuth2) 우선, tossctl fallback으로 계좌·보유주식·시세·주문조회 등 조회 전용 | 필요 | [토스증권 조회 가이드](docs/features/toss-securities.md) |
| 하이패스 영수증 발급 | `hipass-receipt` | 하이패스 사용내역 조회 및 영수증 출력 payload 준비 | 필요 | [하이패스 영수증 발급 가이드](docs/features/hipass-receipt.md) |
| 캐치테이블 예약 스나이핑 | `catchtable-sniper` | 로그인된 캐치테이블 Chrome 세션으로 빈자리 감시, 오픈런, 자동 예약 시도 | 필요 | [캐치테이블 예약 스나이핑 가이드](docs/features/catchtable-sniper.md) |
| 공연 일정·잔여석 조회 | `ticket-availability` | YES24·인터파크 공연의 회차별 일정과 등급별 잔여석 수를 단일 HTTP 호출로 조회 (조회 전용, 예매·결제 없음) | 불필요 | [공연 일정·잔여석 조회 가이드](docs/features/ticket-availability.md) |
| 로또 당첨 확인 | `lotto-results` | 로또 최신 회차, 특정 회차, 번호 대조 | 불필요 | [로또 결과 가이드](docs/features/lotto-results.md) |
| HWP 문서 조회/변환 | `hwp` | `.hwp/.hwpx` → Markdown/JSON 변환, 문서 비교, 양식 필드 추출, Markdown→HWPX 역변환 (kordoc 기반 read-only) | 불필요 | [HWP 문서 처리 가이드](docs/features/hwp.md) |
| HWP 문서 편집 | `rhwp-edit` | `.hwp` 본문 텍스트 삽입/삭제, 표 생성, 셀 수정, replace-all (`k-skill-rhwp` CLI + `@rhwp/core` WASM, HWP 5.x round-trip) | 불필요 | [HWP 문서 편집 가이드](docs/features/rhwp-edit.md) |
| HWP 레이아웃·IR 디버깅 | `rhwp-advanced` | 업스트림 `rhwp` Rust CLI(`export-svg --debug-overlay`, `dump`, `dump-pages`, `ir-diff`, `thumbnail`, `convert`)로 HWP 레이아웃 진단·IR 덤프·버전 비교·썸네일 추출·배포용 문서 잠금 해제 | 불필요 | [HWP 레이아웃·IR 디버깅 가이드](docs/features/rhwp-advanced.md) |
| 근처 술집 조회 | `kakao-bar-nearby` | 현재 위치 기준 영업 상태·메뉴·좌석·전화번호가 포함된 근처 술집 조회 | 불필요 | [근처 술집 조회 가이드](docs/features/kakao-bar-nearby.md) |
| 우편번호 검색 | `zipcode-search` | 주소 키워드로 우편번호 + 공식 영문주소 조회 | 불필요 | [우편번호 검색 가이드](docs/features/zipcode-search.md) |
| 다이소 상품 조회 | `daiso-product-search` | 다이소 매장별 상품 픽업 가능 여부 확인 (정확한 매장별 재고 수량은 다이소몰 보안 정책으로 2026-05-05 부터 차단됨) | 불필요 | [다이소 상품 조회 가이드](docs/features/daiso-product-search.md) |
| 강남언니 병원 조회 | `gangnamunni-clinic-search` | 강남언니 공개 검색 페이지에서 성형외과·피부과 병원 후보, 평점, 리뷰 수, 지원 언어, 공개 링크 조회 | 불필요 | [강남언니 병원 조회 가이드](docs/features/gangnamunni-clinic-search.md) |
| 마켓컬리 상품 조회 | `market-kurly-search` | 마켓컬리 상품 검색, 현재 가격, 할인 여부, 품절 여부 조회 | 불필요 | [마켓컬리 상품 조회 가이드](docs/features/market-kurly-search.md) |
| 올리브영 검색 | `olive-young-search` | 올리브영 매장·상품·재고 조회 | 불필요 | [올리브영 검색 가이드](docs/features/olive-young-search.md) |
| 영화관 검색 | `korean-cinema-search` | CGV·메가박스·롯데시네마 영화관, 상영작, 시간표, 잔여석 조회 | 불필요 | [영화관 검색 가이드](docs/features/korean-cinema-search.md) |
| 올라포케 역삼 포케 | `hola-poke-yeoksam` | 올라포케 역삼점 메뉴, 매장 정보, 이벤트 참여 흐름 안내 | 불필요 | [올라포케 역삼 포케 가이드](docs/features/hola-poke-yeoksam.md) |
| 마이리얼트립 MCP 검색 | `myrealtrip-search` | 공식 MCP 서버로 항공권, 숙소, 투어·티켓·액티비티 검색과 상세·옵션 확인 | 불필요 | [마이리얼트립 MCP 검색 가이드](docs/features/myrealtrip-search.md) |
| 항공권 가격 조회 | `flight-ticket-search` | `fast-flights` 기반 Google Flights 공개 검색으로 항공권 후보, 예약 검색 링크, 날짜/월/연도별 최저가·평균가 비교 (조회 전용, 예매·결제 없음) | 불필요 | [항공권 가격 조회 가이드](docs/features/flight-ticket-search.md) |
| 택배 배송조회 | `delivery-tracking` | CJ대한통운·우체국 송장 번호로 배송 상태 조회 | 불필요 | [택배 배송조회 가이드](docs/features/delivery-tracking.md) |
| 쿠팡 상품 검색 | `coupang-product-search` | 쿠팡 상품 검색, 로켓배송 필터, 가격대 검색, 비교, 베스트, 골드박스 특가 조회 | 선택사항 (운영 키 있으면 로컬 HMAC 경로, 없으면 hosted fallback) | [쿠팡 상품 검색 가이드](docs/features/coupang-product-search.md) |
| 오늘의집 오늘의딜 조회 | `ohou-today-deal` | 오늘의집 공개 오늘의딜 특가 상품의 할인율·가격·리뷰·링크 조회 | 불필요 | [오늘의집 오늘의딜 조회 가이드](docs/features/ohou-today-deal.md) |
| 번개장터 검색 | `bunjang-search` | 번개장터 검색, 상세조회, 선택적 찜/채팅, AI TOON export | 불필요 | [번개장터 검색 가이드](docs/features/bunjang-search.md) |
| 당근 중고거래 검색 | `daangn-used-goods-search` | 당근 중고거래 공개 웹 데이터 표면으로 키워드·지역 기반 매물 검색과 상세 조회 | 불필요 | [당근 중고거래 검색 가이드](docs/features/daangn-used-goods-search.md) |
| 당근부동산 검색 | `daangn-realty-search` | 당근부동산 공개 웹 데이터 표면으로 지역 기반 부동산 매물 검색과 상세 확인 | 불필요 | [당근부동산 검색 가이드](docs/features/daangn-realty-search.md) |
| 당근알바 검색 | `daangn-jobs-search` | 당근알바 공개 웹 데이터 표면으로 키워드·지역 기반 알바 공고 검색과 상세 조회 | 불필요 | [당근알바 검색 가이드](docs/features/daangn-jobs-search.md) |
| 당근중고차 검색 | `daangn-cars-search` | 당근중고차 공개 웹 데이터 표면으로 지역·가격 조건 기반 차량 검색과 상세 조회 | 불필요 | [당근중고차 검색 가이드](docs/features/daangn-cars-search.md) |
| 중고차 가격 조회 | `used-car-price-search` | 중고차 인수가/월 렌트료 비교 조회 | 불필요 | [중고차 가격 조회 가이드](docs/features/used-car-price-search.md) |
| 한국어 맞춤법 검사 | `korean-spell-check` | 한국어 텍스트 맞춤법/문법 검사 및 교정안 정리 | 불필요 | [한국어 맞춤법 검사 가이드](docs/features/korean-spell-check.md) |
| 네이버 블로그 리서치 | `naver-blog-research` | 네이버 블로그 검색, 원문 읽기, 이미지 다운로드, 한국어 콘텐츠 교차 검증 | 불필요 | [네이버 블로그 리서치 가이드](docs/features/naver-blog-research.md) |
| 네이버 쇼핑 가격비교 | `naver-shopping-search` | 네이버 검색 Open API 우선, 공개 BFF JSON fallback으로 상품 후보·현재 노출가·판매처 링크 비교 | 불필요 | [네이버 쇼핑 가격비교 가이드](docs/features/naver-shopping-search.md) |
| 다나와 최저가 비교 | `danawa-price-search` | 다나와 공개 검색/가격비교 표면으로 상품 후보·쇼핑몰별 가격·배송비 포함 실구매가·카드 할인가·무이자 할부 비교 | 불필요 | [다나와 최저가 비교 가이드](docs/features/danawa-price-search.md) |
| 네이버 뉴스 검색 | `naver-news-search` | 네이버 검색 Open API 뉴스 검색으로 기사 제목·요약·발행시각·원문/네이버 링크를 정리 | 불필요 | [네이버 뉴스 검색 가이드](docs/features/naver-news-search.md) |
| 한국어 글자 수 세기 | `korean-character-count` | 한국어 텍스트의 글자 수·줄 수·UTF-8/NEIS byte 수를 결정론적으로 계산 | 불필요 | [한국어 글자 수 세기 가이드](docs/features/korean-character-count.md) |
| 한국어 유행어 글쓰기 | `korean-slang-writing` | 나무위키 유행어 기반 큐레이션 시드로 한국 유행어 후보 조회, 무드/문맥/safety 필터 및 나무위키 best-effort 요약으로 한국어 글을 유행어 느낌으로 작성 | 불필요 | [한국어 유행어 글쓰기 가이드](docs/features/korean-slang-writing.md) |
| 한국어 AI 윤문 | `korean-humanizer` | AI가 쓴 티 나는 한국어 글을 번역체·AI 상투어·과장된 의의·줄표/이모지 등 흔적을 심각도(S1/S2/S3)로 분류해 의미는 보존하며 사람 글로 윤문, 목표 글자수도 맞춤 | 불필요 | [한국어 AI 윤문 가이드](docs/features/korean-humanizer.md) |
| 한국 중세 국어풍 변환 | `korean-middle-korean` | 한국어 입력문을 중세국어풍 조사·어미·Hanja 힌트·성조점이 섞인 창작용 문체로 결정론적 변환 | 불필요 | [한국 중세 국어풍 변환 가이드](docs/features/korean-middle-korean.md) |
| K-스킬 클리너 | `k-skill-cleaner` | 인터뷰와 코딩 에이전트별 트리거 횟수 통계를 합쳐 불필요한 K-스킬 삭제 후보를 추천 | 불필요 | [K-스킬 클리너 가이드](docs/features/k-skill-cleaner.md) |
## Claude Code 플러그인으로 설치
[Claude Code](https://claude.com/claude-code)에서는 마켓플레이스로 전체 스킬을 한 번에 설치할 수 있습니다.
```
/plugin marketplace add NomaDamas/k-skill
/plugin install k-skill@k-skill
```
설치하면 스킬이 `/k-skill:<스킬 이름>` 네임스페이스로 호출됩니다 (예: `/k-skill:lotto-results`). 개별 디렉토리를 직접 복사하는 수동 설치나 다른 에이전트 설치는 [설치 방법](docs/install.md)을 참고하세요.
## 처음 시작하는 순서
@ -50,6 +143,8 @@ Claude Code, Codex, OpenCode, OpenClaw/ClawHub 등 각종 코딩 에이전트
| 문서 | 설명 |
| --- | --- |
| [설치 방법](docs/install.md) | 패키지 설치, 선택 설치, 로컬 테스트 방법 |
| [기여 가이드](CONTRIBUTING.md) | 외부 기여자를 위한 소통, PR 대상 브랜치, 스킬 문서, Changesets, 프록시 정책 |
| [Manus.ai 에서 가져오기](docs/install-manus.md) | Manus.ai 에서 개별 스킬 폴더 URL 가져오기 또는 `npm run build:manus-bundle` 로 빌드한 `.skill` 파일을 드래그-드롭으로 업로드하는 방법 |
| [공통 설정 가이드](docs/setup.md) | credential resolution order, 기본 secrets 파일 준비 |
| [보안/시크릿 정책](docs/security-and-secrets.md) | 인증 정보 저장 원칙, 금지 패턴, 표준 환경변수 이름 |
| [k-skill 프록시 서버 가이드](docs/features/k-skill-proxy.md) | 무료 API를 프록시 서버로 바로 호출하는 방법 |
@ -61,21 +156,96 @@ Claude Code, Codex, OpenCode, OpenClaw/ClawHub 등 각종 코딩 에이전트
- [SRT 예매](docs/features/srt-booking.md)
- [KTX 예매](docs/features/ktx-booking.md)
- [카카오톡 Mac CLI](docs/features/kakaotalk-mac.md)
- [고속버스 예매](docs/features/express-bus-booking.md)
- [시외버스 예매](docs/features/intercity-bus-booking.md)
- [자연휴양림 빈 객실 조회](docs/features/foresttrip-vacancy.md)
- [카카오톡 Mac 아카이브 검색](docs/features/kakaotalk-mac.md)
- [서울 지하철 도착정보 조회](docs/features/seoul-subway-arrival.md)
- [서울 실시간 혼잡도 조회](docs/features/seoul-density.md)
- [한국 대중교통 길찾기 가이드](docs/features/korean-transit-route.md)
- [카카오맵 가이드](docs/features/kakao-map.md)
- [지하철 분실물 조회 가이드](docs/features/subway-lost-property.md)
- [긱뉴스 조회 가이드](docs/features/geeknews-search.md)
- [한국 날씨 조회 가이드](docs/features/korea-weather.md)
- [사용자 위치 미세먼지 조회](docs/features/fine-dust-location.md)
- [한강 수위 정보 가이드](docs/features/han-river-water-level.md)
- [한국 법령 검색 가이드](docs/features/korean-law-search.md)
- [한국 개인정보처리방침·이용약관 자동 생성 가이드](docs/features/korean-privacy-terms.md)
- [사업자등록정보 확인 가이드](docs/features/nts-business-registration.md)
- [사업자 실사 종합 가이드](docs/features/biz-health-check.md)
- [국민연금 가입 사업장 조회 가이드](docs/features/national-pension-workplace.md)
- [국세 체납 명단공개 검색 가이드](docs/features/nts-tax-delinquency.md)
- [금융위 기업기본정보 조회 가이드](docs/features/fsc-corporate-info.md)
- [부정당제재업체 조회 가이드](docs/features/g2b-sanctioned-supplier.md)
- [인허가 영업상태 조회 가이드](docs/features/localdata-business-status.md)
- [창업진흥원 K-Startup 조회 가이드](docs/features/kstartup-search.md)
- [지방선거 후보자 조회 가이드](docs/features/local-election-candidate-search.md)
- [한국 사업자 장부 자동화 가이드](docs/features/korean-jangbu-for.md)
- [한국 부동산 실거래가 조회 가이드](docs/features/real-estate-search.md)
- [개별공시지가 조회 가이드](docs/features/gongsijiga-search.md)
- [LH 청약 공고문 조회 가이드](docs/features/lh-notice-search.md)
- [SH 청약·주택 공고문 조회 가이드](docs/features/sh-notice-search.md)
- [법원 경매 부동산 매각공고 조회 가이드](docs/features/court-auction-notice-search.md)
- [장학금 검색 및 조회 가이드](docs/features/korean-scholarship-search.md)
- [생활쓰레기 배출정보 조회 가이드](docs/features/household-waste-info.md)
- [학교 급식 식단 조회 가이드](docs/features/k-schoollunch-menu.md)
- [도서관 도서 조회 가이드](docs/features/library-book-search.md)
- [기부처 조회 가이드](docs/features/donation-place-search.md)
- [의약품 안전 체크 가이드](docs/features/mfds-drug-safety.md)
- [식품 안전 체크 가이드](docs/features/mfds-food-safety.md)
- [한국 주식 정보 조회 가이드](docs/features/korean-stock-search.md)
- [국가데이터처 KOSIS 통계 조회 가이드](docs/features/kosis-stats.md)
- [조선왕조실록 검색 가이드](docs/features/joseon-sillok-search.md)
- [한국 특허 정보 검색 가이드](docs/features/korean-patent-search.md)
- [근처 가장 싼 주유소 찾기 가이드](docs/features/cheap-gas-nearby.md)
- [근처 공중화장실 찾기 가이드](docs/features/public-restroom-nearby.md)
- [근처 공영주차장 찾기 가이드](docs/features/parking-lot-search.md)
- [근처 응급실 병상 상태 확인 가이드](docs/features/emergency-room-beds.md)
- [한국 마라톤 일정 조회 가이드](docs/features/korean-marathon-schedule.md)
- [KBO 경기 결과 조회](docs/features/kbo-results.md)
- [KBL 경기 결과 가이드](docs/features/kbl-results.md)
- [K리그 경기 결과 조회](docs/features/kleague-results.md)
- [LCK 경기 분석 가이드](docs/features/lck-analytics.md)
- [토스증권 조회 가이드](docs/features/toss-securities.md)
- [대신증권 리포트 조회 가이드](docs/features/daishin-report-search.md)
- [하이패스 영수증 발급 가이드](docs/features/hipass-receipt.md)
- [캐치테이블 예약 스나이핑 가이드](docs/features/catchtable-sniper.md)
- [공연 일정·잔여석 조회 가이드](docs/features/ticket-availability.md)
- [로또 당첨 확인](docs/features/lotto-results.md)
- [HWP 문서 처리](docs/features/hwp.md)
- [근처 블루리본 맛집 가이드](docs/features/blue-ribbon-nearby.md)
- [등기부등본 자동화 가이드](docs/features/iros-registry-automation.md)
- [법인등기 신청 컨설팅](docs/features/corporate-registration-consulting.md)
- [HWP 문서 조회/변환](docs/features/hwp.md)
- [HWP 문서 편집](docs/features/rhwp-edit.md)
- [HWP 레이아웃·IR 디버깅](docs/features/rhwp-advanced.md)
- [근처 술집 조회 가이드](docs/features/kakao-bar-nearby.md)
- [우편번호 검색](docs/features/zipcode-search.md)
- [다이소 상품 조회](docs/features/daiso-product-search.md)
- [강남언니 병원 조회 가이드](docs/features/gangnamunni-clinic-search.md)
- [마켓컬리 상품 조회 가이드](docs/features/market-kurly-search.md)
- [올리브영 검색 가이드](docs/features/olive-young-search.md)
- [영화관 검색 가이드](docs/features/korean-cinema-search.md)
- [올라포케 역삼 포케 가이드](docs/features/hola-poke-yeoksam.md)
- [마이리얼트립 MCP 검색 가이드](docs/features/myrealtrip-search.md)
- [항공권 가격 조회 가이드](docs/features/flight-ticket-search.md)
- [택배 배송조회](docs/features/delivery-tracking.md)
- [쿠팡 상품 가격 조회](docs/features/coupang-product-search.md)
- [쿠팡 상품 검색](docs/features/coupang-product-search.md)
- [오늘의집 오늘의딜 조회](docs/features/ohou-today-deal.md)
- [번개장터 검색 가이드](docs/features/bunjang-search.md)
- [당근 중고거래 검색 가이드](docs/features/daangn-used-goods-search.md)
- [당근부동산 검색 가이드](docs/features/daangn-realty-search.md)
- [당근알바 검색 가이드](docs/features/daangn-jobs-search.md)
- [당근중고차 검색 가이드](docs/features/daangn-cars-search.md)
- [중고차 가격 조회 가이드](docs/features/used-car-price-search.md)
- [한국어 맞춤법 검사 가이드](docs/features/korean-spell-check.md)
- [네이버 블로그 리서치 가이드](docs/features/naver-blog-research.md)
- [네이버 쇼핑 가격비교 가이드](docs/features/naver-shopping-search.md)
- [다나와 최저가 비교 가이드](docs/features/danawa-price-search.md)
- [네이버 뉴스 검색 가이드](docs/features/naver-news-search.md)
- [한국어 글자 수 세기 가이드](docs/features/korean-character-count.md)
- [한국어 유행어 글쓰기 가이드](docs/features/korean-slang-writing.md)
- [한국어 AI 윤문 가이드](docs/features/korean-humanizer.md)
- [한국 중세 국어풍 변환 가이드](docs/features/korean-middle-korean.md)
- [K-스킬 클리너 가이드](docs/features/k-skill-cleaner.md)
- [릴리스/배포 가이드](docs/releasing.md)
설치 기본 흐름은 "전체 스킬 설치 → `k-skill-setup` 실행 → 개별 기능 사용" 입니다.

79
biz-health-check/SKILL.md Normal file
View file

@ -0,0 +1,79 @@
---
name: biz-health-check
description: 사업자등록번호 하나로 "이 사업자, 실제 문제 없나"를 확인한다 — 국세청 사업자등록 상태·국민연금 가입 사업장·국세 체납 명단·금융위 법인개요·조달청 부정당제재·지방행정 인허가 영업상태를 무료 공공 데이터로 교차 조회해 사실만 병렬하는 실사 리포트(점수·등급·위험 판정 없음).
license: MIT
metadata:
category: business
locale: ko-KR
phase: v1
---
# 사업자 실사 복합 조회 (biz-health-check)
## What this skill does
사업자등록번호(+상호/지역)를 입력하면 무료 공공 데이터 6종을 한 번에 교차 조회해 실사 리포트 한 장을 만든다. 같은 레포의 단품 스킬 helper를 그대로 재사용한다(단일 진실원천).
| 섹션 | 데이터 | 단품 스킬 | 경로 |
|---|---|---|---|
| 국세청 상태 | 계속/휴업/폐업·과세유형 | `nts-business-registration` | proxy |
| 국민연금 | 가입자수·당월 고지금액·월별 | `national-pension-workplace` | proxy |
| 체납 명단 | 고액·상습체납자 명단공개 대조 | `nts-tax-delinquency` | 직접(무인증) |
| 금융위 | 대표자·설립일·업종 법인개요 | `fsc-corporate-info` | proxy |
| 부정당제재 | 조회시점 유효 제재 | `g2b-sanctioned-supplier` | proxy |
| 인허가 영업상태 | 동네 사업장(208업종) 영업/폐업·업력 | `localdata-business-status` | 직접(무인증) |
공시 유무는 기존 `k-dart` 스킬을 함께 쓰면 된다.
## Design principles
- **점수·등급·"위험" 같은 해석 라벨을 산출하지 않는다.** 각 항목의 사실 + 출처 + 조회시각만 병렬한다. 판단은 사용자 몫이다.
- 한 항목 조회가 실패해도 전체를 막지 않고 그 항목만 정직하게 강등한다(`unavailable` + 사유).
- 단품 helper를 찾지 못하면(개별 설치 등) 해당 섹션만 건너뛰고 나머지를 진행한다.
## When to use
- "이 사업자(거래처/의뢰인) 실제 문제 없는지 한 번에 확인해줘"
- "○○○-○○-○○○○○ 살아있는 회사야? 직원은 좀 있고, 체납·입찰 제재 이력은 없어?"
## Prerequisites
- 인터넷 연결, `python3`
- 같은 레포의 단품 스킬 6종(이 복합이 helper를 재사용)
- proxy 섹션을 켜려면 hosted/self-host `k-skill-proxy` 접근 가능
## Credential requirements
- 사용자 측 필수 시크릿 없음.
- proxy 섹션(국세청 상태·국민연금·금융위·부정당)은 운영 서버의 `DATA_GO_KR_API_KEY`로 동작한다. 활용신청 항목은 각 단품 스킬 문서를 따른다.
- 무인증 섹션(체납·인허가)은 키 없이 사용자 머신에서 직접 동작한다.
## Inputs
- `b_no`: 사업자등록번호 10자리(하이픈 허용) — 상태조회·부정당제재에 필요
- `--name`: 상호·법인명 — 국민연금·금융위·체납·인허가에 필요
- `--region`: 시군구 — 인허가(동네 사업장) 조회에 필요 (예: `제주제주시`)
- `--industry`: 인허가 업종(여러 번 지정 가능). 생략 시 음식점·카페·숙박
## CLI examples
```bash
python3 biz-health-check/scripts/biz_health_check.py 124-81-00998 --name "삼성전자"
# 동네 사업장까지 포함
python3 biz-health-check/scripts/biz_health_check.py --name "호텔샬롬" --region 제주제주시 --industry 숙박업
```
## Output
- `sections`: 6개 섹션 각각의 `data`(단품 응답 원문) 또는 `status: unavailable` + `note`
- 입력에 따라 일부 섹션은 생략된다(예: `--name` 없으면 국민연금/금융위/체납 생략).
## Failure modes
- 섹션별 강등은 리포트에 그대로 남는다(전체 실패가 아니다).
- proxy 섹션이 `503/502`면 운영 서버 키·활용신청 문제 — 각 단품 스킬 문서 참고.
## Official surfaces
- 각 단품 스킬 문서(`docs/features/<skill>.md`)의 공식 출처를 따른다.

View file

@ -0,0 +1,161 @@
"""Business due-diligence composite — runs the sibling k-skill providers at once.
사업자등록번호(+상호/지역) 하나로 "이 사업자, 실제 문제 없나" 무료 공공 데이터로
교차 조회해 실사 리포트 장을 만든다. 점수·등급·"위험" 라벨을 만들지 않고,
항목의 사실 + 출처 + 조회시각만 병렬한다. 판단은 사용자 몫이다.
복합 스킬은 같은 레포의 단품 스킬 helper들을 그대로 재사용한다(단일 진실원천):
- nts-business-registration 상태조회 (k-skill-proxy)
- national-pension-workplace 국민연금 사업장 (k-skill-proxy)
- fsc-corporate-info 금융위 법인개요 (k-skill-proxy)
- g2b-sanctioned-supplier 부정당제재 (k-skill-proxy)
- nts-tax-delinquency 체납 명단 (무인증 직접)
- localdata-business-status 인허가 영업상태 (무인증 직접, --region 필요)
단품 helper를 찾지 못하면 해당 항목만 정직하게 강등하고 나머지는 계속 진행한다.
"""
from __future__ import annotations
import argparse
import datetime as dt
import importlib.util
import json
import pathlib
import re
import sys
from typing import Any, Callable
KST = dt.timezone(dt.timedelta(hours=9))
_REPO_ROOT = pathlib.Path(__file__).resolve().parent.parent.parent
# (섹션 키, 사람이 읽는 라벨, 단품 스킬 디렉토리, helper 파일명)
_SIBLINGS = {
"nts_status": ("국세청 사업자등록 상태", "nts-business-registration", "nts_business_registration.py"),
"national_pension": ("국민연금 가입 사업장", "national-pension-workplace", "national_pension_workplace.py"),
"fsc_corp": ("금융위 기업기본정보", "fsc-corporate-info", "fsc_corporate_info.py"),
"g2b_sanction": ("조달청 부정당제재", "g2b-sanctioned-supplier", "g2b_sanctioned_supplier.py"),
"tax_delinquency": ("국세 체납 명단공개", "nts-tax-delinquency", "nts_tax_delinquency.py"),
"localdata": ("지방행정 인허가 영업상태", "localdata-business-status", "localdata_business_status.py"),
}
def _now_iso() -> str:
return dt.datetime.now(KST).isoformat(timespec="seconds")
def _normalize_b_no(value: Any) -> str:
normalized = re.sub(r"\D", "", str(value or ""))
if not re.fullmatch(r"\d{10}", normalized):
raise ValueError("사업자등록번호는 숫자 10자리여야 합니다 (하이픈 허용).")
return normalized
def _unavailable(module_key: str, note: str) -> dict:
label, skill_dir, _ = _SIBLINGS[module_key]
return {"provider": label, "skill": skill_dir, "status": "unavailable",
"looked_up_at": _now_iso(), "data": None, "note": note}
def _load(module_key: str) -> Any | None:
"""단품 스킬 helper를 레포 레이아웃 기준 파일 경로로 로드. 없으면 None."""
_, skill_dir, filename = _SIBLINGS[module_key]
path = _REPO_ROOT / skill_dir / "scripts" / filename
if not path.exists():
return None
spec = importlib.util.spec_from_file_location(f"_bhc_{module_key}", path)
if spec is None or spec.loader is None:
return None
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def _section(module_key: str, caller: Callable[[Any], dict]) -> dict:
"""단품 helper 하나를 호출해 섹션 결과로 감싼다. 어떤 오류든 강등."""
label, skill_dir, _ = _SIBLINGS[module_key]
base = {"provider": label, "skill": skill_dir, "looked_up_at": _now_iso()}
try:
module = _load(module_key)
except Exception as err:
return {**base, "status": "unavailable", "data": None,
"note": f"단품 스킬 '{skill_dir}' helper import 실패({type(err).__name__}: {err})."}
if module is None:
return {**base, "status": "unavailable", "data": None,
"note": f"단품 스킬 '{skill_dir}' helper를 찾지 못해 건너뜀 (개별 설치 시 함께 두세요)."}
try:
data = caller(module)
status = "unavailable" if isinstance(data, dict) and (data.get("status") == "unavailable" or data.get("error")) else "ok"
return {**base, "status": status, "data": data}
except Exception as err: # 경계 계약: 한 항목 실패가 전체를 막지 않는다
return {**base, "status": "unavailable", "data": None, "note": f"조회 실패({type(err).__name__}: {err})."}
def run(b_no: str | None, name: str | None = None, region: str | None = None,
industries: list[str] | None = None, *, base_url: str | None = None) -> dict:
no = _normalize_b_no(b_no) if b_no else None
name = (name or "").strip() or None
sections: dict[str, dict] = {}
if no:
sections["nts_status"] = _section(
"nts_status", lambda m: m.query_status([no], base_url=base_url))
else:
sections["nts_status"] = _unavailable("nts_status", "사업자등록번호가 없어 상태조회 생략.")
sections["national_pension"] = _section(
"national_pension",
lambda m: m.query_workplace(name, no, base_url=base_url)) if name else \
_unavailable("national_pension", "상호(--name)가 없어 국민연금 조회 생략.")
sections["fsc_corp"] = _section(
"fsc_corp",
lambda m: m.query_corp_outline(name, no, base_url=base_url)) if name else \
_unavailable("fsc_corp", "법인명(--name)이 없어 금융위 조회 생략.")
sections["g2b_sanction"] = _section(
"g2b_sanction", lambda m: m.query_sanctions(no, base_url=base_url)) if no else \
_unavailable("g2b_sanction", "사업자등록번호가 없어 부정당제재 조회 생략.")
sections["tax_delinquency"] = _section(
"tax_delinquency", lambda m: m.lookup(name)) if name else \
_unavailable("tax_delinquency", "상호(--name)가 없어 체납 명단 조회 생략.")
if name and region:
sections["localdata"] = _section(
"localdata", lambda m: m.lookup(name, region, industries))
else:
sections["localdata"] = _unavailable("localdata", "동네 사업장 인허가 조회는 상호(--name)와 지역(--region)이 함께 필요.")
return {
"query": {"b_no": no, "name": name, "region": region, "industries": industries},
"generated_at": _now_iso(),
"disclaimer": ("무료 공공 데이터의 사실만 병렬한 실사 리포트다. 점수·등급·위험 판정은 "
"하지 않으며, 동일성·해석은 사용자가 판단한다."),
"sections": sections,
}
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="사업자 실사 복합 조회 (단품 k-skill 6종 묶음)")
parser.add_argument("b_no", nargs="?", default=None, help="사업자등록번호 10자리(하이픈 허용)")
parser.add_argument("--name", help="상호·법인명 — 국민연금/금융위/체납/인허가 조회에 필요")
parser.add_argument("--region", help="시군구 (동네 사업장 인허가 조회용 — 예: 제주제주시)")
parser.add_argument("--industry", action="append", dest="industries", help="인허가 업종(여러 번 지정 가능)")
parser.add_argument("--proxy-base-url")
return parser
def main(argv: list[str] | None = None) -> int:
args = build_parser().parse_args(argv)
try:
report = run(args.b_no, args.name, args.region, args.industries, base_url=args.proxy_base_url)
except ValueError as err:
print(json.dumps({"error": str(err)}, ensure_ascii=False, indent=2), file=sys.stderr)
return 1
print(json.dumps(report, ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())

164
bunjang-search/SKILL.md Normal file
View file

@ -0,0 +1,164 @@
---
name: bunjang-search
description: 번개장터 검색, 상세조회, 찜, 채팅, 대량 수집, AI TOON export를 bunjang-cli로 안내한다.
license: MIT
metadata:
category: marketplace
locale: ko-KR
phase: v1
---
# Bunjang Search
## What this skill does
upstream [`bunjang-cli`](https://www.npmjs.com/package/bunjang-cli) / [`pinion05/bunjangcli`](https://github.com/pinion05/bunjangcli) 를 사용해 번개장터에서 아래 흐름을 처리한다.
- 상품 검색
- 상품 상세조회
- 선택적 찜/채팅
- 다페이지 대량 수집
- AI 분석용 TOON chunk export
## Core policy
- 기본 경로는 **항상 CLI first** 다.
- 기본 명령은 `npx --yes bunjang-cli ...` 형식을 쓴다.
- `auth login` 은 headful 브라우저 + **TTY / interactive 터미널**이 필요하다.
- 로그인 전에는 검색/상세조회/대량 수집 위주로 답하고, `favorite` / `chat` / `purchase` 는 **선택적 로그인 플로우**로만 안내한다.
- 대량 수집은 `--start-page`, `--pages`, `--max-items`, `--with-detail`, `--output` 조합을 우선 쓴다.
- AI 분석용 export 는 `--ai --output <directory>``.toon` chunk 를 만든다.
- 찜/채팅은 명시적으로 요청받지 않으면 실행하지 않는다.
## When to use
- "번개장터에서 아이폰 검색해줘"
- "번장에서 이 상품 상세 봐줘"
- "여러 페이지 모아서 JSON으로 저장해줘"
- "AI 평가용으로 번개장터 결과를 chunk 로 만들어줘"
## When not to use
- 계정 로그인 없이 바로 찜/채팅을 강행해야 하는 경우
- 구매 확정/결제 자동화를 기대하는 경우
- 번개장터 외 다른 중고거래 플랫폼을 동시에 다뤄야 하는 경우
## Quick smoke test
```bash
npx --yes bunjang-cli --help
npx --yes bunjang-cli --json auth status
npx --yes bunjang-cli --json search "아이폰" --max-items 3 --sort date
npx --yes bunjang-cli --json item get 354957625
```
## Login flow
```bash
npx --yes bunjang-cli auth login
npx --yes bunjang-cli auth logout
npx --yes bunjang-cli --json auth status
```
- `auth login` 은 브라우저에서 로그인한 뒤 **터미널로 돌아와 Enter 를 눌러야** 완료된다.
- 그래서 비-TTY 실행 대신 interactive 세션에서만 진행한다.
## Search flow
```bash
npx --yes bunjang-cli search "아이폰"
npx --yes bunjang-cli search "아이폰" --price-min 500000 --price-max 1200000
npx --yes bunjang-cli search "아이폰" --sort date
npx --yes bunjang-cli --json search "아이폰" --max-items 5
```
검색 결과는 광고/매입글/악세서리 노이즈가 섞이고, search summary 의 `location` 이 noisy 하거나 `description` / `status` 가 비어 있을 수 있다. 그래서 **검색 단계는 제목/가격 중심 1차 triage** 로만 쓴다.
- 기기명/용량 키워드 일치 여부
- 가격대 범위
- 판매 링크/썸네일 중복 여부
`description`, `status`, 깔끔한 `location` 이 필요하면 **반드시 `item get` 또는 `--with-detail` 이후** 에만 판단한다.
## Detail flow
```bash
npx --yes bunjang-cli item get 354957625
npx --yes bunjang-cli --json item get 354957625
npx --yes bunjang-cli --json item list --ids 354957625,354801707
```
상세조회에서는 아래 필드를 먼저 읽는다.
- `price`
- `description`
- `location`
- `category`
- `status`
- `sellerName`
- `sellerItemCount`
- `sellerFollowerCount`
- `sellerReviewCount`
- `favoriteCount`
- `transportUsed`
## Bulk collection
```bash
npx --yes bunjang-cli search "아이폰" \
--start-page 1 \
--pages 5 \
--max-items 50 \
--sort date \
--with-detail \
--output artifacts/bunjang-iphone.json
```
검증할 때는 export 파일 생성 여부와 top-level `items[]` 안의 `summary` / `detail` / optional `error` 구조, 그리고 각 item 의 `sourcePage` 또는 `summary.raw.page` 를 같이 확인한다.
## AI export
```bash
npx --yes bunjang-cli search "아이폰" \
--start-page 1 \
--pages 5 \
--max-items 50 \
--with-detail \
--ai \
--output artifacts/bunjang-iphone-ai
```
- `--ai` 에서는 `--output`**파일이 아니라 디렉토리** 여야 한다.
- 결과는 `items-1.toon` 형태 chunk 로 저장된다.
- AI 평가용으로 여러 서브에이전트에 분산 읽기시키기 좋다.
## Optional favorite/chat flow
로그인된 interactive 세션에서만 아래 액션을 진행한다.
```bash
npx --yes bunjang-cli --json favorite list
npx --yes bunjang-cli --json favorite add 354957625
npx --yes bunjang-cli --json favorite remove 354957625
npx --yes bunjang-cli --json chat list
npx --yes bunjang-cli --json chat start 354957625 --message "안녕하세요"
npx --yes bunjang-cli --json chat send 84191651 --message "상품 상태 괜찮을까요?"
```
- 찜/채팅은 **로그인이 필요한 선택적 기능**이다.
- 검증 목적이면 `favorite list` 로 세션을 먼저 확인하고, 같은 상품에 대해 `favorite add` / `favorite remove` 를 왕복 실행한다.
- `chat start` 는 상품 페이지에서 새 대화를 열 때, `chat send` 는 기존 thread 에 메시지를 보낼 때 쓴다.
## Recommended response format
1. 검색어가 넓으면 예산/모델/지역을 먼저 좁힌다.
2. 검색 결과 상위 3~5개는 제목/가격 중심 1차 요약만 한다.
3. `description` / `status` / `location` 판단이 필요하면 `item get` 또는 `--with-detail` 로 상세를 먼저 읽는다.
4. 로그인 액션이 필요하면 "지금은 로그인 세션이 없으니 interactive TTY 에서 `auth login` 후 다시 진행" 이라고 분명히 말한다.
5. 대량 분석이면 JSON export 또는 TOON chunk 생성 경로를 제안한다.
## Done when
- 검색/상세조회/대량 수집/AI export 중 필요한 경로가 안내되었다.
- 찜/채팅은 로그인 필요성과 선택적 성격이 명확히 고지되었다.
- 자동 구매/결제는 범위 밖이라고 분명히 말했다.

284
catchtable-sniper/SKILL.md Normal file
View file

@ -0,0 +1,284 @@
---
name: catchtable-sniper
description: Monitor Catchtable for open reservation slots and attempt booking using a logged-in Chrome session.
license: MIT
metadata:
category: lifestyle
subcategory: food
locale: ko-KR
phase: v1
requires:
- Chrome MCP
- Logged-in Catchtable Chrome session
---
# catchtable-sniper
## 📋 기본 정보
- **스킬명**: catchtable-sniper
- **라이선스**: MIT
- **단계**: v1
- **카테고리**: lifestyle / food
- **로케일**: ko-KR
- **요구사항**: Chrome MCP, 캐치테이블 로그인된 Chrome 세션
---
## 🎯 주요 기능
캐치테이블에서 원하는 식당의 빈자리(취소 슬롯)를 30초 간격으로 감시하다가 발견하는 즉시 자동 예약합니다.
멀티 타겟 동시 감시, 예약 오픈런 모드, 인원 유연 매칭, Dry-run 알림 전용 모드를 지원합니다.
---
## ✅ 적합한 사용 사례
- `"온지음 5월 토요일 저녁 2인 빈자리 나오면 예약해줘"`
- `"온지음, 밍글스, 라연 중 5월 주말 2인 아무데나 먼저 뜨는 거 잡아줘"` ← 멀티 타겟
- `"라연 5월 예약 오픈이 4월 30일 오전 10시야, 그때 맞춰서 잡아줘"` ← 오픈런 모드
- `"스시야마 이번달 안에 2인 — 못 잡으면 4인 있으면 알려줘"` ← 인원 유연
- `"밍글스 빈자리 뜨면 예약은 내가 할게 알림만 줘"` ← Dry-run 모드
- `"https://app.catchtable.co.kr/ct/shop/mingles 토요일 4명 자동예약"`
---
## ❌ 부적합한 사용 사례
- 로그인 자동화 (카카오/네이버 로그인은 직접 해야 함)
- 선결제 식당의 결제 정보 자동 입력 (결제 단계는 사람이 직접)
- 캐치테이블 외 플랫폼 예약 (네이버 예약, 식신 등)
- 30초 미만 폴링 간격 (서버 부하 방지)
---
## 🔧 기술 요구사항
- **Chrome MCP** 연결 필수
- 캐치테이블(`app.catchtable.co.kr`)에 **로그인된 Chrome 세션** 필요
- 별도 API 키, 패키지 설치 불필요
---
## 🔐 인증 처리
이 스킬은 이미 Chrome에 로그인된 세션을 그대로 사용합니다.
로그인 정보를 스킬에 전달하지 않습니다.
로그인 안 된 경우:
```
"캐치테이블에 로그인되어 있지 않습니다.
Chrome에서 캐치테이블에 카카오/네이버 로그인 후 다시 실행해주세요."
```
→ 스킬 중단. 로그인 자동화 없음.
---
## 🗂️ 입력 파싱
사용자 입력에서 다음을 추출한다:
| 항목 | 예시 | 필수 여부 |
|------|------|----------|
| 식당명 또는 URL | `"온지음"` / `app.catchtable.co.kr/ct/shop/onjium` | 필수 (복수 가능) |
| 날짜 | `"5월 3일"`, `"이번 주 토요일"`, `"5월 주말 전체"` | 필수 |
| 인원 | `"2명"`, `"4인"` | 필수 |
| 시간대 | `"저녁"`, `"19시 이후"` | 선택 (없으면 전체) |
| 모드 | `"알림만"`, `"dry-run"` | 선택 (없으면 자동예약) |
| 인원 유연 | `"2인 없으면 4인도 괜찮아"` | 선택 |
| 오픈 시간 | `"4월 30일 오전 10시 오픈"` | 선택 (오픈런 모드) |
| 폴링 간격 | `"30초마다"` | 선택 (기본: 30초) |
**멀티 타겟 감지**: 식당명이 쉼표/슬래시로 구분되거나 "중 아무데나", "먼저 뜨는 거" 표현이 있으면 멀티 타겟 모드로 전환.
---
## 📊 실행 플로우
### STEP 1 — 브라우저 준비 및 로그인 확인
Chrome MCP로 캐치테이블 접속:
```
navigate: https://app.catchtable.co.kr
```
MY 탭에서 로그인 상태 확인. 미로그인 시 중단.
---
### STEP 2 — 모드 분기
```
입력 파싱 완료
├─ 오픈 시간 명시됨 → STEP 2-A (오픈런 모드)
└─ 오픈 시간 없음 → STEP 2-B (취소 스나이핑 모드)
```
#### STEP 2-A: 오픈런 모드
예약 오픈 시간까지 대기:
```
[10:00:00 오픈 예정] 현재 09:58:42 — 77초 후 오픈
[10:00:00] ✅ 오픈 시각 도달 — 즉시 예약 시도
```
오픈 시각 정각에 날짜 선택 → 슬롯 클릭 → 예약 폼 진입.
슬롯이 이미 마감이면 → 취소 스나이핑 모드(STEP 2-B)로 자동 전환.
#### STEP 2-B: 취소 스나이핑 모드 (폴링 루프)
```
while 빈자리 없음:
{폴링 간격}초 대기
페이지 새로고침 또는 날짜 재클릭
슬롯 파싱
빈자리 발견 → STEP 3
```
---
### STEP 3 — 멀티 타겟 처리
**단일 타겟**: 해당 식당 슬롯 확인.
**멀티 타겟**: 지정된 식당들을 순차 순회하며 슬롯 확인.
```
[14:23:15] 온지음 5/3 확인 중... 없음
[14:23:17] 밍글스 5/3 확인 중... 없음
[14:23:19] 라연 5/3 확인 중... 없음 (30초 후 재시도)
[14:23:49] ✅ 밍글스 5/3 19:30 빈자리 발견! — 예약 시작
```
한 곳에서 슬롯 발견 시 나머지 감시 즉시 중단 → 발견된 식당 예약 진행.
---
### STEP 4 — 인원 유연 매칭
지정 인원(예: 2인) 슬롯이 없을 경우:
```
if 인원_유연 == True:
대안_인원(예: 4인) 슬롯 확인
발견 시:
"2인 슬롯은 없지만 4인 슬롯(19:00)이 있습니다.
4인으로 예약할까요? (예/아니오)"
→ 사용자 확인 후 진행
```
---
### STEP 5 — 예약 진행 (모드 분기)
**Dry-run 모드** (`"알림만"` / `"dry-run"` 입력 시):
```
✅ 빈자리 발견! 예약은 진행하지 않습니다.
식당: 밍글스
날짜: 5월 3일(토)
시간: 19:30
인원: 2명
→ 지금 바로 예약하시겠습니까? (예/아니오)
```
→ 예약 여부는 사람이 결정.
**자동예약 모드** (기본):
빈 슬롯 버튼 즉시 클릭 → 예약 폼 진입.
폼 자동 입력:
- 인원수: 지정한 인원 선택
- 방문 목적: "식사" (기본값)
- 주의사항 동의: 전체 동의 체크
- 예약자 정보: 앱 저장 정보 자동 사용
**선결제 식당인 경우**:
```
"빈자리를 발견했습니다! 결제가 필요합니다.
결제 금액: {금액}원
지금 결제를 진행할까요? (예/아니오)"
```
→ 결제 정보 자동 입력 없음. 사용자 확인 후 결제 진행.
**무료 예약**: "예약하기" 최종 확인 버튼 클릭.
---
### STEP 6 — 완료 확인
```
🎉 예약 완료!
식당: {식당명}
날짜: {날짜}
시간: {시간}
인원: {인원}명
모드: {자동예약 / Dry-run}
예약번호: {예약번호}
캐치테이블 앱 > MY > 예약내역에서 확인 가능합니다.
```
---
## 💡 중간 상태 출력 형식
```
[14:23:15] 밍글스 5/3 저녁 슬롯 확인 중... 빈자리 없음 (30초 후 재시도)
[14:23:45] 온지음 5/3 저녁 슬롯 확인 중... 빈자리 없음
[14:24:15] ✅ 밍글스 5/3 19:30 (2인) 빈자리 발견! — 예약 시작
```
---
## ⚙️ 설정값
| 항목 | 기본값 | 범위 |
|------|--------|------|
| 폴링 간격 | 30초 | 30초 이상 |
| 최대 감시 시간 | 2시간 | — |
| 멀티 타겟 최대 수 | 5개 | — |
2시간 초과 시:
```
"2시간 동안 빈자리가 없었습니다. 계속 시도할까요? (예/아니오)"
```
---
## 🚨 에러 핸들링
| 상황 | 대응 |
|------|------|
| 식당 페이지 404 | "식당을 찾을 수 없습니다. 이름을 다시 확인해주세요." |
| 예약 오픈 전 | 오픈 일정 안내 후 오픈런 모드로 전환 제안 |
| 슬롯 클릭 후 이미 마감 | 즉시 재폴링 재개 |
| 네트워크 오류 | 10초 후 재시도, 3회 연속 실패 시 사용자 알림 |
| 멀티 타겟 중 일부 404 | 해당 식당 제외, 나머지 계속 감시 |
| 2시간 초과 | "계속 시도할까요?" 확인 후 연장 또는 종료 |
---
## ✨ 완료 기준
다음 중 하나:
- 예약 완료 화면 확인 + 예약번호 수집
- Dry-run 모드에서 빈자리 발견 및 사용자 알림 완료
- 사용자가 명시적으로 중단 요청
---
## 사용 예시
```
"온지음 5월 10일 저녁 2인 빈자리 나오면 예약해줘"
"온지음, 밍글스, 라연 5월 토요일 저녁 2인 중 아무데나 먼저 뜨는 거 잡아줘"
"라연 5월 예약이 4월 30일 오전 10시 오픈이야, 그때 맞춰 2인 잡아줘"
"스시야마 이번달 2인 — 없으면 4인도 괜찮아, dry-run으로"
"https://app.catchtable.co.kr/ct/shop/mingles 토요일 4명 자동예약"
```
---
## ⚠️ 주의사항
- Chrome에 캐치테이블 로그인 세션이 있어야 동작합니다.
- 선결제 식당의 결제 정보는 직접 입력해야 합니다.
- 폴링 간격은 최소 30초를 유지합니다 (서버 부하 방지).
- 캐치테이블 이용약관을 준수하는 범위에서 사용하세요.

117
cheap-gas-nearby/SKILL.md Normal file
View file

@ -0,0 +1,117 @@
---
name: cheap-gas-nearby
description: Use when the user asks for nearby cheapest gas stations or 근처 가장 싼 주유소. Always ask the user's current location first, then use Kakao Map anchor resolution plus official Opinet fuel-price APIs.
license: MIT
metadata:
category: transport
locale: ko-KR
phase: v1
---
# Cheap Gas Nearby
## What this skill does
유저가 알려준 현재 위치를 기준으로 **근처에서 가장 싼 주유소**를 찾아준다.
- 위치는 자동으로 추정하지 않는다.
- **반드시 먼저 현재 위치를 질문**한다.
- 가격 데이터는 한국석유공사 **Opinet 공식 API**를 우선 사용한다.
- 동네/역명/랜드마크 입력은 Kakao Map anchor 검색으로 좌표를 잡은 뒤 Opinet nearby 검색으로 연결한다.
- 기본 제품은 **휘발유(B027)** 이고, 유저가 경유라고 명시하면 **경유(D047)** 로 바꾼다.
## When to use
- "근처 가장 싼 주유소 찾아줘"
- "서울역 근처 휘발유 제일 싼 데 어디야?"
- "강남에서 경유 싼 주유소 몇 군데만 보여줘"
- "지금 여기 근처 셀프주유소 중 싼 순으로 알려줘"
## Mandatory first question
위치 정보 없이 바로 검색하지 말고 반드시 먼저 물어본다.
- 권장 질문: `현재 위치를 알려주세요. 동네/역명/랜드마크/위도·경도 중 편한 형식으로 보내주시면 근처에서 가장 싼 주유소를 찾아볼게요.`
- 제품이 불명확하면: `휘발유 기준으로 볼까요, 경유 기준으로 볼까요? 따로 말씀 없으면 휘발유로 찾을게요.`
- 위치가 애매하면: `가까운 역명이나 동 이름으로 한 번만 더 알려주세요.`
## Default path
기본적으로 `https://k-skill-proxy.nomadamas.org/v1/opinet/around``/v1/opinet/detail` 을 경유해 조회한다. 사용자 쪽에서 별도 `OPINET_API_KEY` 를 준비할 필요가 없다.
## Official Opinet surfaces
- 오픈 API 안내: `https://www.opinet.co.kr/user/custapi/openApiInfo.do`
- 반경 내 주유소: `https://www.opinet.co.kr/api/aroundAll.do`
- 주유소 상세정보(ID): `https://www.opinet.co.kr/api/detailById.do`
- 지역코드: `https://www.opinet.co.kr/api/areaCode.do`
반경 검색 핵심 파라미터:
- `x`, `y`: 기준 위치 **KATEC** 좌표
- `radius`: 반경(m, 최대 5000)
- `prodcd`: `B027`(휘발유), `D047`(경유), `B034`(고급휘발유), `C004`(등유), `K015`(LPG)
- `sort=1`: 가격순
## Location resolution surface
- Kakao Map 모바일 검색: `https://m.map.kakao.com/actions/searchView?q=<query>`
- Kakao Map 장소 패널 JSON: `https://place-api.map.kakao.com/places/panel3/<confirmId>`
위치 문자열은 Kakao Map으로 **anchor 좌표(WGS84)** 를 구한 뒤, 내부적으로 **WGS84 → KATEC** 변환을 적용해 Opinet `aroundAll.do` 에 넘긴다.
## Workflow
1. 유저에게 반드시 현재 위치를 묻는다.
2. 위치 문자열을 받으면 Kakao Map anchor 검색으로 좌표를 찾는다.
- 위도/경도를 직접 받으면 anchor 검색을 생략한다.
3. 좌표를 KATEC으로 변환한다.
4. Opinet `aroundAll.do``sort=1` 가격순으로 조회한다.
5. 상위 후보에 대해 `detailById.do` 를 호출해 도로명주소, 전화번호, 셀프 여부, 세차장, 경정비, 품질인증 여부를 보강한다.
6. 보통 3~5개만 짧게 정리한다.
## Responding
결과는 보통 아래 필드를 포함해 짧게 정리한다.
- 주유소명
- 가격(휘발유/경유 중 요청한 제품)
- 거리
- 주소
- 셀프 여부
- 세차장/경정비/품질인증 여부(있으면)
## Node.js example
```js
const { searchCheapGasStationsByLocationQuery } = require("cheap-gas-nearby");
async function main() {
const result = await searchCheapGasStationsByLocationQuery("서울역", {
productCode: "B027",
radius: 1000,
limit: 3
});
console.log(result.anchor);
console.log(result.items);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
```
## Done when
- 유저의 현재 위치를 먼저 확인했다.
- 기본 proxy 경유로 Opinet 데이터를 조회했다.
- 공식 Opinet nearby 결과를 최소 1개 이상 찾았거나, 못 찾은 이유와 다음 질문을 제시했다.
- 가격순 상위 결과를 3~5개 이내로 정리했다.
## Failure modes
- 프록시 서버가 내려가 있거나 `OPINET_API_KEY` 가 서버에 설정되지 않은 경우.
- Kakao Map anchor가 애매하면 좌표가 잘못 잡힐 수 있어 추가 위치 확인이 필요하다.
- Opinet Open API 응답이 일시적으로 비거나 갱신 중일 수 있다.

View file

@ -0,0 +1,128 @@
---
name: corporate-registration-consulting
description: 법인등기소/인터넷등기소 상업등기 신청을 처음 하는 사용자를 위해 일반 영리 주식회사 발기설립 절차, 정관·첨부서류 실제 HWP 양식 작성, 등록면허세·과밀억제권역 중과 체크, rhwp 기반 순차 검토 흐름을 참고용으로 안내한다.
license: MIT
metadata:
category: legal-documents
locale: ko-KR
---
# 법인등기 신청 컨설팅
## 가장 중요한 면책
이 스킬은 **참고용** 절차 안내와 문서 초안 자동화 도구다. **법률 자문, 세무 자문, 법무사 업무 대행이 아니다.**
등기소 보정명령·각하, 세금 산정, 정관 유효성, 업종별 인허가 여부는 사건별로 달라질 수 있으므로 제출 전에는 관할 등기소, 위택스/지방자치단체 세무부서, 세무사, 법무사, 변호사 확인을 권한다.
## When to use
- “주식회사 법인 설립등기 처음 하는데 전체 절차 알려줘”
- “법인명, 이사, 주소를 넣어 정관과 첨부서류 초안을 만들어줘”
- “등록면허세, 과밀억제권역 중과, 소프트웨어 업종 감면/중과 제외 가능성을 체크해줘”
- “등기 신청서류를 HWP로 만들어야 해서 rhwp-edit/k-skill-rhwp로 채울 수 있게 준비해줘”
## 운영 원칙
1. **사용자 결정 사항만 묻고 나머지는 에이전트가 처리한다.** 법인명, 본점 주소, 목적, 자본금, 1주의 금액, 발기인/주주, 이사/감사, 공고방법, 결산기, 주금납입 은행, 제출 방식처럼 사용자가 결정해야 하는 값만 확인한다.
2. **쉬운 말로 설명한다.** “발기인=처음 회사를 세우는 사람”, “정관=회사 기본 규칙”, “등록면허세=등기 전에 내는 지방세”처럼 어려운 말을 풀어쓴다.
3. **최신 확인이 필요한 법령·세율은 공식 출처를 다시 확인한다.** 법령은 국가법령정보센터(law.go.kr), 신청 절차는 인터넷등기소(iros.go.kr) 또는 온라인법인설립시스템(startbiz.go.kr), 지방세는 위택스(wetax.go.kr)·관할 지자체를 우선한다.
4. **정관은 최대한 저장된 표준정관을 그대로 따른다.** 불필요한 창작 문구를 만들지 말고 `templates/attachment-hwp/standard-articles-startup-moj.hwp`, `templates/attachment-hwp/articles-of-incorporation.hwp`의 구조와 표현을 우선 유지한다. 목적/업태·종목, 상호, 본점, 주식 수, 임원, 결산기처럼 사용자 회사에 맞게 바꿔야 하는 부분만 고치고, 애매한 부분은 에이전트가 법령·양식·기존 템플릿을 더 확인해 가능한 초안을 제시한다.
5. **일반 영리 주식회사 발기설립을 기본값으로 빠르게 진행한다.** 모집설립은 일반적이지 않으므로 기본 플로우에서는 제외하고, 사용자가 별도로 요청하지 않는 한 저장된 발기설립 양식과 첨부서류 양식을 채우는 데 집중한다.
6. **이미 저장해 둔 HWP 양식을 우선 활용해 완성한다.** 에이전트가 매번 공식 양식을 새로 찾게 하지 말고, 이 스킬에 저장된 `templates/official/form-65-1-stock-company-incorporation-promoter.hwp``templates/attachment-hwp/*.hwp` 사본을 레포 밖 작업 디렉터리에 복사해 채운다. 최신 양식 확인은 제출 전 대조 안내로만 두고, 실제 초안 작성의 기본 경로는 저장된 양식 채우기다. 발기설립 신청서는 `scripts/fill_official_hwp.py``templates/official/form-65-1-fill-map.json`으로 작성하고, 정관·주식발행사항동의서·주식인수증·발기인회의사록·주주명부·조사보고서·취임승낙서·이사회의사록·인감신고서·위임장 등은 `templates/attachment-hwp/`의 저장된 HWP 양식을 채운다.
7. **사용자가 서류 작성을 요청하면 기본 산출물은 실제 HWP 파일이다.** 단순 절차 설명만 요청한 경우가 아니면 Markdown만 반환하지 말고, 레포 밖 비공개 작업 디렉터리에 공식 신청서와 첨부서류 HWP 사본을 만들고, `rhwp-edit`/`k-skill-rhwp`로 그 사본의 자리표시자와 표 셀에 사용자 값을 입력한다. Markdown은 검토용 요약·체크리스트·정관 대조본으로만 보조 제공한다.
8. **HWP 편집은 기존 rhwp 계열 스킬을 적극적으로 재사용한다.** 문서 생성/편집은 [`rhwp-edit`](../rhwp-edit/SKILL.md)의 `k-skill-rhwp`, HWP/HWPX 조회·필드 추출은 [`hwp`](../hwp/SKILL.md), 레이아웃 디버깅은 [`rhwp-advanced`](../rhwp-advanced/SKILL.md)를 사용한다. 본문 자리표시자는 `replace-all`, 공식 신청서와 표 기반 첨부서류는 `set-cell-text`, 구조 확인은 `info`/`list-paragraphs`, 생성 후 확인은 `info`와 가능하면 `render` 또는 `kordoc` 변환으로 검증한다.
9. **양식의 어느 부분을 고칠지 문서마다 명시한다.** 특히 정관은 앞부분 제2조 목적/사업 내용에 실제 수행할 업태와 종목을 빠짐없이 채우고, 맨 마지막 부칙 아래 작성일자·발기인 성명·서명/기명날인란을 제출일·발기인별 실제 인감 날인 기준으로 확인한다. 각 첨부서류는 상단 법인명/본점, 중간 결의·인수·취임 내용, 하단 날짜·성명·날인란을 순서대로 확인한다.
10. **dummy 값을 지양하고 필요한 개인정보를 직접 받아 로컬 제출본에 채운다.** 빠른 초안을 위해 기본값을 제안하되, 제출용 HWP에는 `홍길동`, `서울특별시 ...`, `000000-0000000` 같은 dummy를 남기지 않는다. 사용자가 실제 이름·주소·생년월일·주식 수·인감 관련 표시 등 필요한 정보를 입력하면 에이전트가 그 값을 레포 밖 사본에 반영한다. 모르는 항목만 자리표시자로 남기고, 남은 자리표시자 목록을 사용자에게 알려준다.
11. **간인·법인인감 준비를 반드시 안내한다.** 정관처럼 여러 장으로 된 문서, 의사록/결정서, 위임장 등 원본성이 중요한 문서는 제출 전 각 장 사이에 간인이 필요한지 관할 등기소 요구를 확인하고 간인하도록 안내한다. 법인인감은 인감신고서와 함께 사용할 실제 도장을 미리 제작·준비해야 하며, 발기인/임원 개인 인감 또는 서명 요구와 구분해 설명한다.
12. **사람만 할 수 있는 최종 행위를 대신하지 않는다.** 에이전트는 초안·체크리스트·자리표시자 치환까지만 돕고, 인터넷등기소/위택스 로그인, 전자서명, 세금 납부, 등기 제출, 사용자 사칭, 최종 법률 판단, 최종 세무 판단은 수행하지 않는다.
13. **개인정보는 최소화하되 제출용 작성에는 실제 값을 받는다.** 초안 단계에서는 가능한 한 `{{OFFICER_NAME}}`, `{{OFFICER_ADDRESS}}` 같은 자리표시자를 쓰고, 실제 생성 직전에 필요한 필드만 받는다. 주민등록번호 원문, 신분증 이미지, 인감증명서 스캔본은 꼭 필요한 경우가 아니면 요구하지 않으며, 요약·로그·테스트·PR에는 이름/주소/생년월일 등 개인정보를 마스킹한다. 채워진 산출물은 로컬에만 두고 레포에 커밋하지 말라고 안내한다.
## 먼저 묻는 최소 정보
아래 값을 한 번에 표로 받는다. 모르는 항목은 기본안을 제안하고 사용자가 승인하게 한다.
| 항목 | 쉬운 설명 | 기본 제안 |
| --- | --- | --- |
| 법인명/상호 | 회사 이름. 같은 특별시·광역시·시·군 안의 같은 업종·같은 상호는 문제가 될 수 있어 인터넷등기소 상호검색을 먼저 한다. | `주식회사 {{COMPANY_NAME}}` |
| 본점 주소 | 회사 주소. 과밀억제권역/대도시 중과 판단의 핵심이다. | 임대차계약서 주소 그대로 |
| 사업 목적/업태·종목 | 등기부와 정관 제2조 앞부분에 들어가는 실제 수행 사업. 너무 넓거나 불명확하면 보정될 수 있고, 업태·종목은 사업자등록·세금 검토에도 이어진다. | 소프트웨어 개발 및 공급업, 정보통신업, 전자상거래업 등 실제 할 목적만 |
| 자본금·1주의 금액 | 초기 자금과 주식 한 장 가격. 등록면허세 계산에도 쓰인다. | 자본금 1,000,000원 / 1주 100원 또는 500원 |
| 발기인/주주 실제 정보 | 회사를 세우고 주식을 인수하는 사람. 제출용에는 성명, 주소, 생년월일/주민등록번호 필요 여부, 인수 주식 수, 날인 방식이 필요하다. | 대표 1인 설립 가능 |
| 이사/대표이사/감사 실제 정보 | 등기 이사와 대표자. 제출용 취임승낙서에는 성명, 주소, 생년월일, 취임일, 개인 인감/서명 방식이 필요하다. 소규모 회사는 감사 생략 가능 여부를 검토한다. | 1인 이사 회사 기본안 |
| 공고방법 | 회사 공고를 어디에 낼지. | 회사 홈페이지, 없으면 일간신문 |
| 결산기 | 회계연도 종료일. | 매년 12월 31일 |
| 주금납입 증빙 | 자본금이 입금됐다는 은행 잔고증명/거래내역. | 대표/발기인 명의 계좌 잔고증명 |
| 제출 방식 | 인터넷등기소 전자신청/방문/법무사 위임. | 전자신청 가능성 우선 확인 |
## 전체 절차 체크리스트
1. **상호·본점·목적 결정**: 인터넷등기소 상호검색으로 같은 관할 내 충돌 가능성을 확인한다.
2. **과밀억제권역/대도시 세금 체크**: 본점 주소가 수도권 과밀억제권역 등 중과 대상인지 먼저 본다. 대도시 법인 설립은 등록면허세가 중과될 수 있다.
3. **저장된 HWP 양식 준비**: 먼저 `templates/official/form-65-1-stock-company-incorporation-promoter.hwp``templates/attachment-hwp/`의 필요한 양식을 레포 밖 작업 디렉터리에 복사한다. 에이전트가 새 양식을 찾는 흐름이 아니라, 저장된 양식 사본을 채워 서류 초안을 완성하는 흐름을 기본으로 한다.
4. **정관 및 첨부서면 HWP 작성**: `templates/attachment-hwp/articles-of-incorporation.hwp`, `founder-meeting-minutes.hwp`, `share-subscription.hwp`, `share-issuance-consent.hwp`, `inspection-report.hwp`, `officer-acceptance-director-ceo.hwp / officer-acceptance-auditor.hwp` 등 공개 배포 HWP 양식을 레포 밖 작업 디렉터리에 복사해 자리표시자를 채운다. 각 HWP는 한 장 한 장 열람/구조 확인 후 상단·본문·하단·날인란을 순차 점검하며, replace-all은 shortcut일 뿐 최종 검토를 대체하지 않는다.
5. **발기인 결정서·주식인수·주금납입**: 발기인이 주식을 인수하고 자본금을 입금한 뒤 잔고증명서 또는 주금납입보관증명에 준하는 증빙을 준비한다.
6. **임원 취임승낙서·인감·주민등록/주소 증빙 준비**: 이사·대표이사·감사가 취임을 승낙했다는 서류와 인감 관련 서류를 준비한다. **등기이사는 개인 인감증명서 또는 본인서명사실확인서, 주민등록초본/등본 등 주소·신원 확인 발급서류가 필요하다는 점을 사용자에게 반드시 안내한다.** 법인인감 도장을 미리 준비하고 인감신고서 날인란과 위임장/의사록 날인 요구를 확인한다. 주민등록번호·신분증·인감증명 같은 민감정보는 원문을 대화나 로그에 남기지 말고 제출 직전 로컬 문서에만 반영한다.
7. **조사보고서/이사회·발기인 의사록 작성**: 현물출자 등 특수 사정이 없더라도 설립 경과를 확인하는 문서를 준비한다. 1인 회사면 결정을 단순화한다.
8. **등록면허세 신고·납부 준비**: 위택스 또는 관할 지자체 납부 화면에 넣을 금액·근거·체크리스트를 정리한다. 사용자가 실제 신고와 세금 납부를 직접 수행하고 영수필확인서를 확보한다.
9. **등기신청서 작성·첨부서류 묶기**: `templates/incorporation-document-pack.md`의 순서대로 신청서, 정관, 취임승낙서, 조사보고서, 주금납입 증빙, 인감신고서, 세금 영수증을 점검한다. 정관 등 여러 장 문서는 간인 필요 여부를 확인하고, 실제 간인·날인은 사용자가 원본에 수행하도록 안내한다.
10. **인터넷등기소/관할 등기소 제출 준비**: 전자신청이면 인증서·전자서명·스캔본 품질 체크리스트를 만들고, 방문이면 원본/사본과 도장 확인 목록을 만든다. 사용자가 실제 로그인, 전자서명, 등기 제출 절차를 직접 완료한다.
11. **보정 대응**: 등기소가 보정명령을 내리면 문구·첨부서류·세금 계산을 수정한다. 보정 사유를 쉬운 말로 풀고 다음 조치만 제시한다.
12. **등기 완료 후 후속 작업**: 법인등기사항증명서, 법인인감증명서, 사업자등록, 4대보험, 은행 법인계좌, 통신판매업/소프트웨어사업자 신고 등 후속 일정을 안내한다.
## 반드시 작성할 문서와 저장된 양식 경로
아래 표를 기준으로 법원등기소/인터넷등기소 기준 설립등기 필요 문서명과 실제 양식 경로를 대조한다. 사용자가 서류 작성을 요청하면 이 경로의 원본을 레포 밖 작업 디렉터리에 복사해 채우고, 원본 파일은 수정하지 않는다. 저장 양식이 없는 영수필확인서, 등기이사 인감증명서/본인서명사실확인서, 주민등록초본/등본은 사용자가 직접 발급해야 하는 필수 첨부서류로 체크리스트에 남긴다.
| 필요 문서 | 저장된 양식 경로 | 고쳐야 하는 부분 | 제출 전 확인 |
| --- | --- | --- | --- |
| 주식회사설립등기신청서(발기설립) | `corporate-registration-consulting/templates/official/form-65-1-stock-company-incorporation-promoter.hwp` | 상호, 본점, 등기 목적, 자본금, 발행주식, 임원, 첨부서류 목록, 신청인/대리인, 날짜 | 첨부서류 통수와 실제 묶음 일치 |
| 정관 | `corporate-registration-consulting/templates/attachment-hwp/standard-articles-startup-moj.hwp` 또는 `corporate-registration-consulting/templates/attachment-hwp/articles-of-incorporation.hwp` | 앞부분 제1조 상호, **제2조 목적/사업 내용의 업태·종목**, 본점, 주식 수/1주의 금액, 임원 규정, 결산기, 부칙 | 맨 마지막 작성일자, 발기인 성명, 서명/기명날인, 여러 장이면 간인 |
| 발기인이 정한 주식발행사항 등 증명정보(상법 제291조 사항) | `corporate-registration-consulting/templates/attachment-hwp/share-issuance-consent.hwp` | 발행주식 수, 1주의 금액, 납입기일, 발기인 동의 내용, 날짜·날인란 | 정관·신청서의 주식 수와 일치 |
| 주식인수증 | `corporate-registration-consulting/templates/attachment-hwp/share-subscription.hwp` | 인수인별 주식 수와 인수가액, 인수일, 성명·주소·날인란 | 인수 주식 수 합계가 설립 시 발행주식 수와 일치 |
| 발기인회의사록 | `corporate-registration-consulting/templates/attachment-hwp/founder-meeting-minutes.hwp` | 회의 일시·장소, 의안, 발기인, 결의 내용, 날짜, 날인란 | 1인/복수 발기인 구조와 맞는지, 여러 장이면 간인 |
| 발기인총회 기간단축 동의서 | `corporate-registration-consulting/templates/attachment-hwp/founder-meeting-period-shortening-consent.hwp` | 동의자, 기간단축 대상 절차, 날짜, 날인란 | 실제 절차상 필요한 경우에만 포함 |
| 주주명부 | `corporate-registration-consulting/templates/attachment-hwp/shareholder-register.hwp` | 주주 성명/주소, 주식 수, 1주 금액, 총액 | 정관·주식인수증·신청서의 주식 수와 일치 |
| 조사보고서 | `corporate-registration-consulting/templates/attachment-hwp/inspection-report.hwp` | 조사자, 조사 대상, 주금납입 확인, 변태설립사항 유무, 날짜, 날인란 | 근거 확인 가능한 표현으로 작성 |
| 이사/대표이사 취임승낙서 | `corporate-registration-consulting/templates/attachment-hwp/officer-acceptance-director-ceo.hwp` | 임원 성명, 주소, 생년월일, 직책, 취임일, 날짜, 서명/개인 인감 날인란 | 등기 임원 명단과 일치 |
| 감사 취임승낙서 | `corporate-registration-consulting/templates/attachment-hwp/officer-acceptance-auditor.hwp` | 감사 성명, 주소, 생년월일, 취임일, 날짜, 서명/개인 인감 날인란 | 감사 선임 시에만 포함 |
| 이사회의사록 | `corporate-registration-consulting/templates/attachment-hwp/board-minutes.hwp` | 대표이사 선임 등 결의, 일시·장소, 출석 이사, 날인란 | 이사회가 있는 구조에서만 사용 |
| 인감신고서 | `corporate-registration-consulting/templates/attachment-hwp/corporate-seal-report.hwp` | 상호, 본점, 대표자, 법인인감 날인란, 개인 인감/서명 관련 칸 | 실제 법인인감 도장 준비 및 날인 위치 |
| 위임장 | `corporate-registration-consulting/templates/attachment-hwp/power-of-attorney.hwp` | 위임인, 수임인, 위임 범위, 날짜, 날인란 | 대리 제출 시 원본 날인·간인 요구 확인 |
| 등록면허세 영수필확인서 | 저장 양식 없음. 위택스/지자체 납부 결과물 첨부 | 납부번호, 납부자, 세액, 관할 | **필수 발급/첨부.** 최종 신고·납부 결과 기준 |
| 등기신청수수료 영수필확인서 | 저장 양식 없음. 인터넷등기소/등기소 수수료 납부 결과물 첨부 | 납부번호, 납부자, 수수료, 신청 사건 | **필수 발급/첨부.** 등록면허세 영수필확인서와 별도 문서로 관리 |
| 등기이사 개인 인감증명서 또는 본인서명사실확인서 | 저장 양식 없음. 주민센터/정부24 등에서 임원 본인이 발급 | 등기이사별 성명, 발급일, 인감/서명 확인 정보 | **등기이사는 무조건 준비해야 하는 발급서류로 안내.** 주민등록번호·인감 정보는 로컬 제출본에만 보관 |
| 등기이사 주민등록초본/등본 등 주소·주민등록번호 확인 증빙 | 저장 양식 없음. 주민센터/정부24 등에서 임원 본인이 발급 | 등기이사별 성명, 주소, 주민등록번호/생년월일 확인 정보 | **등기이사는 무조건 준비해야 하는 발급서류로 안내.** 발급일·주민등록번호 표시 범위는 관할 요구 확인 |
| 주금납입/잔고증명 | 저장 양식 없음. 은행 증명서 또는 거래내역 첨부 | 계좌명의, 납입금액, 기준일 | 자본금·주식인수 금액과 일치 |
### 조건부 추가 서류 체크
아래는 일반 영리 주식회사 발기설립에서도 사실관계에 따라 추가될 수 있다. 해당 여부를 사용자에게 물어보고, 해당하면 체크리스트와 첨부서류 목록에 추가한다.
| 조건 | 추가 확인/서류 | 안내 방식 |
| --- | --- | --- |
| 명의개서대리인을 둔 경우 | 명의개서대리인 계약 또는 선임을 증명하는 정보 | 정관·주식 관련 문서와 일치 여부 확인 |
| 현물출자, 재산인수, 설립비용 등 변태설립사항이 있는 경우 | 검사인/공증인 조사보고, 감정인 감정, 관련 재판서류 등 상업등기규칙 제129조상 첨부정보 | 표준 1인/현금출자 설립보다 복잡하므로 사실관계를 먼저 정리하고 필요한 양식을 별도 작성 |
| 인허가가 필요한 업종인 경우 | 허가·인가·등록·신고 수리 증명 등 영업 가능 증빙 | 목적/업태·종목을 정관 제2조와 신청서 목적에 넣기 전 인허가 필요 여부 확인 |
| 자본금 10억 원 이상 또는 소규모회사 특례를 벗어나는 경우 | 정관 공증, 의사록 인증, 감사/이사회 구성 등 추가 요건 | 저장된 표준 양식은 유지하되 공증·인증 필요 여부를 제출 전 체크 |
## 등록면허세·중과·소프트웨어 업종 체크
- **등록면허세**: 법인 설립등기 전에 납부하는 지방세다. 지방세법 제28조의 등록면허세 세율을 기준으로 자본금, 본점 소재지, 대도시 중과 여부에 따라 달라진다. 지방교육세가 추가된다.
- **과밀억제권역/대도시 중과**: 수도권 과밀억제권역 등 대도시 안에서 법인을 설립하면 지방세법 제28조 제2항의 법인등기 중과가 문제될 수 있다. 지방세법 제13조는 취득세 맥락에서만 별도로 다루고, “서울/수도권이면 무조건”으로 단정하지 말고 본점 주소와 법령상 예외 업종을 같이 확인한다.
- **소프트웨어 업종**: 소프트웨어 개발·공급, 정보통신업은 창업중소기업 세액감면(조세특례제한법 제6조) 또는 대도시 중과 제외 업종 검토 대상이 될 수 있다. 단, 감면·제외는 업종코드, 실제 사업내용, 창업 요건, 이전/합병/개인사업 전환 여부에 따라 달라지므로 세무사/지자체 확인 전에는 확정 표현을 쓰지 않는다.
- **응답 방식**: 세금은 “예상 체크리스트”로 안내하고, 최종 금액은 위택스/관할 시군구 계산 결과를 기준으로 한다.
## 응답 끝에 항상 붙일 문구
> 이 안내와 문서 초안은 참고용이며 법률·세무 자문이 아닙니다. 실제 등기 제출 전 관할 등기소, 위택스/지자체 세무부서, 법무사·변호사·세무사 확인을 권합니다.
## 출처 확인 우선순위
- `templates/official-form-sources.md`: 저장된 공식/공개 HWP 양식 목록, 문서별 양식 경로, 공식 출처 대조 메모
- 대법원 인터넷등기소: https://www.iros.go.kr (등기신청양식, 첨부서면예시)
- 온라인법인설립시스템: https://www.startbiz.go.kr
- 위택스: https://www.wetax.go.kr
- 국가법령정보센터 지방세법/지방세법 시행령/상법/상업등기법/상업등기규칙/상업등기신청서의 양식에 관한 예규: https://www.law.go.kr
- 국세청 창업중소기업 세액감면 안내 및 조세특례제한법 제6조: https://www.nts.go.kr / https://www.law.go.kr

View file

@ -0,0 +1,106 @@
#!/usr/bin/env python3
"""Fill the bundled official Korean stock-company incorporation HWP form.
This script intentionally writes to a caller-provided output path and never
modifies the bundled official source form in place.
"""
from __future__ import annotations
import argparse
import json
import shutil
import subprocess
import sys
from pathlib import Path
SCRIPT_DIR = Path(__file__).resolve().parent
SKILL_DIR = SCRIPT_DIR.parent
OFFICIAL_DIR = SKILL_DIR / "templates" / "official"
DEFAULT_FORM = OFFICIAL_DIR / "form-65-1-stock-company-incorporation-promoter.hwp"
DEFAULT_MAP = OFFICIAL_DIR / "form-65-1-fill-map.json"
def load_json(path: Path) -> dict:
with path.open("r", encoding="utf-8") as f:
return json.load(f)
def stringify(value) -> str:
if value is None:
return ""
if isinstance(value, list):
return "\n".join(str(item) for item in value if item is not None)
if isinstance(value, (int, float)):
return f"{value:,.0f}"
return str(value)
def run_set_cell(current: Path, output: Path, spec: dict, text: str, cwd: Path) -> None:
cmd = [
"npx",
"k-skill-rhwp",
"set-cell-text",
str(current),
str(output),
"--section",
str(spec["section"]),
"--parent-paragraph",
str(spec["parentParagraph"]),
"--control",
str(spec["control"]),
"--cell",
str(spec["cell"]),
"--text",
text,
]
result = subprocess.run(cmd, cwd=cwd, text=True, capture_output=True)
if result.returncode != 0:
raise RuntimeError(result.stderr.strip() or result.stdout.strip())
def fill_form(data: dict, form_path: Path, map_path: Path, output_path: Path, cwd: Path) -> list[str]:
fill_map = load_json(map_path)
output_path.parent.mkdir(parents=True, exist_ok=True)
temp_path = output_path.with_suffix(output_path.suffix + ".tmp")
shutil.copyfile(form_path, temp_path)
written: list[str] = []
for field_name, spec in fill_map["fields"].items():
if field_name in data:
text = stringify(data[field_name])
elif "default" in spec:
text = stringify(spec["default"])
else:
continue
next_path = output_path.with_suffix(output_path.suffix + f".{len(written)}.tmp")
run_set_cell(temp_path, next_path, spec, text, cwd)
temp_path.unlink(missing_ok=True)
temp_path = next_path
written.append(field_name)
shutil.move(str(temp_path), str(output_path))
return written
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description="Fill official form 65-1 HWP with JSON data")
parser.add_argument("--input-json", required=True, type=Path, help="JSON file with form field values")
parser.add_argument("--output", required=True, type=Path, help="Output HWP path outside the repository")
parser.add_argument("--form", type=Path, default=DEFAULT_FORM, help="Official HWP source form")
parser.add_argument("--map", dest="map_path", type=Path, default=DEFAULT_MAP, help="HWP cell fill map")
parser.add_argument("--cwd", type=Path, default=Path.cwd(), help="Directory where npx k-skill-rhwp is available")
args = parser.parse_args(argv)
data = load_json(args.input_json)
written = fill_form(data, args.form, args.map_path, args.output, args.cwd)
print(json.dumps({"ok": True, "output": str(args.output), "fields_written": written}, ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except Exception as exc: # noqa: BLE001 - CLI boundary
print(f"fill_official_hwp.py: {exc}", file=sys.stderr)
raise SystemExit(1)

View file

@ -0,0 +1,124 @@
{
"downloaded_at": "2026-05-02",
"note": "Publicly downloadable HWP templates collected from the listed source pages. Where a downloaded form contained real/sample company names, personal names, addresses, resident-registration-like numbers, bank names, or concrete sample dates, those values were sanitized to placeholders after download. The bundled standard articles reference is a general Ministry of Justice stock-company articles form suitable as a non-listed/startup reference, not a listed-company standard articles form. Verify suitability, licensing, and current official requirements before submission.",
"files": [
{
"source_name": "위시라이트 블로그 공개 주식회사 설립 첨부서류 HWP",
"page_url": "https://wishright81.tistory.com/23",
"original_name": "정관.hwp",
"url": "https://blog.kakaocdn.net/dna/kPhhH/btqvUyGvYvl/AAAAAAAAAAAAAAAAAAAAAMldtkYuC46ZKQT-PZBrQ5xlYghtGK_BcJDzi8M3oORj/%EC%A0%95%EA%B4%80.hwp?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&expires=1780239599&allow_ip=&allow_referer=&signature=2tn9fayBdYzwsfZua4mPP2qzQtc%3D&attach=1&knm=tfile.hwp",
"path": "articles-of-incorporation.hwp",
"sha256": "244f5eddd3bda1b200ad28c2a4bd1295182949c502255821d418f6605dc8cb52",
"bytes": 21504
},
{
"source_name": "위시라이트 블로그 공개 주식회사 설립 첨부서류 HWP",
"page_url": "https://wishright81.tistory.com/23",
"original_name": "주식발행사항동의서.hwp",
"url": "https://blog.kakaocdn.net/dna/uWZvQ/btqvTVWemNI/AAAAAAAAAAAAAAAAAAAAAAv4YzUGetrPiGJ6nrBjJ0mHUZrBAHcP2SNRqEK06V3E/%EC%A3%BC%EC%8B%9D%EB%B0%9C%ED%96%89%EC%82%AC%ED%95%AD%EB%8F%99%EC%9D%98%EC%84%9C.hwp?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&expires=1780239599&allow_ip=&allow_referer=&signature=5URf5oei3BwYKvi1vppdNRGziqg%3D&attach=1&knm=tfile.hwp",
"path": "share-issuance-consent.hwp",
"sha256": "cacdf5e9b95f734fc708b9258f72836c6acb9dfc0f0550a52a0ca36df6b1f3eb",
"bytes": 9216
},
{
"source_name": "위시라이트 블로그 공개 주식회사 설립 첨부서류 HWP",
"page_url": "https://wishright81.tistory.com/23",
"original_name": "주식인수증.hwp",
"url": "https://blog.kakaocdn.net/dna/cFhyio/btqvUPOKPBe/AAAAAAAAAAAAAAAAAAAAAFzsXsWe7nQ6V43IEUWYpDMn7zjIQs_36miFqzw0x2s7/%EC%A3%BC%EC%8B%9D%EC%9D%B8%EC%88%98%EC%A6%9D.hwp?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&expires=1780239599&allow_ip=&allow_referer=&signature=S2yCAsjaWS%2Fs6XKOcPgq%2BTH3AQE%3D&attach=1&knm=tfile.hwp",
"path": "share-subscription.hwp",
"sha256": "4b6cd23af21211294362dea2ff5c0e3018c7a8ccc93543d28e1cdd1f8ef5dad8",
"bytes": 9216
},
{
"source_name": "위시라이트 블로그 공개 주식회사 설립 첨부서류 HWP",
"page_url": "https://wishright81.tistory.com/23",
"original_name": "발기인총회 기간단축 동의서.hwp",
"url": "https://blog.kakaocdn.net/dna/blsbID/btqvTVhH67A/AAAAAAAAAAAAAAAAAAAAAH87uTWGvHw4EvRi6tHMRf6XpLm2pCDhng0o98F5RteI/%EB%B0%9C%EA%B8%B0%EC%9D%B8%EC%B4%9D%ED%9A%8C%20%EA%B8%B0%EA%B0%84%EB%8B%A8%EC%B6%95%20%EB%8F%99%EC%9D%98%EC%84%9C.hwp?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&expires=1780239599&allow_ip=&allow_referer=&signature=WU3918ycgKAZPT3UU%2BzZygmysfI%3D&attach=1&knm=tfile.hwp",
"path": "founder-meeting-period-shortening-consent.hwp",
"sha256": "d65e2f21cfa29ee98a24770965ba5f3c9e78dbaee4e6062f0e01c19e5bf1f31b",
"bytes": 9216
},
{
"source_name": "위시라이트 블로그 공개 주식회사 설립 첨부서류 HWP",
"page_url": "https://wishright81.tistory.com/23",
"original_name": "조사보고서.hwp",
"url": "https://blog.kakaocdn.net/dna/bzY2if/btqvUQ1dSyn/AAAAAAAAAAAAAAAAAAAAAKsezsmgGy42M8uVt_hjH5SKf4ix8QS2uqM70wZF8RsY/%EC%A1%B0%EC%82%AC%EB%B3%B4%EA%B3%A0%EC%84%9C.hwp?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&expires=1780239599&allow_ip=&allow_referer=&signature=PkaZuqkfncOKJxmyaEkj%2BjULtkk%3D&attach=1&knm=tfile.hwp",
"path": "inspection-report.hwp",
"sha256": "7ad5c9bbfb416a4196352ab91715b6e7ed87d329714c81f624285a46b6c437ea",
"bytes": 10752
},
{
"source_name": "위시라이트 블로그 공개 주식회사 설립 첨부서류 HWP",
"page_url": "https://wishright81.tistory.com/23",
"original_name": "취임승낙서(감사).hwp",
"url": "https://blog.kakaocdn.net/dna/lmctu/btqvWp2GinG/AAAAAAAAAAAAAAAAAAAAAPjANE6MFT3qQ8FxVKmZtyWc3tp76wLpjBvx59m-Znka/%EC%B7%A8%EC%9E%84%EC%8A%B9%EB%82%99%EC%84%9C(%EA%B0%90%EC%82%AC).hwp?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&expires=1780239599&allow_ip=&allow_referer=&signature=KjnFd3mTy6ip1UNPWBNwd8bJD50%3D&attach=1&knm=tfile.hwp",
"path": "officer-acceptance-auditor.hwp",
"sha256": "6d9ca0907faf7ddb976b8d7e2569d87f7d5efc7c9e1550d12a7d90aed7a517c6",
"bytes": 8704
},
{
"source_name": "위시라이트 블로그 공개 주식회사 설립 첨부서류 HWP",
"page_url": "https://wishright81.tistory.com/23",
"original_name": "취임승낙서(이사.대표이사).hwp",
"url": "https://blog.kakaocdn.net/dna/bv9fD4/btqvTGrs7v5/AAAAAAAAAAAAAAAAAAAAAJxrlq6pQhlDomIn1bvd3jd_AVSEMyMmkUOriHaLw20E/%EC%B7%A8%EC%9E%84%EC%8A%B9%EB%82%99%EC%84%9C(%EC%9D%B4%EC%82%AC.%EB%8C%80%ED%91%9C%EC%9D%B4%EC%82%AC).hwp?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&expires=1780239599&allow_ip=&allow_referer=&signature=9WNbZDPbyDtjx09ZwrpNTzSGQQM%3D&attach=1&knm=tfile.hwp",
"path": "officer-acceptance-director-ceo.hwp",
"sha256": "7c5f4e7d44d69db6578c1a12640a2ef5ef0c2f3279f7faad7c65fca505878f24",
"bytes": 8704
},
{
"source_name": "위시라이트 블로그 공개 주식회사 설립 첨부서류 HWP",
"page_url": "https://wishright81.tistory.com/23",
"original_name": "이사회의사록.hwp",
"url": "https://blog.kakaocdn.net/dna/bR9OJQ/btqvUQNG0yZ/AAAAAAAAAAAAAAAAAAAAADd7sShQDM0Pv6_Df3LIL5H7wovm-TBSqcfMlaMpK_N2/%EC%9D%B4%EC%82%AC%ED%9A%8C%EC%9D%98%EC%82%AC%EB%A1%9D.hwp?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&expires=1780239599&allow_ip=&allow_referer=&signature=gdXVuFaDSUNHNCuMoXihjRDGWSQ%3D&attach=1&knm=tfile.hwp",
"path": "board-minutes.hwp",
"sha256": "735eaece6ec2fc617d8768bd30098f1f4808099aeeeed91aebf48e988238d003",
"bytes": 10752
},
{
"source_name": "우택스 블로그 공개 주식회사 설립등기 첨부서류 HWP",
"page_url": "https://wootax.tistory.com/entry/%EC%A3%BC%EC%8B%9D%ED%9A%8C%EC%82%AC-%EC%84%A4%EB%A6%BD%ED%95%98%EA%B8%B0-%EB%B2%95%EC%9D%B8%EB%93%B1%EA%B8%B0%EB%B6%80%EB%93%B1%EB%B3%B8-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0",
"original_name": "5. 발기인회의사록.hwp",
"url": "https://blog.kakaocdn.net/dna/bwE3eR/btso6ks6Rz1/AAAAAAAAAAAAAAAAAAAAAGXjN0fmZvlgAGf9McFFnfvkxxo5s51mVwOvolJ6MSFN/5.%20%EB%B0%9C%EA%B8%B0%EC%9D%B8%ED%9A%8C%EC%9D%98%EC%82%AC%EB%A1%9D.hwp?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&expires=1780239599&allow_ip=&allow_referer=&signature=Gmc9qugkolm7QOXIFEvUg7AfO7c%3D&attach=1&knm=tfile.hwp",
"path": "founder-meeting-minutes.hwp",
"sha256": "62971efa2ace88d8df7fa827538fc6b9d31b60d14e945d0769327531fee6a77b",
"bytes": 58368
},
{
"source_name": "우택스 블로그 공개 주식회사 설립등기 첨부서류 HWP",
"page_url": "https://wootax.tistory.com/entry/%EC%A3%BC%EC%8B%9D%ED%9A%8C%EC%82%AC-%EC%84%A4%EB%A6%BD%ED%95%98%EA%B8%B0-%EB%B2%95%EC%9D%B8%EB%93%B1%EA%B8%B0%EB%B6%80%EB%93%B1%EB%B3%B8-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0",
"original_name": "6. 주주명부.hwp",
"url": "https://blog.kakaocdn.net/dna/Yu8V2/btso10oEbPP/AAAAAAAAAAAAAAAAAAAAAAVv7HSE5W8icMvgAPu5O1oDuYfJU9papuMZ17SolMpe/6.%20%EC%A3%BC%EC%A3%BC%EB%AA%85%EB%B6%80.hwp?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&expires=1780239599&allow_ip=&allow_referer=&signature=L%2F17AHZt9FAz%2Fuvkv4fhWQfrGSU%3D&attach=1&knm=tfile.hwp",
"path": "shareholder-register.hwp",
"sha256": "5dc27e306c0384bad4c59073c402f0bd3085d1049c24afe68cc75dc83702c3af",
"bytes": 36352
},
{
"source_name": "우택스 블로그 공개 주식회사 설립등기 첨부서류 HWP",
"page_url": "https://wootax.tistory.com/entry/%EC%A3%BC%EC%8B%9D%ED%9A%8C%EC%82%AC-%EC%84%A4%EB%A6%BD%ED%95%98%EA%B8%B0-%EB%B2%95%EC%9D%B8%EB%93%B1%EA%B8%B0%EB%B6%80%EB%93%B1%EB%B3%B8-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0",
"original_name": "9. 인감신고서.hwp",
"url": "https://blog.kakaocdn.net/dna/K8StW/btso08AvjvK/AAAAAAAAAAAAAAAAAAAAAAg8rnGRLiZrbNJpvZ3uaK1vS0_QZ_wd9oAYMPZPWjJe/9.%20%EC%9D%B8%EA%B0%90%EC%8B%A0%EA%B3%A0%EC%84%9C.hwp?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&expires=1780239599&allow_ip=&allow_referer=&signature=ZOKfYeOyzgqyx05a30tMlcPfhPQ%3D&attach=1&knm=tfile.hwp",
"path": "corporate-seal-report.hwp",
"sha256": "26aa32cce44a4cb1d3a92e5684c02fc210c65b2d344acaa470ebc91c2579bd5b",
"bytes": 52736
},
{
"source_name": "우택스 블로그 공개 주식회사 설립등기 첨부서류 HWP",
"page_url": "https://wootax.tistory.com/entry/%EC%A3%BC%EC%8B%9D%ED%9A%8C%EC%82%AC-%EC%84%A4%EB%A6%BD%ED%95%98%EA%B8%B0-%EB%B2%95%EC%9D%B8%EB%93%B1%EA%B8%B0%EB%B6%80%EB%93%B1%EB%B3%B8-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0",
"original_name": "10. 위임장.hwp",
"url": "https://blog.kakaocdn.net/dna/beg039/btso1ui7lKh/AAAAAAAAAAAAAAAAAAAAAAwYRb3rbFNGA338GYRNAaxNjLxi81b6M-HeAya9eRDY/10.%20%EC%9C%84%EC%9E%84%EC%9E%A5.hwp?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&expires=1780239599&allow_ip=&allow_referer=&signature=Wx7HctsqSHGJzFsBs9gNxTHrN%2Bs%3D&attach=1&knm=tfile.hwp",
"path": "power-of-attorney.hwp",
"sha256": "d9141458d779dd781178ca187c2466fe9c2ec7467971b9fcdb6ad52e4ff42d66",
"bytes": 33280
},
{
"source_name": "법무부 주식회사 표준정관 공개 재배포 HWP",
"page_url": "https://mbolt.tistory.com/60",
"original_name": "주식회사표준정관_법무부.hwp",
"url": "https://blog.kakaocdn.net/dna/bnYmGS/btqQmP25auG/AAAAAAAAAAAAAAAAAAAAAOftXuAiup09pc65AD9memsLydWwWBZ3dWCh-W5jgLvF/%EC%A3%BC%EC%8B%9D%ED%9A%8C%EC%82%AC%ED%91%9C%EC%A4%80%EC%A0%95%EA%B4%80_%EB%B2%95%EB%AC%B4%EB%B6%80.hwp?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&expires=1780239599&allow_ip=&allow_referer=&signature=P9ehS7wc0K3d%2FsEuWvvUJvwxJrE%3D&attach=1&knm=tfile.hwp",
"path": "standard-articles-startup-moj.hwp",
"sha256": "64d84ff1bd9ba10db1f5ba9c7ee0442725385ce44b587730e7984c69bd3034d6",
"bytes": 19456,
"note": "상장회사 표준정관이 아니라 일반 비상장/스타트업 발기설립 정관 참고용으로 쓰는 법무부 주식회사 표준정관 HWP 재배포본."
}
]
}

View file

@ -0,0 +1,95 @@
# {{COMPANY_NAME}} 설립등기 첨부서류 묶음 초안
> 참고용 자동작성 템플릿입니다. 법률·세무 자문이 아니며 제출 전 원본성, 인감, 세금, 관할 등기소 요구사항을 확인하세요.
> 개인정보·민감정보 주의: 이 템플릿을 채울 때 주민등록번호 원문, 신분증 이미지, 인감증명서 스캔본은 꼭 필요한 로컬 제출본에만 넣고, 예시·로그·테스트·PR에는 마스킹하세요. 채워진 서류와 HWP 산출물은 레포에 커밋하지 마세요.
## 1. 등기신청 기본정보
- 법인명: {{COMPANY_NAME}}
- 본점 주소: {{HEAD_OFFICE_ADDRESS}}
- 자본금: {{CAPITAL_KRW}}원
- 1주의 금액: {{PAR_VALUE_KRW}}원
- 설립 시 발행주식 수: {{INCORPORATION_SHARES}}주
- 대표이사: {{CEO_NAME}}
- 등기 이사: {{DIRECTOR_NAMES}}
## 2. 첨부서류 체크리스트
| 순서 | 문서 | 쉬운 설명 | 준비 상태 |
| --- | --- | --- | --- |
| 1 | 설립등기신청서 | 등기소에 “이 회사를 등기해 달라”고 내는 표지 서류 | {{STATUS_APPLICATION}} |
| 2 | 정관 | 회사의 기본 규칙. 앞부분 제2조 목적/업태·종목과 맨 마지막 날짜·발기인 서명/기명날인·간인을 중점 확인 | {{STATUS_ARTICLES}} |
| 3 | 발기인 의사록/결정서 | 회사를 세우기로 정했다는 기록 | {{STATUS_FOUNDERS_MINUTES}} |
| 4 | 주식인수증 | 누가 몇 주를 가져가는지 적은 문서 | {{STATUS_SHARE_SUBSCRIPTION}} |
| 5 | 주금납입 증빙/잔고증명 | 자본금이 실제로 입금됐다는 증빙 | {{STATUS_BANK_BALANCE}} |
| 6 | 조사보고서 | 설립 과정과 재산 상태를 확인했다는 보고 | {{STATUS_INSPECTION_REPORT}} |
| 7 | 취임승낙서 | 이사/감사가 직책을 맡겠다고 동의한 문서 | {{STATUS_ACCEPTANCE}} |
| 8 | 인감신고서 | 법인인감을 등기소에 신고하는 서류. 실제 법인인감 도장을 미리 준비 | {{STATUS_SEAL_REPORT}} |
| 9 | 등록면허세 영수필확인서 | 등기 전 지방세를 냈다는 증명. 납부 후 반드시 발급/첨부 | {{STATUS_TAX_RECEIPT}} |
| 10 | 등기신청수수료 영수필확인서 | 등기 신청 수수료를 냈다는 증명. 등록면허세와 별도로 반드시 발급/첨부 | {{STATUS_REGISTRY_FEE_RECEIPT}} |
| 11 | 등기이사 개인 인감증명서 또는 본인서명사실확인서 | 등기이사 본인의 인감/서명을 확인하는 발급서류. 등기이사는 무조건 준비해야 함 | {{STATUS_DIRECTOR_SEAL_CERTS}} |
| 12 | 등기이사 주민등록초본/등본 등 주소 확인 증빙 | 등기이사 주소·주민등록번호/생년월일 확인 발급서류. 등기이사는 무조건 준비해야 함 | {{STATUS_DIRECTOR_RESIDENT_DOCS}} |
공유용 체크리스트에는 위 증빙의 보유 여부만 표시하고, 주민등록번호·상세 주소·인감증명서 번호 같은 개인정보/민감정보 원문은 적지 않는다. 등기이사에게는 개인 인감증명서/본인서명사실확인서와 주민등록초본/등본이 별도 발급서류로 필요하다는 점을 누락 없이 안내한다.
## 2-1. 양식별 수정 위치·간인·인감 체크
| 문서 | 어느 부분을 고칠지 | 제출 전 추가 확인 |
| --- | --- | --- |
| 설립등기신청서 | 상단 상호/본점, 등기 목적, 자본금·주식 수, 임원, 첨부서류 목록, 신청일·신청인 | 발기설립 양식 제65-1호인지 확인. 모집설립 양식은 사용하지 않음 |
| 정관 | 제1조 상호, **제2조 목적의 실제 사업 업태·종목**, 본점, 주식/임원/결산 규정, 부칙 | 맨 마지막 날짜와 발기인 전원 서명/기명날인, 여러 장이면 간인 |
| 주식인수/주식발행 동의 | 인수인, 주식 수, 1주의 금액, 인수가액, 납입기일 | 합계가 신청서·정관과 일치 |
| 발기인회의사록/결정서 | 일시·장소, 의안, 결의 내용, 발기인, 날짜, 날인란 | 여러 장이면 간인 필요 여부 확인 |
| 조사보고서 | 조사자, 주금납입 확인, 변태설립사항 유무, 결론 문구 | 최종 적법 판단을 에이전트가 단정하지 않음 |
| 취임승낙서 | 임원 성명, 주소, 생년월일, 직책, 취임일, 날짜, 개인 인감/서명 | 등기 임원 명단과 일치 |
| 인감신고서 | 대표자, 상호/본점, 법인인감 날인란 | 법인인감 도장 준비 및 날인 위치 확인 |
| 위임장 | 위임인·수임인, 위임 범위, 날짜, 날인란 | 대리 제출 시 원본 날인·간인 요구 확인 |
## 2-2. 조건부 추가서류
| 조건 | 추가서류/확인 | 준비 상태 |
| --- | --- | --- |
| 명의개서대리인을 둔 경우 | 명의개서대리인 계약 또는 선임 증명 | {{STATUS_TRANSFER_AGENT}} |
| 현물출자/재산인수 등 변태설립사항이 있는 경우 | 검사인·공증인 조사보고, 감정인 감정, 관련 재판서류 | {{STATUS_SPECIAL_FORMATION_DOCS}} |
| 인허가 업종인 경우 | 허가·인가·등록·신고 수리 증명 | {{STATUS_LICENSE_DOCS}} |
| 자본금 10억 원 이상 또는 소규모회사 특례 밖인 경우 | 정관 공증, 의사록 인증, 감사/이사회 요건 확인 | {{STATUS_NOTARIZATION_REVIEW}} |
## 3. 취임승낙서 초안
본인은 {{COMPANY_NAME}}의 {{OFFICER_ROLE}}로 선임되었음을 승낙합니다.
- 성명: {{OFFICER_NAME}}
- 주소: {{OFFICER_ADDRESS}}
- 생년월일: {{OFFICER_BIRTHDATE}}
- 취임일: {{APPOINTMENT_DATE}}
{{APPOINTMENT_DATE}}
{{OFFICER_NAME}} (인)
## 4. 조사보고서 초안
조사보고자 {{INSPECTOR_NAME}}은 {{COMPANY_NAME}}의 설립에 관하여 다음 사항을 조사하였습니다.
1. 정관의 작성 및 발기인의 기명날인 여부
2. 발행주식 총수와 인수 여부
3. 주금납입 또는 잔고증명 확인 여부
4. 현물출자, 재산인수 등 변태설립사항 유무: {{SPECIAL_FORMATION_ITEMS}}
5. 등록면허세 및 지방교육세 납부 여부
조사 결론: {{INSPECTION_CONCLUSION_AFTER_USER_OR_EXPERT_REVIEW}}
> 에이전트는 “설립 절차에 중대한 흠이 없다”는 최종 법률 판단을 임의로 쓰지 않습니다. 위 결론은 사용자, 조사보고자, 법무사·변호사 등 전문가가 근거를 확인한 뒤 확정하세요.
{{REPORT_DATE}}
조사보고자 {{INSPECTOR_NAME}} (인)
## 5. 등록면허세 확인 메모
- 본점 주소: {{HEAD_OFFICE_ADDRESS}}
- 과밀억제권역/대도시 중과 검토 결과: {{OVERCONCENTRATION_REVIEW}}
- 소프트웨어/정보통신 업종 감면 또는 중과 제외 검토: {{SOFTWARE_TAX_REVIEW}}
- 위택스/지자체 최종 납부번호: {{WETAX_PAYMENT_ID}}

View file

@ -0,0 +1,91 @@
# 주식회사 설립등기 공식 양식 출처 맵
> 이 파일은 스킬에 **이미 저장된 HWP 양식 경로**와 제출 전 대조 기준을 정리한 매핑 문서입니다. 에이전트는 새 양식을 찾는 것부터 시작하지 말고, 2026-05-02 기준 저장된 `templates/official/``templates/attachment-hwp/` 파일 사본을 레포 밖 작업 디렉터리에 복사해 채웁니다. 인터넷등기소/법원 제공 HWP/HWPX/PDF 원본은 수시로 바뀔 수 있으므로 제출 직전 최신본과 대조만 안내합니다.
## 저장 양식 우선 사용 순서
1. **번들 HWP 스냅샷** `templates/official/`
- `form-65-1-stock-company-incorporation-promoter.hwp`: [양식 제65-1호] 주식회사 설립 등기(발기설립).
- `form-65-2-stock-company-incorporation-subscription.hwp`: [양식 제65-2호] 주식회사 설립 등기(모집설립). 모집설립은 일반적이지 않으므로 이 스킬은 대응하지 않고 대조자료로만 둔다.
- `form-65-1-fill-map.json`: 발기설립 공식 HWP의 주요 입력 셀 매핑.
- `source-manifest.json`: 다운로드일, flSeq, SHA-256, 원천 URL 메타데이터.
2. **국가법령정보센터 등기예규** `상업등기신청서의 양식에 관한 예규`
- 대조할 양식: **양식 제65-1호(주식회사설립등기신청서·발기설립)**, **양식 제65-2호(주식회사설립등기신청서·모집설립)**.
- 용도: 인터넷등기소 양식이 최신 예규의 별지 양식과 맞는지 확인.
3. **찾기쉬운 생활법령정보: 주식회사 설립등기**
- 용도: 신청 방식, 신청정보, 첨부정보 목록의 쉬운 말 확인.
- 확인 포인트: 생활법령 페이지는 인터넷등기소에 등기신청서·첨부서류 양식 및 작성방식이 있다고 안내하고, 첨부서면은 등기예규 제65-1호/제65-2호를 보라고 안내한다.
4. **온라인법인설립시스템** `https://www.startbiz.go.kr`
- 용도: 온라인 법인설립 진행 시 실제 입력 흐름, 기관 연계 제출 흐름 확인.
## 실제 공개 배포 첨부서류 HWP 양식 묶음
공식 설립등기신청서 외에 실제 발기설립에서 자주 필요한 첨부서면은 `templates/attachment-hwp/`에 **공개 웹에서 실제 배포되는 HWP 파일**로 함께 둔다. 이 파일들은 에이전트가 임의 생성한 양식이 아니며, 각 파일의 출처 URL·원 파일명·SHA-256은 `templates/attachment-hwp/source-manifest.json`에 기록한다. 공개 배포본에 포함되어 있던 실제/샘플 법인명·성명·주소·주민등록번호형 문자열·은행명·구체 날짜는 HWP 안에서 자리표시자로 치환했다. 다만 공식 양식이 아닌 민간/공개 배포 양식은 제출 전 인터넷등기소 첨부서면예시, 상법 제289조, 상업등기규칙 제129조, 관할 등기소 요구와 반드시 대조한다.
- `articles-of-incorporation.hwp`: 공개 배포 정관 양식.
- `standard-articles-startup-moj.hwp`: 법무부 주식회사 표준정관 공개 재배포 HWP. 상장회사 표준정관이 아니라 비상장/스타트업 발기설립 정관 참고용으로 사용한다.
- `share-issuance-consent.hwp`: 주식발행사항동의서.
- `share-subscription.hwp`: 주식인수증.
- `founder-meeting-minutes.hwp`: 발기인회의사록.
- `founder-meeting-period-shortening-consent.hwp`: 발기인총회 기간단축 동의서.
- `shareholder-register.hwp`: 주주명부.
- `inspection-report.hwp`: 조사보고서.
- `officer-acceptance-director-ceo.hwp`: 이사/대표이사 취임승낙서.
- `officer-acceptance-auditor.hwp`: 감사 취임승낙서.
- `board-minutes.hwp`: 이사회의사록.
- `corporate-seal-report.hwp`: 인감신고서.
- `power-of-attorney.hwp`: 위임장.
## 표준 발기설립 문서별 공식/초안 대응
| 문서 | 공식 확인 위치 | 레포 내 HWP/보조자료 | 사용 원칙 |
| --- | --- | --- | --- |
| 주식회사설립등기신청서(발기설립) | 인터넷등기소 등기신청양식, 등기예규 양식 제65-1호, 번들 `templates/official/form-65-1-stock-company-incorporation-promoter.hwp` | `templates/incorporation-document-pack.md`의 기본정보 섹션 및 `scripts/fill_official_hwp.py` | 번들 HWP에 주요 값을 자동 작성하되, 제출 전 최신 공식본 대조와 사람 검토 필수 |
| 주식회사설립등기신청서(모집설립) | 등기예규 양식 제65-2호, 번들 `templates/official/form-65-2-stock-company-incorporation-subscription.hwp` | 기본 플로우에서는 사용하지 않음 | 모집설립은 일반적이지 않으므로 사용자가 별도 요청하지 않는 한 발기설립 양식을 채운다 |
| 정관 | 상법, 상업등기규칙, 인터넷등기소 첨부서면예시, 공개 배포 정관 HWP | `templates/attachment-hwp/articles-of-incorporation.hwp`, `templates/attachment-hwp/standard-articles-startup-moj.hwp` | 실제 공개 배포 HWP를 우선 복사해 작성한다. 앞부분 제2조 목적에는 실제 사업 업태·종목을 채우고, 맨 마지막 날짜·발기인 서명/기명날인·간인을 확인한다. 종류주식·스톡옵션·투자계약 등은 표준정관 구조를 우선 확인해 별도 조항으로 보강 |
| 발기인 의사록/결정서 | 인터넷등기소 첨부서면예시, 공개 배포 발기인회의사록 HWP | `templates/attachment-hwp/founder-meeting-minutes.hwp`, `templates/attachment-hwp/founder-meeting-period-shortening-consent.hwp` | 공개 배포 HWP를 복사해 회사 구조별 필수 결의사항을 공식 예시와 대조 |
| 주식인수증/주식청약서 | 인터넷등기소 첨부서면예시, 상업등기규칙 제129조, 공개 배포 HWP | `templates/attachment-hwp/share-subscription.hwp`, `templates/attachment-hwp/share-issuance-consent.hwp` | 발기설립은 주식 인수를 증명하는 정보, 모집설립은 청약 관련 정보가 달라질 수 있음 |
| 조사보고서 | 인터넷등기소 첨부서면예시, 상법 조사보고 조항, 공개 배포 HWP | `templates/attachment-hwp/inspection-report.hwp` | 공개 배포 HWP를 복사해 작성하되, 에이전트가 최종 적법 판단 문구를 단정하지 않음 |
| 취임승낙서 | 인터넷등기소 첨부서면예시, 상업등기규칙 제129조, 공개 배포 HWP | `templates/attachment-hwp/officer-acceptance-director-ceo.hwp`, `templates/attachment-hwp/officer-acceptance-auditor.hwp` | 성명·주소·생년월일 등 개인정보는 로컬 제출본에만 입력 |
| 등기이사 개인 인감증명서 또는 본인서명사실확인서 | 상업등기규칙 제129조 및 관할 등기소 요구 | 저장 양식 없음. 주민센터/정부24 등에서 등기이사 본인이 발급 | 등기이사는 무조건 필요한 발급서류로 안내하고 원문 정보는 로컬 제출본에만 보관 |
| 등기이사 주민등록초본/등본 등 주소 확인 증빙 | 상업등기규칙 제129조 및 관할 등기소 요구 | 저장 양식 없음. 주민센터/정부24 등에서 등기이사 본인이 발급 | 등기이사는 무조건 필요한 발급서류로 안내하고 발급일·주민등록번호 표시 범위 확인 |
| 인감신고서 | 인터넷등기소 등기신청양식/첨부서면예시, 공개 배포 HWP | `templates/attachment-hwp/corporate-seal-report.hwp` | 공개 배포 HWP를 참고하되 법인인감 날인·인감 관련 증빙은 공식 요구 확인 |
| 등록면허세 영수필확인서 | 위택스/관할 지자체 | `templates/incorporation-document-pack.md` 세금 확인 메모 | 필수 발급/첨부. 최종 세액·납부번호는 위택스/지자체 결과 기준 |
| 등기신청수수료 영수필확인서 | 인터넷등기소/등기소 수수료 납부 | `templates/incorporation-document-pack.md` 체크리스트 | 필수 발급/첨부. 등록면허세 영수필확인서와 별도 문서로 관리 |
## 조건부 추가서류 대조
- 명의개서대리인을 둔 경우: 명의개서대리인 계약 또는 선임 증명 정보를 첨부서류 목록에 추가한다.
- 현물출자·재산인수·설립비용 등 변태설립사항이 있는 경우: 상업등기규칙 제129조상 검사인/공증인 조사보고, 감정인 감정, 관련 재판서류 등 해당 증명정보를 별도로 확인한다.
- 인허가 업종인 경우: 허가·인가·등록·신고 수리 증명 등 영업 가능 증빙을 추가한다.
- 자본금 10억 원 이상 또는 소규모회사 특례 밖인 경우: 정관 공증, 의사록 인증, 감사/이사회 구성 필요 여부를 확인한다.
## 에이전트 답변에 포함할 공식 양식 안내 문구
- “실제 제출 양식은 인터넷등기소의 **등기신청양식**과 **첨부서면예시**에서 최신 HWP/HWPX/PDF를 다시 내려받아 사용하세요.”
- “주식회사 발기설립 신청서는 국가법령정보센터의 `상업등기신청서의 양식에 관한 예규` **양식 제65-1호**, 모집설립은 **양식 제65-2호**와 대조하세요.”
- “이 레포의 공개 배포 HWP 묶음과 Markdown 템플릿은 작성 보조자료이며, 최신 공식 양식·관할 등기소 요구를 대체하지 않습니다.”
## 저장 양식 기반 작성 흐름
1. 레포 밖 비공개 작업 디렉터리를 만든다.
2. 위 표의 저장된 HWP 양식을 작업 디렉터리로 복사한다.
3. 사용자 입력 JSON을 만든다. 주민등록번호 원문은 마스킹하거나 제출 직전 로컬 파일에만 둔다.
4. `scripts/fill_official_hwp.py`로 번들 [양식 제65-1호] HWP에 주요 셀을 채운다.
5. 첨부서류는 저장된 `templates/attachment-hwp/*.hwp` 사본을 한 장씩 확인하며 채운다. 단순 replace-all은 shortcut이므로 모든 양식을 순차 확인하고, 정관·의사록·위임장 등 간인 대상 가능 문서와 법인인감 준비 여부를 별도 체크한다.
```bash
workdir="$(mktemp -d "${TMPDIR:-/tmp}/corp-reg.XXXXXX")"
chmod 700 "$workdir"
python3 corporate-registration-consulting/scripts/fill_official_hwp.py \
--input-json "$workdir/form-data.json" \
--output "$workdir/form-65-1-filled.hwp"
npx k-skill-rhwp info "$workdir/form-65-1-filled.hwp"
```
## HWP/HWPX 처리 주의
- 공식 파일을 새로 내려받거나 번들 HWP를 채운 산출물은 레포 밖 임시 디렉터리에 보관한다.
- `k-skill-rhwp info <공식양식>`로 구조를 확인한 뒤 표/셀은 `set-cell-text`, 본문 자리표시자는 `replace-all`을 우선 사용한다. 다만 replace-all에 의존하지 말고 각 양식의 앞부분·본문·하단 날짜·서명/날인란을 순차 검토한다. 번들 발기설립 HWP는 `form-65-1-fill-map.json`의 셀 매핑을 사용한다.
- 공식 양식은 표와 칸이 많으므로 자동 치환 후 반드시 사람이 한컴오피스/호환 뷰어로 열어 누락 셀, 줄바꿈, 날인란, 첨부서류 목록을 확인한다.

View file

@ -0,0 +1,32 @@
{
"form": "form-65-1-stock-company-incorporation-promoter.hwp",
"title": "[양식 제65-1호] 주식회사 설립 등기(발기설립)",
"engine": "k-skill-rhwp set-cell-text",
"warning": "이 매핑은 국가법령정보센터 HWP 별지 양식의 표 셀 인덱스에 맞춘 보조 자동작성 맵입니다. 자동 작성 후 한컴오피스/호환 뷰어에서 셀 위치, 줄바꿈, 날인란, 첨부서면 통수를 반드시 사람이 확인하세요.",
"fields": {
"registration_purpose": { "section": 0, "parentParagraph": 2, "control": 0, "cell": 18, "default": "주식회사설립" },
"registration_reason": { "section": 0, "parentParagraph": 2, "control": 0, "cell": 19 },
"company_name": { "section": 0, "parentParagraph": 2, "control": 0, "cell": 21 },
"head_office_address": { "section": 0, "parentParagraph": 2, "control": 0, "cell": 23 },
"public_notice_method": { "section": 0, "parentParagraph": 2, "control": 0, "cell": 25 },
"par_value_krw": { "section": 0, "parentParagraph": 2, "control": 0, "cell": 27 },
"authorized_shares": { "section": 0, "parentParagraph": 2, "control": 0, "cell": 29 },
"issued_shares_summary": { "section": 0, "parentParagraph": 2, "control": 0, "cell": 31 },
"capital_krw": { "section": 0, "parentParagraph": 2, "control": 0, "cell": 33 },
"purposes_text": { "section": 0, "parentParagraph": 2, "control": 0, "cell": 35 },
"directors_auditors_text": { "section": 0, "parentParagraph": 3, "control": 0, "cell": 2 },
"ceo_name_address": { "section": 0, "parentParagraph": 3, "control": 0, "cell": 4 },
"share_class_details": { "section": 0, "parentParagraph": 3, "control": 0, "cell": 6, "default": "해당없음" },
"branches": { "section": 0, "parentParagraph": 3, "control": 0, "cell": 8, "default": "해당없음" },
"duration_or_dissolution": { "section": 0, "parentParagraph": 3, "control": 0, "cell": 10, "default": "해당없음" },
"other_registration_items": { "section": 0, "parentParagraph": 3, "control": 0, "cell": 12 },
"registration_tax_krw": { "section": 0, "parentParagraph": 6, "control": 0, "cell": 1 },
"local_education_tax_krw": { "section": 0, "parentParagraph": 6, "control": 0, "cell": 3 },
"special_rural_tax_krw": { "section": 0, "parentParagraph": 6, "control": 0, "cell": 5, "default": "0" },
"tax_total_krw": { "section": 0, "parentParagraph": 6, "control": 0, "cell": 7 },
"application_fee_krw": { "section": 0, "parentParagraph": 6, "control": 0, "cell": 9 },
"fee_payment_number": { "section": 0, "parentParagraph": 6, "control": 0, "cell": 11 },
"tax_base_krw": { "section": 0, "parentParagraph": 6, "control": 0, "cell": 13 },
"application_date": { "section": 0, "parentParagraph": 6, "control": 0, "cell": 17 }
}
}

View file

@ -0,0 +1,27 @@
{
"downloaded_at": "2026-05-02",
"source": {
"name": "국가법령정보센터 상업등기신청서의 양식에 관한 예규",
"page_url": "https://www.law.go.kr/행정규칙/상업등기신청서의양식에관한예규",
"adm_rul_seq": "2200000106061",
"note": "별지 양식 HWP 다운로드 링크(flDownload.do)에서 내려받은 공식 별지 서식입니다. 제출 전에는 최신 예규/인터넷등기소 양식과 다시 대조하세요."
},
"files": [
{
"path": "form-65-1-stock-company-incorporation-promoter.hwp",
"title": "[양식 제65-1호] 주식회사 설립 등기(발기설립)",
"flSeq": "146330897",
"bylClsCd": "200206",
"sha256": "793dc933df06bd7dee117c6613e369d6918be22114d8682d6e8c5f54850ddc98",
"bytes": 17408
},
{
"path": "form-65-2-stock-company-incorporation-subscription.hwp",
"title": "[양식 제65-2호] 주식회사 설립 등기(모집설립)",
"flSeq": "146330901",
"bylClsCd": "200206",
"sha256": "aefa665b1d57f856041a648bb8b9cbcfd291049112d683e9504310295d2a7848",
"bytes": 17408
}
]
}

View file

@ -1,61 +1,86 @@
---
name: coupang-product-search
description: 공식 쿠팡 쇼핑 URL과 브라우저 캡처 HTML을 이용해 상품 후보, 가격, 상세, 리뷰를 정리한다. 먼저 Open API 제한과 anti-bot 차단 여부를 확인하고, 가능하면 검색→상세→리뷰 순으로 답한다.
description: retention-corp/coupang_partners의 로컬 Coupang MCP 호환 레이어로 쿠팡 상품 검색, 로켓배송 필터, 가격대 검색, 상품 비교, 베스트 상품, 골드박스 특가를 조회한다.
license: MIT
metadata:
category: retail
locale: ko-KR
phase: v1
phase: v2
---
# Coupang Product Search
## What this skill does
쿠팡에서 사용자의 니즈에 맞는 상품을 찾기 위해 다음을 지원한다.
[retention-corp/coupang_partners](https://github.com/retention-corp/coupang_partners) 저장소의 로컬 Coupang MCP 호환 레이어를 사용해 쿠팡 상품 조회 도구를 실행한다. 기존 유지보수형 HF Space MCP 엔드포인트 대신, 이 저장소의 `bin/coupang_mcp.py`가 제공하는 `local://coupang-mcp` 계약을 호출한다.
- 공식 쿠팡 검색 URL 생성
- 브라우저 세션에서 캡처한 검색 결과 HTML 파싱
- 상품 상세/가격/판매자/배지/필수 표기 정보 파싱
- 상품 리뷰 요약/개별 리뷰 파싱
- direct fetch / headless browser 차단 여부 probe
- 키워드 상품 검색
- 로켓배송 전용 필터 검색
- 가격대 범위 검색
- 상품 비교표 생성
- 카테고리별 베스트 상품
- 골드박스 당일 특가
- 인기 검색어/계절 상품 추천
## Important limitation first
## How it works
2026-03-31 기준으로 확인한 사실:
```
Claude Code / Codex
→ coupang-product-search/scripts/coupang_partners_mcp.py
→ git clone/update retention-corp/coupang_partners (user cache)
→ python3 bin/coupang_mcp.py
→ local://coupang-mcp compatible tool layer
├─ Coupang Partners API client (operator keys present)
└─ hosted fallback → https://a.retn.kr/v1/public/assist (no keys)
```
- 쿠팡 개발자 Open API는 **판매자/WING 중심** 문서만 확인되었다.
- 일반 소비자용 상품 검색·리뷰 조회 Open API는 확인하지 못했다.
- 이 저장소 환경에서 desktop direct HTTP 는 `403 Access Denied` 로 차단되었다.
- mobile direct HTTP 도 차단되었지만, rerun 마다 `200 challenge-html` 또는 `403 access-denied-html` 처럼 **차단 응답이 달라질 수 있었다.**
- headless Playwright-core probe 역시 차단되었고, **blocked shape 도 edge/challenge 상태에 따라 달라질 수 있었다.**
Hard rules:
따라서 이 스킬은 **anti-bot 우회**를 시도하지 않는다. 대신:
- `COUPANG_MCP_ENDPOINT`는 호환성 knob로만 유지한다. 기본값은 `local://coupang-mcp`다.
- 구형 HF Space hosted MCP 엔드포인트를 사용하거나 새로 지어내지 않는다.
- upstream 저장소는 `https://github.com/retention-corp/coupang_partners.git`만 사용한다.
- `tools``init`은 로컬 MCP 계약 확인용으로 먼저 실행한다.
1. 공식 URL을 만든다.
2. 브라우저 세션에서 확보한 HTML 이 있으면 파싱한다.
3. HTML 확보가 막히면 probe 결과와 함께 제한을 설명한다.
## Execution paths
`retention-corp/coupang_partners`는 하나의 CLI 뒤에서 두 가지 경로를 자동으로 선택한다. 래퍼(`coupang_partners_mcp.py`)는 두 경로 모두를 그대로 통과시킨다.
1. **Operator (local HMAC) path**`COUPANG_ACCESS_KEY``COUPANG_SECRET_KEY`가 둘 다 설정된 경우. upstream이 Coupang Partners API를 HMAC 서명해 직접 호출한다. 키/시크릿은 절대 답변·문서·커밋에 노출하지 않는다.
2. **Credentialless hosted fallback path** — 위 두 키 중 하나라도 없는 경우(또는 `OPENCLAW_SHOPPING_FORCE_HOSTED=1`). upstream이 자동으로 Retention Corp의 hosted 백엔드(`https://a.retn.kr/v1/public/assist`)로 떨어진다. 이 경로는 `X-OpenClaw-Client-Id` allowlist로 게이트되어 있으며, upstream이 기본으로 실어 보내는 `openclaw-skill` 값이 현재 Retention Corp allowlist에 등록된 값이다. k-skill 래퍼는 `OPENCLAW_SHOPPING_CLIENT_ID`를 별도로 설정하지 않고 이 upstream 기본값을 그대로 사용한다.
두 경로 모두 JSON envelope(`ok`/`data.session_id`/`data.tool`/`data.payload`/`data.result`) 모양은 동일하므로, 답변 로직은 경로를 구별할 필요가 없다. short deeplink는 hosted fallback에서는 `https://a.retn.kr/s/...` 형태로, operator path에서는 `https://link.coupang.com/...` 형태로 온다.
### 관련 환경변수
| 환경변수 | 역할 | 기본값 |
|---------|------|--------|
| `COUPANG_ACCESS_KEY`, `COUPANG_SECRET_KEY` | 운영자 Coupang Partners API 크리덴셜. 둘 다 있을 때만 로컬 HMAC 경로가 활성화된다. | 없음 (없으면 hosted fallback) |
| `OPENCLAW_SHOPPING_CLIENT_ID` | hosted fallback이 보낼 `X-OpenClaw-Client-Id`. upstream이 `openclaw-skill`을 기본으로 실어 보내며 이 값이 현재 Retention Corp allowlist에 등록되어 있다. k-skill 래퍼는 이 변수를 오버라이드하지 않는 것을 권장한다. | `openclaw-skill` |
| `OPENCLAW_SHOPPING_FORCE_HOSTED` | `1`이면 키가 있어도 hosted 경로를 강제한다. | 비어있음 |
| `OPENCLAW_SHOPPING_BASE_URL` | hosted 백엔드 base URL 오버라이드. 스테이징/로컬 backend 테스트용. | `https://a.retn.kr` |
## MCP endpoint / contract
```
local://coupang-mcp
```
프로토콜 호환 버전: MCP `2025-03-26`. 네트워크로 붙는 Streamable HTTP 서버가 아니라, upstream 저장소의 로컬 MCP 호환 CLI가 같은 도구 이름과 JSON-RPC 모양의 payload를 반환한다.
## When to use
- "쿠팡에서 생수 가격 좀 찾아줘"
- "이 쿠팡 상품 상세/리뷰 요약해줘"
- "쿠팡 headless 자동화가 가능한지 먼저 테스트해줘"
- "쿠팡 검색 URL부터 만들고 상품 후보 정리해줘"
- "로켓배송 에어팟 찾아줘"
- "20만원 이하 키보드 추천해줘"
- "아이패드 vs 갤럭시탭 비교"
- "오늘 쿠팡 특가 뭐 있어?"
- "전자제품 베스트 보여줘"
## When not to use
- 로그인, 장바구니, 결제 자동화가 필요한 경우
- anti-bot 우회나 계정/session 탈취가 필요한 경우
- 공식 URL이나 브라우저 HTML 없이 결과를 단정해야 하는 경우
## Official surfaces checked
- seller Open API docs: `https://developers.coupangcorp.com/hc/ko/sections/360004260614-상품-API`
- desktop search URL: `https://www.coupang.com/np/search?q=<query>`
- mobile search URL: `https://m.coupang.com/nm/search?q=<query>`
- product URL pattern: `https://www.coupang.com/vp/products/<productId>?itemId=<itemId>&vendorItemId=<vendorItemId>`
- review section anchor: `#sdpReview`
- 쿠팡 계정/session 접근이 필요한 경우
- 실시간 재고/품절 여부를 100% 보장해야 하는 경우 (hosted fallback과 Partners API 모두 캐시·지연이 있을 수 있다)
## Workflow
@ -63,88 +88,127 @@ metadata:
검색어가 너무 넓으면 먼저 의도를 좁힌다.
- 권장 질문: `어떤 용도/예산/브랜드/용량을 우선할까요? 예: 생수 2L / 무라벨 / 로켓배송`
- URL이 이미 있으면 바로 상세/리뷰 단계로 간다.
- 권장 질문: `어떤 용도/예산/브랜드/용량을 우선할까요?`
### 2. Probe the environment
### 2. Bootstrap and check the tool contract
가능 여부를 먼저 확인한다.
래퍼는 기본적으로 `~/.cache/k-skill/coupang_partners`에 upstream 저장소를 clone한다. 이미 clone되어 있으면 그대로 사용하고, 최신화가 필요할 때만 `--update`를 붙인다.
```js
const { probeAutomation } = require("coupang-product-search")
const probe = await probeAutomation("생수")
console.log(probe)
```bash
python3 coupang-product-search/scripts/coupang_partners_mcp.py tools
python3 coupang-product-search/scripts/coupang_partners_mcp.py init
```
- `blocked: true` 면 direct fetch / headless browser 가 막힌 것이다.
- `browserFetchHtml` 을 주입하지 않은 clean checkout 에서는 `browser === null` 이고, `browser` 값은 수동/외부 Playwright-core runner 같은 `browserFetchHtml` 주입 경로를 붙였을 때만 채워진다.
- 이 경우 **브라우저 세션에서 캡처한 HTML** 또는 사용자가 제공한 쿠팡 상품 URL/HTML 이 필요하다고 설명한다.
기존 checkout을 명시하거나 CI/검증에서 네트워크 clone을 막으려면:
### 3. Search products when browser HTML is available
```js
const { searchProducts } = require("coupang-product-search")
const search = await searchProducts("생수", {
fetchHtml: browserCapture
})
console.log(search.items)
```bash
python3 coupang-product-search/scripts/coupang_partners_mcp.py \
--repo-dir /path/to/coupang_partners \
--no-clone \
tools
python3 coupang-product-search/scripts/coupang_partners_mcp.py \
--repo-dir /path/to/coupang_partners \
--no-clone \
init
```
정리 우선순위:
### 3. Call tools
- 제목 정확도
- 가격
- 로켓배송/무료배송/와우가 등 배지
- 평점/리뷰 수
- 판매자명
구체적인 사용자 요청에 맞춰 upstream CLI 명령을 호출한다. 결과는 `ok`, `data.tool`, `data.payload`, `data.result`를 포함하는 JSON으로 반환된다.
### 4. Read product detail
```bash
# 일반 검색 (키 없이도 hosted fallback으로 작동)
python3 coupang-product-search/scripts/coupang_partners_mcp.py search "32인치 4K 모니터"
```js
const { getProductDetail } = require("coupang-product-search")
# 로켓배송 필터
python3 coupang-product-search/scripts/coupang_partners_mcp.py rocket "에어팟"
const detail = await getProductDetail(search.items[0].productUrl, {
fetchHtml: browserCapture
})
# 가격대 검색
python3 coupang-product-search/scripts/coupang_partners_mcp.py budget "키보드" --max-price 100000
console.log(detail)
# 비교
python3 coupang-product-search/scripts/coupang_partners_mcp.py compare "아이패드 vs 갤럭시탭"
# 골드박스 (운영자 키가 필요한 upstream 경로)
python3 coupang-product-search/scripts/coupang_partners_mcp.py goldbox
```
반환값에는 보통 아래 정보가 포함된다.
### 4. (optional) hosted fallback 강제
- 상품명
- 가격 / 할인 전 가격 / 단위당 가격
- 판매자명
- 배송 배지 / 도착 문구
- 필수 표기 정보
- 리뷰 요약
운영자 키가 있는 상태에서도 hosted fallback 경로를 점검하고 싶으면 `OPENCLAW_SHOPPING_FORCE_HOSTED=1`만 추가하면 된다. `OPENCLAW_SHOPPING_CLIENT_ID`는 upstream이 보내는 기본값 `openclaw-skill`이 현재 Retention Corp allowlist에 등록된 값이므로 별도로 설정하지 않는다.
### 5. Read reviews
```bash
export OPENCLAW_SHOPPING_FORCE_HOSTED=1
python3 coupang-product-search/scripts/coupang_partners_mcp.py search "에어팟"
```
```js
const { getProductReviews } = require("coupang-product-search")
## Available tools
const reviews = await getProductReviews(detail.productUrl, {
fetchHtml: browserCapture
})
| 도구명 | CLI 명령 | 기능 | 파라미터 예시 |
|--------|----------|------|-------------|
| `search_coupang_products` | `search` | 일반 상품 검색 | `"생수"` |
| `search_coupang_rocket` | `rocket` | 로켓배송만 필터링 | `"에어팟"` |
| `search_coupang_budget` | `budget` | 가격대 범위 검색 | `"키보드" --max-price 100000` |
| `compare_coupang_products` | `compare` | 상품 비교표 생성 | `"아이패드 vs 갤럭시탭"` |
| `get_coupang_recommendations` | `recommendations` | 인기 검색어 제안 | `--category 전자제품` |
| `get_coupang_seasonal` | `seasonal` | 계절/상황별 추천 | `"설날 선물"` |
| `get_coupang_best_products` | `best` | 카테고리별 베스트 | `--category-id 1016` |
| `get_coupang_goldbox` | `goldbox` | 당일 특가 정보 | `--limit 10` |
console.log(reviews.summary)
console.log(reviews.items.slice(0, 3))
주의: `get_coupang_goldbox``get_coupang_best_products`는 upstream 기준 Coupang Partners API 권한이 필요한 경로이므로, 키가 없는 환경에서는 실패할 수 있다. 이런 경우 에러 메시지를 그대로 전달하고 hosted fallback이 커버하는 `search`/`rocket`/`budget`/`compare` 경로로 우회 제안한다.
## Response format
upstream CLI는 JSON을 출력한다. `data.result` 안의 상품 배열 또는 도구별 객체를 읽고, 답변에서는 로켓배송(rocket)과 일반배송(normal)을 구분한다.
```json
{
"ok": true,
"data": {
"session_id": "session-...",
"tool": "search_coupang_products",
"payload": {
"jsonrpc": "2.0",
"result": {
"content": [
{"type": "text", "text": "[...]"}
]
}
},
"result": []
}
}
```
사용자에게 보여줄 때는 다음처럼 짧게 정리한다.
```
## rocket (상위 후보)
1) LG전자 4K UHD 모니터
가격: 397,750원 (참고용)
보러가기: https://a.retn.kr/s/... # hosted fallback shortlink
또는: https://link.coupang.com/a/... # operator HMAC 경로 딥링크
## normal (상위 후보)
1) 삼성전자 QHD 오디세이 G5 게이밍 모니터
가격: 283,000원 (참고용)
보러가기: https://a.retn.kr/s/...
```
## Response policy
- probe 가 막혔으면 **막혔다고 먼저 말한다.**
- 브라우저 HTML 없이 live 결과를 단정하지 않는다.
- 후보가 여러 개면 상위 3개만 짧게 비교한다.
- 리뷰는 전체 평균/개수 + 대표 리뷰 2~3개 정도만 요약한다.
- 후보가 여러 개면 상위 3~5개만 짧게 비교한다.
- 로켓배송/일반배송 구분을 명시한다.
- 가격/품절/배송 정보는 실시간 변동될 수 있음을 안내한다.
- upstream checkout, 권한, Coupang Partners 환경변수 문제로 실패하면 실패 원인과 재시도/설정 방법을 짧게 안내한다.
- **Affiliate 고지(필수)**: 응답에 포함되는 shortlink(`https://a.retn.kr/s/...`)와 직접 coupang 딥링크(`link.coupang.com/...?lptag=AF...`)는 Retention Corp의 쿠팡 파트너스(affiliate) 채널로 트래킹된다. upstream이 돌려주는 `disclosure` 문자열(`"파트너스 활동을 통해 일정액의 수수료를 제공받을 수 있음"`)이 있으면 그대로 노출하고, 없으면 같은 취지의 고지를 답변 말미에 덧붙인다.
## Done when
- 검색어 또는 상품 URL이 확보되었다.
- probe 결과를 확인했다.
- 가능하면 검색 → 상세 → 리뷰를 순서대로 정리했다.
- 차단되면 차단 사실과 필요한 다음 입력(브라우저 HTML / 상품 URL)을 분명히 설명했다.
- `tools``init` 또는 실제 명령으로 retention-corp/coupang_partners 로컬 MCP 계약을 확인했다.
- 검색 결과가 로켓배송/일반배송으로 구분되어 정리되었다.
- 사용자 니즈에 맞는 추천 TOP 3이 제시되었다.
- 가격/배송 정보와 변동 가능성 안내가 포함되었다.
- affiliate 고지(disclosure)가 답변에 포함되었다.

View file

@ -0,0 +1,146 @@
#!/usr/bin/env python3
"""Bootstrap and run retention-corp/coupang_partners Coupang MCP tools.
The k-skill repo intentionally does not vendor the third-party implementation.
This wrapper keeps the skill pointed at the approved upstream repository, clones it
into a user cache when needed, and then delegates to its local MCP-compatible CLI.
"""
from __future__ import annotations
import argparse
import os
import pathlib
import subprocess
import sys
from typing import Sequence
UPSTREAM_REPO_URL = "https://github.com/retention-corp/coupang_partners.git"
DEFAULT_MCP_ENDPOINT = "local://coupang-mcp"
DEFAULT_REPO_DIR = pathlib.Path(os.getenv("COUPANG_PARTNERS_REPO_DIR", "~/.cache/k-skill/coupang_partners")).expanduser()
UPSTREAM_CLI = pathlib.Path("bin") / "coupang_mcp.py"
class BootstrapError(RuntimeError):
"""Raised when the upstream checkout cannot be prepared."""
def parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Run the retention-corp/coupang_partners local Coupang MCP-compatible CLI.",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=(
"Examples:\n"
" coupang_partners_mcp.py tools\n"
" coupang_partners_mcp.py init\n"
" coupang_partners_mcp.py search 생수\n"
" coupang_partners_mcp.py budget 키보드 --max-price 100000\n"
"\n"
"Honored upstream environment variables (forwarded as-is):\n"
" COUPANG_ACCESS_KEY, COUPANG_SECRET_KEY\n"
" Operator Coupang Partners API credentials. When set, upstream\n"
" uses the local HMAC-signed Coupang Partners path.\n"
" OPENCLAW_SHOPPING_CLIENT_ID\n"
" Allowlisted client id for the hosted fallback. upstream sends\n"
" openclaw-skill by default, which is the value currently on the\n"
" Retention Corp allowlist; k-skill does not override this.\n"
" OPENCLAW_SHOPPING_FORCE_HOSTED=1\n"
" Force the hosted fallback even when Coupang keys are present.\n"
" OPENCLAW_SHOPPING_BASE_URL\n"
" Override the hosted backend base URL. Default upstream target\n"
" is https://a.retn.kr and /v1/public/assist is the public entry.\n"
"\n"
"When both COUPANG_ACCESS_KEY and COUPANG_SECRET_KEY are missing,\n"
"upstream falls back to the hosted Retention Corp backend so this\n"
"skill keeps working without Coupang Partners credentials."
),
)
parser.add_argument(
"--repo-dir",
default=str(DEFAULT_REPO_DIR),
help="Checkout directory for retention-corp/coupang_partners (default: %(default)s).",
)
parser.add_argument(
"--no-clone",
action="store_true",
help="Do not clone the upstream repository if it is missing; fail with setup guidance instead.",
)
parser.add_argument(
"--update",
action="store_true",
help="Run git pull --ff-only in an existing upstream checkout before delegating.",
)
parser.add_argument(
"upstream_args",
nargs=argparse.REMAINDER,
help="Arguments passed to bin/coupang_mcp.py, for example: tools, search 생수, rocket 에어팟.",
)
args = parser.parse_args(argv)
if args.upstream_args and args.upstream_args[0] == "--":
args.upstream_args = args.upstream_args[1:]
if not args.upstream_args:
parser.error("missing upstream command; try: tools, init, search <keyword>, rocket <keyword>, budget <keyword>")
return args
def upstream_cli_path(repo_dir: pathlib.Path) -> pathlib.Path:
return repo_dir / UPSTREAM_CLI
def ensure_repo(repo_dir: pathlib.Path, *, clone: bool = True, update: bool = False) -> pathlib.Path:
cli_path = upstream_cli_path(repo_dir)
if cli_path.exists():
if update:
run_checked(["git", "-C", str(repo_dir), "pull", "--ff-only"], "failed to update upstream checkout")
return cli_path
if repo_dir.exists():
raise BootstrapError(
f"{repo_dir} exists but does not look like retention-corp/coupang_partners "
f"(missing {UPSTREAM_CLI}). Recreate it with: git clone {UPSTREAM_REPO_URL} {repo_dir}"
)
if not clone:
raise BootstrapError(
f"Missing retention-corp/coupang_partners checkout at {repo_dir}. "
f"Create it with: git clone {UPSTREAM_REPO_URL} {repo_dir}"
)
repo_dir.parent.mkdir(parents=True, exist_ok=True)
run_checked(["git", "clone", "--depth", "1", UPSTREAM_REPO_URL, str(repo_dir)], "failed to clone upstream checkout")
if not cli_path.exists():
raise BootstrapError(f"Cloned {UPSTREAM_REPO_URL}, but {UPSTREAM_CLI} was not found in {repo_dir}")
return cli_path
def run_checked(command: Sequence[str], context: str) -> None:
try:
subprocess.run(command, check=True)
except FileNotFoundError as exc:
raise BootstrapError(f"{context}: required executable not found: {command[0]}") from exc
except subprocess.CalledProcessError as exc:
raise BootstrapError(f"{context}: {exc}") from exc
def build_command(cli_path: pathlib.Path, upstream_args: Sequence[str]) -> list[str]:
return [sys.executable, str(cli_path), *upstream_args]
def main(argv: Sequence[str] | None = None) -> int:
args = parse_args(argv)
repo_dir = pathlib.Path(args.repo_dir).expanduser().resolve()
try:
cli_path = ensure_repo(repo_dir, clone=not args.no_clone, update=args.update)
except BootstrapError as exc:
print(f"coupang_partners_mcp.py: {exc}", file=sys.stderr)
return 2
env = os.environ.copy()
env.setdefault("COUPANG_MCP_ENDPOINT", DEFAULT_MCP_ENDPOINT)
completed = subprocess.run(build_command(cli_path, args.upstream_args), env=env)
return int(completed.returncode)
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -0,0 +1,210 @@
---
name: court-auction-notice-search
description: Browse 대법원경매정보(courtauction.go.kr) 부동산 매각공고 by 매각기일·법원·기일/기간 입찰, expand each notice into 사건번호·용도·주소·감정평가액·최저매각가, search property items by free conditions(지역·용도·가격·면적·유찰횟수), and look up a case directly by 법원+사건번호. Read-only, slow-by-design (~2s/call) to avoid IP blocks.
license: MIT
metadata:
category: real-estate
locale: ko-KR
phase: v1
---
# Court Auction Notice Search
## What this skill does
대한민국 법원이 운영하는 공식 **법원경매정보** 사이트(`courtauction.go.kr`) 의 매각공고와 사건정보를 에이전트가 활용할 수 있는 JSON 형태로 변환해서 돌려준다.
- 공식 OPEN API가 없어 사이트 내부의 WebSquare JSON XHR endpoint를 그대로 호출한다.
- 1차 transport 는 직접 HTTP다. Workflow C 자유검색에서 raw-HTTP WAF성 HTTP 400이 날 때만 Playwright fallback 으로 전환하며, 명시적 차단(`BLOCKED`/`ipcheck=false`)은 기본적으로 중단한다 (`rebrowser-playwright` 또는 `playwright-core` 가 있을 때만).
- 사이트는 **IP 단위 봇 차단** 이 매우 공격적이다 (16회/30초 정도면 1시간 차단). 이 패키지는 호출 간 최소 2초 jitter, 세션당 호출 budget(기본 10회), `data.ipcheck === false` 즉시 throw 로 보수적으로 동작한다.
- **참고용 도구**다. 실제 입찰 전에는 반드시 법원 원문 매각공고를 다시 확인해야 한다.
## When to use
- "오늘/내일 어디서 부동산 경매 열려?"
- "서울중앙지방법원 2026-04-27 매각공고 보여줘"
- "기일입찰 vs 기간입찰만 나눠서 보여줘"
- "이 매각공고 안의 사건번호/용도/주소/감정평가액 다 보여줘"
- "사건번호 2024타경100001 진행 상황 알려줘"
- "서울 강남구 아파트 최저가 5억 이하 유찰 1회 이상 물건 찾아줘"
- "법원사무소 코드 표 줘"
## When not to use
- 동산(자동차·중기) 경매 (이번 v1 범위 밖)
- 특정 매각기일 날짜의 모든 법원 일정을 한 번에 (Workflow D 별도 follow-up 이슈)
- 매각물건 사진(전경/개황/내부) URL 노출 (별도 follow-up 이슈)
- 매각물건명세서 / 현황조사서 / 감정평가서 PDF 다운로드 (별도 follow-up 이슈)
- 입찰서 자동 작성·자동 제출 (지원하지 않는다, 입찰은 반드시 법원에서 사람이 직접)
## Inputs
- `date` — 매각기일 월(YYYY-MM 또는 YYYYMM) 또는 특정일(YYYY-MM-DD 또는 YYYYMMDD). 필수. 실제 사이트 검색 버튼은 월(YYYYMM) 단위로 조회하므로 특정일 입력은 월 조회 후 해당 일자만 필터링한다.
- `courtCode` — 법원사무소코드 (예: `B000210` = 서울중앙지방법원). 비우면 전체. `getCourtCodes()` 또는 `codes courts` 로 받아온다.
- `bidType``date` (= 기일입찰, code 000331) 또는 `period` (= 기간입찰, code 000332). 빈값이면 둘 다.
- `caseNumber` — 사건번호. `2024타경100001` 형식 권장. `2024-100001` 도 받아서 `2024타경100001` 로 정규화한다.
## Mandatory honest framing
이 스킬은 사용자에게 다음 사실을 항상 알려야 한다.
1. 데이터는 법원경매정보 사이트의 공개 정보를 그대로 옮긴 것이며 **실제 입찰 전에 법원 원문을 재확인**해야 한다.
2. 사이트는 자동화 호출에 매우 민감해서 **빠른 연속 조회 시 IP가 1시간 차단**될 수 있다. 차단되면 같은 IP에서는 약 1시간을 기다려야 한다.
3. 가격(감정평가액·최저매각가격)·매각기일·매각장소는 **공고 시점 기준** 이며 정정·취하·연기로 변경될 수 있다 (`correctionCount`, `cancellationCount` 필드를 참고).
4. 본 스킬은 **read-only**다. 입찰 자체는 자동화하지 않는다.
## Official surfaces
- 법원경매정보 메인: `https://www.courtauction.go.kr`
- 부동산매각공고 진입: `https://www.courtauction.go.kr/pgj/index.on?w2xPath=/pgj/ui/pgj100/PGJ143M01.xml&pgjId=143M01`
- 경매사건검색 진입: `https://www.courtauction.go.kr/pgj/index.on?w2xPath=/pgj/ui/pgj100/PGJ159M00.xml&pgjId=159M00`
- 직접 호출 endpoint (이 스킬이 사용하는 것):
- `POST /pgj/pgj143/selectRletDspslPbanc.on` — 매각공고 목록
- `POST /pgj/pgj143/selectRletDspslPbancDtl.on` — 매각공고 상세 (사건/물건 펼치기)
- `POST /pgj/pgj15A/selectAuctnCsSrchRslt.on` — 사건 단건 조회
- `POST /pgj/pgjsearch/searchControllerMain.on` — 물건 자유 조건검색 (PGJ151F00 → PGJ151M01)
- `POST /pgj/pgjComm/selectCortOfcCdLst.on` — 법원사무소코드 전체
## Workflow A — 매각공고 → 사건/물건 펼치기
1. 사용자에게 **매각기일(YYYY-MM-DD)** 과 (선택) 법원·입찰구분을 받는다.
2. `searchSaleNotices({ date, courtCode, bidType })` 호출 → 그 날·그 법원의 매각공고 카드 목록.
3. 사용자가 카드를 고르면 카드 객체(또는 `raw`)를 그대로 `getSaleNoticeDetail(notice)` 에 넘긴다.
4. 응답의 `items[]``caseNumber`, `usage`, `address`, `appraisedPrice`, `minimumSalePrice`, `remarks` 를 가진다 (이슈 본문이 명시한 4필드 모두 포함).
5. 가격은 원 단위 정수다. 사용자에게 보여줄 때는 한국식 천단위 콤마 + 억/만 단위 환산을 같이 제시한다.
## Workflow B — 사건번호 직접 조회
1. 사용자에게 **법원사무소코드** + **사건번호(2024타경100001)** 를 받는다.
2. `getCaseByCaseNumber({ courtCode, caseNumber })` 호출.
3. `found:false / status:204` 면 사건이 존재하지 않거나 비공개. 사건번호 형식·법원이 맞는지 사용자에게 다시 확인한다.
4. `found:true``caseInfo`(사건명·접수일·청구액·재판부·진행상태), `items[]`(매각목적물 — 주소/배당요구종기), `schedule[]`(매각기일별 최저가/감정가/결과), `claimDeadline`, `relatedCases`, `stakeholders` 가 채워진다.
## Workflow C — 부동산 물건 자유 조건검색
1. 사용자의 조건을 `searchProperties()` 입력으로 매핑한다.
- `region: { sido, sigungu, dong }` — 코드 또는 대표 정적 sido 코드테이블의 한국어명. 지역을 주면 지번주소 검색(`cortStDvs:"2"`)으로, 지역이 없으면 매각공고 모드(`cortStDvs:"1"`)로 조회한다. 시군구/읍면동은 정적 표가 없으므로 코드로 직접 전달(예: `{ sido:"11", sigungu:"11680", dong:"11680101" }`)한다.
- `usage: { large, medium, small }` — 용도 대/중/소분류 코드(5자리, 예: 건물=`20000`) 또는 대분류 한국어명(`토지`/`건물`/`차량및운송장비`/`기타`).
- `priceRange` — 최저매각가격 원 단위 `{ min, max }` (실수 허용)
- `appraisedPriceRange` — 감정평가액 원 단위 `{ min, max }` (실수 허용)
- `saleDate``{ from, to }`
- `flbdCount` — 유찰횟수 `{ min, max }` **정수만**
- `area` — 면적(㎡) `{ min, max }` (실수 허용)
- `pageSize` — 페이지당 결과 수, upstream PGJ151 드롭다운에서 확인된 `10`/`20`/`50`/`100` 중 하나(기본 10). `1` 등 임의 값은 live endpoint 가 HTTP 400을 반환하므로 로컬에서 거부한다.
2. `searchProperties({ ... })` 호출 → `POST /pgj/pgjsearch/searchControllerMain.on`.
- 1차로 direct HTTP 시도. Workflow C raw-HTTP WAF의 HTTP 400을 만나면 자동으로 Playwright fallback 으로 재시도한다. fallback 을 끄려면 `{ fallback: false }`. `BLOCKED`(`ipcheck=false`)는 사이트의 명시적 차단 신호이므로 기본적으로 즉시 중단하며, 사용자가 위험을 이해하고 명시적으로 `{ fallbackOnBlocked: true }` 를 준 경우에만 재시도한다.
3. 응답의 `items[]` 는 핵심 raw 컬럼을 영문 키로 정규화한다:
- `saNo``caseNumber`, `srnSaNo`/`printCsNo``displayCaseNumber`
- `mokmulSer`/`maemulSer``itemNumber`
- `hjguSido + hjguSigu + hjguDong + daepyoLotno + buldNm``address`
- `gamevalAmt``appraisedPrice`, `minmaePrice``minimumSalePrice`
- `yuchalCnt``flbdCount`, `mulStatcd``statusCode`, `jinstatCd``progressStatusCode`
- `boCd``courtCode`, `jiwonNm``courtName`, `jpDeptNm``judgeDeptName`
- `lclsUtilCd/mclsUtilCd/sclsUtilCd``usageCodes.{large,medium,small}`
- `srchHjguSidoCd/SiguCd/DongCd``regionCodes.{sido,sigungu,dong}`
- `xCordi/yCordi``coordinates`, `wgs84Xcordi/Ycordi``coordinatesWgs84`
- `buldList/areaList/jimokList``buildingList/areaList/landCategoryList`
- `pjbBuldList``propertyDescription`, `mulBigo``remarks`
4. `getUsageCodes()` 는 4개 대분류(`10000=토지`, `20000=건물`, `30000=차량및운송장비`, `40000=기타`)와 일부 대표 중/소분류를 정적으로 반환한다. `getRegionCodes()` 는 19개 시도 + 코드만 반환한다. 시군구/읍면동은 upstream cascade XHR이 안정적이지 않아 정적 표에 포함하지 않으며 raw 코드를 그대로 전달하면 된다. 알 수 없는 값은 fail-open으로 통과한다.
5. **Same-name usage codes 보호**: `resolveUsageCode("아파트", "large")` 처럼 입력 이름이 다른 level 에만 존재하면, 같은 이름의 medium/small 코드를 잘못 리턴하지 않고 fail-open(원문 통과)한다.
## Throttling and call-budget rules
- 호출 간 최소 2초 (기본). 더 늘리려면 `--min-delay-ms 3000`.
- 기본 세션 budget 은 **10회**. 더 많은 조회가 필요하면 새 세션을 열거나 (`new CourtAuctionHttpClient`) `maxCallsPerSession` 을 명시적으로 늘린다.
- 차단(`data.ipcheck === false`)을 만나면 `BLOCKED` 에러를 즉시 throw 하고 멈춘다. 자동 retry 하지 않는다 (차단 연장 위험).
- 차단된 IP는 **약 1시간** 후 자연 복구된다. 그 사이에는 다른 IP/네트워크에서 작업하거나 사람이 브라우저로 사이트에 접속해서 차단 해제 화면을 거친다.
- **Workflow C 자유검색은 사이트 WAF 가 raw HTTP 호출을 더 엄격하게 차단**한다. `searchProperties()` 는 1차 direct HTTP에서 WAF성 HTTP 400을 만났을 때만 Playwright fallback 으로 재시도한다. 명시적 차단(`BLOCKED`/`ipcheck=false`)은 기본적으로 즉시 중단하며, 사용자가 위험을 이해하고 `fallbackOnBlocked:true` 를 준 경우에만 재시도한다. Playwright fallback 모듈(`rebrowser-playwright` 또는 `playwright-core`)이 없으면 첫 HTTP 400 실패가 그대로 throw 된다.
- searchProperties 는 같은 Playwright 클라이언트로 연속 호출하면 **10~15회 간격 호출에서 안정**하다. 그 이상 burst 호출이 필요하면 호출 사이 3~5초 sleep 을 두고 새 클라이언트를 열어라.
## Node.js example
```js
const {
searchSaleNotices,
getSaleNoticeDetail,
getCaseByCaseNumber,
getCourtCodes
} = require("court-auction-notice-search");
async function main() {
const courts = await getCourtCodes();
console.log(`법원사무소 ${courts.count}개 로드됨`);
const notices = await searchSaleNotices({
date: "2026-04-27",
courtCode: "B000210",
bidType: "date"
});
console.log(`서울중앙지방법원 매각공고 ${notices.count}건`);
if (notices.items.length > 0) {
const detail = await getSaleNoticeDetail(notices.items[0]);
for (const item of detail.items) {
console.log(
`${item.caseNumber} (${item.usage}) — 감정 ${item.appraisedPrice}원 / 최저 ${item.minimumSalePrice}원`
);
console.log(` 주소: ${item.address}`);
}
}
const caseInfo = await getCaseByCaseNumber({
courtCode: "B000210",
caseNumber: "2024타경100001"
});
if (caseInfo.found) {
console.log(`사건명: ${caseInfo.caseInfo.caseName}`);
console.log(`매각기일 횟수: ${caseInfo.schedule.length}`);
}
}
main().catch((error) => {
if (error.code === "BLOCKED") {
console.error("[BLOCKED] 사이트가 1시간 차단했습니다. 다른 IP에서 다시 시도하거나 1시간 뒤 재시도하세요.");
} else {
console.error(error);
}
process.exitCode = 1;
});
```
## CLI example
```bash
# 1. 법원사무소 코드표
court-auction-notice-search codes courts --pretty | head -40
# 2. 입찰구분 (정적 코드)
court-auction-notice-search codes bid-types --pretty
court-auction-notice-search codes usages --pretty
court-auction-notice-search codes regions --pretty
# 3. 매각공고 목록
court-auction-notice-search notices --date 2026-04 --court-code B000210 --bid-type date --pretty
# 4. 매각공고 상세 — list 응답의 row 의 raw 필드를 그대로 detail 호출에 사용한다.
# (CLI 단발 호출에서는 list -> detail 으로 결과를 파이프할 수 있도록 jq 등을 함께 사용)
# 5. 사건번호 직접 조회
court-auction-notice-search case --court-code B000210 --case-number "2024타경100001" --pretty
# 6. 자유 조건검색
court-auction-notice-search search --sido 서울특별시 --sigungu 11680 --usage-large 건물 --usage-medium 21200 \
--price-min 100000000 --price-max 500000000 --sale-from 2026-05-01 --sale-to 2026-05-20 --pretty
```
## Block / Error handling
- `error.code === "BLOCKED"``data.ipcheck === false`. 1시간 대기 후 다른 IP에서 재시도. 사용자에게 차단 사실과 대기 안내를 그대로 전달한다.
- `error.code === "BUDGET_EXCEEDED"` — 세션 budget 초과. 의도적인 안전장치다. 정말 필요하면 `--max-calls 20` 같이 늘리지만 차단 위험을 함께 안내한다.
- `error.code === "UPSTREAM_ERROR"` — 사이트가 일반적인 에러를 돌려준 경우. 세션 만료 또는 잘못된 jdbnCd 가 가장 흔한 원인. warmup 부터 다시.
- `error.code === "NETWORK_ERROR"` — 타임아웃/연결 실패.
- `error.code === "PLAYWRIGHT_UNAVAILABLE"` — Playwright fallback 을 명시적으로 쓰려는데 모듈이 깔려있지 않음. `npm i rebrowser-playwright` 또는 `npm i playwright-core` 로 해결.
## Done when
- 사용자에게 IP 차단 위험과 "참고용·실제 입찰 전 법원 원문 재확인" 고지를 했다.
- 매각공고를 펼쳐서 `caseNumber/usage/address/appraisedPrice/minimumSalePrice` 가 채워진 JSON을 돌려줬다.
- 사건번호로 직접 조회한 경우, `found:false` 일 때 사용자가 후속 조치를 알 수 있도록 안내했다.
- 차단 발생 시 자동 재시도하지 않고 즉시 멈췄다.
- 작업 후 호출 budget 이 남아있는지 사용자에게 알려서 추가 호출 여지를 명시했다.

101
daangn-cars-search/SKILL.md Normal file
View file

@ -0,0 +1,101 @@
---
name: daangn-cars-search
description: 당근중고차 공개 웹 데이터 표면으로 지역·가격 조건 기반 차량 검색과 상세 조회를 수행한다. 문의/구매 자동화는 제외한다.
license: MIT
metadata:
category: automotive
locale: ko-KR
phase: v1
---
# Daangn Cars Search
## What this skill does
당근중고차 공개 Remix `_data` JSON route를 사용해 차량 목록과 상세 정보를 읽기 전용으로 조회한다.
최종 사용자는 자연어로 요청해도 되고, 필요하면 아래의 Python helper를 직접 실행한다. 외부 패키지나 k-skill-proxy 없이 Python 표준 라이브러리만 사용한다.
## When to use
- "당근중고차 합정동 레이 찾아봐"
- "당근에서 천만원 이하 중고차 검색"
- "이 당근 중고차 URL 상세 봐줘"
## When not to use
- 당근 계정 로그인이 필요한 작업
- 채팅, 찜, 거래 제안, 문의, 지원, 예약, 계약, 구매처럼 상대방 또는 계정에 영향을 주는 작업
- CAPTCHA/봇 차단/로그인벽 우회가 필요한 작업
## Prerequisites
- 인터넷 연결
- Python 3.9+
- 이 저장소 루트에서 실행하거나, 스크립트 경로를 절대경로로 지정
## Data surfaces
- Region resolver: `https://www.daangn.com/kr/api/v1/regions/keyword?keyword=<지역명>`
- Search `_data`: `/kr/cars/?in=<지역명>-<id>&onlyOnSale=1&_data=routes/kr.cars._index`
- Detail `_data`: `<car-url>?_data=routes%2Fkr.cars.%24car_post_id`
## Workflow
1. 사용자 요청에서 키워드, 지역명, 가격/거래 유형 같은 필터를 추출한다.
2. 지역명이 있으면 region resolver로 내부 region id를 찾는다.
3. 목록 검색은 category별 `_data` route를 호출한다.
4. 상세 URL이 주어지면 category별 detail route 또는 공개 HTML 메타를 조회한다.
5. 결과를 짧게 정리하되 source URL과 적용 지역을 보존한다.
## Commands
```bash
python3 daangn-cars-search/scripts/daangn_cars.py search "레이" --region "합정동" --limit 5
python3 daangn-cars-search/scripts/daangn_cars.py search --region "합정동" --price-max 10000000 --limit 5
python3 daangn-cars-search/scripts/daangn_cars.py detail "https://www.daangn.com/kr/cars/.../"
```
## Output fields
- title, price, price_text, region, status, driveDistance, carData, chatRoomCount, url
- detail: carPost 원문
## Region handling
지역 필터가 있으면 먼저 당근 지역 검색 API로 내부 지역 id를 해석한다.
```text
https://www.daangn.com/kr/api/v1/regions/keyword?keyword=합정동
→ 서울특별시 마포구 합정동, id=231
→ in=합정동-231
```
동일한 지명이 여러 지역에 있으면 다음 우선순위로 선택한다.
1. 사용자가 입력한 문자열이 `name`, `name1`, `name2`, `name3` 중 하나와 정확히 맞는 후보
2. 서울 `depth=3` 동 단위 후보
3. 첫 번째 후보
응답에는 항상 `effective_region` 또는 실제 적용된 지역명을 포함한다. 사용자의 의도와 다른 지역으로 보이면 결과를 단정하지 말고 후보 확인을 요청한다. IP/쿠키 기본 위치에 의존하지 않는다.
## Safety and scope
- 읽기 전용 검색/상세 조회만 수행한다.
- 로그인, 채팅, 찜, 거래 제안, 지원, 문의, 예약, 계약, 구매 자동화는 하지 않는다.
- 공개 웹 표면이 바뀌거나 빈 응답/봇 차단/로그인벽이 나오면 실패 모드로 보고하고 우회하지 않는다.
- 결과는 실시간 재고/공고 상태와 달라질 수 있으므로 source URL을 함께 제시한다.
## Failure modes
- 당근의 Remix route 이름이나 JSON shape가 변경되면 `_data` 조회가 실패할 수 있다.
- 지역명이 넓거나 중복되면 다른 행정동이 선택될 수 있다.
- 검색 결과가 0건이어도 사이트 정책/지역 기본값/필터 조합 때문일 수 있으므로 source URL을 보존한다.
- 상세 조회는 삭제/종료/비공개 전환된 글에서 실패할 수 있다.
## Done when
- 지역명이 있으면 지역 id를 해석하고 적용했다.
- 목록 조회 또는 상세 조회를 최소 1회 수행했다.
- 결과에 source URL과 effective region을 포함했다.
- 인증/거래성 액션은 수행하지 않았다.

View file

@ -0,0 +1,72 @@
#!/usr/bin/env python3
import argparse, json, re, sys, urllib.parse, urllib.request
from html import unescape
HEADERS = {"User-Agent":"Mozilla/5.0", "Accept":"application/json,text/html;q=0.9,*/*;q=0.8"}
def fetch_json(url):
req = urllib.request.Request(url, headers=HEADERS)
with urllib.request.urlopen(req, timeout=25) as r:
return json.load(r)
def fetch_text(url):
req = urllib.request.Request(url, headers={"User-Agent":"Mozilla/5.0", "Accept":"text/html"})
with urllib.request.urlopen(req, timeout=25) as r:
return r.read().decode('utf-8', 'ignore')
def won(v):
if v in (None, ''): return '-'
try: return f"{int(float(v)):,}"
except Exception: return str(v)
def resolve_region(region):
if not region: return None
url = 'https://www.daangn.com/kr/api/v1/regions/keyword?keyword=' + urllib.parse.quote(region)
data = fetch_json(url)
locs = data.get('locations') or []
if not locs: raise SystemExit(f'지역 후보 없음: {region}')
# Exact dong/name match first, then Seoul depth-3, then first candidate.
exact = [x for x in locs if region in (x.get('name'), x.get('name1'), x.get('name2'), x.get('name3'))]
seoul = [x for x in locs if x.get('name1') == '서울특별시' and x.get('depth') == 3]
sel = (exact or seoul or locs)[0]
return sel
def region_param(sel):
return urllib.parse.quote(f"{sel['name']}-{sel['id']}")
def absolute(href):
if not href: return ''
if href.startswith('http'): return href
return 'https://www.daangn.com' + href
def print_json(obj):
print(json.dumps(obj, ensure_ascii=False, indent=2))
def cmd_search(args):
sel=resolve_region(args.region) if args.region else None
params=[]
if sel: params.append(('in', f"{sel['name']}-{sel['id']}"))
if args.only_on_sale: params.append(('onlyOnSale','1'))
if args.price_max: params.append(('priceMax', str(args.price_max)))
if args.price_min: params.append(('priceMin', str(args.price_min)))
params.append(('_data','routes/kr.cars._index'))
url='https://www.daangn.com/kr/cars/?'+urllib.parse.urlencode(params)
data=fetch_json(url); arr=((data.get('carAllPage') or {}).get('carPosts') or [])
if args.keyword:
arr=[a for a in arr if args.keyword.lower() in (a.get('title') or '').lower()]
arr=arr[:args.limit]
items=[{'title':a.get('title'),'price':a.get('price'),'price_text':won(a.get('price')),'region':(a.get('region') or {}).get('name'),
'status':a.get('status'),'driveDistance':a.get('driveDistance'),'carData':a.get('carData'),
'chatRoomCount':a.get('chatRoomCount'),'url':absolute(a.get('href'))} for a in arr]
print_json({'source':url,'effective_region':data.get('searchRegion') or sel,'count':len(items),'items':items})
def cmd_detail(args):
u=args.url.rstrip('/')+'/?_data=routes%2Fkr.cars.%24car_post_id'
data=fetch_json(u); print_json({'source':u,'carPost':data.get('carPost') or data})
p=argparse.ArgumentParser(description='Daangn cars read-only search/detail')
sub=p.add_subparsers(dest='cmd', required=True)
s=sub.add_parser('search'); s.add_argument('keyword', nargs='?'); s.add_argument('--region'); s.add_argument('--price-min',type=int); s.add_argument('--price-max',type=int); s.add_argument('--only-on-sale',action='store_true',default=True); s.add_argument('--limit',type=int,default=10); s.set_defaults(func=cmd_search)
d=sub.add_parser('detail'); d.add_argument('url'); d.set_defaults(func=cmd_detail)
args=p.parse_args(); args.func(args)

100
daangn-jobs-search/SKILL.md Normal file
View file

@ -0,0 +1,100 @@
---
name: daangn-jobs-search
description: 당근알바 공개 웹 데이터 표면으로 키워드·지역 기반 알바 공고 검색과 상세 조회를 수행한다. 지원/채팅 자동화는 제외한다.
license: MIT
metadata:
category: jobs
locale: ko-KR
phase: v1
---
# Daangn Jobs Search
## What this skill does
당근알바 공개 Remix `_data` JSON route로 채용/알바 공고 목록과 상세 정보를 읽기 전용으로 조회한다.
최종 사용자는 자연어로 요청해도 되고, 필요하면 아래의 Python helper를 직접 실행한다. 외부 패키지나 k-skill-proxy 없이 Python 표준 라이브러리만 사용한다.
## When to use
- "당근알바 합정동 카페 찾아봐"
- "홍대 근처 주말 알바 검색"
- "이 당근알바 공고 상세 봐줘"
## When not to use
- 당근 계정 로그인이 필요한 작업
- 채팅, 찜, 거래 제안, 문의, 지원, 예약, 계약, 구매처럼 상대방 또는 계정에 영향을 주는 작업
- CAPTCHA/봇 차단/로그인벽 우회가 필요한 작업
## Prerequisites
- 인터넷 연결
- Python 3.9+
- 이 저장소 루트에서 실행하거나, 스크립트 경로를 절대경로로 지정
## Data surfaces
- Region resolver: `https://www.daangn.com/kr/api/v1/regions/keyword?keyword=<지역명>`
- Search `_data`: `/kr/jobs/?in=<지역명>-<id>&search=<keyword>&_data=routes/kr.jobs._index`
- Detail fallback: `<job-url>` redirects to `jobs.daangn.com/job-posts/<id>` and exposes public HTML title/meta/JSON-LD. The helper first tries the legacy `_data` route and falls back to HTML meta when that route returns an empty response.
## Workflow
1. 사용자 요청에서 키워드, 지역명, 가격/거래 유형 같은 필터를 추출한다.
2. 지역명이 있으면 region resolver로 내부 region id를 찾는다.
3. 목록 검색은 category별 `_data` route를 호출한다.
4. 상세 URL이 주어지면 category별 detail route 또는 공개 HTML 메타를 조회한다.
5. 결과를 짧게 정리하되 source URL과 적용 지역을 보존한다.
## Commands
```bash
python3 daangn-jobs-search/scripts/daangn_jobs.py search "카페" --region "합정동" --limit 5
python3 daangn-jobs-search/scripts/daangn_jobs.py detail "https://www.daangn.com/kr/jobs/.../"
```
## Output fields
- title, company, region, address, salary, salaryType, workDays, workTimeStart, workTimeEnd, closed, url
- detail: `jobPost` 원문 if the `_data` route is available; otherwise public page `title`, `meta`, and `json_ld`
## Region handling
지역 필터가 있으면 먼저 당근 지역 검색 API로 내부 지역 id를 해석한다.
```text
https://www.daangn.com/kr/api/v1/regions/keyword?keyword=합정동
→ 서울특별시 마포구 합정동, id=231
→ in=합정동-231
```
동일한 지명이 여러 지역에 있으면 다음 우선순위로 선택한다.
1. 사용자가 입력한 문자열이 `name`, `name1`, `name2`, `name3` 중 하나와 정확히 맞는 후보
2. 서울 `depth=3` 동 단위 후보
3. 첫 번째 후보
응답에는 항상 `effective_region` 또는 실제 적용된 지역명을 포함한다. 사용자의 의도와 다른 지역으로 보이면 결과를 단정하지 말고 후보 확인을 요청한다. IP/쿠키 기본 위치에 의존하지 않는다.
## Safety and scope
- 읽기 전용 검색/상세 조회만 수행한다.
- 로그인, 채팅, 찜, 거래 제안, 지원, 문의, 예약, 계약, 구매 자동화는 하지 않는다.
- 공개 웹 표면이 바뀌거나 빈 응답/봇 차단/로그인벽이 나오면 실패 모드로 보고하고 우회하지 않는다.
- 결과는 실시간 재고/공고 상태와 달라질 수 있으므로 source URL을 함께 제시한다.
## Failure modes
- 당근의 Remix route 이름이나 JSON shape가 변경되면 `_data` 조회가 실패할 수 있다.
- 지역명이 넓거나 중복되면 다른 행정동이 선택될 수 있다.
- 검색 결과가 0건이어도 사이트 정책/지역 기본값/필터 조합 때문일 수 있으므로 source URL을 보존한다.
- 상세 조회는 삭제/종료/비공개 전환된 글에서 실패할 수 있다.
## Done when
- 지역명이 있으면 지역 id를 해석하고 적용했다.
- 목록 조회 또는 상세 조회를 최소 1회 수행했다.
- 결과에 source URL과 effective region을 포함했다.
- 인증/거래성 액션은 수행하지 않았다.

View file

@ -0,0 +1,98 @@
#!/usr/bin/env python3
import argparse, json, re, sys, urllib.parse, urllib.request
from html import unescape
HEADERS = {"User-Agent":"Mozilla/5.0", "Accept":"application/json,text/html;q=0.9,*/*;q=0.8"}
def fetch_json(url):
req = urllib.request.Request(url, headers=HEADERS)
with urllib.request.urlopen(req, timeout=25) as r:
body = r.read()
if not body:
raise ValueError(f'빈 JSON 응답: {url}')
return json.loads(body)
def fetch_text(url):
req = urllib.request.Request(url, headers={"User-Agent":"Mozilla/5.0", "Accept":"text/html"})
with urllib.request.urlopen(req, timeout=25) as r:
return r.read().decode('utf-8', 'ignore')
def won(v):
if v in (None, ''): return '-'
try: return f"{int(float(v)):,}"
except Exception: return str(v)
def resolve_region(region):
if not region: return None
url = 'https://www.daangn.com/kr/api/v1/regions/keyword?keyword=' + urllib.parse.quote(region)
data = fetch_json(url)
locs = data.get('locations') or []
if not locs: raise SystemExit(f'지역 후보 없음: {region}')
# Exact dong/name match first, then Seoul depth-3, then first candidate.
exact = [x for x in locs if region in (x.get('name'), x.get('name1'), x.get('name2'), x.get('name3'))]
seoul = [x for x in locs if x.get('name1') == '서울특별시' and x.get('depth') == 3]
sel = (exact or seoul or locs)[0]
return sel
def region_param(sel):
return urllib.parse.quote(f"{sel['name']}-{sel['id']}")
def absolute(href):
if not href: return ''
if href.startswith('http'): return href
return 'https://www.daangn.com' + href
def print_json(obj):
print(json.dumps(obj, ensure_ascii=False, indent=2))
def parse_html_detail(url):
html = fetch_text(url)
title = re.search(r'<title>(.*?)</title>', html, re.S)
meta = {}
for m in re.finditer(r'<meta[^>]+(?:property|name)=["\']([^"\']+)["\'][^>]+content=["\']([^"\']*)["\']', html):
key, value = m.group(1), unescape(m.group(2)).strip()
if key in ('description', 'og:title', 'og:description', 'og:image'):
meta[key] = value
json_ld = []
for m in re.finditer(r'<script[^>]*type=["\']application/ld\+json["\'][^>]*>(.*?)</script>', html, re.S):
try:
json_ld.append(json.loads(unescape(m.group(1))))
except Exception:
pass
return {
'source': url,
'title': unescape(title.group(1)).strip() if title else meta.get('og:title'),
'meta': meta,
'json_ld': json_ld[:3],
}
def cmd_search(args):
sel=resolve_region(args.region) if args.region else None
params=[]
if sel: params.append(('in', f"{sel['name']}-{sel['id']}"))
if args.keyword: params.append(('search', args.keyword))
params.append(('_data','routes/kr.jobs._index'))
url='https://www.daangn.com/kr/jobs/?'+urllib.parse.urlencode(params)
data=fetch_json(url); arr=((data.get('jobsAllPage') or {}).get('jobPosts') or [])[:args.limit]
items=[{'title':a.get('title'),'company':a.get('workplaceCompanyName'),'region':a.get('workplaceRegion'),
'address':a.get('workplaceRoadNameAddress'),'salary':a.get('salary'),'salaryType':a.get('salaryType'),
'workDays':a.get('workDays'),'workTimeStart':a.get('workTimeStart'),'workTimeEnd':a.get('workTimeEnd'),
'closed':a.get('closed'),'url':absolute(a.get('href') or a.get('jobsWebDetailUrl'))} for a in arr]
print_json({'source':url,'effective_region':data.get('searchRegion') or sel,'count':len(items),'items':items})
def cmd_detail(args):
u=args.url.rstrip('/')+'/?_data=routes%2Fkr.jobs.%24job_post_id'
try:
data=fetch_json(u)
print_json({'source':u,'jobPost':data.get('jobPost') or data})
except Exception:
detail = parse_html_detail(args.url)
detail['data_source_attempted'] = u
print_json(detail)
p=argparse.ArgumentParser(description='Daangn jobs read-only search/detail')
sub=p.add_subparsers(dest='cmd', required=True)
s=sub.add_parser('search'); s.add_argument('keyword', nargs='?'); s.add_argument('--region'); s.add_argument('--limit',type=int,default=10); s.set_defaults(func=cmd_search)
d=sub.add_parser('detail'); d.add_argument('url'); d.set_defaults(func=cmd_detail)
args=p.parse_args(); args.func(args)

View file

@ -0,0 +1,101 @@
---
name: daangn-realty-search
description: 당근부동산 공개 웹 데이터 표면으로 지역 기반 부동산 매물 검색과 상세 확인을 수행한다. 문의/예약/계약 자동화는 제외한다.
license: MIT
metadata:
category: real-estate
locale: ko-KR
phase: v1
---
# Daangn Realty Search
## What this skill does
당근부동산 목록의 공개 Remix `_data` JSON과 상세 페이지의 JSON-LD/HTML 메타를 읽어 매물 후보를 정리한다.
최종 사용자는 자연어로 요청해도 되고, 필요하면 아래의 Python helper를 직접 실행한다. 외부 패키지나 k-skill-proxy 없이 Python 표준 라이브러리만 사용한다.
## When to use
- "당근부동산 합정동 전세 찾아봐"
- "마포구 월세 매물 봐줘"
- "이 당근부동산 URL 상세 요약해줘"
## When not to use
- 당근 계정 로그인이 필요한 작업
- 채팅, 찜, 거래 제안, 문의, 지원, 예약, 계약, 구매처럼 상대방 또는 계정에 영향을 주는 작업
- CAPTCHA/봇 차단/로그인벽 우회가 필요한 작업
## Prerequisites
- 인터넷 연결
- Python 3.9+
- 이 저장소 루트에서 실행하거나, 스크립트 경로를 절대경로로 지정
## Data surfaces
- Region resolver: `https://www.daangn.com/kr/api/v1/regions/keyword?keyword=<지역명>`
- Search `_data`: `/kr/realty/?in=<지역명>-<id>&_data=routes/kr.realty._index`
- Detail: `https://realty.daangn.com/articles/<id>``application/ld+json``<title>`
## Workflow
1. 사용자 요청에서 키워드, 지역명, 가격/거래 유형 같은 필터를 추출한다.
2. 지역명이 있으면 region resolver로 내부 region id를 찾는다.
3. 목록 검색은 category별 `_data` route를 호출한다.
4. 상세 URL이 주어지면 category별 detail route 또는 공개 HTML 메타를 조회한다.
5. 결과를 짧게 정리하되 source URL과 적용 지역을 보존한다.
## Commands
```bash
python3 daangn-realty-search/scripts/daangn_realty.py search --region "합정동" --limit 5
python3 daangn-realty-search/scripts/daangn_realty.py search --region "합정동" --sales-type "APARTMENT" --trade-type "MONTHLY_RENT"
python3 daangn-realty-search/scripts/daangn_realty.py detail "https://realty.daangn.com/articles/..."
```
## Output fields
- title, salesType, trade, area, areaPyeong, totalManageCost, url
- detail: JSON-LD, page title
## Region handling
지역 필터가 있으면 먼저 당근 지역 검색 API로 내부 지역 id를 해석한다.
```text
https://www.daangn.com/kr/api/v1/regions/keyword?keyword=합정동
→ 서울특별시 마포구 합정동, id=231
→ in=합정동-231
```
동일한 지명이 여러 지역에 있으면 다음 우선순위로 선택한다.
1. 사용자가 입력한 문자열이 `name`, `name1`, `name2`, `name3` 중 하나와 정확히 맞는 후보
2. 서울 `depth=3` 동 단위 후보
3. 첫 번째 후보
응답에는 항상 `effective_region` 또는 실제 적용된 지역명을 포함한다. 사용자의 의도와 다른 지역으로 보이면 결과를 단정하지 말고 후보 확인을 요청한다. IP/쿠키 기본 위치에 의존하지 않는다.
## Safety and scope
- 읽기 전용 검색/상세 조회만 수행한다.
- 로그인, 채팅, 찜, 거래 제안, 지원, 문의, 예약, 계약, 구매 자동화는 하지 않는다.
- 공개 웹 표면이 바뀌거나 빈 응답/봇 차단/로그인벽이 나오면 실패 모드로 보고하고 우회하지 않는다.
- 결과는 실시간 재고/공고 상태와 달라질 수 있으므로 source URL을 함께 제시한다.
## Failure modes
- 당근의 Remix route 이름이나 JSON shape가 변경되면 `_data` 조회가 실패할 수 있다.
- 지역명이 넓거나 중복되면 다른 행정동이 선택될 수 있다.
- 검색 결과가 0건이어도 사이트 정책/지역 기본값/필터 조합 때문일 수 있으므로 source URL을 보존한다.
- 상세 조회는 삭제/종료/비공개 전환된 글에서 실패할 수 있다.
## Done when
- 지역명이 있으면 지역 id를 해석하고 적용했다.
- 목록 조회 또는 상세 조회를 최소 1회 수행했다.
- 결과에 source URL과 effective region을 포함했다.
- 인증/거래성 액션은 수행하지 않았다.

View file

@ -0,0 +1,85 @@
#!/usr/bin/env python3
import argparse, json, re, sys, urllib.parse, urllib.request
from html import unescape
HEADERS = {"User-Agent":"Mozilla/5.0", "Accept":"application/json,text/html;q=0.9,*/*;q=0.8"}
def fetch_json(url):
req = urllib.request.Request(url, headers=HEADERS)
with urllib.request.urlopen(req, timeout=25) as r:
return json.load(r)
def fetch_text(url):
req = urllib.request.Request(url, headers={"User-Agent":"Mozilla/5.0", "Accept":"text/html"})
with urllib.request.urlopen(req, timeout=25) as r:
return r.read().decode('utf-8', 'ignore')
def won(v):
if v in (None, ''): return '-'
try: return f"{int(float(v)):,}"
except Exception: return str(v)
def resolve_region(region):
if not region: return None
url = 'https://www.daangn.com/kr/api/v1/regions/keyword?keyword=' + urllib.parse.quote(region)
data = fetch_json(url)
locs = data.get('locations') or []
if not locs: raise SystemExit(f'지역 후보 없음: {region}')
# Exact dong/name match first, then Seoul depth-3, then first candidate.
exact = [x for x in locs if region in (x.get('name'), x.get('name1'), x.get('name2'), x.get('name3'))]
seoul = [x for x in locs if x.get('name1') == '서울특별시' and x.get('depth') == 3]
sel = (exact or seoul or locs)[0]
return sel
def region_param(sel):
return urllib.parse.quote(f"{sel['name']}-{sel['id']}")
def absolute(href):
if not href: return ''
if href.startswith('http'): return href
return 'https://www.daangn.com' + href
def print_json(obj):
print(json.dumps(obj, ensure_ascii=False, indent=2))
def norm_trade(t):
if not t: return None
return t
def cmd_search(args):
sel = resolve_region(args.region) if args.region else None
params=[]
if sel: params.append(('in', f"{sel['name']}-{sel['id']}"))
if args.sales_type: params.append(('salesType', args.sales_type))
if args.trade_type: params.append(('tradeType', args.trade_type))
if args.only_verified: params.append(('onlyVerified','true'))
params.append(('_data','routes/kr.realty._index'))
url='https://www.daangn.com/kr/realty/?'+urllib.parse.urlencode(params)
data=fetch_json(url)
arr=((data.get('realtyPosts') or {}).get('realtyPosts') or [])
if args.keyword:
arr=[a for a in arr if args.keyword.lower() in json.dumps(a, ensure_ascii=False).lower()]
arr=arr[:args.limit]
items=[]
for a in arr:
tr=(a.get('trades') or [{}])[0]
items.append({'title':a.get('title'),'salesType':a.get('salesType') or a.get('salesTypeV2'),'trade':tr,
'area':a.get('area'),'areaPyeong':a.get('areaPyeong'),'totalManageCost':a.get('totalManageCost'),
'url':a.get('webUrl') or absolute(a.get('href'))})
print_json({'source':url,'effective_region':data.get('searchRegion') or sel,'count':len(items),'items':items})
def cmd_detail(args):
html=fetch_text(args.url)
lds=[]
for m in re.finditer(r'<script[^>]*type=["\']application/ld\+json["\'][^>]*>(.*?)</script>', html, re.S):
try: lds.append(json.loads(unescape(m.group(1))))
except Exception: pass
title=re.search(r'<title>(.*?)</title>', html, re.S)
print_json({'source':args.url,'title':unescape(title.group(1)).strip() if title else None,'json_ld':lds[:3]})
p=argparse.ArgumentParser(description='Daangn realty read-only search/detail')
sub=p.add_subparsers(dest='cmd', required=True)
s=sub.add_parser('search'); s.add_argument('--region'); s.add_argument('--keyword'); s.add_argument('--sales-type'); s.add_argument('--trade-type'); s.add_argument('--only-verified',action='store_true'); s.add_argument('--limit',type=int,default=10); s.set_defaults(func=cmd_search)
d=sub.add_parser('detail'); d.add_argument('url'); d.set_defaults(func=cmd_detail)
args=p.parse_args(); args.func(args)

View file

@ -0,0 +1,100 @@
---
name: daangn-used-goods-search
description: 당근 중고거래 공개 웹 데이터 표면으로 키워드·지역 기반 매물 검색과 상세 조회를 수행한다. 로그인/채팅/찜/구매 자동화는 제외한다.
license: MIT
metadata:
category: marketplace
locale: ko-KR
phase: v1
---
# Daangn Used-Goods Search
## What this skill does
당근 중고거래 공개 Remix `_data` JSON route를 사용해 매물 목록과 상세 정보를 읽기 전용으로 조회한다.
최종 사용자는 자연어로 요청해도 되고, 필요하면 아래의 Python helper를 직접 실행한다. 외부 패키지나 k-skill-proxy 없이 Python 표준 라이브러리만 사용한다.
## When to use
- "당근에서 맥북 찾아봐"
- "합정동 아이폰 매물 검색"
- "이 당근 중고거래 URL 상세 봐줘"
## When not to use
- 당근 계정 로그인이 필요한 작업
- 채팅, 찜, 거래 제안, 문의, 지원, 예약, 계약, 구매처럼 상대방 또는 계정에 영향을 주는 작업
- CAPTCHA/봇 차단/로그인벽 우회가 필요한 작업
## Prerequisites
- 인터넷 연결
- Python 3.9+
- 이 저장소 루트에서 실행하거나, 스크립트 경로를 절대경로로 지정
## Data surfaces
- Region resolver: `https://www.daangn.com/kr/api/v1/regions/keyword?keyword=<지역명>`
- Search `_data`: `/kr/buy-sell/all/?in=<지역명>-<id>&search=<keyword>&only_on_sale=true&_data=routes/kr.buy-sell._index`
- Detail `_data`: `<listing-url>?_data=routes%2Fkr.buy-sell.%24buy_sell_id`
## Workflow
1. 사용자 요청에서 키워드, 지역명, 가격/거래 유형 같은 필터를 추출한다.
2. 지역명이 있으면 region resolver로 내부 region id를 찾는다.
3. 목록 검색은 category별 `_data` route를 호출한다.
4. 상세 URL이 주어지면 category별 detail route 또는 공개 HTML 메타를 조회한다.
5. 결과를 짧게 정리하되 source URL과 적용 지역을 보존한다.
## Commands
```bash
python3 daangn-used-goods-search/scripts/daangn_used_goods.py search "맥북" --region "합정동" --limit 5
python3 daangn-used-goods-search/scripts/daangn_used_goods.py detail "https://www.daangn.com/kr/buy-sell/.../"
```
## Output fields
- title, price, price_text, status, region, url
- detail: product 원문, view/chat/count류 필드가 있으면 함께 확인
## Region handling
지역 필터가 있으면 먼저 당근 지역 검색 API로 내부 지역 id를 해석한다.
```text
https://www.daangn.com/kr/api/v1/regions/keyword?keyword=합정동
→ 서울특별시 마포구 합정동, id=231
→ in=합정동-231
```
동일한 지명이 여러 지역에 있으면 다음 우선순위로 선택한다.
1. 사용자가 입력한 문자열이 `name`, `name1`, `name2`, `name3` 중 하나와 정확히 맞는 후보
2. 서울 `depth=3` 동 단위 후보
3. 첫 번째 후보
응답에는 항상 `effective_region` 또는 실제 적용된 지역명을 포함한다. 사용자의 의도와 다른 지역으로 보이면 결과를 단정하지 말고 후보 확인을 요청한다. IP/쿠키 기본 위치에 의존하지 않는다.
## Safety and scope
- 읽기 전용 검색/상세 조회만 수행한다.
- 로그인, 채팅, 찜, 거래 제안, 지원, 문의, 예약, 계약, 구매 자동화는 하지 않는다.
- 공개 웹 표면이 바뀌거나 빈 응답/봇 차단/로그인벽이 나오면 실패 모드로 보고하고 우회하지 않는다.
- 결과는 실시간 재고/공고 상태와 달라질 수 있으므로 source URL을 함께 제시한다.
## Failure modes
- 당근의 Remix route 이름이나 JSON shape가 변경되면 `_data` 조회가 실패할 수 있다.
- 지역명이 넓거나 중복되면 다른 행정동이 선택될 수 있다.
- 검색 결과가 0건이어도 사이트 정책/지역 기본값/필터 조합 때문일 수 있으므로 source URL을 보존한다.
- 상세 조회는 삭제/종료/비공개 전환된 글에서 실패할 수 있다.
## Done when
- 지역명이 있으면 지역 id를 해석하고 적용했다.
- 목록 조회 또는 상세 조회를 최소 1회 수행했다.
- 결과에 source URL과 effective region을 포함했다.
- 인증/거래성 액션은 수행하지 않았다.

View file

@ -0,0 +1,80 @@
#!/usr/bin/env python3
import argparse, json, re, sys, urllib.parse, urllib.request
from html import unescape
HEADERS = {"User-Agent":"Mozilla/5.0", "Accept":"application/json,text/html;q=0.9,*/*;q=0.8"}
def fetch_json(url):
req = urllib.request.Request(url, headers=HEADERS)
with urllib.request.urlopen(req, timeout=25) as r:
return json.load(r)
def fetch_text(url):
req = urllib.request.Request(url, headers={"User-Agent":"Mozilla/5.0", "Accept":"text/html"})
with urllib.request.urlopen(req, timeout=25) as r:
return r.read().decode('utf-8', 'ignore')
def won(v):
if v in (None, ''): return '-'
try: return f"{int(float(v)):,}"
except Exception: return str(v)
def resolve_region(region):
if not region: return None
url = 'https://www.daangn.com/kr/api/v1/regions/keyword?keyword=' + urllib.parse.quote(region)
data = fetch_json(url)
locs = data.get('locations') or []
if not locs: raise SystemExit(f'지역 후보 없음: {region}')
# Exact dong/name match first, then Seoul depth-3, then first candidate.
exact = [x for x in locs if region in (x.get('name'), x.get('name1'), x.get('name2'), x.get('name3'))]
seoul = [x for x in locs if x.get('name1') == '서울특별시' and x.get('depth') == 3]
sel = (exact or seoul or locs)[0]
return sel
def region_param(sel):
return urllib.parse.quote(f"{sel['name']}-{sel['id']}")
def absolute(href):
if not href: return ''
if href.startswith('http'): return href
return 'https://www.daangn.com' + href
def print_json(obj):
print(json.dumps(obj, ensure_ascii=False, indent=2))
def cmd_search(args):
params = []
effective = None
path = '/kr/buy-sell/'
if args.region:
effective = resolve_region(args.region)
path = '/kr/buy-sell/all/'
params.append(('in', f"{effective['name']}-{effective['id']}"))
params.append(('search', args.keyword))
if args.only_on_sale: params.append(('only_on_sale','true'))
params.append(('_data','routes/kr.buy-sell._index'))
url = 'https://www.daangn.com' + path + '?' + urllib.parse.urlencode(params)
data = fetch_json(url)
arr = (((data.get('allPage') or {}).get('fleamarketArticles')) or [])[:args.limit]
print_json({
'source': url,
'effective_region': effective or data.get('region'),
'count': len(arr),
'items': [{
'title': a.get('title'), 'price': a.get('price'), 'price_text': won(a.get('price')),
'region': (a.get('region') or {}).get('name'), 'status': a.get('status'),
'url': absolute(a.get('href') or a.get('webUrl')),
} for a in arr]
})
def cmd_detail(args):
u = args.url.rstrip('/') + '/?_data=routes%2Fkr.buy-sell.%24buy_sell_id'
data = fetch_json(u); p = data.get('product') or data.get('article') or data
print_json({'source': u, 'product': p})
p=argparse.ArgumentParser(description='Daangn used-goods read-only search/detail')
sub=p.add_subparsers(dest='cmd', required=True)
s=sub.add_parser('search'); s.add_argument('keyword'); s.add_argument('--region'); s.add_argument('--limit',type=int,default=10); s.add_argument('--only-on-sale',action='store_true',default=True); s.set_defaults(func=cmd_search)
d=sub.add_parser('detail'); d.add_argument('url'); d.set_defaults(func=cmd_detail)
args=p.parse_args(); args.func(args)

View file

@ -0,0 +1,148 @@
---
name: daishin-report-search
description: 대신증권 리포트 GitHub Pages 미러에서 최신 HTML 리포트 목록과 원문/설명 페이지를 조회한다.
license: MIT
metadata:
category: finance
locale: ko-KR
phase: v1
---
# Daishin Report Search
## What this skill does
대신증권 리포트 HTML 미러(`jay-jo-0/github_pages_repo`)에서 최신 리포트 목록을 찾고, 특정 리포트의 원문 텍스트·제목·헤딩·Rating/Target 표·원문 링크를 에이전트가 재사용하기 쉬운 JSON으로 반환한다.
이 스킬은 투자 조언, 매매 자동화, 추천을 하지 않는다. 공개 HTML 리포트를 읽어 요약 가능한 자료로 정리하는 조회 전용 스킬이다.
## When to use
- "대신증권 최신 리포트 보여줘"
- "대신증권 반도체 리포트 찾아줘"
- "20260511082352 리포트 원문과 설명 페이지를 읽어줘"
- "대신증권 리포트 목록을 에이전트가 쓰기 좋은 JSON으로 줘"
## Prerequisites
- 인터넷 연결
- Node.js 18+
- 이 저장소의 `daishin-report-search` npm package 또는 동일 로직
## Public access path discovered
### Primary source: GitHub recursive tree API
- list endpoint: `https://api.github.com/repos/jay-jo-0/github_pages_repo/git/trees/main?recursive=1`
- selected paths: repository-root files matching `YYYYMMDDHHMMSS.html`
- optional companion paths: `YYYYMMDDHHMMSS_explain.html`
- detail raw HTML: `https://raw.githubusercontent.com/Jay-jo-0/github_pages_repo/main/<path>`
- browser detail URL: `https://jay-jo-0.github.io/github_pages_repo/<path>`
- reason selected: the sample GitHub Pages URL maps directly to a public GitHub repository. The recursive tree API exposes all timestamped HTML filenames without relying on a brittle directory listing screen scrape. Raw GitHub URLs provide stable unauthenticated detail fetches.
### Fallback source: GitHub contents API for an exact file
- exact-file endpoint: `https://api.github.com/repos/jay-jo-0/github_pages_repo/contents/<path>?ref=main`
- used automatically for a known timestamp when the raw detail URL is unavailable; it also provides GitHub content metadata for manual diagnostics.
No `k-skill-proxy` route is used because the upstream is public and does not require an API key.
## Workflow
### 1. List latest reports
```js
const { listReports } = require("daishin-report-search")
const result = await listReports({
limit: 10,
query: "반도체", // optional; matches title/headings/detail text
maxInspect: 100, // optional query crawl budget among newest pages
githubToken: process.env.GITHUB_TOKEN // optional; raises GitHub API limits when caller has one
})
console.log(result.items)
```
CLI:
```bash
node packages/daishin-report-search/src/cli.js --limit 10
node packages/daishin-report-search/src/cli.js 반도체 --limit 5 --max-inspect 100
```
Return each item with:
- `id` (`YYYYMMDDHHMMSS`)
- `date`, `time`, `timestamp` (filename-derived KST timestamp)
- `title`
- `headings`
- `excerpt`
- `ratingTargets` when a Rating/Target table is present
- `pageUrl`, `rawUrl`, `apiUrl`
- `hasExplain`, `explainUrl` when a companion explanation page exists
### 2. Fetch one report
```js
const { fetchReport } = require("daishin-report-search")
const report = await fetchReport("20260511082352", {
includeExplain: true
})
console.log(report.title)
console.log(report.text)
console.log(report.explain?.text)
```
CLI:
```bash
node packages/daishin-report-search/src/cli.js --id 20260511082352 --include-explain
```
### 3. Summarize conservatively
When answering a user, show:
```text
- 제목: ...
게시 추정 시각: 2026-05-11 08:23:52 KST (파일명 기준)
주요 헤딩: ...
Rating/Target: ... (있는 경우)
원문: https://jay-jo-0.github.io/github_pages_repo/...
설명 페이지: ... (있는 경우)
```
Always state that the timestamp is filename-derived and that report contents can change in the public mirror.
## Fallback order
1. GitHub recursive tree API → filter timestamped root HTML files → sort newest filename first → fetch raw detail HTML for selected/latest candidates.
2. If a query is present, inspect newer candidates up to `maxInspect` until enough matches are found or the budget is exhausted; return a warning if the budget is exhausted.
3. For a known id, fetch raw detail directly. If explanation is requested, fetch `<id>_explain.html`; if absent, return the original report plus a warning.
4. If the tree endpoint is truncated, blocked, rate-limited, or changed, report that as a source warning/failure instead of guessing hidden pages.
5. For a known id, if the raw detail URL fails, fall back to the GitHub contents API for that exact file path. Explanation pages use the same exact-file fallback but remain optional and return a warning if unavailable.
6. If the caller has authenticated GitHub access, pass `githubToken` / `githubHeaders` in library calls or set `DAISHIN_GITHUB_TOKEN` / `GITHUB_TOKEN` for the CLI; these credentials are scoped to `api.github.com` requests and are not sent to raw detail URLs. Do not require or proxy a token by default.
## Done when
- Latest report rows or a specific report are returned with direct source URLs.
- Query and limit were applied or explicitly left broad.
- Explanation pages were included only when requested or when listing metadata shows they exist.
- Empty results and upstream warnings are disclosed.
## Failure modes
- GitHub unauthenticated API rate limits can return 403/429; latest/search returns empty `items` plus `source.error.kind = "rate_limit"` and rate-limit reset metadata when GitHub exposes it. Retry later or use caller-supplied authenticated GitHub access if appropriate.
- The repository path or branch can change; then tree/raw URLs will fail.
- The tree response could become truncated; in that case the latest-list completeness is not guaranteed.
- HTML structure can change; title/headings/table extraction may be partial, but URLs and raw text fallback should still be returned when available.
- Some pages may not be authored by Daishin even though they are in the issue-scoped public mirror. Do not infer provenance beyond page title/content.
## Notes
- Read-only lookup only; no login, trading, order placement, recommendation, or investment advice.
- Do not scrape private Daishin services or bypass CAPTCHA/login walls.
- No secrets or API keys are required. Optional GitHub tokens are caller-owned, used only when explicitly supplied via options or environment, and scoped to GitHub API hosts.

View file

@ -62,7 +62,9 @@ metadata:
- product search summary: `https://www.daisomall.co.kr/ssn/search/Search`
- product search list: `https://www.daisomall.co.kr/ssn/search/SearchGoods`
- product summary list: `https://www.daisomall.co.kr/ssn/search/GoodsMummResult`
- store pickup stock: `https://www.daisomall.co.kr/api/pd/pdh/selStrPkupStck`
- auth (비로그인 JWT 발급): `https://www.daisomall.co.kr/api/auth/request`
- store pickup stock: `https://www.daisomall.co.kr/api/pd/pdh/selStrPkupStck` ← **인증 필요**
- pickup eligibility fallback: `https://www.daisomall.co.kr/api/ms/msg/selPkupStr`
- optional online stock cross-check: `https://www.daisomall.co.kr/api/pdo/selOnlStck`
## Workflow
@ -106,7 +108,17 @@ console.log(productResult.items)
### 3. Check the store pickup stock
공식 매장 픽업 재고 API로 해당 매장의 재고를 확인한다.
`selStrPkupStck``Authorization` 헤더 없이 호출하면 **403**을 반환한다.
로그인 없이 `/api/auth/request`로 비로그인 JWT를 발급받아 AES-CBC로 암호화한 뒤 Bearer 헤더로 전달한다.
**Bearer 토큰 생성 방법:**
1. `GET /api/auth/request` → 응답 바디: JWT 평문, 응답 헤더 `x-dm-uid` 보존 (유효 30초)
2. 랜덤 16바이트 IV 생성 후 JWT를 AES-128-CBC / PKCS7 / 키 `"PRE_AUTH_ENC_KEY"`로 암호화
3. `bearer = base64(IV) + base64(암호문)` 으로 조합 후 `Authorization: Bearer <bearer>`, `X-DM-UID: <uid>` 헤더로 전달
바디는 `{pdNo, strCd}` 쌍 배열로 여러 매장을 한 번에 조회할 수 있다.
응답의 `stck` 필드가 `"0"` 또는 빈 값이면 재고 없음.
```js
const { getStorePickupStock } = require("daiso-product-search")
@ -157,9 +169,14 @@ console.log(result.pickupStock)
- 상품명이 너무 넓으면 다른 용량/호수 후보가 많이 섞일 수 있다.
- 공식 재고는 시점 차이로 실제 방문 시 수량이 달라질 수 있다.
- 현재 확인된 공식 표면은 **매장 내 aisle/진열 위치**를 직접 주지 않을 수 있다.
- `selStrPkupStck` 403 → `/api/auth/request` 재호출 후 Bearer를 새로 빌드해 재시도한다.
- Bearer 재시도 후에도 401/403이면 재고 수량은 `retrievalStatus: "blocked"` 로 표시하고, `selPkupStr` 기반 `pickupEligibility`(픽업 가능 여부)만 보조 정보로 제공한다.
## Notes
- 조회형 스킬이다.
- 공식 표면 우선 원칙을 유지한다.
- 공식 표면이 위치를 주지 않으면 억지 추정을 하지 않는다.
- 인증 키(`PRE_AUTH_ENC_KEY`)는 JS 번들에 하드코딩되어 있으며 변경될 수 있다.
- `selStrPkupStck` 호출 시: `/api/auth/request` 호출 후 Bearer를 만들어 시도한다.
- fallback order: Bearer 재고 조회 → 401/403 시 토큰 재발급 후 1회 재시도 → 구조화된 blocked 재고 → 선택적 `selPkupStr` 픽업 가능 여부.

View file

@ -0,0 +1,193 @@
---
name: danawa-price-search
description: 다나와 공개 검색/가격비교 표면으로 상품 후보를 찾고, 쇼핑몰별 최저가·배송비 포함 실구매가·카드 할인가·무이자 할부 정보를 보수적으로 비교한다.
license: MIT
metadata:
category: retail
locale: ko-KR
phase: v1
---
# Danawa Price Search
## What this skill does
다나와의 로그인 없는 공개 검색/가격비교 표면을 읽기 전용으로 호출해 한국 쇼핑몰 가격을 비교한다.
- 상품명/검색어로 다나와 상품 후보와 `pcode`를 찾는다.
- 선택한 상품의 쇼핑몰별 오퍼를 조회한다.
- 상품가만이 아니라 배송비 포함 실구매가, 무료배송 여부, 카드 할인가, 무이자 할부 문구를 함께 정리한다.
- 구매, 로그인, 장바구니, 찜, 주문 액션은 하지 않는다.
## When to use
- "다나와에서 에어팟 최저가 찾아줘"
- "다나와 가격비교로 쇼핑몰별 가격 비교해줘"
- "무료배송인지, 카드 할인까지 보면 어디가 제일 싸?"
- "무이자 할부 붙은 최저가도 같이 봐줘"
## When not to use
- 실제 구매/주문/결제/로그인이 필요한 경우
- 회원 전용 쿠폰, 개인화 포인트, 앱 전용 혜택을 확정해야 하는 경우
- 대량 모니터링이나 고빈도 크롤링을 해야 하는 경우
- CAPTCHA, 접근 차단, fingerprint 우회를 해야 하는 경우
## Required inputs
상품명 또는 검색어가 필요하다. 검색어가 넓으면 브랜드, 모델명, 용량, 색상, 자급제/통신사 여부 등을 추가로 물어본다.
권장 질문:
> 찾을 다나와 상품명이나 모델명을 알려주세요. 예: 갤럭시 S25 울트라 256GB 자급제, 에어팟 프로 2세대 USB-C
## Public surfaces
현재 구현은 인증 없는 공개 표면만 사용한다.
- 검색 페이지: `https://search.danawa.com/dsearch.php?query=...`
- 상품 상세 페이지: `https://prod.danawa.com/info/?pcode=...`
- 가격비교 AJAX: `https://prod.danawa.com/info/ajax/getAllPriceCompareMallList.ajax.php`
AJAX endpoint는 HTML fragment를 반환한다. helper는 `.diff_item`, 쇼핑몰 로고 `alt`, `em.prc_c`/`em.prc_t`, 배송 문구, 결제조건 배지(`.ico.cash`/`.ico.point`/`.ico.coupon`/`.ico.discount`/`.ico.card`/`.ico.membership` 등), 카드 할인 라인, 무이자 할부 레이어, 다나와 bridge link를 파싱한다.
## Commands
스킬 디렉터리에서 실행한다.
```bash
python scripts/danawa_search.py search "에어팟 프로 2세대" --limit 8
python scripts/danawa_search.py offers 28208783 --limit 10
python scripts/danawa_search.py compare "에어팟 프로 2세대" --limit 5 --offers 5
```
helper는 JSON만 출력한다. 결과를 확인한 뒤 사용자에게는 한국어 표와 짧은 결론으로 정리한다.
## Output shape
### `search`
```json
{
"query": "...",
"source_url": "...",
"count": 0,
"items": []
}
```
`items[]` 주요 필드:
- `pcode`
- `title`
- `price`, `price_text`
- `mall_text`
- `url`
- `image_url`
- `spec`
### `offers`
```json
{
"pcode": "...",
"title": "...",
"source_url": "...",
"count": 0,
"normal_count": 0,
"conditional_count": 0,
"offers": [],
"meta": { "sort": "total_price" }
}
```
`offers[]`는 **배송비 포함 실구매가(`total_price`) 오름차순**으로 정렬된다. `count` / `normal_count` / `conditional_count``limit` 적용 후 실제 반환된 `offers[]` window 기준이다. 결제조건(현금/쿠폰/포인트/할인/특정카드/멤버십 한정)이 붙은 row도 같은 정렬에 그대로 참여한다 — 가장 싸면 1위로 올라온다. 결제조건은 분리 그룹이나 추가 필터링 없이 row 단위 `payment_badges` / `payment_condition_types` / `payment_condition_label` / `cash_only` / `point_only` / `coupon_only` / `card_only_badge` / `discount_badge` / `membership_badge` / `is_conditional_price` 필드로 노출한다. 호출자는 사용자의 결제 수단에 따라 직접 판단한다.
`offers[]` 주요 필드:
- `mall`
- `price`, `price_text`
- `shipping`
- `is_free_shipping`
- `shipping_fee`
- `total_price`, `total_price_text`
- `card_price`, `card_price_text`
- `card_name`
- `card_discount`, `card_discount_text`
- `installment`
- `installment_detail`
- `payment_badges` — Danawa가 가격 옆에 노출한 결제조건 배지의 표시 라벨 목록. 배지 텍스트가 비어 있고 `.ico.cash`처럼 클래스만 있는 경우도 정규화 라벨을 합성한다 (예: `["현금"]`, `["포인트"]`, `["쿠폰"]`, `["카드"]`, `["할인"]`, `["멤버십"]`)
- `payment_condition_types` — 화이트리스트 배지를 정규화한 조건 타입 목록 (`cash`/`point`/`coupon`/`card`/`discount`/`membership`)
- `payment_condition_label` — 사용자 응답용 결제조건 라벨 (예: `현금`, `할인`, `멤버십`, 복수 조건이면 `현금, 할인`)
- `cash_only` — 현금 결제 전용가
- `point_only` — 포인트 차감 적용가
- `coupon_only` — 쿠폰 적용가
- `card_only_badge` — 특정 카드 한정 노출가
- `discount_badge` — 할인 조건 배지 노출가
- `membership_badge` — 멤버십 조건 배지 노출가
- `is_conditional_price``payment_condition_types`가 하나 이상 있으면 True. **일반 결제가가 아니므로 카드 일반 결제 시 가격이 다르거나 불가능할 수 있음**
- `url`
항상 무료배송 여부, 배송비 포함 실구매가, 카드별 할인 가격, 무이자 할부 문구, **그리고 `payment_badges`/`payment_condition_label`/`is_conditional_price`를 함께 확인한다.** 조건부 가격을 일반가처럼 1위로 노출하면 비교 결과가 거짓이 된다.
### `compare`
`compare`는 검색 결과를 먼저 가져온 뒤 각 후보 상품에 대해 `offers[]`를 best-effort로 붙인다. 검색 결과가 애매하면 상위 후보의 제목과 `pcode`를 먼저 보여주고 선택을 요청한다.
## Response style
Discord/Telegram/chat 응답에서는 표 형식을 우선한다.
```md
| 순위 | 판매처 | 상품가 | 결제조건 | 배송 | 실구매가 | 카드할인가 | 무이자 | 링크 |
|---:|---|---:|---|---|---:|---:|---|---|
| 1 | 킴스클럽 | 979,000원 | **현금 전용** | 유/무료 | 979,000원 | - | - | 보기 |
| 2 | 롯데ON | 1,073,890원 | 일반 | 무료배송 | 1,073,890원 | - | - | 보기 |
| 3 | G마켓 | 1,089,590원 | 일반 | 무료배송 | 1,089,590원 | - | 최대 24개월 | 보기 |
| 4 | 옥션 | 1,121,780원 | **쿠폰 적용가** | 무료배송 | 1,121,780원 | 우리카드 303,720원 | 최대 24개월 | 보기 |
```
정렬 기준:
1. **`total_price` 오름차순 단일 기준.** 결제조건(현금/쿠폰/포인트/할인/특정카드/멤버십 한정)이 붙은 row도 같은 정렬에 그대로 참여한다 — 가장 싸면 1위로 올라온다. 결제조건은 분리 그룹화하지 않고 표의 "결제조건" 컬럼에 행별로 표시한다 (`payment_condition_label`이 있으면 그 값을 우선 표시, 없으면 "일반"; 세부 매핑은 `cash` → "현금 전용", `coupon` → "쿠폰 적용가", `point` → "포인트 적용가", `card` → 카드명/카드 조건, `discount` → "할인 조건", `membership` → "멤버십 조건"). 사용자는 자기 결제 수단에 따라 직접 판단한다.
2. `card_price`가 있고 카드 적용 시 승자가 바뀌면 표 아래에 "카드 기준 최저가"를 별도로 적는다.
3. 무이자 할부는 결제 조건이 달라질 수 있으므로 Danawa 노출 문구 기준이라고 밝힌다.
4. 1위가 조건부 가격이면 요약 문장에 결제수단 단서를 짧게 덧붙인다. 예: "**최저 실구매가: 킴스클럽 979,000원 / 현금 결제 한정**, 카드 결제 기준 최저가는 롯데ON 1,073,890원". 카드 결제 가능한 최저가도 같이 알려 사용자가 결제수단별 결과를 한 번에 비교할 수 있게 한다.
요약 예시:
```md
최저 실구매가: G마켓 217,950원 / 무료배송
카드 기준 최저가: 옥션 우리카드 303,720원
무이자: G마켓·옥션 최대 24개월 표기
```
카드 할인 markup이 없으면 "카드 할인가 표기 없음"이라고 쓰고, 체크아웃 할인 자체가 없다고 단정하지 않는다.
## Workflow
1. 검색어를 확인한다.
2. `python scripts/danawa_search.py search "<검색어>" --limit 5`로 후보를 확인한다.
3. 후보가 명확하면 해당 `pcode``offers`를 실행한다.
4. 후보가 애매하면 상위 3~5개 상품명/가격/`pcode`를 보여주고 선택을 요청한다.
5. 오퍼는 **`total_price` 오름차순 단일 기준으로 정렬한다 (결제조건 분리 그룹화하지 않음).** 결제조건은 표의 "결제조건" 컬럼과 row 단위 플래그로만 표기하고, 1위가 현금/쿠폰가여도 그대로 1위로 노출한다.
6. 카드 할인가가 있으면 카드 기준 최저가도 별도 요약한다. 1위가 조건부 가격이면 "카드 결제 기준 최저가"도 요약 문장에 함께 적어 결제수단별 최저가를 한 번에 알게 한다.
7. 조회 시점 기준이며 가격/배송/카드 혜택은 변동될 수 있음을 짧게 덧붙인다.
## Failure modes
- 검색 결과가 0개면 검색어를 더 구체화한다.
- Danawa HTML/AJAX 구조가 바뀌면 selector가 깨져 `offers`가 비거나 필드가 누락될 수 있다.
- 다나와가 새로운 결제조건 배지 클래스나 문구를 도입하면 결제조건 배지 화이트리스트(`cash`/`point`/`coupon`/`discount`/`card`/`membership` 클래스, `현금`/`포인트`/`쿠폰`/`할인`/`카드`/`멤버십` 텍스트 키워드)와 `payment_condition_types`/`payment_condition_label` 매핑을 함께 갱신해야 한다.
- 검색 결과 가격과 오퍼 AJAX 가격은 갱신 시점·카드가·제휴 링크 기준 차이로 다를 수 있다.
- 카드 할인과 무이자 문구는 Danawa가 노출한 경우에만 확정적으로 보여준다.
- 공개 표면 기반이므로 고빈도 요청에는 throttling/backoff를 추가해야 한다.
- 접근 차단이나 CAPTCHA가 나오면 우회를 시도하지 말고 실패 모드로 보고한다.
## Done when
- 검색어 또는 모델명을 확인했다.
- 상품 후보를 최소 1개 이상 반환하거나, 반환 실패 이유를 설명했다.
- 쇼핑몰별 상품가, 배송비, 실구매가, 카드 할인가, 무이자 문구를 조회 시점 기준으로 정리했다.
- 사용자 응답은 표 형식으로 제공했다.
- 로그인/구매/차단 우회 범위를 벗어나지 않았다.

View file

@ -0,0 +1,354 @@
#!/usr/bin/env python3
"""Read-only Danawa search/price comparison helper for Hermes.
Usage:
python scripts/danawa_search.py search "에어팟 프로 2세대" --limit 8
python scripts/danawa_search.py offers 28208783 --limit 10
python scripts/danawa_search.py compare "에어팟 프로 2세대" --limit 5 --offers 5
"""
from __future__ import annotations
import argparse
import json
import re
import sys
import time
import urllib.parse
import urllib.request
from html import unescape
from typing import Any, Dict, List, Optional
try:
from bs4 import BeautifulSoup
except ImportError as exc: # pragma: no cover - environment guard
raise SystemExit("beautifulsoup4 is required: python -m pip install beautifulsoup4") from exc
UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 Chrome/121 Safari/537.36"
def fetch(url: str, *, method: str = "GET", data: Optional[dict] = None, referer: Optional[str] = None) -> str:
headers = {
"User-Agent": UA,
"Accept-Language": "ko-KR,ko;q=0.9,en;q=0.8",
}
body = None
if data is not None:
body = urllib.parse.urlencode(data).encode("utf-8")
headers["Content-Type"] = "application/x-www-form-urlencoded; charset=UTF-8"
headers["X-Requested-With"] = "XMLHttpRequest"
if referer:
headers["Referer"] = referer
req = urllib.request.Request(url, data=body, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=25) as resp:
return resp.read().decode("utf-8", "replace")
def soup_for(html: str) -> BeautifulSoup:
return BeautifulSoup(html, "html.parser")
def clean_text(s: Optional[str]) -> Optional[str]:
if s is None:
return None
return " ".join(unescape(s).split())
def parse_int(s: Optional[str]) -> Optional[int]:
if not s:
return None
digits = re.sub(r"\D", "", s)
return int(digits) if digits else None
def abs_url(url: Optional[str]) -> Optional[str]:
if not url:
return None
if url.startswith("//"):
return "https:" + url
if url.startswith("/"):
return "https://prod.danawa.com" + url
return url
def search(query: str, limit: int = 10) -> Dict[str, Any]:
url = "https://search.danawa.com/dsearch.php?query=" + urllib.parse.quote(query)
html = fetch(url)
soup = soup_for(html)
items: List[Dict[str, Any]] = []
for li in soup.select("li.prod_item"):
pid = (li.get("id") or "").replace("productItem", "") or None
name_el = li.select_one(".prod_name a") or li.select_one("p.prod_name a") or li.select_one('a[name="productName"]')
if not name_el:
continue
name = clean_text(name_el.get_text(" ", strip=True))
link = abs_url(name_el.get("href"))
min_input = li.select_one(f"#min_price_{pid}") if pid else None
price = parse_int(min_input.get("value") if min_input else None)
if price is None:
price_el = li.select_one(".price_sect strong") or li.select_one(".prod_pricelist strong")
price = parse_int(price_el.get_text() if price_el else None)
img = li.select_one(".thumb_image img")
image = abs_url((img.get("data-original") or img.get("src")) if img else None)
mall_el = li.select_one(".prod_pricelist .memory_sect") or li.select_one(".meta_item")
spec = " / ".join(clean_text(e.get_text(" ", strip=True)) or "" for e in li.select(".spec_list a, .spec_list span")[:10])
items.append(
{
"pcode": pid,
"title": name,
"price": price,
"price_text": f"{price:,}" if price else None,
"mall_text": clean_text(mall_el.get_text(" ", strip=True)) if mall_el else None,
"url": link,
"image_url": image,
"spec": spec[:300] if spec else None,
}
)
if len(items) >= limit:
break
return {"query": query, "source_url": url, "count": len(items), "items": items, "meta": {"extraction": "danawa-search-html", "ts": int(time.time())}}
def js_value(html: str, key: str) -> str:
patterns = [
rf"{re.escape(key)}\s*:\s*\"([^\"]*)\"",
rf"{re.escape(key)}\s*:\s*'([^']*)'",
rf"{re.escape(key)}\s*:\s*([0-9]+)",
]
for pat in patterns:
m = re.search(pat, html)
if m:
raw = m.group(1)
if "\\u" in raw or "\\/" in raw:
try:
return json.loads('"' + raw.replace('"', '\\"') + '"')
except Exception:
return raw.replace("\\/", "/")
return raw
return ""
def product_meta(pcode: str) -> Dict[str, str]:
url = f"https://prod.danawa.com/info/?pcode={urllib.parse.quote(str(pcode))}"
html = fetch(url)
meta = {
"pcode": str(pcode),
"source_url": url,
"cate1": js_value(html, "nCategoryCode1"),
"cate2": js_value(html, "nCategoryCode2"),
"cate3": js_value(html, "nCategoryCode3"),
"cate4": js_value(html, "nCategoryCode4") or "0",
"UICategoryCode": js_value(html, "nCategoryCode"),
"powerLinkKeyword": js_value(html, "powerLinkKeyword"),
"minPrice": js_value(html, "nMinPrice"),
"keyword": js_value(html, "sKeyword"),
"NaPm": js_value(html, "sNaPm"),
"sProductFullName": js_value(html, "sProductName"),
"makerCode": js_value(html, "makerCode"),
"makerName": js_value(html, "makerName"),
}
title = soup_for(html).select_one(".prod_tit .title")
if title:
meta["sProductFullName"] = clean_text(title.get_text(" ", strip=True)) or meta["sProductFullName"]
return meta
def offers(pcode: str, limit: int = 20, include_shipping: bool = False) -> Dict[str, Any]:
meta = product_meta(pcode)
post_price = "Y" if include_shipping else "N"
data = {
"pcode": meta["pcode"],
"cate1": meta.get("cate1", ""),
"cate2": meta.get("cate2", ""),
"cate3": meta.get("cate3", ""),
"cate4": meta.get("cate4", "0"),
"UICategoryCode": meta.get("UICategoryCode", "0"),
"powerLinkKeyword": meta.get("powerLinkKeyword", ""),
"minPrice": meta.get("minPrice", ""),
"keyword": meta.get("keyword", ""),
"NaPm": meta.get("NaPm", ""),
"bDeliveryLeftRightYN": "N",
"bQuickPostSortYN": "N",
"sSortType": "minPrice",
"sProductFullName": meta.get("sProductFullName", ""),
"bPostPriceYN": post_price,
"bBadgeDefaultYN": "N",
"bWarrantyDefaultYN": "N",
"nOpenMarketMoreCount": "30",
"nAffiliateMoreCount": "30",
"nOverseasShoppingMoreCount": "30",
"nGeneralAffiliateMoreCount": "3",
"sRelationMenuType": "",
"sRelationType": "",
"bCoupangSortYN": "N",
"makerCode": meta.get("makerCode", ""),
"makerName": meta.get("makerName", ""),
}
html = fetch("https://prod.danawa.com/info/ajax/getAllPriceCompareMallList.ajax.php", method="POST", data=data, referer=meta["source_url"])
soup = soup_for(html)
rows: List[Dict[str, Any]] = []
for div in soup.select(".diff_item"):
mall_img = div.select_one(".d_mall img")
mall = mall_img.get("alt") if mall_img else None
price_el = div.select_one("em.prc_c") or div.select_one("em.prc_t")
price = parse_int(price_el.get_text() if price_el else None)
if not mall or price is None:
continue
ship_el = div.select_one(".ship") or div.select_one(".stxt")
shipping = clean_text(ship_el.get_text(" ", strip=True)) if ship_el else None
shipping_fee = 0 if shipping and "무료" in shipping else parse_int(shipping)
card_line = div.select_one(".card_line")
card_price_el = card_line.select_one(".card_prc") if card_line else None
card_name_el = card_line.select_one(".txt") if card_line else None
card_price = parse_int(card_price_el.get_text() if card_price_el else None)
installment_el = div.select_one(".btn_foi .txt")
installment_detail_el = div.select_one(".foi_layer .ly_cont")
link = div.select_one("a.priceCompareBuyLink")
# 결제조건 ico만 캡처. 다른 ico(빠른배송, 안내, 상품리뷰 등)는 노이즈라 제외.
# 클래스만 있고 텍스트가 비어 있는 아이콘도 row 라벨이 누락되지 않도록
# 같은 정규화 테이블에서 표시 라벨/타입/boolean 필드를 모두 파생한다.
payment_condition_labels = {
"cash": "현금",
"point": "포인트",
"coupon": "쿠폰",
"card": "카드",
"discount": "할인",
"membership": "멤버십",
}
payment_condition_types: List[str] = []
payment_badges: List[str] = []
for el in div.select(".prc_line .ico, .d_dsc .ico"):
classes = set(el.get("class") or [])
text = clean_text(el.get_text(" ", strip=True)) or ""
matched_types = [
kind
for kind, label in payment_condition_labels.items()
if kind in classes or label in text
]
if not matched_types:
continue
for kind in matched_types:
if kind not in payment_condition_types:
payment_condition_types.append(kind)
label = payment_condition_labels[kind]
if label not in payment_badges:
payment_badges.append(label)
cash_only = "cash" in payment_condition_types
point_only = "point" in payment_condition_types
coupon_only = "coupon" in payment_condition_types
card_only_badge = "card" in payment_condition_types
discount_badge = "discount" in payment_condition_types
membership_badge = "membership" in payment_condition_types
payment_condition_label = ", ".join(payment_badges) or None
is_conditional_price = bool(payment_condition_types)
rows.append(
{
"mall": clean_text(mall),
"price": price,
"price_text": f"{price:,}",
"shipping": shipping,
"is_free_shipping": bool(shipping and "무료" in shipping),
"shipping_fee": shipping_fee,
"total_price": price + (shipping_fee or 0),
"total_price_text": f"{price + (shipping_fee or 0):,}",
"card_price": card_price,
"card_price_text": f"{card_price:,}" if card_price else None,
"card_name": clean_text(card_name_el.get_text(" ", strip=True)) if card_name_el else None,
"card_discount": (price - card_price) if card_price else None,
"card_discount_text": f"{price - card_price:,}" if card_price else None,
"installment": clean_text(installment_el.get_text(" ", strip=True)) if installment_el else None,
"installment_detail": clean_text(installment_detail_el.get_text(" ", strip=True)) if installment_detail_el else None,
"payment_badges": payment_badges,
"cash_only": cash_only,
"point_only": point_only,
"coupon_only": coupon_only,
"card_only_badge": card_only_badge,
"discount_badge": discount_badge,
"membership_badge": membership_badge,
"payment_condition_types": payment_condition_types,
"payment_condition_label": payment_condition_label,
"is_conditional_price": is_conditional_price,
"url": abs_url(link.get("href") if link else None),
}
)
# 정렬은 단순히 배송비 포함 실구매가 오름차순. 결제조건(현금/쿠폰/포인트/특정카드)은
# 분리 그룹으로 묶지 않고 row 단위로 payment_badges / payment_condition_types /
# payment_condition_label 및 세부 boolean 플래그로 노출한다. 호출자(또는 사용자)는 자기 결제수단에 맞춰 판단한다.
rows.sort(key=lambda row: (
row["total_price"] is None,
row["total_price"] or row["price"],
row["price"],
row["mall"] or "",
))
rows = rows[:limit]
return {
"pcode": str(pcode),
"title": meta.get("sProductFullName"),
"source_url": meta["source_url"],
"count": len(rows),
"normal_count": sum(1 for r in rows if not r.get("is_conditional_price")),
"conditional_count": sum(1 for r in rows if r.get("is_conditional_price")),
"offers": rows,
"meta": {
"extraction": "danawa-price-ajax",
"include_shipping": include_shipping,
"sort": "total_price",
"ts": int(time.time()),
},
}
def compare(query: str, limit: int, offer_limit: int) -> Dict[str, Any]:
result = search(query, limit=limit)
enriched = []
for item in result["items"]:
row = dict(item)
if item.get("pcode"):
try:
off = offers(item["pcode"], limit=offer_limit)
row["offers"] = off.get("offers", [])
except Exception as exc: # keep search result usable if a detail call fails
row["offers_error"] = f"{type(exc).__name__}: {exc}"
enriched.append(row)
result["items"] = enriched
result["meta"]["detail_extraction"] = "best-effort"
return result
def positive_int(raw: str) -> int:
value = int(raw)
if value < 1:
raise argparse.ArgumentTypeError("must be >= 1")
return value
def main() -> int:
ap = argparse.ArgumentParser()
sub = ap.add_subparsers(dest="cmd", required=True)
s = sub.add_parser("search")
s.add_argument("query")
s.add_argument("--limit", type=positive_int, default=10)
o = sub.add_parser("offers")
o.add_argument("pcode")
o.add_argument("--limit", type=positive_int, default=20)
o.add_argument("--include-shipping", action="store_true")
c = sub.add_parser("compare")
c.add_argument("query")
c.add_argument("--limit", type=positive_int, default=5)
c.add_argument("--offers", type=positive_int, default=5)
args = ap.parse_args()
try:
if args.cmd == "search":
out = search(args.query, args.limit)
elif args.cmd == "offers":
out = offers(args.pcode, args.limit, args.include_shipping)
else:
out = compare(args.query, args.limit, args.offers)
print(json.dumps(out, ensure_ascii=False, indent=2))
return 0
except Exception as exc:
print(json.dumps({"error": f"{type(exc).__name__}: {exc}"}, ensure_ascii=False), file=sys.stderr)
return 2
if __name__ == "__main__":
raise SystemExit(main())

249
docs/adding-a-skill.md Normal file
View file

@ -0,0 +1,249 @@
# 새 스킬 추가 가이드
새 스킬을 k-skill에 추가하는 방법과 스킬이 동작하는 구조를 설명한다.
---
## 스킬이란
스킬은 AI 에이전트(Claude Code 등)가 특정 작업을 수행하는 방법을 정의한 문서+코드 묶음이다. 에이전트는 `SKILL.md`를 읽고 거기 적힌 워크플로우를 따라 실행한다.
스킬에는 네 가지 구현 유형이 있다.
| 유형 | 설명 | 예시 |
|------|------|------|
| **SKILL.md 전용** | 문서만으로 동작 (에이전트가 bash/python 직접 실행) | `kakaotalk-mac`, `srt-booking` |
| **npm 패키지** | `packages/` 아래 Node.js 라이브러리로 구현 | `k-lotto`, `daiso-product-search` |
| **프록시 경유** | `k-skill-proxy`가 upstream API 키를 보관하고 HTTP로 중계 | `seoul-subway-arrival`, `fine-dust-location` |
| **Python 스크립트** | `scripts/`의 Python 파일 직접 실행 | `korean-spell-check`, `sillok-search` |
---
## 스킬의 구조
모든 스킬은 **저장소 루트에 디렉토리 하나**를 갖는다.
```
k-skill/
├── my-new-skill/ ← 스킬 디렉토리 (이름 = 스킬 이름)
│ ├── SKILL.md ← 필수. 에이전트가 읽는 핵심 파일
│ └── (지원 파일들) ← 선택. 스크립트, 데이터 등
├── packages/ ← npm 패키지 유형일 때만
│ └── my-new-skill/
│ ├── package.json
│ ├── src/
│ └── test/
└── scripts/ ← Python 스크립트 유형일 때만
└── my_new_skill.py
```
---
## SKILL.md 형식
`SKILL.md`는 YAML frontmatter + Markdown 본문으로 구성된다.
```markdown
---
name: my-new-skill
description: 한 문장으로 이 스킬이 무엇을 하는지 설명한다. 에이전트 UI에 표시된다.
license: MIT
metadata:
category: utility
locale: ko-KR
phase: v1
---
# My New Skill
## What this skill does
이 스킬이 무엇을 하는지 한두 문단으로 설명한다.
## When to use
- "사용자가 이런 말을 할 때"
- "또는 이런 상황일 때"
## Prerequisites
- Node.js 18+ (필요하면)
- 패키지 설치 명령
## Workflow
### 1. 첫 번째 단계
설명과 실행할 코드를 적는다.
```bash
# 실행할 명령어
```
### 2. 두 번째 단계
...
## Done when
- 이런 조건이 만족되면 완료다
## Failure modes
- 예상 가능한 실패 상황
## Notes
- 특이사항, 보안 정책 등
```
### frontmatter 필드
| 필드 | 필수 | 설명 |
|------|------|------|
| `name` | ✅ | **디렉토리 이름과 정확히 일치**해야 한다 |
| `description` | ✅ | 에이전트 UI 표시용 한 줄 설명 |
| `license` | ✅ | 항상 `MIT` |
| `metadata.category` | ✅ | `utility` / `transit` / `travel` / `messaging` / `legal` / `setup` 등 |
| `metadata.locale` | ✅ | `ko-KR` |
| `metadata.phase` | ✅ | `v1` (안정) / `v1.5` (기능 추가 중) |
---
## 유형별 구현 방법
### A. SKILL.md 전용 스킬
에이전트가 `SKILL.md` 안의 bash/python 코드를 직접 실행한다.
1. 디렉토리 생성: `mkdir my-new-skill`
2. `my-new-skill/SKILL.md` 작성
3. Workflow 섹션에 에이전트가 따를 단계별 명령어를 적는다
외부 라이브러리나 서버 없이 동작해야 한다.
### B. npm 패키지 스킬
`packages/my-new-skill/`에 Node.js 구현체를 만들고, 루트 디렉토리 `my-new-skill/SKILL.md`에서 `require('my-new-skill')`로 호출한다.
```
packages/my-new-skill/
├── package.json # name, version, main, exports 필수
├── README.md
├── src/
│ └── index.js
└── test/
└── index.test.js
```
`package.json``"name": "my-new-skill"` 설정 후 루트 `package.json``workspaces`에 등록한다.
npm에 배포하려면 `.changeset/` 파일을 추가한다 (`docs/releasing.md` 참고).
### C. 프록시 경유 스킬
upstream API 키를 사용자에게 노출하지 않으려면 `k-skill-proxy`를 경유한다.
1. `packages/k-skill-proxy/src/server.js`에 새 route 추가
2. `SKILL.md` Workflow에 `curl $KSKILL_PROXY_BASE_URL/v1/...` 형태로 호출 작성
3. upstream API 키는 서버의 `~/.config/k-skill/secrets.env`에만 보관
프록시 서버는 main 브랜치에 merge되어야 프로덕션에 반영된다 (`CLAUDE.md` 참고).
### D. Python 스크립트 스킬
`scripts/my_skill.py`를 만들고 `SKILL.md`에서 `python3 scripts/my_skill.py`로 호출한다.
---
## 크롤링/검색 스킬을 만들 때: site-agnostic discovery 먼저
웹사이트를 조회하거나 크롤링하는 스킬의 최종 산출물은 결국 **그 사이트에 맞는 site-dependent 접근 방법**이다. 다만 처음부터 특정 화면 구조나 임시 우회법을 감으로 고정하지 않는다. 먼저 `insane-search`식 접근처럼 **사이트에 상관없이 반복 가능한 탐색 절차**를 적용해 대상 사이트에서 실제로 안정적인 경로를 찾아낸 뒤, 그 발견 결과를 해당 스킬의 site-dependent 지식으로 패키징한다.
적용 대상:
- 검색 결과/상세 페이지를 읽어야 하는 스킬
- 공식 API 문서가 없거나 불완전한 사이트
- PC 페이지, 모바일 페이지, RSS, sitemap, 정적 JSON, 공개 데이터 호출 등 여러 입구가 있을 수 있는 사이트
- 브라우저에서는 보이지만 단순 HTTP 요청에서는 빈 화면/차단/로그인 유도만 보이는 사이트
권장 절차:
1. **공개 입구부터 찾기**: 공식 API, 공개 JSON, RSS/Atom, sitemap, 검색 폼, 모바일 페이지, 정적 파일처럼 사이트가 공개적으로 제공하는 경로를 먼저 확인한다.
2. **브라우저 동작을 관찰하기**: 화면을 직접 긁기 전에 검색/상세 화면이 어떤 공개 데이터 요청을 통해 채워지는지 확인한다.
3. **안정적인 경로를 우선하기**: 화면 선택자보다 공개 데이터 호출, 문서화된 endpoint, RSS/sitemap처럼 구조가 덜 흔들리는 경로를 선호한다.
4. **차단과 빈 응답을 실패로 분리하기**: HTTP 성공만으로 완료로 보지 말고, 실제 결과 본문이 있는지 확인한다. 로그인벽, 봇 검사, 빈 껍데기 페이지는 별도 실패 모드로 적는다.
5. **site-dependent 방법을 명시적으로 패키징하기**: 탐색 과정에서 확인한 검색 URL, 필수 파라미터, 결과 해석 규칙, fallback 순서를 `SKILL.md`와 패키지 코드에 좁고 명확하게 기록한다.
6. **권한 경계를 지키기**: 인증, 결제, CAPTCHA, 약관상 제한이 필요한 경로는 자동화하지 말고 사용자 개입 또는 실패 모드로 처리한다.
`SKILL.md`에는 최소한 아래 내용을 남긴다.
- 어떤 공개 접근 경로를 선택했는지와 그 이유
- 검색/상세 조회의 입력값과 출력값
- 기본 경로가 실패했을 때의 fallback 순서
- 빈 결과, 차단, 로그인 필요, upstream 변경 등 실패 모드
- 시크릿/인증이 필요한지 여부와 저장소에 절대 넣지 않을 값
새 dependency는 기본값으로 추가하지 않는다. 기존 Node.js/Python 표준 기능, 이미 있는 패키지, 또는 `k-skill-proxy`의 좁은 allowlist route로 해결할 수 있는지 먼저 확인한다.
---
## 스킬 등록 & 검증
스킬은 **별도 레지스트리 없이 디렉토리 스캔으로 자동 발견**된다.
추가 후 검증:
```bash
npm run ci
```
이 명령은 `scripts/validate-skills.sh`를 실행해 다음을 확인한다.
- 루트 하위 모든 디렉토리에 `SKILL.md`가 있는지
- frontmatter가 `---`로 시작하는지
- `name` 필드가 있는지
- `description` 필드가 있는지
- `name` 필드 값이 디렉토리 이름과 일치하는지
---
## 시크릿이 필요한 스킬
인증이 필요한 스킬은 아래 우선순위로 credential을 확보한다.
1. 이미 환경변수에 있으면 → 그대로 사용
2. 에이전트 vault(1Password, Bitwarden, macOS Keychain) → 주입
3. `~/.config/k-skill/secrets.env` → 파일에서 읽기
4. 아무것도 없으면 → 사용자에게 물어보고 3번에 저장
시크릿 변수 이름 규칙: `KSKILL_<서비스명>_<항목>` (예: `KSKILL_SRT_ID`)
절대 하지 말 것:
- 시크릿을 저장소에 커밋
- 프록시 upstream 키를 클라이언트에 노출
- 사용자 확인 없이 side-effect가 있는 작업 실행
---
## 체크리스트
새 스킬을 PR 올리기 전에 확인한다.
- [ ] `my-new-skill/SKILL.md` 작성 완료
- [ ] frontmatter `name`이 디렉토리 이름과 일치
- [ ] `npm run ci` 통과 (`./scripts/validate-skills.sh` 포함)
- [ ] npm 패키지라면 `packages/`에 구현체와 테스트 추가
- [ ] npm 패키지라면 `.changeset/*.md` 파일 추가 (반드시 **기능 PR에서**, Version Packages PR에서 추가하지 말 것)
- [ ] 프록시 경유라면 `k-skill-proxy/src/server.js`에 route 추가하고 main에 merge
- [ ] 크롤링/검색 스킬이라면 공개 접근 경로, fallback 순서, 차단/로그인/빈 결과 실패 모드 문서화
- [ ] 시크릿이 있다면 `KSKILL_` 접두사 규칙 준수 및 `docs/setup.md` 업데이트
- [ ] `docs/features/my-new-skill.md` 작성 (선택, 상세 가이드)
---
## 관련 문서
- [공통 설정 가이드](setup.md) — 시크릿 설정 방법
- [릴리스와 자동 배포](releasing.md) — npm 패키지 배포 흐름
- [보안/시크릿 정책](security-and-secrets.md) — 인증 정보 취급 원칙

View file

@ -0,0 +1,211 @@
# k-skill-proxy 배포 가이드 (Cloud Run + GitHub Actions)
`k-skill-proxy`는 Google Cloud Run에서 운영되고, `main` 브랜치에 머지되면 GitHub Actions가 자동으로 재배포합니다.
이 문서는 그 자동 배포 파이프라인의 **1회성 셋업 절차**와 **운영 점검 절차**를 정리합니다. 일반 contributor는 읽지 않아도 되며, 프록시 운영을 담당하는 maintainer(현재 `jeffrey@markr.ai`)가 인프라를 처음 만들거나 수리할 때 참고합니다.
## 운영 사실
| 항목 | 값 |
| --- | --- |
| GCP project ID | `k-skill-proxy` |
| Region | `asia-northeast1` (도쿄) |
| Cloud Run service | `k-skill-proxy` |
| Artifact Registry repo | `asia-northeast1-docker.pkg.dev/k-skill-proxy/k-skill` |
| 공개 도메인 | `https://k-skill-proxy.nomadamas.org` (Cloud Run domain mapping) |
| 컨테이너 이미지 정의 | `packages/k-skill-proxy/Dockerfile` |
| 워크플로 | `.github/workflows/deploy-k-skill-proxy.yml` |
| 인증 | Workload Identity Federation (long-lived JSON key 없음) |
| 시크릿 저장소 | GCP Secret Manager (이름 = 환경변수 이름) |
## 배포 흐름
1. `dev` 브랜치에서 작업, PR을 `dev`에 보낸다.
2. `dev``main` 머지 PR이 `@vkehfdl1`에 의해 머지된다.
3. `main` push가 `.github/workflows/deploy-k-skill-proxy.yml`을 트리거한다.
4. 워크플로가:
- WIF로 `${GCP_DEPLOY_SERVICE_ACCOUNT}`로 impersonate
- `packages/k-skill-proxy/Dockerfile`로 컨테이너 빌드
- Artifact Registry에 `:${GITHUB_SHA}` 태그로 push
- Cloud Run `k-skill-proxy` 서비스를 새 이미지로 재배포 (Secret Manager 시크릿 + 런타임 env 주입)
- 새 revision의 `*.run.app` URL과 `https://k-skill-proxy.nomadamas.org/health`에 smoke test
5. 실패 시 GitHub Actions 페이지에서 로그 확인. Cloud Run 자체는 마지막 healthy revision에 트래픽을 유지한다.
## 1회성 GCP 셋업
> 이미 한 번 셋업되어 있다면 다시 실행할 필요 없음. 새 maintainer가 인계받거나 SA를 새로 만들 때만 사용.
```bash
export PROJECT_ID="k-skill-proxy"
export PROJECT_NUMBER="$(gcloud projects describe "$PROJECT_ID" --format='value(projectNumber)')"
export GH_REPO="NomaDamas/k-skill" # owner/repo
export POOL_ID="github-actions-pool"
export PROVIDER_ID="github-actions-provider"
export DEPLOY_SA="k-skill-proxy-deploy"
export DEPLOY_SA_EMAIL="${DEPLOY_SA}@${PROJECT_ID}.iam.gserviceaccount.com"
```
### 1) 필요한 API 활성화
```bash
gcloud services enable \
iamcredentials.googleapis.com \
run.googleapis.com \
artifactregistry.googleapis.com \
secretmanager.googleapis.com \
--project="$PROJECT_ID"
```
### 2) Workload Identity Pool + GitHub OIDC provider
```bash
gcloud iam workload-identity-pools create "$POOL_ID" \
--project="$PROJECT_ID" \
--location=global \
--display-name="GitHub Actions"
gcloud iam workload-identity-pools providers create-oidc "$PROVIDER_ID" \
--project="$PROJECT_ID" \
--location=global \
--workload-identity-pool="$POOL_ID" \
--display-name="GitHub OIDC" \
--issuer-uri="https://token.actions.githubusercontent.com" \
--attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository,attribute.repository_owner=assertion.repository_owner" \
--attribute-condition="assertion.repository == '${GH_REPO}'"
```
> `attribute-condition`은 토큰 발급 단계에서 우리 저장소만 허용해 풀 자체를 좁힙니다. 임의의 다른 repo가 같은 풀을 통해 SA를 impersonate하지 못하게 막는 핵심 가드입니다.
### 3) Deploy service account 생성
```bash
gcloud iam service-accounts create "$DEPLOY_SA" \
--project="$PROJECT_ID" \
--display-name="GitHub Actions k-skill-proxy deployer"
```
### 4) 풀 → service account impersonation 허용
```bash
gcloud iam service-accounts add-iam-policy-binding "$DEPLOY_SA_EMAIL" \
--project="$PROJECT_ID" \
--role=roles/iam.workloadIdentityUser \
--member="principalSet://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${POOL_ID}/attribute.repository/${GH_REPO}"
```
### 5) deploy SA에 필요한 권한 부여
```bash
gcloud projects add-iam-policy-binding "$PROJECT_ID" \
--member="serviceAccount:${DEPLOY_SA_EMAIL}" \
--role=roles/run.admin
gcloud projects add-iam-policy-binding "$PROJECT_ID" \
--member="serviceAccount:${DEPLOY_SA_EMAIL}" \
--role=roles/artifactregistry.writer
gcloud projects add-iam-policy-binding "$PROJECT_ID" \
--member="serviceAccount:${DEPLOY_SA_EMAIL}" \
--role=roles/iam.serviceAccountUser
```
`iam.serviceAccountUser`는 Cloud Run의 런타임 service account(`${PROJECT_NUMBER}-compute@developer.gserviceaccount.com`)를 deploy SA가 대신 지정할 수 있게 하기 위함입니다.
### 6) Cloud Run 런타임 SA에 Secret Manager accessor 부여
```bash
RUNTIME_SA="${PROJECT_NUMBER}-compute@developer.gserviceaccount.com"
for s in \
AIR_KOREA_OPEN_API_KEY KMA_OPEN_API_KEY SEOUL_OPEN_API_KEY HRFCO_OPEN_API_KEY \
OPINET_API_KEY DATA_GO_KR_API_KEY KEDU_INFO_KEY \
DATA4LIBRARY_AUTH_KEY FOODSAFETYKOREA_API_KEY KAKAO_REST_API_KEY KRX_API_KEY \
KOSIS_API_KEY NAVER_SEARCH_CLIENT_ID NAVER_SEARCH_CLIENT_SECRET LAW_OC; do
gcloud secrets add-iam-policy-binding "$s" \
--project="$PROJECT_ID" \
--member="serviceAccount:${RUNTIME_SA}" \
--role=roles/secretmanager.secretAccessor \
--condition=None >/dev/null
done
```
### 7) WIF provider 리소스 이름 확인
```bash
gcloud iam workload-identity-pools providers describe "$PROVIDER_ID" \
--project="$PROJECT_ID" \
--location=global \
--workload-identity-pool="$POOL_ID" \
--format='value(name)'
# 예: projects/123456789/locations/global/workloadIdentityPools/github-actions-pool/providers/github-actions-provider
```
이 값과 `${DEPLOY_SA_EMAIL}`을 GitHub에 등록합니다.
## GitHub repository secrets
다음 두 개의 **secret**을 `Settings → Secrets and variables → Actions → Repository secrets`에 등록합니다.
| Name | Value |
| --- | --- |
| `GCP_WIF_PROVIDER` | 위 7번에서 얻은 provider 리소스 전체 이름 |
| `GCP_DEPLOY_SERVICE_ACCOUNT` | `k-skill-proxy-deploy@k-skill-proxy.iam.gserviceaccount.com` |
> 값 자체가 민감하진 않지만, 외부에 노출되면 reconnaissance에 도움이 될 수 있으므로 secret으로 둡니다. variable로 옮겨도 동작은 동일합니다.
## Secret Manager에 upstream key 업로드
```bash
KEYS=(
AIR_KOREA_OPEN_API_KEY KMA_OPEN_API_KEY SEOUL_OPEN_API_KEY HRFCO_OPEN_API_KEY
OPINET_API_KEY DATA_GO_KR_API_KEY KEDU_INFO_KEY
DATA4LIBRARY_AUTH_KEY FOODSAFETYKOREA_API_KEY KAKAO_REST_API_KEY KRX_API_KEY
KOSIS_API_KEY NAVER_SEARCH_CLIENT_ID NAVER_SEARCH_CLIENT_SECRET LAW_OC
)
set -a; source ~/.config/k-skill/secrets.env; set +a
for k in "${KEYS[@]}"; do
value="${!k:-}"
[[ -z "$value" ]] && { echo "skip $k (empty)"; continue; }
if gcloud secrets describe "$k" --project="$PROJECT_ID" >/dev/null 2>&1; then
printf '%s' "$value" | gcloud secrets versions add "$k" --data-file=- --project="$PROJECT_ID"
else
printf '%s' "$value" | gcloud secrets create "$k" --data-file=- --replication-policy=automatic --project="$PROJECT_ID"
fi
done
```
키 값을 회전(rotate)할 때도 같은 명령을 다시 실행하면 새 version이 추가됩니다. Cloud Run은 `:latest`로 바인딩되어 있어 다음 배포부터 자동 반영됩니다(즉시 적용이 필요하면 새 revision을 한 번 더 deploy).
## 운영 점검 절차
- 자동 배포 상태: GitHub `Actions` 탭의 "Deploy k-skill-proxy to Cloud Run" 워크플로
- 라이브 헬스체크: `curl -fsS https://k-skill-proxy.nomadamas.org/health`
- Cloud Run revision/로그: GCP Console → Cloud Run → `k-skill-proxy` (`asia-northeast1`)
- 이미지 태그: `asia-northeast1-docker.pkg.dev/k-skill-proxy/k-skill/k-skill-proxy:<commit-sha>`
- 트래픽 롤백: 이전 revision으로 traffic split을 100% 되돌리거나, 직전 commit을 revert해서 main에 머지 → 워크플로가 다시 돈다.
## 로컬에서 동일한 배포를 수동으로 돌리고 싶을 때
`gcloud auth login`으로 maintainer 계정에 로그인된 상태에서:
```bash
SHA="$(git rev-parse HEAD)"
IMAGE_URI="asia-northeast1-docker.pkg.dev/k-skill-proxy/k-skill/k-skill-proxy:${SHA}"
gcloud auth configure-docker asia-northeast1-docker.pkg.dev --quiet
docker build -t "$IMAGE_URI" -f packages/k-skill-proxy/Dockerfile .
docker push "$IMAGE_URI"
gcloud run deploy k-skill-proxy \
--image="$IMAGE_URI" \
--region=asia-northeast1 \
--platform=managed \
--allow-unauthenticated \
--execution-environment=gen2 \
--cpu=1 --memory=512Mi --min-instances=0 --max-instances=3 \
--concurrency=80 --timeout=60 --cpu-boost \
--project=k-skill-proxy
```
이 명령은 평상시에는 필요 없습니다. GitHub Actions가 같은 일을 하기 때문입니다.

View file

@ -0,0 +1,45 @@
# 사업자 실사 종합 (biz-health-check)
`biz-health-check` 스킬은 사업자등록번호(+상호/지역) 하나로 무료 공공 데이터 6종을 한 번에 교차 조회해 실사 리포트 한 장을 만든다. 같은 레포의 단품 스킬 helper를 그대로 재사용한다(단일 진실원천).
## 묶는 단품 스킬
| 섹션 | 단품 스킬 | 경로 |
| --- | --- | --- |
| 국세청 사업자등록 상태 | `nts-business-registration` | proxy |
| 국민연금 가입 사업장 | `national-pension-workplace` | proxy |
| 국세 체납 명단공개 | `nts-tax-delinquency` | 직접(무인증) |
| 금융위 기업기본정보 | `fsc-corporate-info` | proxy |
| 조달청 부정당제재 | `g2b-sanctioned-supplier` | proxy |
| 지방행정 인허가 영업상태 | `localdata-business-status` | 직접(무인증) |
## 설계 원칙
- 점수·등급·"위험" 같은 해석 라벨을 산출하지 않는다. 각 항목의 사실 + 출처 + 조회시각만 병렬한다.
- 한 항목 조회가 실패해도 전체를 막지 않고 그 항목만 `unavailable` + 사유로 강등한다.
- 단품 helper를 찾지 못하면 해당 섹션만 건너뛰고 나머지를 진행한다.
## 인증/시크릿
- 사용자 측 필수 시크릿 없음.
- proxy 섹션(국세청 상태·국민연금·금융위·부정당)은 운영 서버의 `DATA_GO_KR_API_KEY`로 동작한다.
- 무인증 섹션(체납·인허가)은 키 없이 사용자 머신에서 직접 동작한다.
## 예시
```bash
python3 biz-health-check/scripts/biz_health_check.py 124-81-00998 --name "삼성전자"
python3 biz-health-check/scripts/biz_health_check.py --name "호텔샬롬" --region 제주제주시 --industry 숙박업
```
## 입력
- `b_no`: 사업자등록번호 10자리(하이픈 허용) — 상태조회·부정당제재에 필요
- `--name`: 상호·법인명 — 국민연금·금융위·체납·인허가에 필요
- `--region`: 시군구 — 인허가(동네 사업장) 조회에 필요
- `--industry`: 인허가 업종(여러 번 지정 가능)
## 공식 출처
- 각 단품 스킬 문서의 공식 출처를 따른다. 통합 목록은 [sources](../sources.md)의 "사업자 실사" 항목 참조.

View file

@ -0,0 +1,152 @@
# 번개장터 검색 가이드
## 이 기능으로 할 수 있는 일
upstream [`bunjang-cli`](https://www.npmjs.com/package/bunjang-cli) / [`pinion05/bunjangcli`](https://github.com/pinion05/bunjangcli) 를 사용해 번개장터 **검색, 상세조회, 선택적 찜/채팅, 대량 수집, AI TOON export** 를 처리한다.
- `search` 로 검색
- `item get` / `item list` 로 상세조회
- `favorite add` / `favorite remove` / `favorite list` 로 선택적 찜 관리
- `chat list` / `chat start` / `chat send` 로 선택적 채팅
- `--start-page`, `--pages`, `--max-items`, `--with-detail`, `--output` 으로 대량 수집
- `--ai --output <directory>` 로 TOON chunk 저장
## 가장 중요한 규칙
이 기능은 upstream 원본을 그대로 쓴다.
`k-skill` 안에 번개장터 수집기를 새로 넣지 않고, **CLI first** 문서/스킬만 유지한다.
즉 기본 경로는 아래다.
1. `npx --yes bunjang-cli ...`
2. 반복 사용이면 `npm install -g bunjang-cli`
## 먼저 필요한 것
- 인터넷 연결
- `node` 22+
- `npx` 또는 `npm`
- 선택적으로 interactive TTY 터미널
2026-04-06 기준 `npm view bunjang-cli version``0.2.1` 이고, README 요구 사항은 Node.js 22+ 다.
## 가장 빠른 시작: npx CLI
```bash
npx --yes bunjang-cli --help
npx --yes bunjang-cli --json auth status
npx --yes bunjang-cli --json search "아이폰" --max-items 3 --sort date
npx --yes bunjang-cli --json item get 354957625
```
반복 사용이면 전역 설치도 가능하다.
```bash
npm install -g bunjang-cli
export NODE_PATH="$(npm root -g)"
bunjang-cli --help
```
## 로그인은 선택적 interactive 플로우
```bash
npx --yes bunjang-cli auth login
npx --yes bunjang-cli auth logout
npx --yes bunjang-cli --json auth status
```
- `auth login` 은 브라우저에서 로그인 후 **터미널로 돌아와 Enter 를 눌러야** 완료된다.
- 즉 **TTY / interactive 세션** 이 아닌 환경에서는 로그인 완료 처리가 멈출 수 있다.
- 그래서 기본 문서는 검색/상세조회/대량 수집을 먼저 안내하고, 찜/채팅은 로그인된 경우에만 선택적으로 진행한다.
## 기본 흐름
### 1. 검색
```bash
npx --yes bunjang-cli search "아이폰"
npx --yes bunjang-cli search "아이폰" --price-min 500000 --price-max 1200000
npx --yes bunjang-cli search "아이폰" --sort date
npx --yes bunjang-cli --json search "아이폰" --max-items 5
```
검색 결과에는 매입글/광고/악세서리 글이 섞이고, live `search` payload 에서는 `location` 이 noisy 하거나 `description` / `status` 가 빠질 수 있다. 그래서 **검색 단계는 제목/가격 중심 1차 triage** 로만 쓰고, 세부 판단은 상세조회 이후로 미룬다.
### 2. 상세조회
```bash
npx --yes bunjang-cli item get 354957625
npx --yes bunjang-cli --json item get 354957625
npx --yes bunjang-cli --json item list --ids 354957625,354801707
```
상세에서는 `price`, `description`, `location`, `category`, `status`, `sellerName`, `sellerReviewCount`, `favoriteCount`, `transportUsed` 를 우선 본다. 즉 `description` / `status` / 믿을 만한 `location` 이 필요하면 **반드시 `item get` 또는 `--with-detail` 이후** 에만 판정한다.
### 3. 대량 수집
```bash
npx --yes bunjang-cli search "아이폰" \
--start-page 1 \
--pages 5 \
--max-items 50 \
--sort date \
--with-detail \
--output artifacts/bunjang-iphone.json
```
export 검증 시에는 결과 파일 존재 여부와 top-level `items[]` 안의 `summary` / `detail` / optional `error` 구조, item 별 `sourcePage` 또는 `summary.raw.page` 를 함께 확인한다.
### 4. AI TOON chunk
```bash
npx --yes bunjang-cli search "아이폰" \
--start-page 1 \
--pages 5 \
--max-items 50 \
--with-detail \
--ai \
--output artifacts/bunjang-iphone-ai
```
- `--ai` 에서는 `--output`**디렉토리** 여야 한다.
- 결과는 `items-1.toon` 같은 chunk 로 나뉜다.
- 대량 후보를 여러 에이전트에게 분산 평가시키기 좋다.
### 5. 찜/채팅은 로그인 후에만
```bash
npx --yes bunjang-cli --json favorite list
npx --yes bunjang-cli --json favorite add 354957625
npx --yes bunjang-cli --json favorite remove 354957625
npx --yes bunjang-cli --json chat list
npx --yes bunjang-cli --json chat start 354957625 --message "안녕하세요"
npx --yes bunjang-cli --json chat send 84191651 --message "상품 상태 괜찮을까요?"
```
- `favorite``chat` 은 **로그인이 필요한 선택적 기능**이다.
- 기본 검색/분석 요청이라면 실행하지 않고 명령만 안내한다.
- 검증이 필요하면 `favorite list` 로 세션을 먼저 확인한 뒤 add/remove 를 왕복 실행한다.
## 라이브 확인 메모
2026-04-06 기준 아래 흐름을 실제로 실행해 응답을 확인했다.
- `npx --yes bunjang-cli --help` → top-level commands (`auth`, `search`, `item`, `chat`, `favorite`, `purchase`) 확인
- `npx --yes bunjang-cli --json auth status``authenticated: false`, `headfulLoginRequired: true` 확인
- `npx --yes bunjang-cli --json search "아이폰" --max-items 1``items[0].id = 354957625`, `transportUsed = browser` 확인
- `npx --yes bunjang-cli --json item get 354957625``description`, `location`, `sellerReviewCount`, `favoriteCount`, `transportUsed = api` 확인
같은 날짜에 `search` summary 는 title/price 중심 triage 용으로만 안전했고, `status` / `description` 은 summary 에서 안정적으로 오지 않았다. 그래서 문서에서는 상세 필드 의존 판단을 `item get` / `--with-detail` 뒤로 고정한다.
같은 날짜에 `favorite list` 는 로그인 브라우저를 띄운 뒤 터미널 Enter 를 기다렸다. 그래서 문서에서는 찜/채팅을 **로그인 후 선택적으로만 실행** 하도록 고정한다.
## 제한사항
- 로그인 없는 환경에서는 찜/채팅/구매 흐름을 바로 검증하기 어렵다.
- 검색 결과에는 매입글/광고/악세서리 노이즈가 섞일 수 있다.
- DOM/API 변경에 따라 browser transport 동작이 깨질 수 있다.
- 구매 자동 확정/결제는 다루지 않는다.
## 참고 링크
- npm package: `https://www.npmjs.com/package/bunjang-cli`
- upstream repo: `https://github.com/pinion05/bunjangcli`

View file

@ -0,0 +1,90 @@
# 캐치테이블 예약 스나이핑 가이드
## 이 기능으로 할 수 있는 일
- 로그인된 Chrome 세션을 재사용해 캐치테이블 예약 페이지 진입
- 원하는 식당의 취소 슬롯/빈자리 폴링
- 여러 식당을 순차 감시하다가 먼저 열린 슬롯에 예약 시도
- 예약 오픈 시간에 맞춘 오픈런 시도
- dry-run 모드로 빈자리 발견까지만 알리고 최종 예약은 사용자에게 넘기기
## 먼저 알아둘 점
- 이 기능은 **Chrome MCP + 로그인된 캐치테이블 세션**이 있어야만 동작한다.
- 카카오/네이버 로그인 자동화는 하지 않는다.
- 결제 정보 자동 입력은 하지 않는다.
- 선결제 매장은 결제 단계에서 반드시 사용자가 직접 확인해야 한다.
- 서버 부하를 줄이기 위해 폴링 간격은 **30초 이상**으로 유지한다.
## 입력 형태
다음 정보를 자연어에서 추출해 사용한다.
- 식당명 또는 캐치테이블 URL
- 날짜 또는 날짜 범위
- 인원 수
- 시간대(선택)
- dry-run 여부
- 인원 유연 매칭 여부
- 예약 오픈 시각(오픈런 모드일 때)
예시:
- `온지음 5월 토요일 저녁 2인 빈자리 나오면 예약해줘`
- `온지음, 밍글스, 라연 중 5월 주말 2인 아무데나 먼저 뜨는 거 잡아줘`
- `라연 5월 예약 오픈이 4월 30일 오전 10시야, 그때 맞춰 2인 잡아줘`
- `밍글스 빈자리 뜨면 예약은 내가 할게, dry-run으로`
- `https://app.catchtable.co.kr/ct/shop/mingles 토요일 4명 자동예약`
## 동작 흐름
1. 캐치테이블 홈 또는 식당 페이지에 접속한다.
2. 로그인 상태를 확인한다.
3. 오픈런 모드면 지정 시각까지 대기 후 즉시 예약을 시도한다.
4. 일반 스나이핑 모드면 30초 간격으로 새로고침/재조회하며 슬롯을 감시한다.
5. 슬롯이 열리면 날짜/인원/시간을 선택하고 예약 흐름으로 진입한다.
6. 무료 예약이면 최종 예약 버튼까지 진행하고, 선결제 매장이면 결제 직전 단계에서 사용자 확인을 요구한다.
## 멀티 타겟 / 인원 유연 모드
### 멀티 타겟
- 여러 식당을 순차적으로 감시한다.
- 한 곳에서 예약 가능한 슬롯을 발견하면 나머지 감시는 즉시 중단한다.
### 인원 유연 매칭
- 예를 들어 2인 자리가 없을 때 4인 자리를 대안으로 확인할 수 있다.
- 대안 인원 슬롯을 발견하면 사용자에게 확인을 받고 다음 단계로 진행한다.
## dry-run 모드
`알림만`, `dry-run` 같은 표현이 있으면 예약 완료 대신 다음까지만 수행한다.
- 빈자리 발견
- 식당/날짜/시간/인원 요약
- 사용자가 직접 예약할 수 있도록 알림
## 제한사항
- 로그인 자동화 없음
- 카드/간편결제 정보 자동 입력 없음
- 캐치테이블 외 예약 플랫폼 미지원
- UI 변경 시 selector/흐름이 깨질 수 있음
## 검증 메모
2026-04-22 기준 로컬 검증에서 다음을 확인했다.
- 캐치테이블 식당 페이지 진입
- 예약 가능한 식당에서 날짜/인원/시간 선택
- 방문 확인 단계 진입
- 결제 방식 선택 단계 진입
다만 최종 예약 완료는 **로그인된 캐치테이블 세션이 없는 Chrome 프로필**에서는 검증할 수 없었다. 이 기능의 최종 성공 여부는 로그인된 사용자 세션과 실시간 좌석 상황에 직접 의존한다.
## 원칙
- 사용자의 로그인 자격 증명을 새 env var나 repo 문서에 추가하지 않는다.
- 사용자가 이미 로그인해 둔 브라우저 세션만 재사용한다.
- 결제나 취소 수수료가 얽힌 단계에서는 사용자 확인을 우선한다.

View file

@ -0,0 +1,106 @@
# 근처 가장 싼 주유소 찾기 가이드
## 이 기능으로 할 수 있는 일
- 현재 위치 기준 근처 최저가 주유소 검색
- 휘발유/경유 기준 nearby 가격 비교
- Opinet 공식 API(`aroundAll.do`, `detailById.do`) 기반 요약
- 셀프 여부, 세차장, 경정비, 품질인증 여부까지 함께 정리
## 먼저 필요한 것
- 인터넷 연결
- `node` 18+
- `cheap-gas-nearby` package 또는 이 저장소 전체 설치
사용자 쪽에서 별도 `OPINET_API_KEY` 를 준비할 필요가 없다. 기본적으로 `https://k-skill-proxy.nomadamas.org` 프록시를 경유하며, upstream key는 proxy 서버에서만 주입한다.
## 가장 먼저 할 일
이 기능은 **반드시 현재 위치를 먼저 물어본 뒤** 실행합니다.
권장 질문 예시:
```text
현재 위치를 알려주세요. 동네/역명/랜드마크/위도·경도 중 편한 형식으로 보내주시면 근처에서 가장 싼 주유소를 찾아볼게요.
```
제품을 안 알려주면 보통 **휘발유(B027)** 기준으로 시작하고, 경유가 필요하면 `D047` 로 바꿉니다.
## 입력값
- 동네/상권: `강남`, `성수동`, `판교`
- 역명/랜드마크: `서울역`, `강남역`, `코엑스`
- 좌표: `37.55472, 126.97068`
- 제품코드: `B027`(휘발유), `D047`(경유)
위치 문자열은 Kakao Map anchor 검색으로 **WGS84 좌표**를 잡고, 내부적으로 **KATEC** 으로 변환해 Opinet nearby 검색에 사용합니다.
## 공식 표면
- Opinet 오픈 API 안내: `https://www.opinet.co.kr/user/custapi/openApiInfo.do`
- 반경 내 주유소: `https://www.opinet.co.kr/api/aroundAll.do`
- 주유소 상세정보(ID): `https://www.opinet.co.kr/api/detailById.do`
- 지역코드: `https://www.opinet.co.kr/api/areaCode.do`
- Kakao Map 모바일 검색: `https://m.map.kakao.com/actions/searchView`
- Kakao Map 장소 패널 JSON: `https://place-api.map.kakao.com/places/panel3/<confirmId>`
Opinet nearby 검색의 핵심 파라미터:
- `x`, `y`: 기준 위치 **KATEC** 좌표
- `radius`: 반경(m, 최대 5000)
- `prodcd`: `B027`, `D047`, `B034`, `C004`, `K015`
- `sort=1`: 가격순
## 기본 흐름
1. 유저에게 현재 위치를 먼저 묻습니다.
2. 위치 문자열을 받으면 Kakao Map으로 anchor 후보를 고르고 좌표를 확보합니다.
3. 좌표를 **WGS84 → KATEC** 으로 변환합니다.
4. Opinet `aroundAll.do``sort=1` 가격순으로 조회합니다.
5. 상위 후보는 `detailById.do` 로 재조회해 주소/전화번호/편의시설을 보강합니다.
6. 가격순 상위 3~5개만 짧게 응답합니다.
## Node.js 예시
```js
const { searchCheapGasStationsByLocationQuery } = require("cheap-gas-nearby");
async function main() {
const result = await searchCheapGasStationsByLocationQuery("서울역", {
productCode: "B027",
radius: 1000,
limit: 3
});
for (const item of result.items) {
console.log(`${item.name}: ${item.price}원/L, ${item.distanceMeters}m, ${item.roadAddress}`);
}
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
```
## Offline smoke example
실제 키가 없는 환경에서도 패키지 동작은 fixture 기반으로 검증할 수 있습니다.
```bash
node --test packages/cheap-gas-nearby/test/index.test.js
```
## 운영 팁
- 프록시 서버가 내려가 있거나 upstream key가 없으면 503 을 반환하므로 상태를 안내합니다.
- 서울역/강남처럼 넓은 질의는 anchor 위치가 흔들릴 수 있으니 필요하면 더 구체적인 역 출구/동 이름을 한 번 더 받습니다.
- 동일 가격이면 거리순으로 다시 정렬해 보여주는 편이 좋습니다.
- 결과가 너무 많으면 반경을 `1000m` 또는 `2000m` 정도로 유지하는 편이 읽기 쉽습니다.
## 주의할 점
- Opinet Open API 인증키는 proxy 서버에서만 관리합니다. 사용자/client 쪽 secrets 파일에는 넣지 않습니다.
- Kakao Map anchor 검색은 위치 기준점만 잡기 위한 보조 단계이고, 최종 가격/순위 데이터는 Opinet을 기준으로 합니다.
- Opinet 응답의 좌표는 KATEC 이므로 WGS84와 혼동하면 안 됩니다.

View file

@ -0,0 +1,38 @@
# 법인등기 신청 컨설팅
`corporate-registration-consulting`은 일반 영리 주식회사 **발기설립** 등기를 처음 진행하는 사용자를 위해 저장된 HWP 양식 사본을 채우는 법인설립등기 준비 스킬이다. 모집설립은 일반적이지 않으므로 기본 플로우에서 제외한다.
## 핵심 원칙
- 런타임 지침은 `corporate-registration-consulting/SKILL.md`가 담당한다.
- 실제 작성은 Markdown 초안이 아니라 저장된 HWP 양식 사본을 레포 밖 비공개 작업 디렉터리에 복사해 진행한다. 최종 산출물은 실제 `.hwp` 사본이다.
- 정관은 `templates/attachment-hwp/standard-articles-startup-moj.hwp` 또는 `templates/attachment-hwp/articles-of-incorporation.hwp`를 우선 사용한다.
- 단순 `replace-all`은 shortcut일 뿐이며, 각 HWP의 상단·본문·표 셀·하단 날짜·서명/날인란을 한 장 한 장 순차 확인한다.
- 자리표시자와 실제 사용자 입력값을 구분하고, 개인정보가 들어간 산출물은 레포에 커밋하지 않는다.
## 필수 문서와 양식
인터넷등기소/온라인법인설립시스템 제출 전 대조용 저장된 양식 경로와 공식 출처 대조는 `corporate-registration-consulting/templates/official-form-sources.md`, 제출 체크리스트는 `corporate-registration-consulting/templates/incorporation-document-pack.md`를 기준으로 한다.
반드시 포함할 항목:
- 주식회사설립등기신청서(발기설립): `templates/official/form-65-1-stock-company-incorporation-promoter.hwp`
- 정관, 주식발행사항 동의/상법 제291조 사항 증명정보, 주식인수증, 발기인회의사록, 주주명부, 조사보고서, 취임승낙서, 이사회의사록, 인감신고서, 위임장: `templates/attachment-hwp/*.hwp`
- 등록면허세 영수필확인서, 등기신청수수료 영수필확인서
- 등기이사 개인 인감증명서 또는 본인서명사실확인서
- 등기이사 주민등록초본/등본 등 주소 확인 증빙
- 주금납입/잔고증명
조건부로 명의개서대리인, 현물출자·재산인수 등 변태설립사항, 인허가 업종, 정관 공증·의사록 인증 필요 여부를 확인한다.
## 중점 확인
- 정관 제2조 목적에는 실제 사업 업태·종목을 채운다.
- 정관 맨 마지막 작성일자, 발기인 성명, 서명/기명날인, 여러 장 문서의 간인을 확인한다.
- 인감신고서 제출을 위해 실제 법인인감 도장을 준비하도록 안내한다.
- 등록면허세·지방교육세·과밀억제권역/대도시 중과는 지방세법 제28조 및 위택스/관할 지자체 결과를 기준으로 확인한다.
- 소프트웨어 업종은 조세특례제한법 제6조 등 감면/중과 제외 가능성을 체크하되 확정하지 않는다.
## 면책
이 기능은 참고용이며 법률·세무 자문, 법무사 대행이 아니다. 에이전트는 인터넷등기소/위택스 로그인, 전자서명, 세금 납부, 등기 제출, 사용자 사칭, 최종 법률 판단, 최종 세무 판단을 지원하지 않는다. 실제 제출은 사용자가 직접 수행하고 제출 전 관할 등기소·위택스/지자체·법무사·변호사·세무사 확인을 권한다.

View file

@ -1,108 +1,166 @@
# 쿠팡 상품 가격/리뷰 조회 가이드
# 쿠팡 상품 검색 가이드
## 이 기능으로 할 수 있는 일
- 쿠팡 공식 검색 URL 만들기
- 브라우저에서 캡처한 쿠팡 검색 결과 HTML 파싱
- 상품 상세/가격/판매자/배송 배지/필수 표기 정보 파싱
- 상품 리뷰 요약과 개별 리뷰 파싱
- direct fetch / headless browser 가 차단되는지 probe
[retention-corp/coupang_partners](https://github.com/retention-corp/coupang_partners)의 로컬 Coupang MCP 호환 레이어를 이용해 쿠팡 상품 조회 도구를 실행한다. 기존 HF Space 기반 `coupang-mcp` 서버 대신 upstream 저장소의 `bin/coupang_mcp.py``local://coupang-mcp` 계약으로 호출한다.
## 먼저 알아둘 점
- 키워드 상품 검색
- 로켓배송 전용 필터 검색
- 가격대 범위 검색
- 상품 비교표 생성
- 카테고리별 베스트 상품, 골드박스 당일 특가
- 인기 검색어/계절 상품 추천
2026-03-31 기준 확인 내용:
## 동작 방식
- 쿠팡 개발자 Open API 문서는 **판매자/WING 중심**이다.
- 일반 소비자용 상품 검색·상품평 조회 Open API는 확인하지 못했다.
- 이 저장소 환경에서 `www.coupang.com` direct fetch 는 `403 Access Denied` 였다.
- `m.coupang.com` direct fetch 도 차단되었지만, rerun 마다 `200 challenge-html` 또는 `403 access-denied-html` 처럼 **차단 응답 status/reason 이 달라질 수 있었다.**
- headless Playwright-core probe 도 막혔고, 여기서도 **exact blocked shape 는 edge/challenge 상태에 따라 달라질 수 있었다.**
```
Codex/Claude Code → coupang_partners_mcp.py → retention-corp/coupang_partners checkout → bin/coupang_mcp.py → local://coupang-mcp
├─ operator: Coupang Partners API (local HMAC)
└─ credentialless: hosted fallback → https://a.retn.kr/v1/public/assist
```
즉, 이 기능은 **anti-bot 우회**가 아니라 다음 흐름을 기준으로 동작한다.
- **구형 hosted endpoint 제거** — 이전 HF Space 기반 MCP 서버를 사용하지 않는다.
- **upstream 고정** — 래퍼는 `https://github.com/retention-corp/coupang_partners.git`를 clone/update한 뒤 upstream CLI에 위임한다.
- **이중 실행 경로**`COUPANG_ACCESS_KEY`/`COUPANG_SECRET_KEY`가 둘 다 있으면 upstream이 로컬 HMAC으로 Coupang Partners API를 호출하고, 없으면 자동으로 Retention Corp의 hosted 백엔드(`https://a.retn.kr/v1/public/assist`)로 떨어져 공개 추천/검색을 반환한다(hosted fallback). 래퍼는 두 경로를 자동 선택한다.
- **allowlist** — hosted fallback은 `X-OpenClaw-Client-Id` allowlist로 게이트되어 있다. upstream이 기본으로 실어 보내는 `openclaw-skill` 값이 현재 Retention Corp allowlist에 등록되어 있어 credentialless 호출이 200을 받는다. k-skill 래퍼는 이 기본값을 그대로 사용하고 `OPENCLAW_SHOPPING_CLIENT_ID`를 오버라이드하지 않는다. Retention Corp 측 allowlist 정책이 바뀌면 그때 맞춰 가이드를 갱신한다.
- **secret은 runtime 환경변수** — 운영자 모드에서는 `COUPANG_ACCESS_KEY`, `COUPANG_SECRET_KEY`를 환경변수로 주입한다. 키를 저장소나 답변에 노출하지 않는다.
- **계약 확인 우선**`tools`/`init` 명령으로 로컬 MCP 호환 도구 목록과 JSON-RPC payload 형태를 먼저 확인한다.
1. 공식 쿠팡 URL을 만든다.
2. 가능하면 브라우저 세션에서 확보한 HTML 을 파싱한다.
3. 막히면 probe 결과를 그대로 보여주고, 브라우저 HTML 또는 상품 URL을 추가 입력으로 받는다.
## MCP 계약
## 공식 표면
```
local://coupang-mcp
```
- seller Open API docs: `https://developers.coupangcorp.com/hc/ko/sections/360004260614-상품-API`
- desktop search: `https://www.coupang.com/np/search?q=<query>`
- mobile search: `https://m.coupang.com/nm/search?q=<query>`
- product detail: `https://www.coupang.com/vp/products/<productId>?itemId=<itemId>&vendorItemId=<vendorItemId>`
- review anchor: `#sdpReview`
프로토콜 호환 버전: MCP `2025-03-26`. 네트워크 Streamable HTTP 서버가 아니라 upstream 저장소의 로컬 CLI가 같은 도구 이름을 제공한다.
## 환경변수
| 환경변수 | 역할 | 기본값 |
|---------|------|--------|
| `COUPANG_ACCESS_KEY`, `COUPANG_SECRET_KEY` | 운영자 Coupang Partners API 크리덴셜. 둘 다 있을 때만 로컬 HMAC 경로가 활성화된다. | 없음 (없으면 hosted fallback) |
| `OPENCLAW_SHOPPING_CLIENT_ID` | hosted fallback의 `X-OpenClaw-Client-Id`. upstream이 `openclaw-skill`을 기본으로 실어 보내며 이 값이 현재 Retention Corp allowlist에 등록되어 있다. k-skill 래퍼는 이 변수를 오버라이드하지 않는다. | `openclaw-skill` |
| `OPENCLAW_SHOPPING_FORCE_HOSTED` | `1`이면 키가 있어도 hosted 경로를 강제한다. | 비어있음 |
| `OPENCLAW_SHOPPING_BASE_URL` | hosted 백엔드 base URL 오버라이드. 스테이징/로컬 backend 테스트용. | `https://a.retn.kr` |
k-skill 쪽 래퍼(`coupang_partners_mcp.py`)는 위 환경변수를 **오버라이드/디폴트 설정 없이 그대로 upstream에 전달**한다. 사용자가 export한 값이 최종 결정을 가져간다.
## 사용 가능한 도구
| 도구명 | CLI 명령 | 기능 | 사용 예시 |
|--------|----------|------|----------|
| `search_coupang_products` | `search` | 일반 상품 검색 | `python3 coupang-product-search/scripts/coupang_partners_mcp.py search "맥북"` |
| `search_coupang_rocket` | `rocket` | 로켓배송만 필터링 | `python3 coupang-product-search/scripts/coupang_partners_mcp.py rocket "에어팟"` |
| `search_coupang_budget` | `budget` | 가격대 범위 검색 | `python3 coupang-product-search/scripts/coupang_partners_mcp.py budget "키보드" --max-price 100000` |
| `compare_coupang_products` | `compare` | 상품 비교표 생성 | `python3 coupang-product-search/scripts/coupang_partners_mcp.py compare "아이패드 vs 갤럭시탭"` |
| `get_coupang_recommendations` | `recommendations` | 인기 검색어 제안 | `python3 coupang-product-search/scripts/coupang_partners_mcp.py recommendations --category 전자제품` |
| `get_coupang_seasonal` | `seasonal` | 계절/상황별 추천 | `python3 coupang-product-search/scripts/coupang_partners_mcp.py seasonal "설날 선물"` |
| `get_coupang_best_products` | `best` | 카테고리별 베스트 | `python3 coupang-product-search/scripts/coupang_partners_mcp.py best --category-id 1016` |
| `get_coupang_goldbox` | `goldbox` | 당일 특가 정보 | `python3 coupang-product-search/scripts/coupang_partners_mcp.py goldbox --limit 10` |
주의: `get_coupang_goldbox``get_coupang_best_products`는 upstream 기준 Coupang Partners API 권한이 필요한 경로이므로, 키 없이 hosted fallback으로만 실행 중이면 실패할 수 있다. 실패 메시지를 그대로 전달하고 hosted fallback이 커버하는 `search`/`rocket`/`budget`/`compare`로 우회 제안한다.
## 기본 흐름
1. 검색어 또는 상품 URL을 받는다.
2. `probeAutomation()` 으로 이 환경에서 direct/headless 접근이 가능한지 본다.
3. 브라우저 HTML 이 있으면 `searchProducts()` 로 후보를 정리한다.
4. 같은 방식으로 `getProductDetail()``getProductReviews()` 를 호출한다.
5. 차단되면 `현재 환경에서는 쿠팡 anti-bot 에 막혀 브라우저 HTML 이 필요하다`고 답한다.
1. 검색어를 받는다. 너무 넓으면 용도/예산/브랜드를 먼저 물어본다.
2. `tools`와 `init` 명령으로 retention-corp/coupang_partners 로컬 MCP 도구 목록과 handshake payload를 확인한다.
3. 요청에 맞는 CLI 명령을 실행한다(키가 없어도 hosted fallback으로 `search`/`rocket`/`budget`/`compare`는 작동한다).
4. `data.result`를 읽고 로켓배송/일반배송을 구분하여 정리한다.
5. 상위 3~5개 추천과 함께 가격/배송 정보, 변동 가능성, affiliate 고지를 제공한다.
## Node.js 예시
## 호출 예시
```js
const {
getProductDetail,
getProductReviews,
probeAutomation,
searchProducts
} = require("coupang-product-search")
```bash
# 1. 최초 실행: upstream checkout을 자동 clone하고 도구 목록 확인
python3 coupang-product-search/scripts/coupang_partners_mcp.py tools
python3 coupang-product-search/scripts/coupang_partners_mcp.py init
async function browserCapture(url) {
// 호출 환경에서 구현
throw new Error(`Implement browser capture for ${url}`)
}
# 2. 이미 clone된 upstream을 명시해서 네트워크 없이 계약 확인
python3 coupang-product-search/scripts/coupang_partners_mcp.py \
--repo-dir ~/.cache/k-skill/coupang_partners \
--no-clone \
tools
python3 coupang-product-search/scripts/coupang_partners_mcp.py \
--repo-dir ~/.cache/k-skill/coupang_partners \
--no-clone \
init
async function main() {
const probe = await probeAutomation("생수")
console.log(probe)
// browser 결과는 browserFetchHtml 을 주입하지 않으면 null 이다.
# 3. 기존 checkout을 fast-forward로 최신화한 뒤 계약 확인
python3 coupang-product-search/scripts/coupang_partners_mcp.py \
--repo-dir ~/.cache/k-skill/coupang_partners \
--update \
tools
const search = await searchProducts("생수", { fetchHtml: browserCapture })
console.log(search.items.slice(0, 3))
# 4. 상품 검색 (키 없이도 hosted fallback으로 동작)
python3 coupang-product-search/scripts/coupang_partners_mcp.py search "생수"
const detail = await getProductDetail(search.items[0].productUrl, { fetchHtml: browserCapture })
console.log(detail)
# 5. 로켓배송 필터
python3 coupang-product-search/scripts/coupang_partners_mcp.py rocket "에어팟"
const reviews = await getProductReviews(detail.productUrl, { fetchHtml: browserCapture })
console.log(reviews.summary)
}
main().catch((error) => {
console.error(error)
process.exitCode = 1
})
# 6. hosted fallback 강제 (upstream 기본 allowlist client-id 유지)
OPENCLAW_SHOPPING_FORCE_HOSTED=1 \
python3 coupang-product-search/scripts/coupang_partners_mcp.py search "무선청소기"
```
## Live probe 메모
## 결과 형식
아래 값은 **2026-03-31** 기준 `query=생수` 로 확인한 blocked outcome 범위다.
upstream CLI는 다음과 같은 JSON envelope를 반환한다.
```json
{
"directDesktop": {
"blocked": true,
"observed": ["403/access-denied-html"]
},
"directMobile": {
"blocked": true,
"observed": ["200/challenge-html", "403/access-denied-html"]
},
"browser": {
"blocked": true,
"observed": ["access-denied-html"],
"notes": "browserFetchHtml 을 주입한 수동/외부 Playwright-core 검증 기준"
"ok": true,
"data": {
"session_id": "session-...",
"tool": "search_coupang_products",
"payload": {
"jsonrpc": "2.0",
"result": {
"content": [
{"type": "text", "text": "[...]"}
]
}
},
"result": []
}
}
```
즉, **차단 자체는 확인되지만 exact status/reason 은 달라질 수 있고, clean checkout 의 `probeAutomation(\"생수\")` 는 browser 값을 자동으로 채우지 않는다.** 공식 URL 구조 자체는 안정적으로 만들 수 있었지만, live HTML 수집은 환경 의존적이다.
- hosted fallback 경로는 각 상품의 `productUrl``https://a.retn.kr/s/...` 형태의 short deeplink를 붙여 돌려준다.
- operator (로컬 HMAC) 경로는 `https://link.coupang.com/...?lptag=AF...` 형태의 직접 딥링크를 돌려준다.
- 두 경로 모두 Retention Corp의 쿠팡 파트너스 채널로 트래킹된다. affiliate 고지를 반드시 포함한다.
## 운영 팁
사용자 답변은 짧은 비교표 형태로 정리한다.
- 검색어가 넓으면 용도/예산/브랜드/용량을 먼저 물어본다.
- 리뷰는 평균 평점, 총 리뷰 수, 대표 리뷰 2~3개만 먼저 요약한다.
- 상품 URL이 이미 있으면 검색 단계를 건너뛰고 상세/리뷰 파싱으로 바로 들어간다.
- headless probe 가 막히면 우회 시도를 늘리지 말고 브라우저 캡처 HTML 필요 사실을 분명히 말한다.
```
## rocket (상위 후보)
1) LG전자 4K UHD 모니터
가격: 397,750원 (참고용)
보러가기: https://a.retn.kr/s/...
## normal (상위 후보)
1) 삼성전자 QHD 오디세이 G5 게이밍 모니터
가격: 283,000원 (참고용)
보러가기: https://a.retn.kr/s/...
```
## Affiliate 고지 (필수)
hosted fallback이 반환하는 응답에는 `disclosure` 필드가 포함될 수 있다. 예시 문구: `"파트너스 활동을 통해 일정액의 수수료를 제공받을 수 있음"`. 이 문구가 오면 **답변에 그대로 노출**한다. 응답에 disclosure가 없더라도 `a.retn.kr/s/` shortlink나 `lptag=AF` 딥링크가 포함되는 이상 같은 취지의 문구를 반드시 포함한다.
## 제한사항
- 가격/품절/배송 정보는 실시간으로 바뀔 수 있다.
- 로그인, 장바구니, 결제 자동화는 지원하지 않는다.
- hosted fallback(`https://a.retn.kr/v1/public/assist`)은 allowlist로 게이트되어 있어 upstream 정책에 따라 응답 형태나 client-id 요구가 바뀔 수 있다.
- `goldbox`/`best-products` 등 Coupang Partners API 권한이 필요한 도구는 hosted fallback에서는 대체되지 않으므로 실패할 수 있다.
- upstream checkout이 없고 네트워크 clone도 막힌 환경에서는 `--repo-dir`로 기존 checkout을 지정해야 한다.
## 출처
- [retention-corp/coupang_partners GitHub](https://github.com/retention-corp/coupang_partners)
- 로컬 MCP 계약: `local://coupang-mcp`
- hosted fallback endpoint: `https://a.retn.kr/v1/public/assist`
- hosted fallback을 upstream에 추가한 PR: <https://github.com/retention-corp/coupang_partners/pull/1> (merged)
- 래퍼: `coupang-product-search/scripts/coupang_partners_mcp.py`

View file

@ -0,0 +1,112 @@
# 법원 경매 부동산 매각공고 조회
대한민국 법원이 운영하는 공식 **법원경매정보** 사이트(`courtauction.go.kr`) 의 매각공고와 사건정보를 에이전트가 활용할 수 있는 JSON 형태로 변환해서 돌려준다.
> **참고용입니다.** 실제 입찰 전에는 반드시 해당 법원의 원문 매각공고와 매각물건명세서를 직접 확인하세요. 본 스킬은 read-only이며, 입찰서 자동 작성·자동 제출은 지원하지 않습니다.
## 무엇을 할 수 있나
- ✅ Workflow A — **매각공고 브라우징**: 매각기일·법원·기일/기간 입찰을 조건으로 매각공고 목록 → 그 공고 안의 사건번호·용도·주소·감정평가액·최저매각가격 펼치기
- ✅ Workflow B — **사건번호 직접 조회**: 법원사무소코드 + 사건번호(`2024타경100001`) → 사건정보·물건내역·매각기일별 이력·배당요구종기
- ✅ Workflow C — **부동산 물건 자유 조건검색**: 지역·용도·가격대·면적·유찰횟수·매각기일 조건 → 물건 목록 JSON
- ✅ 법원사무소 코드(60+개) + 입찰구분 코드(기일입찰=`000331`, 기간입찰=`000332`) + Workflow C용 대표 용도/지역 코드 변환
- ✅ 2-tier transport — direct HTTP 1차, Playwright fallback 옵션
- ✅ 안티봇 가드 — 호출 간 ≥2초 jitter, 세션당 호출 budget, `data.ipcheck === false` 즉시 `BLOCKED` throw
## 무엇을 할 수 없나 (별도 follow-up 이슈)
- ❌ Workflow D 일별/월별 캘린더
- ❌ 매각물건 사진(전경/개황/내부) URL 노출
- ❌ 매각물건명세서·현황조사서·감정평가서 PDF 다운로드
- ❌ 동산(자동차·중기) 경매
## 차단(BLOCKED) 정책
`courtauction.go.kr` 은 자동화 호출에 매우 민감해서 빠른 연속 조회 시 IP가 약 1시간 차단됩니다. 본 스킬은 다음과 같이 보수적으로 동작합니다.
- 호출 간 최소 2초 + jitter 0~1초 대기 (override: `--min-delay-ms 3000`)
- 세션당 호출 budget 10회 (override: `--max-calls 5`)
- `data.ipcheck === false` 또는 응답 메시지에 "차단" 포함 시 → `BLOCKED` 에러를 즉시 throw, **자동 재시도 금지** (차단 연장 위험)
차단되면 같은 IP에서 약 1시간을 기다려야 합니다. 그 사이에는 다른 IP 또는 사람이 직접 사이트에 접속해서 차단 해제 화면을 거칩니다.
## CLI 사용
```bash
court-auction-notice-search -h
court-auction-notice-search codes courts --pretty | head -40
court-auction-notice-search codes bid-types --pretty
court-auction-notice-search codes usages --pretty
court-auction-notice-search codes regions --pretty
court-auction-notice-search notices --date 2026-04 --court-code B000210 --bid-type date --pretty
court-auction-notice-search search --sido 서울특별시 --sigungu 11680 --usage-large 건물 --usage-medium 21200 \
--price-min 100000000 --price-max 500000000 --sale-from 2026-05-01 --sale-to 2026-05-20 --pretty
court-auction-notice-search case --court-code B000210 --case-number "2024타경100001" --pretty
```
## Node.js 사용
```js
const {
searchSaleNotices,
getSaleNoticeDetail,
getCaseByCaseNumber,
searchProperties
} = require("court-auction-notice-search");
const notices = await searchSaleNotices({
date: "2026-04", // 월 전체 조회. 일자 입력은 같은 월 조회 후 해당일만 필터링
courtCode: "B000210",
bidType: "date"
});
if (notices.items.length > 0) {
const detail = await getSaleNoticeDetail(notices.items[0]);
for (const item of detail.items) {
console.log(item.caseNumber, item.usage, item.address);
console.log(" 감정 ", item.appraisedPrice, "최저 ", item.minimumSalePrice);
}
}
const caseInfo = await getCaseByCaseNumber({
courtCode: "B000210",
caseNumber: "2024타경100001"
});
const properties = await searchProperties({
region: { sido: "서울특별시", sigungu: "11680", dong: "11680101" },
usage: { large: "건물" },
priceRange: { min: 100000000, max: 500000000 },
saleDate: { from: "2026-05-01", to: "2026-05-20" },
flbdCount: { min: 1 },
page: 1,
pageSize: 20
});
```
## 사이트 내부 endpoint (직접 캡처한 것)
| 목적 | 메소드 + 경로 | request body |
| --- | --- | --- |
| 매각공고 목록 | `POST /pgj/pgj143/selectRletDspslPbanc.on` | `{"dma_srchDspslPbanc":{"srchYmd","cortOfcCd","bidDvsCd","srchBtnYn":"Y"}}` (`srchYmd`는 사이트 검색 버튼과 동일하게 `YYYYMM`) |
| 매각공고 상세 | `POST /pgj/pgj143/selectRletDspslPbancDtl.on` | `{"dma_srchGnrlPbanc":{"cortOfcCd","dspslDxdyYmd","jdbnCd",...}}` |
| 사건 단건 | `POST /pgj/pgj15A/selectAuctnCsSrchRslt.on` | `{"dma_srchCsDtlInf":{"cortOfcCd","csNo"}}` |
| 물건 자유 조건검색 | `POST /pgj/pgjsearch/searchControllerMain.on` | canonical body captured via Playwright (`scripts/capture-pgj151-submit.cjs`); fixture at `packages/court-auction-notice-search/test/fixtures/canonical-search-body.json`. `pageNo/pageSize/statNum` 은 number, `pageSize` 는 upstream 드롭다운 값 `10`/`20`/`50`/`100`만 허용, `notifyLoc` 기본 `"off"`. |
| 법원사무소 코드 | `POST /pgj/pgjComm/selectCortOfcCdLst.on` | `{}` |
세션 cookie(`JSESSIONID`, `WMONID`)는 endpoint별 진입 화면을 먼저 열어 받아둡니다. 매각공고/상세는 `GET /pgj/index.on?w2xPath=/pgj/ui/pgj100/PGJ143M01.xml&pgjId=143M01`, 물건 자유 조건검색(Workflow C)은 `GET /pgj/index.on?w2xPath=/pgj/ui/pgj100/PGJ151F00.xml&pgjId=151F00` 으로 warmup 합니다.
## 설치
```bash
npm install court-auction-notice-search
# Playwright fallback 을 쓰려면 (선택)
npm install rebrowser-playwright # 권장
# 또는
npm install playwright-core
```
## 관련 이슈
- 이 패키지는 [Issue #167](https://github.com/NomaDamas/k-skill/issues/167) 에서 출발했고, #184에서 Workflow C 자유 조건검색을 추가했습니다.
- 캘린더·물건 사진·PDF·동산 경매는 별도 follow-up 이슈로 분리되어 추적됩니다.

View file

@ -0,0 +1,43 @@
# 당근중고차 검색 가이드 (`daangn-cars-search`)
당근중고차 공개 웹 데이터 표면을 사용해 지역·키워드·가격 조건 기반 차량을 검색하고, 개별 차량 상세를 읽기 전용으로 확인하는 스킬입니다.
## 사용 시나리오
- "당근중고차 합정동 레이 찾아봐"
- "당근에서 천만원 이하 중고차 검색해줘"
- "이 당근 중고차 URL 상세 요약해줘"
## 구현 표면
브라우저 자동화, 로그인, 채팅, 문의, 구매 자동화를 사용하지 않습니다.
1. 지역 해석: `https://www.daangn.com/kr/api/v1/regions/keyword?keyword=<지역명>`
2. 검색: `https://www.daangn.com/kr/cars/?in=<지역명>-<id>&onlyOnSale=1&_data=routes/kr.cars._index`
3. 상세: `<차량 URL>?_data=routes%2Fkr.cars.%24car_post_id`
## 로컬 실행
```bash
python3 daangn-cars-search/scripts/daangn_cars.py search "레이" --region "합정동" --limit 5
python3 daangn-cars-search/scripts/daangn_cars.py search --region "합정동" --price-max 10000000 --limit 5
python3 daangn-cars-search/scripts/daangn_cars.py detail "https://www.daangn.com/kr/cars/.../"
```
## 지역 필터
지역명은 당근 region API로 내부 id를 해석한 뒤 `in=<지역명>-<id>` 형태로 검색 URL에 넣습니다.
```text
합정동 → 서울특별시 마포구 합정동, id=231 → in=합정동-231
```
## 출력 해석
검색 결과는 `title`, `price`, `price_text`, `region`, `status`, `driveDistance`, `carData`, `chatRoomCount`, `url`을 우선 확인합니다. 차량 연식, 주행거리, 사고/정비 이력처럼 원문 의존도가 높은 정보는 상세 조회의 `carPost` 원문을 함께 확인합니다.
## 제한사항
- 공개 Remix `_data` route 이름이나 JSON shape가 바뀌면 실패할 수 있습니다.
- 문의, 시승 예약, 구매, 결제, 채팅 자동화는 실행하지 않습니다.
- 가격·판매 상태는 실시간으로 바뀔 수 있어 원문 URL을 함께 제시합니다.

View file

@ -0,0 +1,42 @@
# 당근알바 검색 가이드 (`daangn-jobs-search`)
당근알바 공개 웹 데이터 표면을 사용해 키워드·지역 기반 알바 공고를 검색하고, 개별 공고 상세를 읽기 전용으로 확인하는 스킬입니다.
## 사용 시나리오
- "당근알바 합정동 카페 알바 찾아봐"
- "홍대 근처 주말 알바 검색해줘"
- "이 당근알바 공고 상세 요약해줘"
## 구현 표면
브라우저 자동화, 로그인, 채팅, 지원, 문의 자동화를 사용하지 않습니다.
1. 지역 해석: `https://www.daangn.com/kr/api/v1/regions/keyword?keyword=<지역명>`
2. 검색: `https://www.daangn.com/kr/jobs/?in=<지역명>-<id>&search=<키워드>&_data=routes/kr.jobs._index`
3. 상세: `<공고 URL>``jobs.daangn.com/job-posts/<id>` 공개 HTML의 title/meta/JSON-LD(헬퍼는 legacy `_data`를 먼저 시도 후 빈 응답이면 HTML 메타로 fallback)
## 로컬 실행
```bash
python3 daangn-jobs-search/scripts/daangn_jobs.py search "카페" --region "합정동" --limit 5
python3 daangn-jobs-search/scripts/daangn_jobs.py detail "https://www.daangn.com/kr/jobs/.../"
```
## 지역 필터
지역명은 당근 region API로 내부 id를 해석한 뒤 `in=<지역명>-<id>` 형태로 검색 URL에 넣습니다.
```text
합정동 → 서울특별시 마포구 합정동, id=231 → in=합정동-231
```
## 출력 해석
검색 결과는 `title`, `company`, `region`, `address`, `salary`, `salaryType`, `workDays`, `workTimeStart`, `workTimeEnd`, `closed`, `url`을 우선 확인합니다. 상세 조회는 가능하면 `jobPost` 원문을 사용하고, 공개 `_data`가 빈 응답이면 HTML title/meta/JSON-LD를 근거로 정리합니다.
## 제한사항
- 공개 Remix `_data` route 이름이나 JSON shape가 바뀌면 실패할 수 있습니다.
- 마감·삭제·비공개 전환된 공고는 상세 조회가 실패할 수 있습니다.
- 지원, 채팅, 문의, 개인정보 제출 자동화는 범위 밖입니다.

View file

@ -0,0 +1,43 @@
# 당근부동산 검색 가이드 (`daangn-realty-search`)
당근부동산 공개 웹 데이터 표면을 사용해 지역 기반 부동산 매물 후보를 검색하고, 상세 페이지의 공개 메타를 읽기 전용으로 확인하는 스킬입니다.
## 사용 시나리오
- "당근부동산 합정동 월세 매물 찾아봐"
- "마포구 전세 후보 당근에서 봐줘"
- "이 당근부동산 URL 상세 요약해줘"
## 구현 표면
브라우저 자동화, 로그인, 채팅, 문의, 예약, 계약 자동화를 사용하지 않습니다.
1. 지역 해석: `https://www.daangn.com/kr/api/v1/regions/keyword?keyword=<지역명>`
2. 검색: `https://www.daangn.com/kr/realty/?in=<지역명>-<id>&_data=routes/kr.realty._index`
3. 상세: `https://realty.daangn.com/articles/<id>``application/ld+json``<title>`
## 로컬 실행
```bash
python3 daangn-realty-search/scripts/daangn_realty.py search --region "합정동" --limit 5
python3 daangn-realty-search/scripts/daangn_realty.py search --region "합정동" --sales-type "APARTMENT" --trade-type "MONTHLY_RENT"
python3 daangn-realty-search/scripts/daangn_realty.py detail "https://realty.daangn.com/articles/..."
```
## 지역 필터
지역명은 당근 region API로 내부 id를 해석한 뒤 `in=<지역명>-<id>` 형태로 검색 URL에 넣습니다.
```text
합정동 → 서울특별시 마포구 합정동, id=231 → in=합정동-231
```
## 출력 해석
검색 결과는 `title`, `salesType`, `trade`, `area`, `areaPyeong`, `totalManageCost`, `url`을 우선 확인합니다. 부동산 판단에는 실시간 상태, 보증금/월세, 관리비, 면적, 중개/직거래 여부가 중요하므로 원본 URL을 함께 제시합니다.
## 제한사항
- 당근부동산 목록 JSON과 `realty.daangn.com` 상세 HTML 구조 변경에 영향을 받습니다.
- 문의, 방문 예약, 계약, 결제, 채팅은 실행하지 않습니다.
- 공고 내용은 실시간 상태와 달라질 수 있어 최종 판단 전 원문 확인이 필요합니다.

View file

@ -0,0 +1,45 @@
# 당근 중고거래 검색 가이드 (`daangn-used-goods-search`)
당근 중고거래 공개 웹 데이터 표면을 사용해 키워드·지역 기반 매물을 검색하고, 개별 매물 상세를 읽기 전용으로 확인하는 스킬입니다.
## 사용 시나리오
- "당근에서 합정동 맥북 매물 찾아봐"
- "이 당근 중고거래 URL 상세 요약해줘"
- "아이폰 15 Pro 중고 매물 중 판매중인 것만 봐줘"
## 구현 표면
브라우저 자동화, 로그인, 채팅, 찜, 거래 제안, 구매 자동화를 사용하지 않습니다.
1. 지역 해석: `https://www.daangn.com/kr/api/v1/regions/keyword?keyword=<지역명>`
2. 검색: `https://www.daangn.com/kr/buy-sell/all/?in=<지역명>-<id>&search=<키워드>&only_on_sale=true&_data=routes/kr.buy-sell._index`
3. 상세: `<매물 URL>?_data=routes%2Fkr.buy-sell.%24buy_sell_id`
## 로컬 실행
```bash
python3 daangn-used-goods-search/scripts/daangn_used_goods.py search "맥북" --region "합정동" --limit 5
python3 daangn-used-goods-search/scripts/daangn_used_goods.py detail "https://www.daangn.com/kr/buy-sell/.../"
```
## 지역 필터
지역명은 바로 URL에 넣지 않고 당근 region API로 내부 id를 먼저 조회합니다.
```text
합정동 → 서울특별시 마포구 합정동, id=231 → in=합정동-231
```
동일 지명이 여러 곳에 있으면 정확 일치 후보, 서울 동 단위 후보, 첫 번째 후보 순으로 선택합니다. 결과에는 적용 지역(`effective_region`)과 원본 URL을 함께 남깁니다.
## 출력 해석
검색 결과는 `title`, `price`, `price_text`, `status`, `region`, `url` 중심으로 1차 후보를 고릅니다. 조회수, 채팅수, 설명 같은 상세 판단은 상세 조회 결과의 `product` 원문을 확인한 뒤 정리합니다.
## 제한사항
- 공개 Remix `_data` route 이름이나 JSON shape가 바뀌면 실패할 수 있습니다.
- 삭제·판매완료·비공개 전환된 글은 상세 조회가 실패할 수 있습니다.
- CAPTCHA, 로그인벽, 봇 차단이 나오면 실패 모드로 보고하고 우회하지 않습니다.
- 상대방에게 영향을 주는 채팅, 찜, 거래 제안, 구매 자동화는 범위 밖입니다.

View file

@ -0,0 +1,45 @@
# 대신증권 리포트 조회 가이드
`daishin-report-search``jay-jo-0/github_pages_repo` GitHub Pages 미러에 올라오는 대신증권 리포트 HTML을 최신순으로 찾고 원문/설명 페이지를 JSON으로 정리하는 조회 전용 스킬이다.
## 공개 접근 경로
- 목록: `https://api.github.com/repos/jay-jo-0/github_pages_repo/git/trees/main?recursive=1`
- 원문 HTML: `https://raw.githubusercontent.com/Jay-jo-0/github_pages_repo/main/<YYYYMMDDHHMMSS.html>`
- exact-file fallback: `https://api.github.com/repos/jay-jo-0/github_pages_repo/contents/<YYYYMMDDHHMMSS.html>?ref=main`
- 브라우저 URL: `https://jay-jo-0.github.io/github_pages_repo/<YYYYMMDDHHMMSS.html>`
- 설명 페이지: `<YYYYMMDDHHMMSS_explain.html>`이 있을 때만 제공
파일명 timestamp를 KST 게시 추정 시각으로 표시한다. GitHub API와 raw 파일은 공개 unauthenticated endpoint라서 proxy를 쓰지 않는다.
## 사용 예시
```bash
node packages/daishin-report-search/src/cli.js --limit 10
GITHUB_TOKEN=... node packages/daishin-report-search/src/cli.js --limit 10
node packages/daishin-report-search/src/cli.js 반도체 --limit 5 --max-inspect 100
node packages/daishin-report-search/src/cli.js --id 20260511082352 --include-explain
```
```js
const { listReports, fetchReport } = require("daishin-report-search")
const latest = await listReports({ limit: 10 })
const semis = await listReports({ query: "반도체", limit: 5, maxInspect: 100 })
const withToken = await listReports({ githubToken: process.env.GITHUB_TOKEN })
const detail = await fetchReport("20260511082352", { includeExplain: true })
```
## 출력 필드
목록 항목은 `id`, `date`, `time`, `timestamp`, `title`, `headings`, `excerpt`, `ratingTargets`, `pageUrl`, `rawUrl`, `apiUrl`, `hasExplain`, `explainUrl`을 포함한다.
상세 조회는 원문 `text`를 추가하고, `includeExplain`이 켜져 있으면 `explain` 객체에 설명 페이지의 `title`, `headings`, `text`, `excerpt`, `pageUrl`을 포함한다.
## 주의 사항
- 투자 판단이나 매매 추천이 아니라 공개 리포트 조회 보조 기능이다.
- GitHub unauthenticated API rate limit, upstream repository 변경, HTML 구조 변경 시 경고나 오류가 반환될 수 있다. 목록 조회의 GitHub tree API가 403/429로 막히면 예외 대신 빈 `items``source.error`/rate-limit metadata를 반환한다.
- API limit을 높여야 할 때는 caller-owned `githubToken`/`githubHeaders` 옵션 또는 CLI 환경변수 `DAISHIN_GITHUB_TOKEN`/`GITHUB_TOKEN`을 사용할 수 있다. 이 값은 GitHub API host(tree discovery와 exact-file fallback)에만 전송되고 raw 원문 URL에는 전송되지 않는다. 기본 동작에는 토큰이나 proxy가 필요 없다.
- 상세 조회는 raw 원문 URL을 먼저 읽고, 실패하면 알려진 timestamp 경로의 GitHub contents API로 fallback한다.
- 검색어가 있으면 최신 파일부터 `maxInspect`개까지 원문을 읽어 매칭하므로 너무 낮게 잡으면 결과가 누락될 수 있다.

View file

@ -4,8 +4,14 @@
- 다이소 매장명으로 공식 매장 후보 찾기
- 상품명/검색어로 공식 상품 후보 찾기
- 특정 매장의 **매장 픽업 재고** 확인
- 필요하면 온라인 재고 참고값 함께 확인
- 특정 매장의 **매장 픽업 재고 수량** 확인 (Bearer 토큰 인증 기반 공식 `selStrPkupStck` 표면)
- 필요하면 `referenceOnly: true` 온라인 재고 참고값 함께 확인
## 이 기능으로 할 수 없는 일 (스킬 범위 한계)
- 매장 내 진열 위치(aisle/매대)는 공식 표면이 제공하지 않으므로 답하지 않습니다.
- 결제·주문·픽업 예약 자동화는 범위가 아닙니다.
- 비공식 크롤링·헤드리스 브라우저 우회·계정 세션 재사용은 범위가 아닙니다.
## 먼저 필요한 것
@ -27,7 +33,9 @@
- store detail: `https://www.daisomall.co.kr/api/dl/dla-api/selStrInfo`
- product search list: `https://www.daisomall.co.kr/ssn/search/SearchGoods`
- product summary list: `https://www.daisomall.co.kr/ssn/search/GoodsMummResult`
- store pickup stock: `https://www.daisomall.co.kr/api/pd/pdh/selStrPkupStck`
- auth (비로그인 JWT 발급): `https://www.daisomall.co.kr/api/auth/request`
- store pickup stock: `https://www.daisomall.co.kr/api/pd/pdh/selStrPkupStck` (Bearer 인증 필요)
- pickup eligibility fallback: `https://www.daisomall.co.kr/api/ms/msg/selPkupStr`
- optional online stock: `https://www.daisomall.co.kr/api/pdo/selOnlStck`
## 기본 흐름
@ -36,9 +44,12 @@
2. 상품명이 없으면 상품명/검색어를 한 번 더 물어봅니다.
3. `selStr` 로 매장 후보를 찾고, 필요하면 `selStrInfo` 로 매장 상세를 확인합니다.
4. `SearchGoods` 로 상품 후보를 찾습니다.
5. `selStrPkupStck` 로 해당 매장의 상품 재고를 확인합니다.
6. 필요하면 `SearchGoods` 응답의 `onldPdNo` 를 함께 보존해 `selOnlStck` 온라인 재고 교차 확인에 사용합니다.
7. 공식 표면이 매장 내 위치를 주지 않으면 재고 중심으로 답합니다.
5. `GET /api/auth/request` 로 비로그인 JWT를 받아 AES-128-CBC / 키 `"PRE_AUTH_ENC_KEY"` 로 암호화한 뒤 Bearer 헤더를 빌드합니다.
6. `selStrPkupStck` 에 Bearer 헤더를 실어 해당 매장의 상품 재고를 확인합니다.
7. 403 응답이 오면 `/api/auth/request` 를 재호출해 Bearer를 새로 빌드한 뒤 한 번 재시도합니다.
8. Bearer 재시도 후에도 401/403이면 `pickupStock.retrievalStatus: "blocked"` 를 반환하고, 선택적으로 `selPkupStr` 기반 `pickupEligibility` 로 픽업 가능 여부를 보조 확인합니다.
9. 필요하면 `SearchGoods` 응답의 `onldPdNo` 를 함께 보존해 `selOnlStck` 온라인 재고 교차 확인에 사용합니다.
10. 공식 표면이 매장 내 위치를 주지 않으면 재고 중심으로 답합니다.
## 예시
@ -71,13 +82,20 @@ main().catch((error) => {
- 상품 후보가 여러 개면 브랜드, 용량, 호수까지 같이 보여 주는 편이 덜 헷갈립니다.
- 재고 수량은 실시간 100% 보장값이 아니므로, 필요하면 `방문 직전 다시 확인` 문구를 같이 줍니다.
- 공식 표면이 매장 내 위치를 주지 않으면 `공식 표면에서는 매장 재고까지만 확인된다`고 답합니다.
- 매장 픽업 재고의 `status` 는 조회 결과 범주입니다. 상품 재고 여부는 `inStock` 또는 `inventoryStatus` 로 설명하고, `status: "available"` 만으로 재고가 있다고 말하지 않습니다.
- 인증 키(`PRE_AUTH_ENC_KEY`)는 JS 번들에 하드코딩되어 있으며 변경될 수 있습니다. 403이 지속되면 키가 교체된 것일 수 있습니다.
## 라이브 확인 메모
2026-03-27 기준으로 다음 공식 호출이 실제 응답을 반환했습니다.
2026-03-27 기준으로 `selStrPkupStck` 는 실제 매장 픽업 재고를 반환했습니다.
2026-05-15 기준 Bearer 토큰 인증(`/api/auth/request` + AES-128-CBC)으로 정상 접근 가능합니다.
- `POST /api/ms/msg/selStr``강남역2호점` 매장 후보
- `GET /ssn/search/SearchGoods?searchTerm=리들샷...``1049275` 포함 상품 후보
- `POST /api/pd/pdh/selStrPkupStck``strCd=10224`, `pdNo=1049275` 조합의 매장 픽업 재고
현재 운영 원칙은 다음과 같습니다.
같은 날짜 smoke test 에서 `강남역2호점 + VT 리들샷 100` 조합은 재고 수량 `0` 으로 응답했습니다. 즉, **공식 경로가 실제로 동작함은 확인했지만 당시 해당 매장 재고는 없었습니다.**
- `POST /api/ms/msg/selStr` → 매장 후보 확인
- `GET /ssn/search/SearchGoods?searchTerm=...` → 상품 후보 및 `onldPdNo` 확인
- `GET /api/auth/request` → 비로그인 JWT 발급, 헤더 `x-dm-uid` 보존 (유효 30초)
- JWT를 AES-128-CBC / 키 `"PRE_AUTH_ENC_KEY"` 로 암호화 → `bearer = base64(IV) + base64(암호문)` 조합
- `POST /api/pd/pdh/selStrPkupStck` + `Authorization: Bearer <bearer>`, `X-DM-UID: <uid>` → 성공 시 `status: "available"`, `retrievalStatus: "resolved"`. 실제 재고 여부는 `inStock` / `inventoryStatus` 로 표시
- 403 → `/api/auth/request` 재호출 후 Bearer 재빌드 후 1회 재시도
- `POST /api/pdo/selOnlStck` → 가능한 경우 온라인 재고 참고값 표시

View file

@ -0,0 +1,55 @@
# 다나와 최저가 비교 (`danawa-price-search`)
다나와 공개 검색/가격비교 표면을 사용해 상품 후보를 찾고, 쇼핑몰별 가격을 배송비 포함 실구매가 기준으로 비교하는 스킬입니다.
## 사용 시나리오
- "다나와에서 맥북 에어 M4 최저가 비교해줘"
- "이 다나와 pcode 쇼핑몰별 가격 표로 보여줘"
- "배송비랑 카드할인까지 포함해서 어디가 제일 싼지 봐줘"
## 구현 표면
브라우저 자동화나 로그인을 사용하지 않습니다.
1. 검색: `https://search.danawa.com/dsearch.php?query=...`
2. 상품 상세 확인: `https://prod.danawa.com/info/?pcode=...`
3. 쇼핑몰별 가격비교 AJAX: `https://prod.danawa.com/info/ajax/getAllPriceCompareMallList.ajax.php`
## 로컬 실행
```bash
python3 danawa-price-search/scripts/danawa_search.py search "맥북 에어 M4" --limit 5
python3 danawa-price-search/scripts/danawa_search.py offers 28208783 --limit 10
python3 danawa-price-search/scripts/danawa_search.py compare "갤럭시 S25" --limit 3 --offers 5
```
## 출력 해석
`offers``compare` 결과에는 다음 필드가 포함됩니다.
- `mall`: 쇼핑몰명
- `price`: 표시 가격
- `shipping_fee`: 배송비 숫자. 무료배송이면 `0`, 파싱 불가면 `null`
- `is_free_shipping`: 무료배송 여부
- `total_price`: 가격 + 배송비 기준 실구매가 후보
- `card_price`: 카드 적용 표시가
- `card_discount`: 표시가와 카드가 차액
- `installment`: 무이자 할부 문구
- `payment_badges`: Danawa가 가격 옆에 노출한 결제조건 배지의 표시 라벨 목록. 배지 텍스트가 비어 있고 `.ico.cash`처럼 클래스만 있는 경우도 정규화 라벨을 합성합니다 (예: `["현금"]`, `["쿠폰"]`, `["포인트"]`, `["카드"]`, `["할인"]`, `["멤버십"]`)
- `payment_condition_types`: 화이트리스트 배지를 정규화한 조건 타입 목록 (`cash`/`point`/`coupon`/`card`/`discount`/`membership`)
- `payment_condition_label`: 사용자 응답용 결제조건 라벨. 복수 조건이면 쉼표로 연결
- `cash_only` / `point_only` / `coupon_only` / `card_only_badge` / `discount_badge` / `membership_badge`: 각각 현금·포인트·쿠폰·특정 카드·할인·멤버십 조건 가격 여부
- `is_conditional_price`: `payment_condition_types`가 하나 이상 있으면 True. 일반 카드 결제로는 가격이 다르거나 적용 불가할 수 있음
- `url`: 다나와 경유 링크
`count`, `normal_count`, `conditional_count``limit` 적용 후 실제 반환된 `offers[]` 기준입니다.
사용자에게는 `total_price` 기준으로 정렬한 Markdown 표를 먼저 보여주고, 카드가는 별도 열에 표시합니다.
## 주의사항
- 다나와의 공개 HTML/AJAX 구조가 바뀌면 selector와 파싱 규칙을 갱신해야 합니다.
- 자동 구매, 로그인, CAPTCHA 우회, 결제 단계 자동화는 이 스킬의 범위가 아닙니다.
- 동일 상품명이라도 옵션/용량/모델명이 섞일 수 있으므로 검색 후보를 먼저 확인한 뒤 가격비교를 진행합니다.
- 결제조건 배지(현금/쿠폰/포인트/할인/특정 카드/멤버십 한정)는 사용자 응답 표에 반드시 `payment_condition_label` 기반 라벨로 표시해야 합니다. 정렬은 `total_price` 단일 기준이라 조건부 가격이 1위로 올라올 수 있고, 라벨이 없으면 카드 결제 사용자에게 적용 불가능한 가격을 일반 최저가로 안내하게 됩니다.

View file

@ -0,0 +1,34 @@
# 기부처 조회 가이드
`donation-place-search`는 사용자가 제공한 지역과 관심 분야를 기준으로 한국 기부처 후보를 추천하는 조회형 스킬이다.
- 자동 후원 신청, 결제, 개인정보 입력은 하지 않는다.
- 1365 기부포털 공식 진입점(`https://www.1365.go.kr/dntn/main.do`)과 각 단체 공식 홈페이지에서 최신 등록 상태, 모금 기간, 기부금영수증 가능 여부를 확인하도록 안내한다.
- 공개 페이지와 로컬 후보 랭킹만 사용하므로 `k-skill-proxy`나 API key가 필요 없다.
## 사용 예
```js
const {
recommendDonationPlaces,
formatDonationRecommendationReport
} = require("donation-place-search");
const result = recommendDonationPlaces({
location: "서울 마포구",
category: "동물",
limit: 3
});
console.log(formatDonationRecommendationReport(result));
```
## 입력
- `location`: `서울 마포구`, `부산 해운대구`, `제주`, `온라인` 같은 위치 힌트
- `category`: `아동`, `동물보호`, `환경`, `재난`, `장애`, `노인`, `의료`, `생계`, `해외구호`
- `limit`: 기본 5, 최대 20
## 검증 표면
`nanumkorea.go.kr`는 1365 자원봉사/기부 통합 안내를 반환하므로, 스킬은 `www.1365.go.kr/dntn/main.do`를 최신 공식 확인 진입점의 기준으로 사용한다. 1365 페이지가 headless HTTP에서 느리거나 빈 응답을 줄 수 있어 화면 스크래핑 대신 best-effort 확인 보조 링크와 후보 공식 홈페이지를 함께 제시하며, 후보별 등록 검증이 이미 완료됐다고 표현하지 않는다.

View file

@ -0,0 +1,65 @@
# 근처 응급실 병상 상태 확인
`emergency-room-beds` 스킬은 사용자가 알려준 위치 기준으로 가까운 응급실을 찾고, E-Gen 공개 응급실 찾기 표면에서 제공하는 응급실/입원실 운영 상태 플래그를 정리한다.
## 핵심 원칙
- 위치를 자동 추적하지 않는다. 위치가 없으면 먼저 현재 위치를 질문한다.
- 데이터 출처는 NEMC/E-Gen 공개 페이지와 E-Gen nearby 응급실 목록 endpoint다.
- E-Gen nearby 목록은 응급실 운영 여부와 입원실/병상 운영 플래그를 제공하지만, 병원별 정확한 실시간 잔여 병상 수나 병상 가동률 수치를 제공하지 않는다.
- 긴급 상황에서는 결과와 별개로 119 또는 병원 대표전화 확인을 안내한다.
## 사용 예
```text
현재 위치를 알려주세요. 동네/역명/랜드마크/위도·경도 중 편한 형식으로 보내주시면 근처 응급실 상태를 찾아볼게요.
```
위치를 받으면 `emergency-room-beds` 패키지의 `searchNearbyEmergencyRoomsByLocationQuery()`를 사용한다.
## Node.js 예시
```js
const { searchNearbyEmergencyRoomsByLocationQuery } = require("emergency-room-beds");
async function main() {
const result = await searchNearbyEmergencyRoomsByLocationQuery("광화문", {
limit: 3,
radius: 5
});
console.log(result.anchor);
console.log(result.items.map((item) => ({
name: item.name,
distanceKm: item.distanceKm,
emergencyRoomOperating: item.bedStatus.emergencyRoomOperating,
inpatientBedsOperating: item.bedStatus.inpatientBedsOperating,
updatedAt: item.updatedAt,
phone: item.phone,
mapUrl: item.mapUrl
})));
console.log(result.meta.bedCountLimitation);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
```
## 응답 필드
- 병원명, 거리, 응급의료기관 등급, 병원 유형
- 응급실 운영 여부 (`emergencyRoomOperating`)
- 입원실/병상 운영 플래그 (`inpatientBedsOperating`)
- 권역외상센터/소아전문/소아야간진료 여부
- 주소, 대표전화, 갱신시각, 지도 링크
- 공개 데이터 한계 문구: 정확한 실시간 잔여 병상 수/가동률 미제공
## 참고 표면
- NEMC 모니터링: <https://dw.nemc.or.kr/nemcMonitoring/mainmgr/Main.do>
- E-Gen 응급실 찾기: <https://www.e-gen.or.kr/egen/search_emergency_room.do>
- E-Gen nearby endpoint: `https://www.e-gen.or.kr/egen/retrieve_emergency_room_list.do`
- Kakao Map 모바일 검색: `https://m.map.kakao.com/actions/searchView?q=<query>`
- Kakao Map 장소 패널 JSON: `https://place-api.map.kakao.com/places/panel3/<confirmId>`

View file

@ -0,0 +1,40 @@
# 고속버스 예매 가이드
## 이 기능으로 할 수 있는 일
- KOBUS 고속버스 터미널/노선 후보 확인
- 배차 시간표, 버스 등급, 잔여석, 요금 확인
- 좌석 선택 단계 진입 가능 여부 확인
- 필요한 경우 임시 좌석 선점 후 공식 결제정보 입력 페이지로 handoff
- 진행하지 않을 때 임시 선점 해제
## 먼저 필요한 것
- 별도 사용자 계정/비밀번호는 기본 조회·좌석 단계에서 필요하지 않음
- 결제는 공식 KOBUS 페이지에서 사용자가 직접 진행
- 브라우저 자동화보다 `https://www.kobus.co.kr` 공식 HTTP 흐름을 우선 사용
## 입력값
- 출발 터미널
- 도착 터미널
- 날짜: `YYYYMMDD`
- 희망 시간대
- 인원 수와 좌석 선호
## 기본 흐름
1. 쿠키 jar를 만들고 KOBUS 메인/예매 페이지를 열어 세션을 시작한다.
2. `POST /mrs/readRotLinInf.ajax` 로 터미널/노선 코드를 확인한다.
3. `POST /mrs/alcnSrch.do` 로 배차를 조회한다.
4. 결과 HTML의 `fnSatsChc(...)` 인자를 파싱해 후보를 정리한다.
5. 선택 후보는 `POST /mrs/satschc.do` 로 좌석/요금 단계 진입을 확인한다.
6. 사용자가 원하면 `POST /mrs/setPcpy.ajax` 로 임시 선점 후 공식 결제정보 입력 페이지 링크를 제공한다.
7. 사용자가 진행하지 않으면 `POST /mrs/cancPcpy.ajax` 로 선점을 해제한다.
## 주의할 점
- 결제 자동화는 포함하지 않는다. 공식 페이지의 결제 직전 단계까지 보조하는 assisted checkout 흐름이다.
- KOBUS 모바일 페이지는 좁은 화면에서 `/mblIdx.do` 로 리다이렉트할 수 있어 helper 링크 caveat를 확인한다.
- KOBUS 터미널 코드는 티머니 시외버스 코드와 다르므로 혼용하지 않는다.
- stateless POST보다 쿠키와 referer를 유지하는 흐름이 안정적이다.

View file

@ -0,0 +1,179 @@
# 항공권 가격 조회 (`flight-ticket-search`)
[`fast-flights`](https://pypi.org/project/fast-flights/) 라이브러리를 통해 Google Flights 공개 검색 표면을 조회해 항공권 후보, 예약 검색 링크, 날짜·월·연도별 최저가·평균가 비교를 보수적으로 제공하는 스킬입니다. API key, 로그인, 결제, CAPTCHA 우회 없이 무료 공개 표면만 사용합니다.
## 사용 시나리오
- "인천에서 나리타 다음 달 최저가 알려줘"
- "6월 ICN-NRT 월별 비교"
- "올해랑 내년 6월 1일 항공권 가격 비교"
- "ICN-LAX 비즈니스 가격 대략 비교해줘"
- "서울에서 도쿄 왕복 예약 링크 줘"
## 구현 표면
브라우저 자동화나 로그인을 사용하지 않습니다.
1. `fast-flights==2.2` 가 Google Flights 의 공개 검색 결과를 파싱합니다.
2. 예약 링크는 특정 판매자 결제 deep link 가 아니라 **Google Flights 검색 결과 링크**입니다. 실제 구매·결제·좌석 선택은 사용자가 브라우저에서 직접 진행합니다.
3. 첫 실행 시 `~/.cache/k-skill/flight-ticket-search/venv``fast-flights` 가 격리 설치되고 이후 그 venv 로 재실행합니다. 저장소에는 의존성 vendoring 이나 API key 를 두지 않습니다.
## 로컬 실행
### 단일 검색
편도:
```bash
python3 flight-ticket-search/scripts/flight_ticket_search.py search \
--from ICN \
--to NRT \
--date 2026-06-01 \
--adults 1 \
--seat economy \
--limit 5 \
--format markdown
```
왕복:
```bash
python3 flight-ticket-search/scripts/flight_ticket_search.py search \
--from ICN \
--to NRT \
--date 2026-06-01 \
--return-date 2026-06-08 \
--adults 1 \
--seat economy \
--limit 5
```
### 월별 비교
지정 월의 날짜들을 실제 검색해 각 날짜의 최저가·평균가를 비교합니다. 기본은 주 1회 샘플링입니다.
```bash
python3 flight-ticket-search/scripts/flight_ticket_search.py compare-month \
--from ICN \
--to NRT \
--month 2026-06 \
--sample weekly \
--limit 5
```
일별 전체 조회가 필요하면 `--sample daily` 를 씁니다. 28~31 회 요청이 발생하므로 rate limit 보호를 위해 `--sleep` 을 1.5 초 이상 유지합니다.
```bash
python3 flight-ticket-search/scripts/flight_ticket_search.py compare-month \
--from ICN \
--to NRT \
--month 2026-06 \
--sample daily \
--sleep 2 \
--limit 10
```
### 사용자 정의 범위 비교
"다음주부터 2주간", "6월 1일부터 20일까지"처럼 범위를 받을 때 사용합니다.
```bash
python3 flight-ticket-search/scripts/flight_ticket_search.py compare-range \
--from ICN \
--to BKK \
--start-date 2026-06-01 \
--end-date 2026-06-20 \
--step-days 3 \
--limit 5
```
`--step-days 1` 은 일별 비교, `7` 은 주별 비교입니다.
### 연도 비교
같은 월일을 여러 연도에 대해 조회합니다.
```bash
python3 flight-ticket-search/scripts/flight_ticket_search.py compare-years \
--from ICN \
--to NRT \
--years 2026,2027 \
--month-day 06-01 \
--limit 5
```
## 출력 해석
### 단일 검색 응답 주요 필드
- `meta.booking_search_url` — Google Flights 예약 검색 링크
- `meta.price_band` — Google 이 표시하는 `low` / `typical` / `high` 가격 band
- `stats.min_price`, `stats.avg_price`, `stats.max_price`
- `flights[].name`, `departure`, `arrival`, `duration`, `stops`, `price_text`
- `flights[].quality``complete` 또는 `partial` (Google Flights 응답 일부가 누락될 수 있음을 표시)
### 비교 검색 응답 주요 필드
- `stats.min_price` — 샘플 날짜 중 최저가
- `stats.avg_of_daily_min` — 날짜별 최저가의 평균
- `stats.max_of_daily_min` — 날짜별 최저가 중 최고값
- `cheapest_dates[]` — 가장 싼 날짜와 예약 검색 링크
- `rows[]` — 날짜별 성공/실패 및 요약
- `failures[]` — 너무 먼 미래 날짜 등 실패 케이스 (숨기지 않고 보고)
## 입력 가이드
- 출발/도착 공항 IATA 코드: `ICN`, `GMP`, `PUS`, `NRT`, `HND`, `LAX`, `CJU`
- 출발일: `YYYY-MM-DD`
- 선택: 왕복 귀국일, 성인 수(기본 1), 좌석 등급(`economy` / `premium-economy` / `business` / `first`), 비교 샘플 방식(`weekly` / `daily`)
사용자가 도시명만 말하면 IATA 코드를 추론합니다. 흔한 기본값:
- 서울/인천 국제선: `ICN`
- 서울 국내선/제주: `GMP`
- 도쿄: 나리타 `NRT` 또는 하네다 `HND` — 명시 없으면 사용자에게 확인
- 제주: `CJU`
## 예약 링크 정책
- `booking_search_url` 은 Google Flights 검색 URL 입니다.
- 특정 항공사/OTA 결제 단계 deep link 를 자동 추출하거나 클릭하지 않습니다.
- 결제·예약 확정·로그인·여권 정보 입력은 스킬 범위 밖입니다.
- 사용자가 예약까지 원하면 링크를 열어 직접 확인하도록 안내합니다.
## 검증된 노선 (2026-05-10 로컬 프로브 기준)
- 국내선: `GMP-CJU`, `ICN-CJU`
- 동북아: `ICN-NRT`, `ICN-PVG`, `ICN-HKG`, `ICN-TPE`
- 동남아: `ICN-SIN`, `ICN-BKK`
- 중동: `ICN-DXB`
- 북미: `ICN-LAX`, `ICN-JFK`
- 유럽: `ICN-LHR`, `ICN-CDG`, `ICN-FRA`
- 오세아니아: `ICN-SYD`
- 남미: `ICN-GRU`
- 왕복/좌석 등급/성인 다수: `ICN↔NRT`, `GMP↔CJU`, business, 성인 2명
## 실패 모드
- Google Flights HTML/프론트엔드 구조 변경으로 항공사명·시간 파싱이 비거나 `partial` 로 떨어질 수 있습니다.
- 일부 노선은 가격만 나오고 항공편 상세가 누락될 수 있습니다.
- 잘못된 IATA 코드, 동일 출도착 공항, 실제 항공편이 없는 구간은 실패합니다.
- 너무 먼 미래 날짜는 upstream 에 결과가 없을 수 있습니다.
- 비교 기능은 날짜별 실시간 조회라 요청 수가 많습니다. daily 월별 비교는 30 회 안팎의 요청이 발생합니다.
- `fast-flights` fallback 이 외부 fetch helper 를 쓰는 경우 `401 no token provided` 가 날 수 있어, 동일 입력의 실사용성이 낮은 케이스면 사전 validation 으로 막거나 잠시 후 재시도합니다.
- Skyscanner: CAPTCHA/403 으로 직접 provider 부적합 (사용하지 않음).
- Kiwi Tequila API: 무료 계정 API key 가 필요해 기본 no-key 경로에서는 사용하지 않습니다.
## 비범위
- 실제 예약/결제/취소/좌석 지정 자동화
- 로그인 회원가, 카드 할인, 쿠폰, 마일리지 적용가 확정
- CAPTCHA, fingerprint, bot-block 우회
- 스카이스캐너 직접 조회 (CAPTCHA/403 으로 안정 provider 가 아님)
## 출처
- 스킬 정의: [`flight-ticket-search/SKILL.md`](../../flight-ticket-search/SKILL.md)
- 헬퍼 스크립트: [`flight-ticket-search/scripts/flight_ticket_search.py`](../../flight-ticket-search/scripts/flight_ticket_search.py)
- `fast-flights`: <https://pypi.org/project/fast-flights/>
- Google Flights: <https://www.google.com/travel/flights>

View file

@ -0,0 +1,150 @@
# 자연휴양림 빈 객실 조회 가이드
대상 사이트는 숲나들e 공식 사이트 `https://foresttrip.go.kr/index.jsp` 이다. 이 기능은 해당 사이트의 자연휴양림 예약 가능 객실 **조회 자동화**만 수행한다.
## 이 기능으로 할 수 있는 일
- 숲나들e/자연휴양림 예약 가능 객실 조회
- 특정 날짜 또는 여러 날짜 기준 조회
- 전체 자연휴양림 또는 휴양림명/ID 기준 조회
- 숙박/야영 카테고리별 조회
- JSON 또는 사람이 읽기 좋은 텍스트 출력
이 기능은 **조회 전용 자동화**이다. 예약 신청, 결제, 캡차 처리, 대기열 우회, 반복 스나이핑은 하지 않는다.
## 먼저 필요한 것
- Python 3.9+
- Playwright Chromium
- [공통 설정 가이드](../setup.md) 완료
- [보안/시크릿 정책](../security-and-secrets.md) 확인
```bash
python3 -m pip install playwright
python3 -m playwright install chromium
python3 foresttrip-vacancy/scripts/run_foresttrip_vacancy.py --check-deps
```
`--check-deps` 는 숲나들e 로그인이나 네트워크 조회를 수행하지 않고, 로컬 Python/Playwright Chromium 준비 상태만 확인한다.
## 필요한 환경변수
- `KSKILL_FORESTTRIP_ID`
- `KSKILL_FORESTTRIP_PASSWORD`
선택:
- 없음
### Credential resolution order
1. **이미 환경변수에 있으면** 그대로 사용한다.
2. **에이전트가 자체 secret vault(1Password CLI, Bitwarden CLI, macOS Keychain 등)를 사용 중이면** 거기서 꺼내 환경변수로 주입해도 된다.
3. **`~/.config/k-skill/secrets.env`** (기본 fallback) — plain dotenv 파일, 퍼미션 `0600`.
4. **아무것도 없으면** 유저에게 물어서 2 또는 3에 저장한다.
helper는 `KSKILL_FORESTTRIP_ID`, `KSKILL_FORESTTRIP_PASSWORD` 환경변수를 읽는다. secret vault나 `secrets.env` 는 에이전트/사용자가 값을 꺼내 실행 환경에 주입하기 위한 저장 위치이며, helper가 임의로 계정 정보를 다른 곳에 저장하지 않는다.
## 처음 실행 순서
처음 쓰는 사용자는 의존성 확인 후 환경변수를 현재 shell에만 주입해서 1개 휴양림으로 먼저 조회한다.
```bash
export KSKILL_FORESTTRIP_ID="your-foresttrip-id"
export KSKILL_FORESTTRIP_PASSWORD="your-foresttrip-password"
python3 foresttrip-vacancy/scripts/run_foresttrip_vacancy.py --check-deps
python3 foresttrip-vacancy/scripts/run_foresttrip_vacancy.py --forest-name 유명산 --text --dates 20260504
```
성공 여부를 먼저 보려면 전체 조회보다 `--forest-name` 또는 `--forest-id` 로 범위를 좁혀 실행한다. JSON 결과가 필요하면 같은 조건에 `--json` 을 사용한다.
## 입력값
- 날짜: `YYYYMMDD`
- 여러 날짜: `YYYYMMDD,YYYYMMDD`
- 조회 범위: 전체 자연휴양림, 휴양림 ID, 휴양림명 부분 일치
- 카테고리:
- `01`: 숙박
- `02`: 야영/캠핑
- `01,02`: 숙박 + 야영/캠핑
- 고급 옵션:
- `--week-range N`: `--dates` 를 생략했을 때만 오늘부터 N주 조회
- `--concurrency N`: 병렬 조회 worker 수, 1-5 범위
- `--session-cache PATH`: 로그인 세션 캐시 경로 override
## 기본 흐름
1. `KSKILL_FORESTTRIP_ID`, `KSKILL_FORESTTRIP_PASSWORD` 를 확보한다.
2. 필요한 경우 `python3 -m pip install playwright``python3 -m playwright install chromium` 을 실행한다.
3. helper로 read-only 월별예약조회 endpoint를 실행한다.
4. helper가 로그인 세션, CSRF, 공식 휴양림 ID 목록을 확보한다.
5. 날짜, 휴양림명, 객실/시설명, 숙박/야영 구분, 정원 중심으로 요약한다.
6. 응답 정제: API가 `srchDate` 기준 최대 5일 윈도우를 반환할 수 있어 helper가 요청 범위 밖 `useDt`, 운영자 보유분("예비" 포함 객실), 같은 객실 중복 행을 자동 제거한다.
2026-04-29 확인 기준, 로그인 없이 월별예약조회 화면에 접근하면 `401 Unauthorized`가 반환되고, 조회 endpoint는 JSON 대신 안내 HTML을 반환한다. 따라서 현재 구현은 로그인 세션/CSRF 확보를 필수 전제로 둔다.
## 검증 방식
메인테이너가 별도 숲나들e 계정을 새로 만들 필요는 없다.
- CI/리뷰 검증: `./scripts/validate-skills.sh`, `python3 -m py_compile ...`, `--help`, `--check-deps` 로 진행한다.
- 실제 조회 검증: 기여자 또는 이미 숲나들e 계정을 가진 사용자가 개인 계정으로 선택 실행한다.
- PR에는 실제 조회 결과의 `forests_scanned`, `fetch_failures`, `filter_hits` 같은 비민감 요약값만 기록하고, 계정 정보와 세션 쿠키는 공유하지 않는다.
## 예시
전체 자연휴양림에서 하루 조회:
```bash
python3 foresttrip-vacancy/scripts/run_foresttrip_vacancy.py --all --text --dates 20260504
```
JSON으로 조회:
```bash
python3 foresttrip-vacancy/scripts/run_foresttrip_vacancy.py --all --json --dates 20260504
```
여러 날짜 조회:
```bash
python3 foresttrip-vacancy/scripts/run_foresttrip_vacancy.py --all --text --dates 20260504,20260505
```
야영/캠핑만 조회:
```bash
python3 foresttrip-vacancy/scripts/run_foresttrip_vacancy.py --all --text --dates 20260504 --categories 02
```
휴양림명으로 좁혀 조회:
```bash
python3 foresttrip-vacancy/scripts/run_foresttrip_vacancy.py --forest-name 유명산 --text --dates 20260504
```
로그인 세션 캐시를 무시하고 새로 조회:
```bash
python3 foresttrip-vacancy/scripts/run_foresttrip_vacancy.py --all --text --dates 20260504 --refresh-session
```
## 주의할 점
- 예약 자동화가 아니다.
- 결제, 캡차 처리, 대기열 우회는 하지 않는다.
- aggressive polling은 피한다.
- 조회 결과는 시점 차이로 숲나들e 화면과 달라질 수 있다.
- 로그인 실패 시 계정 정보 또는 숲나들e 정책 변경을 먼저 확인한다.
- API가 요청 날짜보다 넓은 5일 윈도우를 반환해도 출력에는 요청 범위(`today``last_day`) 안의 행만 포함된다.
- "예비" 표기가 있는 객실은 사용자 예약 화면에 노출되지 않는 운영자 보유분이라 결과에서 자동 제외된다.
## 흔한 문제 해결
- `Playwright browser missing`: `python3 -m playwright install chromium` 을 실행한다.
- `Missing KSKILL_FORESTTRIP_ID` 또는 `Missing KSKILL_FORESTTRIP_PASSWORD`: 환경변수가 현재 shell에 주입됐는지 확인한다.
- 로그인 실패: 숲나들e 웹사이트에서 같은 계정으로 직접 로그인되는지 먼저 확인한다.
- 날짜/카테고리/출력 옵션 오류: helper가 로그인 전에 argparse error로 중단하므로 메시지에 맞춰 값을 고친다.
- JSON 대신 HTML 안내 페이지가 반환됨: 세션/CSRF가 없거나 만료된 상태일 수 있으므로 `--refresh-session` 으로 1회 재조회한다.
- 일부 휴양림 fetch failure: 성공한 결과와 실패 개수를 함께 보고하고, 반복 polling으로 보정하지 않는다.

View file

@ -0,0 +1,35 @@
# 금융위 기업기본정보 조회 (fsc-corporate-info)
`fsc-corporate-info` 스킬은 공공데이터포털의 **금융위원회_기업기본정보 서비스**(15043184, `getCorpOutline_V2`)를 `k-skill-proxy` 경유로 호출한다.
## 제공 기능
- 법인명(`corpNm`) 기준 후보: 대표자·설립일·업종 등 upstream 필드 원문
- 사업자번호 교차검증: 응답에 `bzno`가 있으면 입력 번호와 정확 일치하는 후보를 분리(없으면 교차검증 불가 표기)
## 인증/시크릿
사용자 로컬 시크릿은 필요 없다. upstream `DATA_GO_KR_API_KEY`는 프록시 서버에만 둔다(15043184 활용신청 필요). self-host 프록시는 `KSKILL_PROXY_BASE_URL`로 지정한다.
## 입력 제한
검색 파라미터가 `crno`(법인등록번호 13자리)/`corpNm`(법인명)뿐이라 **사업자번호 단독 조회가 불가**하다. 법인명으로 조회한다. `crno`는 사업자등록번호와 별개 번호다.
## 예시
```bash
python3 fsc-corporate-info/scripts/fsc_corporate_info.py --name "삼성전자" --b-no 124-81-00998
```
## 실패 모드
- `400 bad_request`: 법인명 미입력
- `503 upstream_not_configured`: 프록시에 `DATA_GO_KR_API_KEY` 없음
- `502 upstream_forbidden`: 프록시 키가 15043184에 미신청
- 빈 결과: 법인명 불일치 — 표기를 바꿔 재시도
## 공식 출처
- 공공데이터포털: <https://www.data.go.kr/data/15043184/openapi.do>
- upstream: `https://apis.data.go.kr/1160100/service/GetCorpBasicInfoService_V2/getCorpOutline_V2`
- 프록시 route: `GET /v1/fsc/corp-outline`

View file

@ -0,0 +1,41 @@
# 부정당제재업체 조회 (g2b-sanctioned-supplier)
`g2b-sanctioned-supplier` 스킬은 공공데이터포털의 **조달청 나라장터 사용자정보 서비스**(15129466, `getUnptRsttCorpInfo02`)를 `k-skill-proxy` 경유로 호출한다.
## 제공 기능
- 사업자등록번호 정확 일치(`inqryDiv=1`)로 **조회시점 현재 유효한** 부정당제재 조회
- 반환: 제재 시작/종료일자, 제재기관명, 계약법구분, 제재근거법률 등 upstream 필드 원문
## 적용 범위 한계
upstream 명세상 다음은 제공되지 않는다(과거 이력 조회가 아니다).
- 조회시점에 제재만료·해제된 건
- 나라장터 미등록업체·개인에 대한 제재
만료 이력까지 보려면 나라장터(<https://www.g2b.go.kr>)에서 수동 확인이 필요하다.
## 인증/시크릿
사용자 로컬 시크릿은 필요 없다. upstream `DATA_GO_KR_API_KEY`는 프록시 서버에만 둔다(15129466 활용신청 필요). self-host 프록시는 `KSKILL_PROXY_BASE_URL`로 지정한다.
## 예시
```bash
python3 g2b-sanctioned-supplier/scripts/g2b_sanctioned_supplier.py --bizno 124-81-00998
```
## 실패 모드
- `400 bad_request`: 사업자번호가 10자리가 아님
- `503 upstream_not_configured`: 프록시에 `DATA_GO_KR_API_KEY` 없음
- `502 upstream_forbidden`: 프록시 키가 15129466에 미신청
- `total_count: 0`: 조회시점 유효 제재 없음(만료·미등록업체는 미제공임에 유의)
## 공식 출처
- 공공데이터포털: <https://www.data.go.kr/data/15129466/openapi.do>
- upstream: `https://apis.data.go.kr/1230000/ao/UsrInfoService02/getUnptRsttCorpInfo02`
- 수동 대조: 나라장터 <https://www.g2b.go.kr>
- 프록시 route: `GET /v1/g2b/sanctioned-supplier`

View file

@ -0,0 +1,32 @@
# 강남언니 병원 조회 가이드
`gangnamunni-clinic-search`는 강남언니 공개 검색 페이지에서 병원 후보를 조회하는 read-only 스킬입니다.
## 공개 접근 경로
- 검색 URL: `https://www.gangnamunni.com/search?q=<keyword>`
- 데이터 위치: HTML 안의 `__NEXT_DATA__` JSON (`props.pageProps.hospitals`)
- 인증/시크릿: 불필요
- 프록시: 사용하지 않음
## 예시
```bash
npx gangnamunni-clinic-search "강남 성형외과" --limit 5
```
```js
const { searchClinics } = require("gangnamunni-clinic-search")
const result = await searchClinics({ query: "코성형", limit: 3 })
```
## 출력
각 후보는 공개 검색 페이지에 포함된 병원명, 평점, 리뷰 수, 지원 언어, 이미지 URL, 공개 병원 링크를 포함합니다.
## 제한사항
- 조회 시점 공개 검색 결과 기준입니다.
- 로그인, 상담, 예약, 결제, 찜, 리뷰 작성은 자동화하지 않습니다.
- CAPTCHA/차단/로그인벽/빈 shell 페이지는 실패 모드로 처리합니다.
- 의료 판단이나 병원 선택 보증을 대신하지 않습니다.

View file

@ -0,0 +1,68 @@
# 긱뉴스 조회 가이드
## 이 기능으로 할 수 있는 일
- GeekNews 공개 RSS/Atom 피드에서 최신 글 목록 조회
- 제목/요약/작성자 기준 키워드 검색
- 특정 항목의 RSS 기반 요약/링크/작성자/게시 시각 확인
## 먼저 필요한 것
- [공통 설정 가이드](../setup.md) 완료
- `python3` 사용 가능 환경
- 인터넷 연결
## v1 범위
이 기능은 **RSS-first / 읽기 전용** 범위로 제공된다.
- 공개 피드(`https://feeds.feedburner.com/geeknews-feed`)만 사용한다.
- 최신 글/검색/상세 조회까지만 다룬다.
- 댓글, 투표, 로그인, 개인화 상태는 다루지 않는다.
## 기본 흐름
1. 최신 글을 훑을 때는 목록 조회부터 실행한다.
2. 원하는 주제가 있으면 제목/요약/작성자 기준 검색으로 좁힌다.
3. 특정 글을 확인할 때는 링크/id/토픽 번호 일부로 상세 조회한다.
## 예시
최신 글 목록:
```bash
python3 scripts/geeknews_search.py list --limit 5
```
검색:
```bash
python3 scripts/geeknews_search.py search --query Claude --limit 5
```
상세:
```bash
python3 scripts/geeknews_search.py detail --id 28439
```
오프라인 fixture 또는 저장된 feed로 검증할 때:
```bash
python3 scripts/geeknews_search.py list \
--feed-file scripts/fixtures/geeknews-feed.xml \
--limit 3
```
## 출력에서 확인할 점
- `source.feed_url` 이 GeekNews RSS feed를 가리키는지
- `items[].title`, `items[].link`, `items[].author_name`, `items[].summary` 가 함께 내려오는지
- 상세 조회에서 `item.content_html``item.summary` 가 모두 포함되는지
- 검색 결과가 제목/요약/작성자 기준으로 보수적으로 매칭되는지
## 주의할 점
- RSS 피드 기반이라 원문 전체/댓글/메타데이터는 제한적일 수 있다.
- FeedBurner 응답이 느리거나 실패하면 재시도하거나 직접 링크를 여는 fallback이 필요하다.
- 상세 조회는 feed에 포함된 `content` 범위까지만 보장한다.

View file

@ -0,0 +1,93 @@
# 개별공시지가 조회 가이드
## 이 기능으로 할 수 있는 일
- 한국 국토교통부 부동산공시가격알리미(`realtyprice.kr`)에서 지번 단위 **개별공시지가**(원/㎡) 조회
- 다년도 추이(과거 수년치)와 전년 대비 변동률 정규화 JSON 출력
- 17개 광역자치단체(서울, 세종특별자치시 포함) 모든 시·군·구 지원
- 산 지번 / 본번-부번 모두 지원
## 가장 중요한 규칙
`realtyprice.kr`는 **API 키가 필요 없는 완전 공개 엔드포인트**이므로 이 스킬은 `k-skill-proxy`를 경유하지 않는다. 사용자 머신에서 직접 upstream을 호출한다. (저장소의 *k-skill-proxy inclusion rule* — 프록시는 API 키가 필요한 upstream만 다룬다.)
## 무엇을 가져오나
- 공시지가는 매년 1월 1일 기준, 4~5월에 공시된다.
- 재산세, 종합부동산세, 양도소득세 등 **세금 산정의 법적 기준 단가**다.
- 공시지가 ≠ 시세. 시세는 통상 공시지가의 1.5~3배.
> 시세, 실거래가, 매매가, 호가가 필요하면 [`real-estate-search`](real-estate-search.md) 또는 다른 스킬을 사용한다.
## 먼저 필요한 것
없음. 인터넷 연결과 Node.js 18+ 만 있으면 된다.
## 사용 방법
### 설치
```bash
npm install gongsijiga-search
```
### 기본 호출
```js
const { lookupGongsijiga } = require("gongsijiga-search");
const result = await lookupGongsijiga("서울특별시 강남구 역삼동 736");
console.log(result.latest.price_per_sqm); // 72340000
console.log(result.yoy_change_pct); // 5.45
```
### 입력 주소 형식
`<시도> <시군구> <읍면동…> [산] <본번[-부번]>`
| 형식 | 예시 |
| --- | --- |
| 일반 | `서울특별시 강남구 역삼동 736` |
| 약칭 시도 | `서울 강남구 역삼동 736` |
| 부번 있음 | `경기 성남시 분당구 정자동 178-3` |
| 산 지번 | `서울 서초구 서초동 산 1-2` |
| 다토큰 읍면동 | `전남 무안군 청계면 청천리 100-5` |
| 세종 (시군구 없음) | `세종 어진동 575` 또는 `세종특별자치시 어진동 575` |
### 응답 모양
```json
{
"address": "서울특별시 강남구 역삼동 736",
"jibun": "736번지",
"san": false,
"latest": {
"year": 2026,
"price_per_sqm": 72340000,
"notice_date": "2026-04-30",
"base_date": "2026-01-01"
},
"history": [
{ "year": 2026, "price_per_sqm": 72340000, "notice_date": "2026-04-30" },
{ "year": 2025, "price_per_sqm": 68600000, "notice_date": "2025-04-30" }
],
"yoy_change_pct": 5.45,
"source_url": "https://www.realtyprice.kr/notice/gsindividual/search.htm"
}
```
## 실패 모드
| `error.code` | 의미 | 처리 |
| --- | --- | --- |
| `ADDRESS_PARSE_FAILED` | 주소 파싱 실패 / 미인식 시도 | "행정구역 + 본번이 포함된 주소가 필요합니다" 안내 후 재요청 |
| `INVALID_BUNJI` | 본번 비숫자 또는 4자리 초과 | 본번 형식 재요청 |
| `REGION_NOT_FOUND` | 시군구/읍면동 매칭 실패 | `err.candidates` 후보(최대 3개) 제안 |
| `LAND_NOT_FOUND` | 해당 지번 미등재 | "본번/부번 오타이거나 도로/하천 등 미과세 토지" 설명 |
| `UPSTREAM_ERROR` | `realtyprice.kr` 비정상 응답 | "데이터 출처 일시 장애. 잠시 후 재시도" + `source_url` |
| `UPSTREAM_TIMEOUT` | 30초 타임아웃 | UPSTREAM_ERROR와 동일 처리 |
## 출처
- [부동산공시가격알리미](https://www.realtyprice.kr/notice/gsindividual/search.htm) — 국토교통부
- 패키지 소스: [`packages/gongsijiga-search/`](../../packages/gongsijiga-search)

View file

@ -0,0 +1,108 @@
# 한강 수위 정보 가이드
## 이 기능으로 할 수 있는 일
- 한강홍수통제소(HRFCO) 관측소명/관측소코드 기준 현재 수위 확인
- 현재 유량(`FW`) 같이 확인
- 관심/주의/경보/심각 기준수위 같이 확인
- 별도 사용자 `ServiceKey` 없이 `k-skill-proxy` 로 조회
## 먼저 필요한 것
- [공통 설정 가이드](../setup.md) 확인
## 기본 경로
기본적으로 `https://k-skill-proxy.nomadamas.org/v1/han-river/water-level` 로 요청한다.
사용자는 별도 HRFCO `ServiceKey` 를 준비하지 않는다. upstream key는 proxy 서버에서만 `HRFCO_OPEN_API_KEY` 로 관리한다.
`KSKILL_PROXY_BASE_URL` 환경변수가 있으면 그 값을 사용하고, 없으면 기본 hosted path를 사용한다.
## 입력값
- 기본: 관측소명/교량명 (`stationName`)
- 대체: 관측소코드 (`stationCode`)
예: `한강대교`, `잠수교`, `1018683`
## 기본 흐름
1. client/skill 은 기본 hosted path 또는 `KSKILL_PROXY_BASE_URL` 아래 `/v1/han-river/water-level` endpoint 를 호출한다.
2. proxy 는 HRFCO `waterlevel/info.json` 을 읽어 관측소명 → `WLOBSCD` 를 해석한다.
3. 해석된 `WLOBSCD``waterlevel/list/10M/{WLOBSCD}.json` 최신 10분 자료를 조회한다.
4. 관측시각, 수위(`WL`), 유량(`FW`), 기준수위 메타데이터를 요약해서 반환한다.
5. 관측소명이 여러 개에 걸리면 `ambiguous_station` + `candidate_stations` 를 반환한다.
## 예시
proxy URL 이 준비된 뒤 조회:
```bash
curl -fsS --get 'https://k-skill-proxy.nomadamas.org/v1/han-river/water-level' \
--data-urlencode 'stationName=한강대교'
```
관측소코드 직접 조회:
```bash
curl -fsS --get 'https://k-skill-proxy.nomadamas.org/v1/han-river/water-level' \
--data-urlencode 'stationCode=1018683'
```
애매한 관측소명 예시:
```bash
curl -fsS --get 'https://k-skill-proxy.nomadamas.org/v1/han-river/water-level' \
--data-urlencode 'stationName=한강'
```
예상 응답 예시:
```json
{
"station_code": "1018683",
"station_name": "한강대교",
"agency_name": "한강홍수통제소",
"address": "서울특별시 용산구 한강대교",
"observed_at": "2026-04-05T19:00:00+09:00",
"water_level": {
"value_m": 0.66,
"unit": "m"
},
"flow_rate": {
"value_cms": 208.58,
"unit": "m^3/s"
},
"thresholds": {
"interest_level_m": 5.5,
"warning_level_m": 8,
"alarm_level_m": 10,
"serious_level_m": 11,
"plan_flood_level_m": 13
},
"special_report_station": true
}
```
## fallback / 대체 흐름
- `KSKILL_PROXY_BASE_URL` 을 별도로 넣으면 해당 proxy를 우선 사용한다.
- 기본 hosted path는 `https://k-skill-proxy.nomadamas.org/v1/han-river/water-level` 이다.
- self-host 운영자는 서버 쪽에만 `HRFCO_OPEN_API_KEY` 를 넣는다.
- 사용자/client 쪽 secrets 파일에는 HRFCO key 를 넣지 않는다.
## 주의할 점
- HRFCO 레퍼런스는 이 데이터를 원시자료로 설명하므로 조회 시각을 함께 적는다.
- 기본 endpoint 는 현재값 중심이라 기간별 시계열은 직접 노출하지 않는다.
- 관측소명이 너무 넓으면 `candidate_stations` 로 좁힌 뒤 다시 조회한다.
- 최신 자료는 보통 10분 단위지만 관측소별 수집 지연이 있을 수 있다.
- 별도 proxy를 쓸 때만 `KSKILL_PROXY_BASE_URL` 을 명시한다.
## 참고 표면
- 공식 레퍼런스: `https://www.hrfco.go.kr/web/openapiPage/reference.do`
- 인증키 안내: `https://www.hrfco.go.kr/web/openapiPage/certifyKey.do`
- 정책: `https://www.hrfco.go.kr/web/openapi/policy.do`
- proxy 운영 안내: [k-skill 프록시 서버 가이드](k-skill-proxy.md)

View file

@ -0,0 +1,108 @@
# 하이패스 영수증 발급 가이드
## 이 기능으로 할 수 있는 일
- 공식 하이패스 홈페이지에서 로그인된 Chrome 세션 재사용
- 사용내역 조회
- 특정 거래의 영수증 팝업/출력 화면 진입
- 세션 만료 감지 후 재로그인 안내
## 먼저 알아둘 점
- 이 기능은 **로그인된 브라우저 세션에서만 동작**한다.
- 하이패스 ID/PW/인증코드 입력을 자동화하지 않는다.
- 공개 페이지 기준으로 세션 타이머는 **20분(`session_time=1200`)** 이고, 세션 연장/종료는 `/comm/sessionCheck.do`, `/comm//sessionout.do` 경로를 사용한다.
- 보호 페이지는 `mgs_type 11/12``/comm/lginpg.do` 이동으로 세션 종료를 알린다.
- 따라서 쿠키 파일만 오래 보관해 재사용하는 완전 자동 로그인 유지 봇은 v1 범위 밖이다.
## 설치
```bash
npm install hipass-receipt
```
배포 패키지에는 CDP 연결용 `playwright-core` 가 함께 들어 있다. 별도 Playwright 브라우저를 내려받지 않고, 사용자가 직접 연 Chrome/Chromium 세션에 붙는다.
이 레포를 clone 한 유지보수자라면 루트에서 `npm install` 로 workspace 패키지까지 함께 설치해도 된다.
## 로그인 브라우저 준비
전용 Chrome 프로필 + CDP 포트로 브라우저를 띄운다.
```bash
hipass-receipt chrome-command --profile-dir "$HOME/.cache/k-skill/hipass-chrome" --debugging-port 9222
```
그 다음 사용자가 직접 `https://www.hipass.co.kr/comm/lginpg.do` 에 로그인한다.
## 사용내역 조회
```bash
hipass-receipt list \
--cdp-url http://127.0.0.1:9222 \
--start-date 2026-04-01 \
--end-date 2026-04-07 \
--page-size 30 \
--encrypted-card-number BASE64_OR_SITE_VALUE
```
내부적으로 다음 흐름을 사용한다.
1. `/usepculr/InitUsePculrTabSearch.do` 진입
2. `hpForm` 에 검색조건 주입
3. `/usepculr/UsePculrTabSearchList.do` 로 submit
4. iframe HTML 파싱 후 정규화 JSON 반환
`--encrypted-card-number` 는 기존 `--ecd-no` 별칭과 같다.
## 영수증 팝업 열기
먼저 `list` 결과에서 원하는 행의 `rowIndex` 를 확인한다.
```bash
hipass-receipt receipt \
--cdp-url http://127.0.0.1:9222 \
--start-date 2026-04-01 \
--end-date 2026-04-07 \
--row-index 1
```
이 명령은 선택한 행의 `영수증`/`출력` control 을 클릭하고, 팝업이 열리면 URL/title 을 반환한다. 공식 안내 기준으로 사용내역 화면에서는 `영수증선택출력` 또는 `영수증전체출력` 으로 이어진다.
## 세션 만료 처리
다음 신호 중 하나가 보이면 즉시 실패시키고 재로그인을 요구한다.
- `/comm/lginpg.do` redirect
- `mgs_type = 11`
- `mgs_type = 12`
- 권한체크(CommonAuthCheck.jsp) 응답
## 검증 전략
### 자동 검증
- query builder 테스트
- 사용내역 목록 HTML parser 테스트
- 상세/영수증 submit field 보존 테스트
- 로그인/권한체크 페이지 감지 테스트
### 로그인 없이 가능한 smoke
```bash
node packages/hipass-receipt/src/cli.js fixture-demo \
--fixture packages/hipass-receipt/test/fixtures/usage-history-list.html
```
### 로그인 세션이 필요한 최종 확인
- 실제 계정으로 로그인된 전용 Chrome 프로필 준비
- `hipass-receipt list` 실행
- `hipass-receipt receipt --row-index <n>` 실행
- 세션 만료 후 다시 실행해 재로그인 요구 메시지 확인
## 보안 원칙
- 하이패스 비밀번호를 새 env var나 repo 문서에 추가하지 않는다.
- 로그인은 반드시 사용자가 브라우저 안에서 직접 수행한다.
- 이 기능은 조회/영수증 보조까지만 다루며 장기 무인 자동화는 약속하지 않는다.

View file

@ -0,0 +1,266 @@
# 올라포케 역삼 포케 가이드
## 이 기능으로 할 수 있는 일
- 올라포케 역삼점 메뉴 조회 (`get_menu`)
- 위치·영업시간·배달 반경·단체주문 링크 조회 (`get_shop_info`)
- 즉석 래플형 이벤트 참여 (`enter_event`)
## 가장 중요한 규칙
이 기능은 원본 [`mnspkm/hola-poke-yeoksam-skill`](https://github.com/mnspkm/hola-poke-yeoksam-skill) 이 연결하는 **remote MCP server** 를 그대로 사용한다.
`k-skill` 안에 별도 수집기나 프록시를 추가하지 않고, skill/docs 가이드만 유지한다.
즉 기본 전제는 아래 endpoint 가 MCP client 에 등록돼 있어야 한다.
- `https://hola-poke-yeoksam-skill.onrender.com/mcp`
## 먼저 필요한 것
- 인터넷 연결
- MCP client (Claude Desktop, Cursor, Codex 등)
- 필요하면 `npx` (`mcp-remote` 경유 stdio 브리지용)
- 이벤트 참여 시 사용자 휴대폰 번호 (`01012345678` 또는 `010-1234-5678`)
## 빠른 연결 예시
### Claude Desktop (`mcp-remote` 경유)
```json
{
"mcpServers": {
"hola-poke-yeoksam": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://hola-poke-yeoksam-skill.onrender.com/mcp"]
}
}
}
```
### Cursor / HTTP MCP
```json
{
"mcpServers": {
"hola-poke-yeoksam": {
"url": "https://hola-poke-yeoksam-skill.onrender.com/mcp"
}
}
}
```
## 기본 흐름
### 1. 메뉴 탐색
- 사용자가 추천/메뉴를 물으면 `get_menu()` 를 호출한다.
- 포케, 사이드, 세트, 토핑 구조를 보고 핵심 메뉴와 가격을 짧게 요약한다.
- 정확한 보상/프로모션 문구는 메뉴 정보와 섞어 임의로 꾸미지 않는다.
### 2. 매장 정보 조회
- 위치, 영업시간, 배달 반경, 단체주문 문의는 `get_shop_info()` 를 호출한다.
- 주소, 영업시간, 배달 가능 범위, `group_order_url` 을 우선 전달한다.
### 3. 이벤트 참여
현재 문서 기준 스킴은 **즉석 래플** 이다.
1. 사용자가 참여 의사를 밝히면 번호를 먼저 받는다.
2. 이름·이메일은 받지 않고 번호만 받는다.
3. 번호는 결과 대조용이며 별도 마케팅 발송/3자 공유 용도가 아니라고 한 번 안내한다.
4. `enter_event(phone)` 를 호출한다.
5. `phone_format` 이면 서버 `message` 를 그대로 보여주고 재입력을 요청한다.
6. `already_entered_today` 이면 서버 `message` 를 그대로 보여주고 더 시도하지 않는다.
7. 성공 응답이면 `message`, `code`, `next_action` 을 함께 전달한다.
## 응답 정리 원칙
- `enter_event``message`**글자 그대로** 전달한다.
- 발급 코드는 `` `Jackpot-A3K9` `` 같이 모노스페이스로 강조한다.
- Jackpot/Claw 사용 방법은 `next_action` 과 함께 짧게 설명한다.
- 단체주문 문의는 `group_order_url` 이 비어 있으면 `group_order_note` 를 대신 제공한다.
- 역삼점 외 다른 지점 문의에는 이 스킬 범위가 아니라는 점을 먼저 밝힌다.
## Verified remote MCP contract snapshot
아래 값은 `2026-04-16 KST` live smoke check(`initialize`, `tools/list`, `get_menu`, `get_shop_info`, `enter_event(phone='010-12')`) 기준으로 정리한 contract fixture다.
### initialize 결과
```json
{
"protocolVersion": "2025-03-26",
"serverInfo": {
"name": "hola-poke-yeoksam",
"version": "3.2.3"
}
}
```
### tools/list 결과
```json
{
"tools": [
{
"name": "get_menu",
"inputSchema": {
"type": "object",
"properties": {},
"additionalProperties": false
},
"outputSchema": {
"type": "object",
"additionalProperties": true
}
},
{
"name": "get_shop_info",
"inputSchema": {
"type": "object",
"properties": {},
"additionalProperties": false
},
"outputSchema": {
"type": "object",
"additionalProperties": true
}
},
{
"name": "enter_event",
"inputSchema": {
"type": "object",
"properties": {
"phone": {
"type": "string"
}
},
"required": [
"phone"
],
"additionalProperties": false
},
"outputSchema": {
"type": "object",
"additionalProperties": true
}
}
]
}
```
### get_menu 구조 예시
```json
{
"updated_at": "2026-04-13",
"currency": "KRW",
"price_unit": "천원",
"signature_poke": [
{
"id": 2,
"name": "갈릭 쉬림프 포케",
"price": 11.5,
"tags": [
"BEST"
]
},
{
"id": 7,
"name": "아보카도 포케",
"price": 10.5,
"tags": [
"VEGAN"
]
}
],
"sets": [
{
"name": "1인 포케+스프 세트",
"items": "포케 + 스프",
"price": 13.5,
"price_note": "13.5~"
},
{
"name": "1인 혼밥 든든세트",
"items": "포케 + 스프 + 음료",
"price": 15.5,
"price_note": "15.5~"
}
],
"addons": [
{
"name": "아보카도",
"price": 3.5
},
{
"name": "메밀면",
"price": 1.5
}
]
}
```
### get_shop_info 구조 예시
```json
{
"name": "올라포케 역삼점",
"address_road": "서울 강남구 논현로95길 29-8 1층 102호",
"hours": {
"weekday": "10:30 - 20:30",
"break_time": "15:00 - 17:00",
"weekend": "영업시간 네이버 스마트플레이스 확인"
},
"delivery_radius_km": 3,
"group_order_url": "",
"group_order_note": "10만원 이상 단체주문은 네이버 단체주문 페이지에서 메뉴 선택 후 네이버페이 결제. 결제 완료 시 예약 확정.",
"delivery_apps": [
"배달의민족",
"쿠팡이츠",
"요기요"
]
}
```
### enter_event 성공 응답 필수 필드
실제 이벤트 참여를 발생시키지 않기 위해 성공 경로는 저장된 스냅샷 fixture 계약으로만 고정한다. 라이브 스모크는 invalid-phone 재시도 흐름만 검증한다.
```json
{
"required_fields": [
"message",
"code",
"next_action"
],
"accepts": [
"01012345678",
"010-1234-5678"
],
"stores_name_or_email": false
}
```
### enter_event(phone='010-12') 예시
```json
{
"error": "phone_format",
"message": "번호는 010으로 시작하는 11자리로 입력해주세요 (예: 01012345678 또는 010-1234-5678)."
}
```
## 제한사항
- 역삼점 전용이다.
- 주문/결제/배달앱 자동화는 하지 않는다.
- 단체주문 자동 예약을 대신 실행하지 않는다.
- 이벤트 스킴은 시기별로 바뀔 수 있으므로 현재 혜택 조건의 진실 소스는 서버 `message` 다.
- 동일 번호는 하루 1번만 응모 가능하므로 반복 요청을 강행하지 않는다.
## 참고 링크
- 원본 repo: `https://github.com/mnspkm/hola-poke-yeoksam-skill`
- remote MCP endpoint: `https://hola-poke-yeoksam-skill.onrender.com/mcp`

View file

@ -0,0 +1,55 @@
# 생활쓰레기 배출정보 조회 가이드
## 이 기능으로 할 수 있는 일
- 시군구 기준 생활쓰레기 배출요일/시간 조회
- 음식물쓰레기/재활용품 배출방법 조회
- 배출장소, 미수거일, 관리부서 연락처 확인
- 공공데이터 원본 API를 k-skill-proxy `/v1/household-waste/info` 라우트로 감싸서 조회 + API 키는 프록시 주입
## 가장 중요한 규칙
스킬은 항상 `k-skill-proxy``/v1/household-waste/info` 라우트를 호출한다.
사용자는 `DATA_GO_KR_API_KEY`를 직접 들고 있지 않고, `serviceKey`는 proxy 서버에서만 원본 API(`https://apis.data.go.kr/1741000/household_waste_info/info`)로 주입한다.
## 먼저 필요한 것
- 인터넷 연결
- 원본 API 접근 가능 환경
- API 키 주입용 proxy 접근 가능 환경
## 기본 조회 예시
```bash
curl -fsS --get 'https://k-skill-proxy.nomadamas.org/v1/household-waste/info' \
--data-urlencode 'cond[SGG_NM::LIKE]=강남구' \
--data-urlencode 'pageNo=1' \
--data-urlencode 'numOfRows=100'
```
클라이언트는 **`cond[SGG_NM::LIKE]`** 와 **`pageNo` / `numOfRows`**(또는 `page_no` / `num_of_rows`)를 **함께** 넘긴다. `pageNo` / `numOfRows` 값은 **반드시 `1` / `100`** 이어야 하고, 그 외 값이나 숫자만으로 표현되지 않는 문자열이면 proxy가 **`400`** 을 반환하고 upstream을 호출하지 않는다. upstream에는 항상 `pageNo=1`, `numOfRows=100`만 전달된다. `returnType`은 항상 `json`으로 강제된다. 원본 API의 `cond[DAT_CRTR_YMD::*]`, `cond[DAT_UPDT_PNT::*]` 같은 부가 필터는 현재 지원하지 않는다 — 일반 사용자 질의("강남구 쓰레기 배출 요일")는 시군구 검색만으로 충분하기 때문이다.
## 조회 흐름 권장 순서
1. 사용자에게 시/군/구를 먼저 확인한다.
2. 입력이 모호하면 상위 행정구역을 포함해 재질문한다.
3. proxy `/v1/household-waste/info` 라우트로 조회한다 (`serviceKey`는 proxy가 주입).
4. 배출장소/요일/시간/미수거일/문의처를 3~6개 포인트로 요약한다.
5. 결과가 여러 건이면 응답 항목을 `DAT_UPDT_PNT` 기준으로 클라이언트에서 정렬해 최신 항목을 우선 보여준다.
## 자주 보는 필드
- `SGG_NM`: 시군구명
- `MNG_ZONE_NM`, `MNG_ZONE_TRGT_RGN_NM`: 관리구역/대상지역
- `EMSN_PLC`, `EMSN_PLC_TYPE`: 배출장소/유형
- `LF_WST_EMSN_MTHD`, `FOD_WST_EMSN_MTHD`, `RCYCL_EMSN_MTHD`: 배출방법
- `LF_WST_EMSN_DOW`, `FOD_WST_EMSN_DOW`, `RCYCL_EMSN_DOW`: 배출요일
- `LF_WST_EMSN_BGNG_TM`, `LF_WST_EMSN_END_TM`: 생활쓰레기 배출시간
- `UNCLLT_DAY`: 미수거일
- `MNG_DEPT_NM`, `MNG_DEPT_TELNO`: 담당부서/연락처
## 참고 링크
- 공식 데이터 출처: 공공데이터포털 (`https://www.data.go.kr`)
- upstream API: `https://apis.data.go.kr/1741000/household_waste_info/info`
- 프록시 역할: 인증키(`DATA_GO_KR_API_KEY`) 서버 측 주입/보호

View file

@ -2,104 +2,141 @@
## 이 기능으로 할 수 있는 일
- `.hwp` 문서를 JSON, Markdown, HTML로 변환
- 문서 안 이미지를 추출
- 폴더 단위 배치 처리
- Windows + 한글 프로그램 설치 환경에서는 직접 문서 조작까지 확장
- `.hwp`, `.hwpx`, `.hwpml` 문서를 Markdown으로 변환
- 문서를 JSON으로 구조화해 `blocks`, `metadata`까지 AI에 넘기기
- 폴더 단위 배치 변환
- `watch`로 폴더를 감시하며 새 문서를 계속 변환
- 두 버전 문서 비교
- HWPX 양식 필드 추출
- Markdown을 다시 HWPX로 역변환
## 먼저 필요한 것
- 기본 경로: Node.js 18+
- 기본 패키지: `npm install -g @ohah/hwpjs`
- 실행 전: `export NODE_PATH="$(npm root -g)"`
- 직접 제어가 필요할 때만: Windows + 한글(HWP) 프로그램 설치 + Python 3.7+
- Node.js 18+
- CLI를 한 번만 쓸 때: `npx --yes --package kordoc --package pdfjs-dist kordoc --help`
- 반복 실행용 전역 설치: `npm install -g kordoc pdfjs-dist`
- Node API 예시를 따라갈 로컬 작업 디렉터리: `npm init -y && npm install kordoc pdfjs-dist`
- 현재 배포된 `kordoc` CLI는 시작 시 `pdfjs-dist`를 바로 불러오므로 PDF를 안 다뤄도 함께 설치해야 한다
- `import { markdownToHwpx } from "kordoc"` 같은 ESM 예시는 전역 `NODE_PATH`가 아니라 로컬 설치 기준으로 실행해야 한다
## 어떤 경로를 선택하나
### 기본값: `@ohah/hwpjs`
이 스킬의 기본 경로는 **항상 `kordoc`** 이다.
다음 상황에서는 `@ohah/hwpjs`를 사용한다.
- 문서 읽기/변환 → `kordoc`
- 구조화 JSON 추출 → `kordoc --format json`
- 연속 입력 폴더 처리 → `kordoc watch`
- 양식 필드 추출 → `parse()` + `extractFormFields()`
- 역변환 → `markdownToHwpx()`
- 문서 비교 → `compare()`
- macOS / Linux / CI
- 읽기, 변환, 이미지 추출, 배치 처리
- Windows여도 한글 프로그램 설치/연동을 확신할 수 없음
### 예외 경로: `hwp-mcp`
다음 조건을 모두 만족할 때만 `hwp-mcp`를 사용한다.
- Windows
- 한글(HWP) 프로그램이 실제 설치되어 있음
- 문서 생성, 텍스트 삽입, 표 채우기처럼 실행 중인 한글 직접 제어가 필요함
즉, **변환은 `@ohah/hwpjs`, 직접 조작은 `hwp-mcp`** 가 기본 규칙이다.
이 스킬은 단일한 `kordoc` 경로를 표준 흐름으로 유지한다.
## 기본 흐름
1. `node -p "process.platform"` 으로 운영체제를 확인한다.
2. `win32` 가 아니면 `@ohah/hwpjs`를 사용한다.
3. `win32` 여도 직접 제어 요건이 분명하지 않으면 `@ohah/hwpjs`를 사용한다.
4. 직접 조작이 필요하고 한글 설치가 확인되면 `hwp-mcp`를 선택한다.
5. 결과 파일 생성 여부와 출력 내용을 확인한다.
1. `kordoc`이 없으면 설치한다.
2. `.hwp`/`.hwpx`/`.hwpml`을 Markdown 또는 JSON으로 변환한다.
3. 표·이미지·메타데이터가 필요하면 JSON의 `blocks` / `metadata`를 확인한다.
4. 반복 입력 폴더는 `watch`, 양식 문서는 `extractFormFields`, 편집 roundtrip은 `markdownToHwpx` 경로로 이어간다.
5. 결과 파일 생성 여부와 구조를 확인한다.
## 예시
### Markdown 변환
```bash
npx --yes --package kordoc --package pdfjs-dist kordoc 보고서.hwp -o 보고서.md
```
### JSON 변환
```bash
hwpjs to-json document.hwp -o output.json --pretty
```
### Markdown 변환 + 이미지 포함
```bash
hwpjs to-markdown document.hwp -o output.md --include-images
```
`--include-images` 는 이미지 파일 경로를 따로 만드는 대신 Markdown 안에 base64 `data:` URI로 포함한다.
### HTML 변환
```bash
hwpjs to-html document.hwp -o output.html
```
### 이미지 추출
```bash
hwpjs extract-images document.hwp -o ./images
npx --yes --package kordoc --package pdfjs-dist kordoc 검토서.hwpx --format json > 검토서.json
```
### 배치 처리
```bash
hwpjs batch ./documents -o ./output --format json --recursive
npx --yes --package kordoc --package pdfjs-dist kordoc ./문서함/* -d ./변환결과
```
### 페이지 범위 지정
```bash
npx --yes --package kordoc --package pdfjs-dist kordoc 보고서.hwp --pages 1-3
```
### 디렉터리 감시 변환
```bash
npx --yes --package kordoc --package pdfjs-dist kordoc watch ./문서함
```
### 양식 필드 추출
아래 Node API 예시는 `package.json`이 있는 로컬 작업 디렉터리에서:
```bash
npm init -y
npm install kordoc pdfjs-dist
```
이미 `package.json`이 있으면 `npm install kordoc pdfjs-dist`만 추가로 실행하면 된다.
```bash
node --input-type=module - <<'EOF'
import { parse, extractFormFields } from "kordoc";
const result = await parse("신청서.hwpx");
if (!result.success) {
console.error(result.error);
process.exit(1);
}
console.log(JSON.stringify(extractFormFields(result.blocks), null, 2));
EOF
```
### Markdown → HWPX 역변환
```bash
node --input-type=module - <<'EOF'
import { markdownToHwpx } from "kordoc";
import { writeFileSync } from "node:fs";
const hwpx = await markdownToHwpx("# 제목\n\n본문\n\n| 항목 | 값 |\n| --- | --- |\n| 성명 | 홍길동 |");
writeFileSync("출력.hwpx", Buffer.from(hwpx));
EOF
```
### 문서 비교
```bash
node --input-type=module - <<'EOF'
import { compare } from "kordoc";
import { readFileSync } from "node:fs";
const before = readFileSync("이전버전.hwp");
const after = readFileSync("최신버전.hwpx");
const diff = await compare(before, after);
console.log(diff.stats);
EOF
```
## 결과 확인 포인트
- JSON 출력: 파일 생성 여부와 최상위 구조를 확인한다.
- Markdown 출력: `--include-images` 를 썼다면 이미지 파일 경로가 따로 생기지 않아도 정상이며, Markdown 안 `data:` URI / base64 인라인 포함 여부를 확인한다.
- HTML 출력: 파일 생성 뒤 브라우저에서 열리는지 확인한다.
- 이미지 추출: 출력 디렉터리에 실제 이미지 파일이 생겼는지 확인한다.
- Markdown 출력: 제목/본문/표가 기대한 순서로 정리됐는지 확인한다.
- JSON 출력: `success`, `blocks`, `metadata`가 있는지 확인한다.
- 이미지/표 구조: `blocks``image`, `table` 타입이 필요한 만큼 잡혔는지 확인한다.
- 배치 처리: 입력 개수와 출력 개수가 크게 어긋나지 않는지 확인한다.
이미지를 별도 파일로 떨궈야 한다면 `--include-images` 대신 `--images-dir` 경로를 쓴다.
## 직접 제어가 필요한 경우
`hwp-mcp`는 Windows + 한글 프로그램 설치 환경에서만 고려한다.
```bash
git clone https://github.com/jkf87/hwp-mcp.git
cd hwp-mcp
pip install -r requirements.txt
```
그 뒤 MCP 서버로 연결해 새 문서 생성, 텍스트 삽입, 표 작성, 저장 같은 작업을 수행한다.
- 양식 필드 추출: `extractFormFields(result.blocks)` 결과가 비어 있지 않은지 확인한다.
- 역변환: 생성된 `.hwpx` 가 열리고 기본 서식/테이블이 유지되는지 확인한다.
- 문서 비교: `diff.stats` 의 added / removed / modified 값이 입력 변화와 맞는지 확인한다.
## 주의할 점
- `hwp-mcp`를 Linux/macOS에서 우회 실행하려 하지 않는다.
- 직접 제어 필요성이 약하면 `@ohah/hwpjs`로 바로 끝내는 편이 더 안정적이다.
- 배치 작업 후에는 입력 개수와 출력 개수를 같이 확인한다.
- 손상된 문서나 일부 특수 양식은 경고가 섞일 수 있다.
- 이미지 기반 PDF는 OCR provider가 없으면 품질이 제한될 수 있다.
- 양식 필드 추출은 템플릿 라벨 품질에 따라 일부 필드가 인식되지 않을 수 있다.
- 공문서 자동화 목적이면 Markdown만 보는 것보다 JSON `blocks`를 같이 확인하는 편이 안전하다.
- 현재 배포본 기준으로 문서화된 CLI 명령은 기본 변환과 `watch` 이며, 양식 처리와 비교는 Node API 예시를 기준으로 잡는 편이 안전하다.

View file

@ -0,0 +1,68 @@
# 시외버스 예매 가이드
## 이 기능으로 할 수 있는 일
- 티머니 시외버스 터미널/노선 후보 확인
- 배차 시간표, 운수사, 잔여석, 요금 확인
- 좌석/요금 단계 진입 가능 여부 확인
- 공식 카드정보 입력 페이지로 handoff
## 먼저 필요한 것
- 별도 사용자 계정/비밀번호는 기본 조회·좌석 단계에서 필요하지 않음
- 결제는 공식 티머니 시외버스 페이지에서 사용자가 직접 진행
- 브라우저 자동화보다 `https://intercitybus.tmoney.co.kr` 공식 HTTP 흐름을 우선 사용
## 입력값
- 출발 터미널
- 도착 터미널
- 날짜: `YYYYMMDD`
- 희망 시간대
- 인원 수와 좌석 선호
## 기본 흐름
1. 쿠키 jar를 만들고 티머니 시외버스 페이지를 열어 세션을 시작한다.
2. `POST /otck/readAlcnList.do` 로 배차를 조회한다. 이때 브라우저 JS가 붙이는 `bef_Aft_Dvs=D`, `req_Rec_Num=10`을 반드시 같이 보낸다.
3. 결과의 `readSasFeeInf(...)` 인자를 파싱해 후보를 정리한다.
4. 선택 후보는 `POST /otck/readSatsFee.do` 로 좌석/요금 단계 진입을 확인한다.
5. 사용자가 원하면 `POST /otck/readPcpySats.do` 로 공식 카드정보 입력 페이지에 진입하도록 handoff한다.
6. 뒤로가기/취소성 이동으로 좌석 선택 단계에 복귀해 임시 선점을 해제할 수 있는지 확인한다.
## read-only 조회 helper
```bash
python3 intercity-bus-booking/scripts/intercity_bus_search.py \
--depart-code 0511601 \
--arrive-code 2482701 \
--depart-name 동서울 \
--arrive-name 속초 \
--date 20260520
```
이 helper는 쿠키 세션을 시작하고 공식 배차 조회 POST를 수행한 뒤 출발시각, 운수사, 등급, 요금, 잔여/총 좌석을 JSON으로 출력한다. 기본은 read-only이며, `--hold-seat` 또는 `--hold-first-seat`를 주면 좌석/요금 단계에 진입해 `readPcpySats.do`로 임시 좌석 선점을 만들고 공식 카드정보 입력 HTML과 cancel/back 필드를 저장한다. 결제 정보 입력·제출은 수행하지 않는다.
### 임시 선점 예시
```bash
python3 intercity-bus-booking/scripts/intercity_bus_search.py \
--depart-code 0511601 \
--arrive-code 2482701 \
--depart-name 동서울 \
--arrive-name 속초 \
--date 20260520 \
--select-index 1 \
--hold-first-seat \
--output-dir /tmp/tmoney-hold
```
성공 조건은 JSON의 `hold.success=true`, `hold.hold_id` 존재, 저장된 HTML에 `카드정보 입력` 표시가 있는 것이다. 라이브 응답 페이지에는 정확한 만료 카운트다운 문구가 노출되지 않았으므로, 선점 후 결제는 즉시 진행하게 안내하고 방치된 선점은 저장된 cancel/back 필드로 해제한다.
## 주의할 점
- 결제 자동화는 포함하지 않는다. 공식 페이지의 결제 직전 단계까지 보조하는 assisted checkout 흐름이다.
- 티머니 시외버스 터미널 코드는 KOBUS 고속버스 코드와 다르므로 혼용하지 않는다.
- 일부 표면은 `txbus` 계열 URL과 연결될 수 있지만, 검증된 기본 URL은 `intercitybus.tmoney.co.kr` 이다.
- stateless POST보다 쿠키와 referer를 유지하는 흐름이 안정적이다.
- `bef_Aft_Dvs` 또는 `req_Rec_Num`을 누락하면 실제 배차가 있어도 `errorCont`가 포함된 일반 오류 페이지가 반환될 수 있다.

View file

@ -0,0 +1,170 @@
# 등기부등본 자동화 가이드
`iros-registry-automation`은 인터넷등기소(IROS)에서 법인/부동산 등기부등본(등기사항증명서)을 여러 건 발급해야 할 때, 사용자가 직접 로그인·결제하는 브라우저 흐름을 전제로 장바구니, 열람, 저장 작업을 보조하는 스킬이다.
이 문서는 원 저작자 `challengekim`의 MIT 참고 구현 [`challengekim/iros-registry-automation`](https://github.com/challengekim/iros-registry-automation)을 기준으로 작성했다. 스킬 답변이나 파생 문서에도 이 원 저작자 링크를 남긴다.
## 할 수 있는 일
- 법인등기부등본: 법인등록번호 기반(`iros_cart_by_corpnum.py`) 또는 상호명 기반(`iros_cart.py`)으로 장바구니에 담고, 사용자가 직접 결제한 뒤 열람·저장한다.
- 부동산등기부등본: 주소/동호수 JSON을 사용해 `iros_cart_realty.py`로 장바구니에 담는다. 결제·열람·다운로드는 인터넷등기소 웹 UI에서 수동 처리하는 것을 기본 권장한다.
- TouchEn nxKey 설치, Playwright/Chromium 준비, 입력 파일 형식, 저장 폴더를 점검한다.
- 다운로드된 PDF와 법인정보 리포트 같은 산출물을 저장소 밖 안전한 경로에 두도록 안내한다.
## 먼저 알아둘 점
- 로그인은 사용자가 직접 한다. 아이디/비밀번호, 공동인증서 비밀번호, 간편인증, OTP, 보안카드 입력을 에이전트에게 맡기지 않는다.
- 결제는 사용자가 직접 한다. 카드 번호와 승인 절차는 브라우저에서 사람이 처리한다.
- 법인 발급은 upstream 문서 기준 **페이지당 10건** 결제 제약을 전제로 한다. 10건을 넘으면 사용자가 10건 단위로 반복 결제한다.
- 부동산은 인터넷등기소 웹 UI의 10만원 미만 일괄 결제와 일괄열람출력/일괄저장 기능을 쓰는 편이 빠르고 안전한 경우가 많다. 이 스킬은 부동산 주소 목록을 장바구니에 반복 담는 부분에 초점을 둔다.
- TouchEn nxKey가 설치되어 있지 않으면 중간에 보안 프로그램 설치 페이지가 뜰 수 있다. 설치 후 브라우저/PC를 재시작하고 처음부터 다시 실행한다.
- 이 기능은 참고용 발급 자동화 가이드다. 법률 자문, 권리관계 해석, 발급 결과의 법적 효력 판단을 하지 않는다.
## 설치
이 스킬은 로그인·인증·결제 인접 브라우저 자동화를 mutable upstream `HEAD`에 맡기지 않는다. 실행 전 이 저장소의 `iros-registry-automation/scripts/upstream.pin`에 적힌 reviewed SHA로 checkout한다.
```bash
git clone https://github.com/challengekim/iros-registry-automation.git
cd iros-registry-automation
git checkout 7c6924b2ff88d693a12556659188cb91041e5097
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
playwright install chromium
cp config.json.example config.json
```
업스트림 핀 업데이트는 새 upstream diff 검토가 필요한 보안/신뢰 경계 변경이다. `scripts/upstream.pin`과 설치 예시의 `git checkout` SHA를 같은 PR에서 함께 갱신한다.
Chrome/Chromium, Python 3.10+, IROS 로그인 수단, 결제 카드, TouchEn nxKey가 필요하다.
## 안전한 작업 폴더
발급 대상 목록과 PDF에는 법인등록번호, 주소, 동호수, 회사명 등 개인정보/민감정보가 들어갈 수 있다. 저장소 밖 비공개 디렉터리에서 다루고, PR·테스트 로그·공개 문서에 실제 값을 커밋하지 않는다.
```bash
workdir="$(mktemp -d "${TMPDIR:-/tmp}/iros-registry.XXXXXX")"
chmod 700 "$workdir"
mkdir -p "$workdir/downloads" "$workdir/logs" "$workdir/output" "$workdir/tmp-downloads"
```
실제 입력은 upstream repo `data/`가 아니라 `$workdir/corp-input.json`, `$workdir/realty-input.json`, `$workdir/customer-list.xlsx`처럼 저장소 밖에 둔다. upstream `data/` 디렉터리는 샘플 형식 확인용으로만 보고, 실제 법인등록번호·주소·동호수·고객 Excel 원문을 넣지 않는다.
```bash
cat > "$workdir/corp-input.json" <<'JSON'
{
"1101111234567": "예시 주식회사",
"1101117654321": "샘플 주식회사"
}
JSON
python3 - "$workdir" <<'PY'
import json
import pathlib
import sys
workdir = pathlib.Path(sys.argv[1])
corp_input = json.loads((workdir / "corp-input.json").read_text())
companies = list(corp_input.values())
(workdir / "companies-input.json").write_text(
json.dumps(companies, ensure_ascii=False, indent=2) + "\n"
)
PY
```
`iros_download.py`는 결제 후 열람·저장 단계에서 `companies_list`를 열어 저장 파일명을 맞춘다. 법인등록번호 기반 `iros_cart_by_corpnum.py`를 쓰더라도 결제 전에 `$workdir/companies-input.json`을 준비해야 결제 후 다운로드가 로컬 `FileNotFoundError` 없이 이어진다.
`config.json`의 입력·로그·save_dir 관련 값을 `$workdir`로 돌리면 upstream 스크립트를 실행해도 저장소 하위 `data/`, `logs/`, `output/`에 실제 업무 정보가 남지 않는다.
```bash
python3 - "$workdir" <<'PY'
import json
import pathlib
import sys
workdir = pathlib.Path(sys.argv[1])
config = json.loads(pathlib.Path("config.json").read_text())
config.update({
"corpnum_list": str(workdir / "corp-input.json"),
"companies_list": str(workdir / "companies-input.json"),
"realty_list": str(workdir / "realty-input.json"),
"excel_path": str(workdir / "customer-list.xlsx"),
"save_dir": str(workdir / "downloads"),
"realty_save_dir": str(workdir / "downloads" / "realty"),
"pdf_dir": str(workdir / "downloads"),
"report_output": str(workdir / "output" / "corp-report.xlsx"),
"extract_output": str(workdir / "output" / "corp-extract.json"),
"bizno_cache": str(workdir / "logs" / "bizno-cache.json"),
"bizno_results": str(workdir / "logs" / "bizno-results.json"),
"realty_cart_log": str(workdir / "logs" / "cart-realty-log.json"),
"realty_download_log": str(workdir / "logs" / "download-realty-log.json"),
"cart_log": str(workdir / "logs" / "cart-log.json"),
"cart_corpnum_log": str(workdir / "logs" / "cart-corpnum-log.json"),
"download_log": str(workdir / "logs" / "download-log.json"),
"download_temp": str(workdir / "tmp-downloads"),
})
pathlib.Path("config.json").write_text(json.dumps(config, ensure_ascii=False, indent=2) + "\n")
PY
```
## 법인등기부등본 흐름
1. 법인등록번호 또는 상호명 목록을 준비한다.
2. 법인등록번호가 있으면 `iros_cart_by_corpnum.py`를 우선 사용한다. 상호명만 있으면 `iros_cart.py`를 사용한다.
3. 브라우저가 열리면 사용자가 직접 IROS에 로그인한다.
4. 자동 처리로 법인 검색 → 말소사항포함 등 선택 → 장바구니 담기를 진행한다.
5. 결제대상목록 페이지가 뜨면 사용자가 직접 카드 결제를 완료한다. 법인은 페이지당 10건 단위 제약을 전제로 한다.
6. 결제 후 `iros_download.py` 또는 마법사 메뉴 2번으로 열람·저장한다.
```bash
python iros_cart_by_corpnum.py
python iros_download.py
```
위 명령은 로컬 `config.json`을 읽으므로, 먼저 `corpnum_list`, `companies_list`, `save_dir`가 각각 `$workdir/corp-input.json`, `$workdir/companies-input.json`, `$workdir/downloads`를 가리키는지 확인한다.
## 부동산등기부등본 흐름
1. 주소/동호수 JSON을 준비한다.
2. `iros_cart_realty.py`로 주소 검색 → 소재지번 선택 → 용도(열람)/등기기록유형(전부)/미공개 → 장바구니 담기를 진행한다.
3. 사용자가 인터넷등기소 웹 UI에서 직접 결제, 일괄열람출력, 일괄저장을 수행한다.
4. 자동 저장이 꼭 필요한 경우에만 `iros_download_realty.py`를 별도로 검토한다.
```bash
python iros_cart_realty.py
```
부동산은 결제·열람·다운로드까지 무조건 자동으로 밀어붙이기보다, 장바구니 단계 자동화 후 브라우저의 일괄 기능을 쓰는 경로를 먼저 권한다.
## 마법사 사용
처음 쓰는 사용자는 upstream 마법사를 먼저 실행한다.
```bash
python iros_wizard.py
```
마법사는 법인/부동산 장바구니, 결제 후 열람·저장, 사업자번호 기반 법인정보 조회, 다운로드된 법인 PDF 종합 리포트 생성을 메뉴로 제공한다. 사업자번호/고객 workbook 경로는 `excel_path``$workdir/customer-list.xlsx`를 가리키게 한 뒤 사용하고, upstream repo `data/고객리스트.xlsx`에는 실제 고객 Excel을 두지 않는다.
## 트러블슈팅
| 증상 | 원인 | 대응 |
| --- | --- | --- |
| 보안 프로그램 설치 페이지가 뜸 | TouchEn nxKey 미설치 | 설치 후 브라우저/PC 재시작, 스크립트 처음부터 재실행 |
| 법인 상호 검색 결과가 맞지 않음 | 사명변경, 특수문자, 동명 법인 | 법인등록번호 기반으로 재시도 |
| 10건 초과 법인 결제가 한 번에 되지 않음 | 페이지당 10건 제약 | 10건 단위로 반복 결제 |
| 부동산 저장 자동화가 느림 | 웹 UI 일괄 기능이 더 적합 | 장바구니만 자동화하고 결제·일괄열람출력·일괄저장은 수동 처리 |
## 보안/개인정보 원칙
- IROS 계정, 인증서 비밀번호, 카드 정보를 저장하지 않는다.
- 발급 대상 JSON, 다운로드 PDF, Excel 리포트는 저장소 밖에 둔다.
- 테스트와 PR에는 샘플/마스킹 값만 사용한다.
- 산출물 경로가 개인 이름·주소를 포함하면 공유 요약에서 경로도 마스킹한다.
## 출처
- 인터넷등기소(IROS): https://www.iros.go.kr
- 원 저작자 참고 구현: `challengekim/iros-registry-automation` — https://github.com/challengekim/iros-registry-automation
- upstream license: MIT

View file

@ -0,0 +1,67 @@
# 잡코리아 인재검색 가이드
## 이 기능으로 할 수 있는 일
- 잡코리아 기업회원 인재검색 화면에서 구인/채용 조건을 입력해 후보를 찾는다.
- 사용자가 직접 로그인한 브라우저 세션에서 현재 보이는 마스킹된 목록/이력서 정보를 읽는다.
- 유료 이력서 열람 전에 후보 적합도를 비교하고 shortlist를 만든다.
- 영업, 마케팅, 디자인, PM/PO, HR, 재무, 운영, 개발/데이터 등 전 직무에 사용할 수 있다.
## 먼저 알아둘 점
- 잡코리아 구인자/채용 담당자가 접근 가능한 기업회원 계정과 사용자 직접 로그인이 필요하다.
- 에이전트는 비밀번호, OTP, 세션 쿠키를 요청하거나 저장하지 않는다.
- 유료 열람, 마스킹 해제, 연락처 확인, 포지션 제안, 스크랩, 메모, 후보 상태 변경은 자동으로 하지 않는다.
- 비로그인 공개 목록 fallback은 가능하지만 정확도가 낮으므로 `목록 기반 1차 shortlist`로 표시한다.
## 공식 표면
- 잡코리아 기업 인재검색: https://www.jobkorea.co.kr/corp/person/find
## 입력값
- 채용 직무명
- 경력 범위
- 지역
- 필수 경험/스킬/업종
- 우대 경험/성과/툴
- 제외할 업무/업종/경력 패턴
- 유료 열람 추천 인원 수
## 기본 흐름
1. 잡코리아 기업 인재검색 페이지를 연다.
2. 로그인 상태를 확인한다. 로그인되지 않았으면 사용자가 열린 브라우저에서 직접 로그인한다.
3. 직무/키워드/경력/지역/제외 조건을 입력한다.
4. 결과 목록에서 후보 pool을 만든다.
5. 유료 열람이나 연락처 확인이 아닌 일반 상세/마스킹 이력서만 연다.
6. 현재 보이는 정보만 근거로 점수화한다.
7. URL과 검토 수준을 포함해 유료 열람 추천 후보를 정리한다.
## 결과 형식
```text
잡코리아 인재 shortlist
검색 조건
- 포지션: ...
- 필수 조건: ...
- 우대 조건: ...
- 제외 조건: ...
- 경력/지역: ...
- 모드: 로그인 마스킹 이력서 / 비로그인 목록 fallback
유료 열람 추천 Top N
1. 후보 A
- 점수: ...
- 근거: ...
- 리스크: ...
- 추천 액션: 채용 담당자가 유료 열람 검토
- URL: ...
```
## 제한사항
- 사이트 UI 변경 시 브라우저 추출 selector를 조정해야 할 수 있다.
- 계정 권한, 유료 상품 상태, 마스킹 정책에 따라 보이는 정보가 다르다.
- 후보 개인정보를 장기 저장하거나 대량 수집하지 않는다.

View file

@ -0,0 +1,90 @@
# 조선왕조실록 검색 가이드
## 이 기능으로 할 수 있는 일
- 조선왕조실록 공식 사이트에서 키워드 검색
- 검색 결과를 왕별로 좁혀 보기
- 서기 연도(`1443`처럼 Gregorian year) 기준으로 결과 필터링
- 기사 상세에서 국역/원문 excerpt 가져오기
- JSON 형태로 후속 자동화에 넘기기
## 먼저 필요한 것
- 인터넷 연결
- `python3`
- 설치된 `joseon-sillok-search` skill 안에 `scripts/sillok_search.py` helper 포함
별도 API 키나 로그인은 필요 없다.
## 공식 표면
- 메인: `https://sillok.history.go.kr`
- 검색 결과: `https://sillok.history.go.kr/search/searchResultList.do`
- 기사 상세: `https://sillok.history.go.kr/id/<article_id>`
## 기본 흐름
1. 검색어를 받는다.
2. 공식 검색 결과 HTML을 POST로 가져온다.
3. 검색 결과에서 기사 링크, 왕별 분류, 요약을 파싱한다.
4. `--king`, `--year`가 있으면 결과를 좁힌다.
5. 선택된 기사 상세 페이지를 열어 국역/원문 excerpt를 붙인다.
6. 최종 결과를 JSON으로 출력한다.
## CLI 예시
### 기본 검색
```bash
python3 scripts/sillok_search.py --query "훈민정음"
```
### 왕 + 연도 필터
```bash
python3 scripts/sillok_search.py --query "훈민정음" --king "세종" --year 1443 --limit 3
```
### 원문 검색
```bash
python3 scripts/sillok_search.py --query "훈민정음" --type w --limit 3
```
## 응답 예시 포맷
```json
{
"query": "훈민정음",
"type": "k",
"filters": {
"king": "세종",
"year": 1443,
"limit": 3
},
"total_results": 21,
"type_count": 11,
"returned_count": 1,
"items": [
{
"article_id": "kda_12512030_002",
"url": "https://sillok.history.go.kr/id/kda_12512030_002",
"title": "세종실록 102권, 세종 25년 12월 30일 경술 2번째기사 / 훈민정음을 창제하다",
"king": "세종",
"gregorian_year": 1443,
"excerpt": "이달에 임금이 친히 언문(諺文) 28자(字)를 지었는데..."
}
]
}
```
## 구현 메모
- v1 은 검색 결과 HTML과 기사 상세 HTML만 읽는다.
- `--year` 는 실록 title metadata의 왕대 연차를 accession year와 합쳐 서기 연도로 변환한 뒤 필터링한다.
- `--king``세종`, `세종실록` 같은 입력을 canonical king label로 정규화한다.
- 결과가 적으면 detail page를 바로 열어 excerpt를 붙이는 편이 가장 단순하고 안정적이다.
## 라이브 검증 메모
2026-04-03 기준 live smoke run에서 `훈민정음` 검색으로 실결과가 반환되었고, `--king 세종 --year 1443` 필터로 `kda_12512030_002` 기사(「훈민정음을 창제하다」)를 안정적으로 다시 찾을 수 있었다.

75
docs/features/k-dart.md Normal file
View file

@ -0,0 +1,75 @@
# 금감원 DART 전자공시 조회 가이드
## 이 기능으로 할 수 있는 일
- 공시검색 (최근 공시 목록, 기업별 공시 이력)
- 기업개황 (대표자, 업종, 주소, 결산월 등)
- 재무제표 (매출액, 영업이익, 당기순이익, 자산/부채/자본 등)
- 배당에 관한 사항
- 증자(감자) 현황
- 자기주식 취득 및 처분 현황
- 회계감사인의 명칭 및 감사의견
- 직원 현황 (부문별/성별 정규직·계약직 인원)
- 주요사항보고서: 유무상증자 결정, 소송, 해외 상장/상장폐지, 전환사채, 교환사채, 회사분할합병
## 먼저 필요한 것
`API_K_DART` 환경변수에 DART OpenAPI 인증키를 설정해야 한다.
키 발급: <https://opendart.fss.or.kr/uss/umt/EgovMberInsertView.do>
## 추천 조회 순서
1. `corpCode.xml` ZIP을 다운로드해 회사명 또는 종목코드로 `corp_code`(8자리 고유번호)를 찾는다.
2. `corp_code`로 기업개황, 재무제표, 감사의견 등 원하는 API를 호출한다.
3. 주요사항보고서는 날짜 범위(`bgn_de`, `end_de`)가 필요하다.
## 검색 예시
공시검색:
```bash
curl -fsS --get 'https://opendart.fss.or.kr/api/list.json' \
--data-urlencode "crtfc_key=$API_K_DART" \
--data-urlencode 'corp_code=00126380' \
--data-urlencode 'bgn_de=20260101' \
--data-urlencode 'end_de=20260419' \
--data-urlencode 'page_count=5'
```
재무제표 (연결, 사업보고서):
```bash
curl -fsS --get 'https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json' \
--data-urlencode "crtfc_key=$API_K_DART" \
--data-urlencode 'corp_code=00126380' \
--data-urlencode 'bsns_year=2024' \
--data-urlencode 'reprt_code=11011' \
--data-urlencode 'fs_div=CFS'
```
## 응답 해석 팁
- `status: "000"`이 정상. 그 외는 에러 (010=미등록 키, 011=사용 불가 키, 012=접근 불가 IP, 013=조회된 데이터 없음, 020=요청 제한 초과, 100=필드 오류).
- 재무제표의 `thstrm_amount`=당기, `frmtrm_amount`=전기, `bfefrmtrm_amount`=전전기.
- `reprt_code`: 11011(사업보고서), 11012(반기), 11013(1분기), 11014(3분기).
- `fs_div`: CFS(연결), OFS(개별).
## 답변 템플릿 권장
- 회사명 / 시장 / 종목코드
- 회계연도 / 보고서 종류
- 핵심 재무 수치 (매출, 영업이익, 순이익, 자산/부채/자본)
- 마지막 한 줄: `금감원 DART 공시 데이터 기준이며 투자 조언은 아닙니다.`
## 에러/제약
- `API_K_DART` 미설정 시 API 호출 불가 → 키 발급 안내
- DART API 요청 한도: 공식 가이드(020 메시지) 기준 "일반적으로 20,000건 이상의 요청"에 대해 020 (요청 제한 초과)이 발생하며, 키별로 별도 한도가 설정된 경우 다른 임계치에서도 동일 코드가 반환될 수 있음. 분당 throttle 등 세부 수치는 공개 가이드에 명시되지 않음. 본인 키의 정확한 사용 현황은 로그인 후 OpenDART 사이트의 [오픈API 이용현황](https://opendart.fss.or.kr/mng/apiUsageStatusView.do) 페이지에서 확인 가능.
- 상장폐지·오래된 비상장 법인은 데이터가 없을 수 있음
- DART OpenAPI `list.json` 의 공식 요청 파라미터 표에 `corp_name` 은 존재하지 않는다. 회사명 기준으로 좁히려면 `corpCode.xml` ZIP을 받아 `corp_code`(8자리 고유번호)를 먼저 확보한 뒤 `corp_code` 로 호출한다. `corp_code` 자체는 선택사항(공식 가이드: N)이지만, 공식 spec 기준 미지정 시 `bgn_de`~`end_de` 검색 기간이 3개월 이내로 제한된다.
## 참고 링크
- 공식 DART OpenAPI: <https://opendart.fss.or.kr/intro/main.do>
- API 목록: <https://opendart.fss.or.kr/intro/infoApiList.do>

View file

@ -0,0 +1,61 @@
# 학교 급식 식단 조회 가이드
## 이 기능으로 할 수 있는 일
- 시도교육청 이름(자연어) + 학교 이름으로 학교 코드 조회
- 특정 일자 급식 식단(조·중·석) 조회
- 나이스(NEIS) Open API 인증키는 프록시 서버(`KEDU_INFO_KEY`)에서만 관리
## 가장 중요한 규칙
1. 클라이언트는 **`KEDU_INFO_KEY`를 들고 있지 않는다.** `k-skill-proxy`만 upstream `KEY`를 붙인다.
2. 학교 식별은 **하드코딩 금지**. 반드시 **`/v1/neis/school-search``/v1/neis/school-meal`** 순서로 조합한다.
## 먼저 필요한 것
- 인터넷 연결
- 프록시 base URL (기본: `https://k-skill-proxy.nomadamas.org`)
## 기본 조회 흐름
### 1) 학교 검색
```bash
curl -fsS --get 'https://k-skill-proxy.nomadamas.org/v1/neis/school-search' \
--data-urlencode 'educationOffice=서울특별시교육청' \
--data-urlencode 'schoolName=미래초등학교'
```
응답에 `resolved_education_office``schoolInfo` 블록이 붙는다. `row`에서 `ATPT_OFCDC_SC_CODE`, `SD_SCHUL_CODE`, `SCHUL_NM`, 주소 필드를 확인한다.
### 2) 급식 조회
```bash
curl -fsS --get 'https://k-skill-proxy.nomadamas.org/v1/neis/school-meal' \
--data-urlencode 'educationOfficeCode=B10' \
--data-urlencode 'schoolCode=7010123' \
--data-urlencode 'mealDate=20260410'
```
`educationOfficeCode` / `schoolCode`는 1단계 검색 결과에서 가져온다.
선택: `mealKindCode=1` (조식), `2` (중식), `3` (석식).
## 파라미터 요약
| 단계 | 주요 쿼리 |
| --- | --- |
| school-search | `educationOffice`, `schoolName` (별칭: `office`, `school`, …) |
| school-meal | `educationOfficeCode`, `schoolCode`, `mealDate` (`YYYYMMDD` 또는 `YYYY-MM-DD`) |
## 자주 보는 필드 (급식)
- `MLSV_YMD`: 급식일
- `MMEAL_SC_NM` / `MMEAL_SC_CODE`: 끼니 구분
- `DDISH_NM`: 메뉴(HTML `<br/>` 구분이 많음)
- `CAL_INFO`, `NTR_INFO`: 칼로리·영양 정보(있는 경우)
## 참고 링크
- 나이스 교육정보 개방 포털: `https://open.neis.go.kr/`
- 프록시 구현·엔드포인트 목록: [k-skill 프록시 서버 가이드](k-skill-proxy.md)

Some files were not shown because too many files have changed in this diff Show more