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.
* 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>
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.
사업자등록번호로 "이 사업자 실제 문제 없나"를 무료 공공 데이터로 교차 조회하는
스킬군을 기여한다. 점수·등급·"위험" 라벨 없이 사실+출처+조회시각만 병렬한다.
단품 스킬:
- 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>
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
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
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(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>
라이브 불변식 검증(예비 제외·dedup·useDt 범위)이 통과한 뒤,
사용자 대면 출력 함수인 print_text가 테스트 0개였던 갭을 메운다.
- PrintTextTest 2개: 결과 렌더링 / 빈 결과 메시지
- stub_fetch는 fetch_one의 미사용 인자를 **_ 로 흡수해 lint 경고 제거
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
월별예약조회 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>
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
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.
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
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.
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
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
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
로컬 개발 환경에서 생성되는 .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
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
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.
- 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
- 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
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.
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.
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.
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>
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.
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
* 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.
* 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
* 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.
* 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>
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가 잡아준다.
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 커밋에서 같이 제거.
* 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>
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.
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.
* 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>
- 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.
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)
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.
* 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>
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.
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>
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.
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.
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.
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.
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.
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.
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.
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.
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.
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 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.
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.
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.
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>
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 불필요.
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.
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
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.
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.
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
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
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
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.
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.
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.
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>
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.
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.
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
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.
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.
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>
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>
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.
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
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
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>
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
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.
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.
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.
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.
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.
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.
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>
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.
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.
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.
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.
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
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
* 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>
* 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>
- 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>
* 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).
* 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>
* 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>
* 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>
* 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>
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.
* 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.
* 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>
* 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>
* 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>
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.
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>
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
* 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>
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>
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>
* 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
* 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>
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
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
* 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
393 changed files with 48679 additions and 5018 deletions
Add the initial `court-auction-notice-search` package and matching skill. Browses 대법원경매정보(`courtauction.go.kr`) 부동산 매각공고 by 매각기일·법원·기일/기간 입찰, expands each notice into 사건번호·용도·주소·감정평가액·최저매각가, and looks up an auction case directly by 법원+사건번호. Direct HTTP transport with optional Playwright fallback, conservative ≥2s throttle and 10-call session budget, and an immediate `BLOCKED` throw when the site returns `data.ipcheck === false`.
Add `/v1/lh-notice/search` and `/v1/lh-notice/detail` routes plus matching `lh-notice-search` skill. Proxies the official LH 청약 (Korea Land & Housing Corporation lease/subscription) notice API on `apis.data.go.kr/B552555/lhLeaseNoticeInfo1/*`, reuses the existing `DATA_GO_KR_API_KEY`, and keeps the user-facing credential surface empty ("불필요"). Handles the LH-specific `[CMN, dsList]` JSON envelope plus the standard data.go.kr XML auth-error envelope, does not cache upstream failures, and exposes `lhNoticeConfigured` on `/health`. Closes #145.
Add `/v1/naver-news/search` route plus matching `naver-news-search` skill. Proxies the official Naver Search Open API news endpoint (`openapi.naver.com/v1/search/news.json`), reuses the existing `NAVER_SEARCH_CLIENT_ID`/`NAVER_SEARCH_CLIENT_SECRET` credentials, and keeps the user-facing credential surface empty ("불필요"). Strips `<b>` highlight tags and decodes HTML entities in titles/descriptions, parses RFC822 `pubDate` into ISO-8601, deduplicates results by canonicalized `link` (query-param order, trailing slash, host casing and fragments are ignored; different paths or query values are preserved), caches successes for 5 minutes (failures are not cached), and exposes `naverNewsApiConfigured` on `/health`. The route rejects `start + display - 1 > 1000` with a `400 bad_request` preflight before calling upstream, so requests outside Naver's 1000-item search window fail fast with a clear message instead of returning empty results. Closes #143.
Introduce the initial `k-skill-rhwp` Node CLI + library that wraps `@rhwp/core` WASM editing bindings as subcommands (`info`, `list-paragraphs`, `search`, `insert-text`, `delete-text`, `replace-all`, `create-table`, `set-cell-text`, `create-blank`, `render`). This is the editing engine backing the new `rhwp-edit` skill and is the counterpart to the existing `hwp` (kordoc, read/convert) and the new `rhwp-advanced` (upstream rhwp Rust CLI) skills. Case-insensitive `replace-all` rejects inputs whose case folding changes UTF-16 length (e.g. Turkish `İ` U+0130) with exit code 1 instead of silently drifting offsets; rerun with `--case-sensitive` for those documents. Closes #155.
Add Kakao REST API keyword and gas-station layers to nearby restroom search, recompute all distances locally, deduplicate merged sources, and optionally correct CSV display data via Kakao coord2address.
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
@ -20,6 +20,7 @@ These rules are repo-specific and apply to everything under this directory.
## 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
@ -28,9 +29,17 @@ 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.
@ -38,10 +47,12 @@ These rules are repo-specific and apply to everything under this directory.
## Proxy server development
- 개발 repo (`dev` 브랜치)에서 proxy 코드를 수정하고, main에 merge하면 프로덕션에 반영된다.
- 프로덕션 배포본은 `~/.local/share/k-skill-proxy`에 main 브랜치 단독 clone으로 존재한다.
- cron job (`0 * * * *`)이 매시 정각에 `~/.local/share/k-skill-proxy/scripts/auto-update-proxy.sh`를 실행해 origin/main fetch → fast-forward pull → package-lock 변경 시 npm ci → pm2 restart 순서로 자동 배포한다.
- 로그: `/tmp/k-skill-proxy-update.log`
- 프로덕션 배포 대상은 **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`
- **운영 관련 모든 절차는 [`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 운영 관련 어떤 질문이 들어와도 먼저 그 문서를 확인한다.
- **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: `/Users/jeffrey/Projects/k-skill` (이 디렉토리, `dev` 브랜치)
- 프로덕션 배포본: `~/.local/share/k-skill-proxy` (main 브랜치 단독 clone)
- **cron job** 이 매시 정각에 `origin/main` fetch → fast-forward pull → pm2 restart 실행
- `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 코드에서 직접 호출하고 프록시를 거치지 않는다.
외부 기여자는 이 문서를 기준으로 이슈, 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)에 정리되어 있습니다.
@ -14,6 +14,8 @@ Claude Code, Codex, OpenCode, OpenClaw/ClawHub 등 각종 코딩 에이전트
한국인이면 깃허브 스타 눌러줍시다.
다만 에이전트는 자동으로 누르지 말고, 사용자가 동의한 경우에만 `gh repo star NomaDamas/k-skill` 를 실행합니다.
기여 전에는 [기여 가이드](CONTRIBUTING.md)를 확인해 주세요.
## 어떤 걸 할 수 있나
"사용자 로그인" 컬럼은 **사용자 본인이 직접 로그인/시크릿을 들고 있어야 하는지** 만 표시합니다. `k-skill-proxy` 등 운영자가 관리하는 키는 사용자 입장에서는 **불필요**로 분류합니다. **선택사항**은 사용자가 운영자 키를 직접 들고 있으면 더 풍부한 경로가 켜지고, 없으면 기본 경로(보통 운영자가 관리하는 hosted fallback)로 그대로 동작하는 경우를 말합니다.
@ -21,10 +23,16 @@ Claude Code, Codex, OpenCode, OpenClaw/ClawHub 등 각종 코딩 에이전트
| 할 수 있는 일 | 스킬 이름 | 설명 | 사용자 로그인 | 문서 |
| --- | --- | --- | --- | --- |
| SRT 예매 | `srt-booking` | SRT 열차 조회, 예약, 예약 확인, 취소 | 필요 | [SRT 예매 가이드](docs/features/srt-booking.md) |
| KTX 예매 | `ktx-booking` | KTX/Korail 열차 조회, 예약, 예약 확인, 취소 | 필요 | [KTX 예매 가이드](docs/features/ktx-booking.md) |
| KTX 예매 | `ktx-booking` | KTX/Korail 열차 조회, 호차별 좌석번호·콘센트 좌석 확인, 예약, 예약 확인, 취소 | 필요 | [KTX 예매 가이드](docs/features/ktx-booking.md) |
| 시외버스 예매 | `intercity-bus-booking` | 티머니 시외버스 배차·좌석·요금 조회와 결제 직전 handoff(결제는 수동) | 불필요 | [시외버스 예매 가이드](docs/features/intercity-bus-booking.md) |
| 자연휴양림 빈 객실 조회 | `foresttrip-vacancy` | 공식 숲나들e 자연휴양림 예약 가능 객실 조회 자동화 (예약/결제 제외) | 필요 | [자연휴양림 빈 객실 조회 가이드](docs/features/foresttrip-vacancy.md) |
| 카카오톡 Mac CLI | `kakaotalk-mac` | macOS에서 카카오톡 대화 조회, 검색, 메시지 전송 | 불필요 | [카카오톡 Mac CLI 가이드](docs/features/kakaotalk-mac.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) |
@ -32,12 +40,24 @@ Claude Code, Codex, OpenCode, OpenClaw/ClawHub 등 각종 코딩 에이전트
| 한강 수위 정보 조회 | `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) |
| 법인등기 신청 컨설팅 | `corporate-registration-consulting` | 일반 영리 주식회사 발기설립 기준으로 법인명·이사·주소 등 사용자 결정사항을 받아 표준 정관, 설립등기 첨부서류, 등록면허세·과밀억제권역 중과 체크, rhwp 기반 HWP 양식 순차 작성 흐름을 참고용으로 안내 | 불필요 | [법인등기 신청 컨설팅 가이드](docs/features/corporate-registration-consulting.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) |
@ -46,48 +66,69 @@ Claude Code, Codex, OpenCode, OpenClaw/ClawHub 등 각종 코딩 에이전트
| 식품 안전 체크 | `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) |
| ~~근처 블루리본 맛집~~ ⚠️ 지원 중단 | ~~`blue-ribbon-nearby`~~ | ~~현재 위치 기준 근처 블루리본 선정 맛집 조회~~ | ~~불필요~~ | ~~[근처 블루리본 맛집 가이드](docs/features/blue-ribbon-nearby.md)~~ |
| 근처 술집 조회 | `kakao-bar-nearby` | 현재 위치 기준 영업 상태·메뉴·좌석·전화번호가 포함된 근처 술집 조회 | 불필요 | [근처 술집 조회 가이드](docs/features/kakao-bar-nearby.md) |
| 우편번호 검색 | `zipcode-search` | 주소 키워드로 우편번호 + 공식 영문주소 조회 | 불필요 | [우편번호 검색 가이드](docs/features/zipcode-search.md) |
| 다이소 상품 조회 | `daiso-product-search` | 다이소 매장별 상품 재고 확인 | 불필요 | [다이소 상품 조회 가이드](docs/features/daiso-product-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) |
| 쿠팡 상품 검색 | `coupang-product-search` | 쿠팡 상품 검색, 로켓배송 필터, 가격대 검색, 비교, 베스트, 골드박스 특가 조회 | 선택사항 (운영 키 있으면 로컬 HMAC 경로, 없으면 hosted fallback) | [쿠팡 상품 검색 가이드](docs/features/coupang-product-search.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) |
description: 사업자등록번호 하나로 "이 사업자, 실제 문제 없나"를 확인한다 — 국세청 사업자등록 상태·국민연금 가입 사업장·국세 체납 명단·금융위 법인개요·조달청 부정당제재·지방행정 인허가 영업상태를 무료 공공 데이터로 교차 조회해 사실만 병렬하는 실사 리포트(점수·등급·위험 판정 없음).
license: MIT
metadata:
category: business
locale: ko-KR
phase: v1
---
# 사업자 실사 복합 조회 (biz-health-check)
## What this skill does
사업자등록번호(+상호/지역)를 입력하면 무료 공공 데이터 6종을 한 번에 교차 조회해 실사 리포트 한 장을 만든다. 같은 레포의 단품 스킬 helper를 그대로 재사용한다(단일 진실원천).
| 섹션 | 데이터 | 단품 스킬 | 경로 |
|---|---|---|---|
| 국세청 상태 | 계속/휴업/폐업·과세유형 | `nts-business-registration` | proxy |
description: 법인등기소/인터넷등기소 상업등기 신청을 처음 하는 사용자를 위해 주식회사 설립등기 절차, 정관·첨부서류 템플릿, 등록면허세·과밀억제권역 중과 체크, rhwp 기반 HWP 양식 작성 흐름을 참고용으로 안내한다.
description: 법인등기소/인터넷등기소 상업등기 신청을 처음 하는 사용자를 위해 일반 영리 주식회사 발기설립 절차, 정관·첨부서류 실제 HWP 양식 작성, 등록면허세·과밀억제권역 중과 체크, rhwp 기반 순차 검토 흐름을 참고용으로 안내한다.
license: MIT
metadata:
category: legal-documents
locale: ko-KR
phase: v1
---
# 법인등기 신청 컨설팅
@ -27,23 +26,16 @@ metadata:
1. **사용자 결정 사항만 묻고 나머지는 에이전트가 처리한다.** 법인명, 본점 주소, 목적, 자본금, 1주의 금액, 발기인/주주, 이사/감사, 공고방법, 결산기, 주금납입 은행, 제출 방식처럼 사용자가 결정해야 하는 값만 확인한다.
2. **쉬운 말로 설명한다.** “발기인=처음 회사를 세우는 사람”, “정관=회사 기본 규칙”, “등록면허세=등기 전에 내는 지방세”처럼 어려운 말을 풀어쓴다.
3. **최신 확인이 필요한 법령·세율은 공식 출처를 다시 확인한다.** 법령은 국가법령정보센터(law.go.kr), 신청 절차는 인터넷등기소(iros.go.kr) 또는 온라인법인설립시스템(startbiz.go.kr), 지방세는 위택스(wetax.go.kr)·관할 지자체를 우선한다.
4. **표준 정관은 ‘보수적 기본안’으로 낸다.** 등기소에서 거부되지 않는 표현을 목표로 하되, 실제 수리 보장을 하지 않는다. 특수한 우선주, 스톡옵션, 종류주식, 전환사채, 외국인투자, 인허가 업종은 전문가 검토로 보낸다.
5. **HWP 편집은 기존 rhwp 계열 스킬을 재사용한다.** 문서 생성/편집은 [`rhwp-edit`](../rhwp-edit/SKILL.md)의 `k-skill-rhwp`, HWP/HWPX 조회·필드 추출은 [`hwp`](../hwp/SKILL.md), 레이아웃 디버깅은 [`rhwp-advanced`](../rhwp-advanced/SKILL.md)를 사용한다.
6. **사람만 할 수 있는 최종 행위를 대신하지 않는다.** 에이전트는 초안·체크리스트·자리표시자 치환까지만 돕고, 인터넷등기소/위택스 로그인, 전자서명, 세금 납부, 등기 제출, 사용자 사칭, 최종 법률 판단, 최종 세무 판단은 수행하지 않는다.
7. **개인정보는 최소화한다.** 초안 단계에서는 가능한 한 `{{OFFICER_NAME}}`, `{{OFFICER_ADDRESS}}` 같은 자리표시자를 쓰고, 실제 생성 직전에 필요한 필드만 받는다. 주민등록번호 원문, 신분증 이미지, 인감증명서 스캔본은 꼭 필요한 경우가 아니면 요구하지 않으며, 요약·로그·테스트·PR에는 이름/주소/생년월일 등 개인정보를 마스킹한다. 채워진 산출물은 로컬에만 두고 레포에 커밋하지 말라고 안내한다.
## 전문가/공식 확인 필요 또는 v1 범위 밖
아래 사실이 하나라도 있으면 표준 초안 흐름을 계속 진행하지 말고, “이 사안은 v1 기본 설립등기 범위를 벗어나므로 관할 등기소·법무사·변호사·세무사 확인 후 문구를 확정해야 합니다”라고 먼저 안내한다.
- 발기설립이 아닌 **모집설립**.
- 현금 납입만이 아닌 **현물출자**, 재산인수, 사후설립 등 **변태설립사항**.
- **자본금 10억** 원 이상이거나 감사/이사회/기관 설계가 일반 소규모 주식회사와 다른 경우.
- **외국인** 임원·주주, 외국법인 주주, 외국인투자신고·아포스티유·번역공증이 필요한 경우.
- 금융, 의료, 교육, 여행, 통신판매 외 별도 허가·등록·신고가 선행되는 **인허가 업종**.
- 종류주식, 스톡옵션, 전환사채, 투자계약, 주주간계약처럼 표준 정관 밖의 권리 설계가 있는 경우.
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에는 이름/주소/생년월일 등 개인정보를 마스킹한다. 채워진 산출물은 로컬에만 두고 레포에 커밋하지 말라고 안내한다.
## 먼저 묻는 최소 정보
@ -53,10 +45,10 @@ metadata:
| --- | --- | --- |
| 법인명/상호 | 회사 이름. 같은 특별시·광역시·시·군 안의 같은 업종·같은 상호는 문제가 될 수 있어 인터넷등기소 상호검색을 먼저 한다. | `주식회사 {{COMPANY_NAME}}` |
| 본점 주소 | 회사 주소. 과밀억제권역/대도시 중과 판단의 핵심이다. | 임대차계약서 주소 그대로 |
| 사업 목적 | 등기부에 올라가는 업무 범위. 너무 넓거나 불명확하면 보정될 수 있다. | 소프트웨어 개발 및 공급업, 정보통신업, 전자상거래업 등 필요한 목적만 |
| 사업 목적/업태·종목 | 등기부와 정관 제2조 앞부분에 들어가는 실제 수행 사업. 너무 넓거나 불명확하면 보정될 수 있고, 업태·종목은 사업자등록·세금 검토에도 이어진다. | 소프트웨어 개발 및 공급업, 정보통신업, 전자상거래업 등 실제 할 목적만 |
| 자본금·1주의 금액 | 초기 자금과 주식 한 장 가격. 등록면허세 계산에도 쓰인다. | 자본금 1,000,000원 / 1주 100원 또는 500원 |
| 발기인/주주 | 회사를 세우고 주식을 인수하는 사람. | 대표 1인 설립 가능 |
| 이사/대표이사/감사 | 등기 이사와 대표자. 소규모 회사는 감사 생략 가능 여부를 검토한다. | 1인 이사 회사 기본안 |
| 발기인/주주 실제 정보 | 회사를 세우고 주식을 인수하는 사람. 제출용에는 성명, 주소, 생년월일/주민등록번호 필요 여부, 인수 주식 수, 날인 방식이 필요하다. | 대표 1인 설립 가능 |
| 이사/대표이사/감사 실제 정보 | 등기 이사와 대표자. 제출용 취임승낙서에는 성명, 주소, 생년월일, 취임일, 개인 인감/서명 방식이 필요하다. 소규모 회사는 감사 생략 가능 여부를 검토한다. | 1인 이사 회사 기본안 |
| 공고방법 | 회사 공고를 어디에 낼지. | 회사 홈페이지, 없으면 일간신문 |
| 결산기 | 회계연도 종료일. | 매년 12월 31일 |
| 주금납입 증빙 | 자본금이 입금됐다는 은행 잔고증명/거래내역. | 대표/발기인 명의 계좌 잔고증명 |
@ -66,15 +58,54 @@ metadata:
1. **상호·본점·목적 결정**: 인터넷등기소 상호검색으로 같은 관할 내 충돌 가능성을 확인한다.
2. **과밀억제권역/대도시 세금 체크**: 본점 주소가 수도권 과밀억제권역 등 중과 대상인지 먼저 본다. 대도시 법인 설립은 등록면허세가 중과될 수 있다.
4. **발기인 결정서·주식인수·주금납입**: 발기인이 주식을 인수하고 자본금을 입금한 뒤 잔고증명서 또는 주금납입보관증명에 준하는 증빙을 준비한다.
5. **임원 취임승낙서·인감·주민등록/주소 증빙 준비**: 이사·대표이사·감사가 취임을 승낙했다는 서류와 인감 관련 서류를 준비한다. 주민등록번호·신분증·인감증명 같은 민감정보는 원문을 대화나 로그에 남기지 말고 제출 직전 로컬 문서에만 반영한다.
6. **조사보고서/이사회·발기인 의사록 작성**: 현물출자 등 특수 사정이 없더라도 설립 경과를 확인하는 문서를 준비한다. 1인 회사면 결정을 단순화한다.
7. **등록면허세 신고·납부 준비**: 위택스 또는 관할 지자체 납부 화면에 넣을 금액·근거·체크리스트를 정리한다. 사용자/전문가가 실제 신고와 세금 납부를 직접 수행하고 영수필확인서를 확보한다.
8. **등기신청서 작성·첨부서류 묶기**: `templates/incorporation-document-pack.md`의 순서대로 신청서, 정관, 취임승낙서, 조사보고서, 주금납입 증빙, 인감신고서, 세금 영수증을 점검한다.
9. **인터넷등기소/관할 등기소 제출 준비**: 전자신청이면 인증서·전자서명·스캔본 품질 체크리스트를 만들고, 방문이면 원본/사본과 도장 확인 목록을 만든다. 사용자/전문가가 실제 로그인, 전자서명, 등기 제출 절차를 직접 완료한다.
10. **보정 대응**: 등기소가 보정명령을 내리면 문구·첨부서류·세금 계산을 수정한다. 보정 사유를 쉬운 말로 풀고 다음 조치만 제시한다.
11. **등기 완료 후 후속 작업**: 법인등기사항증명서, 법인인감증명서, 사업자등록, 4대보험, 은행 법인계좌, 통신판매업/소프트웨어사업자 신고 등 후속 일정을 안내한다.
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/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/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억 원 이상 또는 소규모회사 특례를 벗어나는 경우 | 정관 공증, 의사록 인증, 감사/이사회 구성 등 추가 요건 | 저장된 표준 양식은 유지하되 공증·인증 필요 여부를 제출 전 체크 |
## 등록면허세·중과·소프트웨어 업종 체크
@ -83,46 +114,15 @@ metadata:
- **소프트웨어 업종**: 소프트웨어 개발·공급, 정보통신업은 창업중소기업 세액감면(조세특례제한법 제6조) 또는 대도시 중과 제외 업종 검토 대상이 될 수 있다. 단, 감면·제외는 업종코드, 실제 사업내용, 창업 요건, 이전/합병/개인사업 전환 여부에 따라 달라지므로 세무사/지자체 확인 전에는 확정 표현을 쓰지 않는다.
- **응답 방식**: 세금은 “예상 체크리스트”로 안내하고, 최종 금액은 위택스/관할 시군구 계산 결과를 기준으로 한다.
## 문서 자동화 흐름
1. 사용자 입력을 JSON으로 정규화한다. 공유용 예시는 개인정보를 마스킹하고, 실제 문서 생성 전까지는 주소·생년월일·인감 관련 값 대신 자리표시자를 유지한다.
```json
{
"company_name": "예시소프트 주식회사",
"head_office_address": "서울특별시 ...",
"purposes": ["소프트웨어 개발 및 공급업", "정보통신업"],
"capital_krw": 1000000,
"par_value_krw": 100,
"founders": [{ "name": "홍길동", "shares": 10000 }],
"directors": [{ "name": "홍길동", "role": "대표이사" }],
"fiscal_year_end": "12월 31일"
}
```
2. `templates/standard-articles-of-incorporation.md`와 `templates/incorporation-document-pack.md`의 `{{PLACEHOLDER}}`를 채워 Markdown 초안을 만든다.
3. HWPX가 필요하면 `hwp` 스킬의 kordoc `markdownToHwpx` 경로를 사용한다.
4. 기존 HWP 양식을 편집해야 하면 `rhwp-edit` 스킬의 `k-skill-rhwp replace-all` 또는 `set-cell-text`로 자리표시자를 채운다. `replace-all`은 본문 문단 자리표시자 치환에만 우선 사용하고, 표/셀 안의 입력란은 `k-skill-rhwp info`로 구조를 확인한 뒤 `set-cell-text` 같은 셀 인식 명령으로 채운다. 법원·등기소 공식 HWP 양식은 표가 많으므로 제출 전에는 반드시 사람이 열어 레이아웃과 누락 셀을 검토한다.
5. 채워진 HWP에는 이름·주소·생년월일 같은 개인정보가 들어갈 수 있으므로 레포 밖의 비공개 임시 디렉터리나 사용자가 지정한 안전한 로컬 폴더에만 저장한다. 예시는 `mktemp -d`와 `chmod 700`으로 저장소 외부 작업 디렉터리를 만든 뒤 사용한다.
k-skill-rhwp info "$workdir/court-form-filled.hwp"
```
6. 생성 직후 `k-skill-rhwp info "$workdir/court-form-filled.hwp"`로 round-trip 여부를 확인하고, 사용자가 제출 전 읽을 수 있도록 쉬운 말 요약본을 함께 만든다. 채워진 산출물 경로는 PR·테스트 로그·공유 요약에 노출하지 않는다.
## 응답 끝에 항상 붙일 문구
> 이 안내와 문서 초안은 참고용이며 법률·세무 자문이 아닙니다. 실제 등기 제출 전 관할 등기소, 위택스/지자체 세무부서, 법무사·변호사·세무사 확인을 권합니다.
"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.",
> 이 파일은 스킬에 **이미 저장된 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의 주요 입력 셀 매핑.
- 확인 포인트: 생활법령 페이지는 인터넷등기소에 등기신청서·첨부서류 양식 및 작성방식이 있다고 안내하고, 첨부서면은 등기예규 제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. 상장회사 표준정관이 아니라 비상장/스타트업 발기설립 정관 참고용으로 사용한다.
| 주식회사설립등기신청서(발기설립) | 인터넷등기소 등기신청양식, 등기예규 양식 제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` 세금 확인 메모 | 필수 발급/첨부. 최종 세액·납부번호는 위택스/지자체 결과 기준 |
- “이 레포의 공개 배포 HWP 묶음과 Markdown 템플릿은 작성 보조자료이며, 최신 공식 양식·관할 등기소 요구를 대체하지 않습니다.”
## 저장 양식 기반 작성 흐름
1. 레포 밖 비공개 작업 디렉터리를 만든다.
2. 위 표의 저장된 HWP 양식을 작업 디렉터리로 복사한다.
3. 사용자 입력 JSON을 만든다. 주민등록번호 원문은 마스킹하거나 제출 직전 로컬 파일에만 둔다.
4. `scripts/fill_official_hwp.py`로 번들 [양식 제65-1호] HWP에 주요 셀을 채운다.
5. 첨부서류는 저장된 `templates/attachment-hwp/*.hwp` 사본을 한 장씩 확인하며 채운다. 단순 replace-all은 shortcut이므로 모든 양식을 순차 확인하고, 정관·의사록·위임장 등 간인 대상 가능 문서와 법인인감 준비 여부를 별도 체크한다.
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`의 셀 매핑을 사용한다.
- 공식 양식은 표와 칸이 많으므로 자동 치환 후 반드시 사람이 한컴오피스/호환 뷰어로 열어 누락 셀, 줄바꿈, 날인란, 첨부서류 목록을 확인한다.
> 참고용 표준 정관 초안입니다. 법률 자문이 아니며, 제출 전 전문가 검토가 필요합니다.
## 제1장 총칙
### 제1조(상호)
이 회사는 {{COMPANY_NAME}}라 한다. 영문으로는 {{COMPANY_NAME_EN}}라 표기한다.
### 제2조(목적)
이 회사는 다음 사업을 영위함을 목적으로 한다.
1. {{BUSINESS_PURPOSE_1}}
2. {{BUSINESS_PURPOSE_2}}
3. 위 각 호에 부대하거나 관련되는 일체의 사업
### 제3조(본점 소재지)
이 회사의 본점은 {{HEAD_OFFICE_CITY}}에 둔다.
### 제4조(공고방법)
이 회사의 공고는 {{PUBLIC_NOTICE_METHOD}}에 게재한다.
## 제2장 주식
### 제5조(발행예정주식총수)
이 회사가 발행할 주식의 총수는 {{AUTHORIZED_SHARES}}주로 한다.
### 제6조(1주의 금액)
이 회사가 발행하는 주식 1주의 금액은 금 {{PAR_VALUE_KRW}}원으로 한다.
### 제7조(설립 시 발행주식)
이 회사가 설립 시 발행하는 주식의 총수는 {{INCORPORATION_SHARES}}주로 한다.
### 제8조(주식의 종류)
이 회사가 발행하는 주식은 보통주식으로 한다. 종류주식, 전환주식, 상환주식은 별도 전문가 검토 후 정한다.
## 제3장 주주총회
### 제9조(소집)
정기주주총회는 매 사업연도 종료 후 3개월 이내에 소집하고, 임시주주총회는 필요에 따라 소집한다.
### 제10조(의결권)
주주의 의결권은 1주마다 1개로 한다.
## 제4장 임원
### 제11조(이사)
이 회사의 이사는 {{DIRECTOR_COUNT}}명 이상으로 한다.
### 제12조(대표이사)
대표이사 선정 방식은 이사 수와 이사회 설치 여부에 따라 다음 중 하나를 선택하고, 제출 전 관할 등기소 또는 전문가 확인을 받는다.
1. 이사가 3명 이상이고 이사회가 있는 경우: 대표이사는 이사회 결의로 선정한다.
2. 이사가 2명인 소규모 회사로 이사회가 없는 경우: 각 이사가 회사를 대표하는 기본 구조인지, 특정 이사 1명을 대표이사로 둘 수 있는지 정관·주주총회 결의·등기 실무를 관할 등기소 또는 전문가에게 확인한 뒤 문구를 확정한다. 이 경우 이사회 결의라는 표현은 쓰지 않는다.
3. 이사가 1명인 경우: 그 이사가 회사를 대표한다.
### 제13조(감사)
감사는 {{AUDITOR_RULE}}. 소규모 회사의 감사 선임 생략 가능 여부는 상법 요건을 별도로 확인한다.
description: Browse 대법원경매정보(courtauction.go.kr) 부동산 매각공고 by 매각기일·법원·기일/기간 입찰, expand each notice into 사건번호·용도·주소·감정평가액·최저매각가, and look up a case directly by 법원+사건번호. Read-only, slow-by-design (~2s/call) to avoid IP blocks. Use when the user asks "오늘 어디서 부동산 경매가 열려?" "이 사건번호 정보 알려줘" or wants 매각공고 데이터를 에이전트가 다룰 수 있는 JSON으로.
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
@ -15,7 +15,7 @@ metadata:
대한민국 법원이 운영하는 공식 **법원경매정보** 사이트(`courtauction.go.kr`) 의 매각공고와 사건정보를 에이전트가 활용할 수 있는 JSON 형태로 변환해서 돌려준다.
- 공식 OPEN API가 없어 사이트 내부의 WebSquare JSON XHR endpoint를 그대로 호출한다.
- 1차 transport 는 직접 HTTP, 차단되거나 5xx 가 떨어질 때만 Playwright fallback 으로 전환한다 (`rebrowser-playwright` 또는 `playwright-core` 가 있을 때만).
- 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 로 보수적으로 동작한다.
- **참고용 도구**다. 실제 입찰 전에는 반드시 법원 원문 매각공고를 다시 확인해야 한다.
@ -26,12 +26,12 @@ metadata:
- "기일입찰 vs 기간입찰만 나눠서 보여줘"
- "이 매각공고 안의 사건번호/용도/주소/감정평가액 다 보여줘"
- "사건번호 2024타경100001 진행 상황 알려줘"
- "서울 강남구 아파트 최저가 5억 이하 유찰 1회 이상 물건 찾아줘"
- "법원사무소 코드 표 줘"
## When not to use
- 동산(자동차·중기) 경매 (이번 v1 범위 밖)
- 자유 조건검색(지역·용도·가격대·면적·유찰횟수) — Workflow C 별도 follow-up 이슈에서 다룬다
- 특정 매각기일 날짜의 모든 법원 일정을 한 번에 (Workflow D 별도 follow-up 이슈)
- 매각물건 사진(전경/개황/내부) URL 노출 (별도 follow-up 이슈)
- 매각물건명세서 / 현황조사서 / 감정평가서 PDF 다운로드 (별도 follow-up 이슈)
@ -39,7 +39,7 @@ metadata:
## Inputs
- `date` — 매각기일 (YYYY-MM-DD 또는 YYYYMMDD). 필수.
- `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` 로 정규화한다.
@ -62,6 +62,7 @@ metadata:
- `POST /pgj/pgj143/selectRletDspslPbanc.on` — 매각공고 목록
- `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을 반환하므로 로컬에서 거부한다.
- 1차로 direct HTTP 시도. Workflow C raw-HTTP WAF의 HTTP 400을 만나면 자동으로 Playwright fallback 으로 재시도한다. fallback 을 끄려면 `{ fallback: false }`. `BLOCKED`(`ipcheck=false`)는 사이트의 명시적 차단 신호이므로 기본적으로 즉시 중단하며, 사용자가 위험을 이해하고 명시적으로 `{ fallbackOnBlocked: true }` 를 준 경우에만 재시도한다.
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 을 두고 새 클라이언트를 열어라.
- 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 메타를 조회한다.
- 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
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.
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`를 먼저 보여주고 선택을 요청한다.
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개 이상 반환하거나, 반환 실패 이유를 설명했다.
- 쇼핑몰별 상품가, 배송비, 실구매가, 카드 할인가, 무이자 문구를 조회 시점 기준으로 정리했다.
| **프록시 경유** | `k-skill-proxy`가 upstream API 키를 보관하고 HTTP로 중계 | `seoul-subway-arrival`, `fine-dust-location` |
| **Python 스크립트** | `scripts/`의 Python 파일 직접 실행 | `korean-spell-check`, `sillok-search` |
@ -156,6 +156,38 @@ upstream API 키를 사용자에게 노출하지 않으려면 `k-skill-proxy`를
---
## 크롤링/검색 스킬을 만들 때: 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로 해결할 수 있는지 먼저 확인한다.
---
## 스킬 등록 & 검증
스킬은 **별도 레지스트리 없이 디렉토리 스캔으로 자동 발견**된다.
@ -204,6 +236,7 @@ npm run ci
- [ ] npm 패키지라면 `packages/`에 구현체와 테스트 추가
- [ ] npm 패키지라면 `.changeset/*.md` 파일 추가 (반드시 **기능 PR에서**, Version Packages PR에서 추가하지 말 것)
# k-skill-proxy 배포 가이드 (Cloud Run + GitHub Actions)
`k-skill-proxy`는 Google Cloud Run에서 운영되고, `main` 브랜치에 머지되면 GitHub Actions가 자동으로 재배포합니다.
이 문서는 그 자동 배포 파이프라인의 **1회성 셋업 절차**와 **운영 점검 절차**를 정리합니다. 일반 contributor는 읽지 않아도 되며, 프록시 운영을 담당하는 maintainer(현재 `jeffrey@markr.ai`)가 인프라를 처음 만들거나 수리할 때 참고합니다.
- 런타임 지침은 `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`를 기준으로 한다.
에이전트는 초안·체크리스트·자리표시자 치환만 지원한다. 인터넷등기소/위택스 로그인, 전자서명, 세금 납부, 등기 제출, 사용자 사칭, 최종 법률 판단, 최종 세무 판단은 지원하지 않는다. 이러한 최종 행위와 판단은 사용자가 직접 또는 전문가를 통해 수행해야 한다.
- **외국인** 임원·주주, 외국법인 주주, 외국인투자신고·아포스티유·번역공증이 필요한 경우.
- 금융, 의료, 교육, 여행, 통신판매 외 별도 허가·등록·신고가 선행되는 **인허가 업종**.
- 종류주식, 스톡옵션, 전환사채, 투자계약, 주주간계약처럼 표준 정관 밖의 권리 설계가 있는 경우.
## 면책
## 기본 워크플로우
1. 상호·본점·목적을 정하고 인터넷등기소에서 상호 충돌 가능성을 확인한다.
2. 본점 주소가 과밀억제권역 또는 지방세법상 대도시 중과 검토 대상인지 확인한다.
3. `corporate-registration-consulting/templates/standard-articles-of-incorporation.md`를 채워 정관 초안을 만든다.
4. 발기인, 주식인수, 주금납입 또는 잔고증명을 준비한다.
5. 이사·감사 취임승낙서, 조사보고서, 인감신고서, 주소/신분 관련 증빙을 준비한다.
6. 위택스 또는 지자체 등록면허세·지방교육세 신고·납부 체크리스트를 만들고, 사용자/전문가가 실제 신고·납부를 수행한다.
7. 인터넷등기소 전자신청 또는 관할 등기소 방문 제출 체크리스트를 만들고, 사용자/전문가가 실제 제출 절차를 완료한다.
8. 보정명령이 오면 어려운 표현을 쉬운 말로 풀어 원인·수정 문구·추가서류만 정리한다.
9. 등기 완료 후 사업자등록, 법인계좌, 4대보험, 업종별 신고를 이어서 안내한다.
## 세금 체크 포인트
- **등록면허세**: 지방세법 제28조가 법인 설립등기 때 문제되는 등록면허세 세율의 직접 근거다. 자본금과 소재지에 따라 산정하며 지방교육세가 추가된다.
- **과밀억제권역/대도시 중과**: 본점이 수도권 과밀억제권역 등 대도시에 있으면 지방세법 제28조 제2항의 법인등기 중과가 문제될 수 있다. 지방세법 제13조는 취득세 쟁점이 있을 때만 별도로 확인한다.
- **소프트웨어 업종**: 실제 사업이 소프트웨어 개발·공급 또는 정보통신업이면 지방세법 시행령의 중과 제외 업종, 조세특례제한법 제6조의 창업중소기업 세액감면 가능성을 검토한다. 단, 창업 요건과 업종코드, 이전/합병/개인사업 전환 여부에 따라 달라지므로 확정 안내는 하지 않는다.
## HWP/RHWP 연계
- 새 초안은 Markdown 템플릿을 먼저 채운다.
- HWPX 산출이 필요하면 `hwp` 스킬의 kordoc `markdownToHwpx` 경로를 사용한다.
- 이미 받은 법원/법무사 HWP 양식이 있으면 `rhwp-edit` 스킬의 `k-skill-rhwp replace-all`로 `{{COMPANY_NAME}}`, `{{CEO_NAME}}`, `{{HEAD_OFFICE_ADDRESS}}` 같은 자리표시자를 채운다.
- `replace-all`은 본문 문단 자리표시자 치환용으로 우선 사용한다. 표/셀 안의 입력란은 `k-skill-rhwp info`로 구조를 확인하고 `set-cell-text` 같은 셀 인식 명령으로 채운 뒤, 복잡한 공식 양식은 사람이 열어 레이아웃과 누락 셀을 검토한다.
- 생성 후 `k-skill-rhwp info`로 파일이 열리는지 확인하고, 사람이 읽는 제출 전 체크리스트를 함께 제공한다.
- 채워진 HWP에는 개인정보가 들어갈 수 있으므로 레포 밖의 비공개 임시 디렉터리나 사용자가 지정한 안전한 로컬 폴더에 저장하고, PR·테스트 로그·공유 요약에는 경로와 원문 개인정보를 노출하지 않는다.
- 등록면허세, 과밀억제권역/대도시 중과, 소프트웨어 업종 감면/제외 가능성 안내가 포함됐다.
- 모든 답변 끝에 참고용/비자문 면책이 들어갔다.
이 기능은 참고용이며 법률·세무 자문, 법무사 대행이 아니다. 에이전트는 인터넷등기소/위택스 로그인, 전자서명, 세금 납부, 등기 제출, 사용자 사칭, 최종 법률 판단, 최종 세무 판단을 지원하지 않는다. 실제 제출은 사용자가 직접 수행하고 제출 전 관할 등기소·위택스/지자체·법무사·변호사·세무사 확인을 권한다.
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"}}` |
| 매각공고 목록 | `POST /pgj/pgj143/selectRletDspslPbanc.on` | `{"dma_srchDspslPbanc":{"srchYmd","cortOfcCd","bidDvsCd","srchBtnYn":"Y"}}` (`srchYmd`는 사이트 검색 버튼과 동일하게 `YYYYMM`) |
| 사건 단건 | `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`)는 `GET /pgj/index.on?w2xPath=/pgj/ui/pgj100/PGJ143M01.xml&pgjId=143M01` 으로 사전에 한 번 받아둡니다.
세션 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 합니다.
## 설치
@ -92,5 +108,5 @@ npm install playwright-core
## 관련 이슈
- 이 패키지는 [Issue #167](https://github.com/NomaDamas/k-skill/issues/167) 에서 출발했고, A/B 워크플로 + 코드테이블 MVP만 포함합니다.
- 자유 조건검색·캘린더·물건 사진·PDF·동산 경매는 별도 follow-up 이슈로 분리되어 추적됩니다.
- 이 패키지는 [Issue #167](https://github.com/NomaDamas/k-skill/issues/167) 에서 출발했고, #184에서 Workflow C 자유 조건검색을 추가했습니다.
- 캘린더·물건 사진·PDF·동산 경매는 별도 follow-up 이슈로 분리되어 추적됩니다.
지역명은 당근 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가 바뀌면 실패할 수 있습니다.
지역명은 당근 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가 바뀌면 실패할 수 있습니다.
지역명은 당근 region API로 내부 id를 해석한 뒤 `in=<지역명>-<id>` 형태로 검색 URL에 넣습니다.
```text
합정동 → 서울특별시 마포구 합정동, id=231 → in=합정동-231
```
## 출력 해석
검색 결과는 `title`, `salesType`, `trade`, `area`, `areaPyeong`, `totalManageCost`, `url`을 우선 확인합니다. 부동산 판단에는 실시간 상태, 보증금/월세, 관리비, 면적, 중개/직거래 여부가 중요하므로 원본 URL을 함께 제시합니다.
## 제한사항
- 당근부동산 목록 JSON과 `realty.daangn.com` 상세 HTML 구조 변경에 영향을 받습니다.
목록 항목은 `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`개까지 원문을 읽어 매칭하므로 너무 낮게 잡으면 결과가 누락될 수 있다.
- 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` → 가능한 경우 온라인 재고 참고값 표시
- `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위로 올라올 수 있고, 라벨이 없으면 카드 결제 사용자에게 적용 불가능한 가격을 일반 최저가로 안내하게 됩니다.
`nanumkorea.go.kr`는 1365 자원봉사/기부 통합 안내를 반환하므로, 스킬은 `www.1365.go.kr/dntn/main.do`를 최신 공식 확인 진입점의 기준으로 사용한다. 1365 페이지가 headless HTTP에서 느리거나 빈 응답을 줄 수 있어 화면 스크래핑 대신 best-effort 확인 보조 링크와 후보 공식 홈페이지를 함께 제시하며, 후보별 등록 검증이 이미 완료됐다고 표현하지 않는다.
[`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 를 두지 않습니다.
- 한국 국토교통부 부동산공시가격알리미(`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) 또는 다른 스킬을 사용한다.
이 helper는 쿠키 세션을 시작하고 공식 배차 조회 POST를 수행한 뒤 출발시각, 운수사, 등급, 요금, 잔여/총 좌석을 JSON으로 출력한다. 기본은 read-only이며, `--hold-seat` 또는 `--hold-first-seat`를 주면 좌석/요금 단계에 진입해 `readPcpySats.do`로 임시 좌석 선점을 만들고 공식 카드정보 입력 HTML과 cancel/back 필드를 저장한다. 결제 정보 입력·제출은 수행하지 않는다.
성공 조건은 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`가 포함된 일반 오류 페이지가 반환될 수 있다.
4. Cloud Run service `k-skill-proxy` 재배포 (Secret Manager 시크릿 + 런타임 환경변수 주입)
5. 직접 Cloud Run URL과 `https://k-skill-proxy.nomadamas.org/health` smoke test
따라서 **main에 merge되어야 프로덕션에 반영**된다. dev 브랜치 변경은 프로덕션에 영향 없음.
로그: `/tmp/k-skill-proxy-update.log`
배포 상태와 로그는 GitHub Actions의 "Deploy k-skill-proxy to Cloud Run" 워크플로 실행 페이지와 GCP Console의 Cloud Run revision/log에서 확인한다.
### 초기 설정 (PM2 + cloudflared)
### 초기 셋업 (운영자 1회 수행)
1. `pm2 start ecosystem.config.cjs`
2. `pm2 save`
3. `pm2 startup` 출력대로 launchd 등록
4. Cloudflare Tunnel ingress 에 `k-skill-proxy.nomadamas.org -> http://localhost:4020` 추가
WIF pool/provider, deploy service account, Secret Manager 시크릿 생성 등 1회성 GCP 셋업 절차와 GitHub repository secrets/variables 등록 방법은 [`docs/deploy-k-skill-proxy.md`](../deploy-k-skill-proxy.md)에 정리되어 있다.
- self-host 또는 배포 확인이 끝난 proxy base URL: `KSKILL_PROXY_BASE_URL`
- optional: `KSKILL_PROXY_BASE_URL` (self-host·별도 프록시를 쓸 때만 설정. 비우면 기본 hosted `https://k-skill-proxy.nomadamas.org` 를 사용)
## 필요한 환경변수
- `KSKILL_PROXY_BASE_URL` (필수: self-host 또는 배포 확인이 끝난 proxy base URL)
- 없음. `KSKILL_PROXY_BASE_URL` 은 선택 사항이며, 비우면 기본 hosted `https://k-skill-proxy.nomadamas.org` 를 사용한다.
사용자가 공공데이터포털 기상청 단기예보 API key를 직접 발급할 필요는 없다. 대신 `KSKILL_PROXY_BASE_URL` 은 `/v1/korea-weather/forecast` route가 실제로 배포된 proxy 를 가리켜야 한다. upstream `KMA_OPEN_API_KEY` 는 proxy 서버에서만 관리한다.
사용자가 공공데이터포털 기상청 단기예보 API key를 직접 발급할 필요는 없다. `/v1/korea-weather/forecast` route는 기본 hosted proxy에서 호출하고, upstream `KMA_OPEN_API_KEY` 는 proxy 서버에서만 관리한다. 별도 proxy를 쓰는 경우에만 `KSKILL_PROXY_BASE_URL` 을 설정한다.
## 입력값
@ -26,7 +26,7 @@
## 기본 흐름
1. `KSKILL_PROXY_BASE_URL`로 self-host 또는 배포 확인이 끝난 proxy base URL 을 확인한다.
1. `KSKILL_PROXY_BASE_URL`이 있으면 그 값을 사용하고, 없거나 비어 있으면 기본 hosted proxy `https://k-skill-proxy.nomadamas.org` 를 사용한다.
2. `/v1/korea-weather/forecast` 로 한국 기상청 단기예보를 조회한다.
3. `baseDate` / `baseTime` 을 생략하면 proxy 가 KST 기준 최신 발표 시각을 자동으로 선택한다.
4. 응답의 `item[]` 에서 `TMP`, `SKY`, `PTY`, `POP`, `PCP`, `SNO`, `REH`, `WSD` 를 우선 요약한다.
원본 [`hmmhmmhm/daiso-mcp`](https://github.com/hmmhmmhm/daiso-mcp) 와 npm package [`daiso`](https://www.npmjs.com/package/daiso) 를 사용해 CGV, 메가박스, 롯데시네마의 영화관 검색, 상영작, 시간표, 잔여석 조회를 한다.
## 가장 중요한 규칙
`k-skill` 안에 별도 영화관 수집기를 추가하지 않는다.
기본 경로는 **MCP 서버를 직접 설치하지 않고 CLI로 먼저 확인하는 방식**이다.
1. `npx --yes daiso ...`
2. 필요하면 `git clone https://github.com/hmmhmmhm/daiso-mcp.git && cd daiso-mcp && npm install && npm run build`
3. clone fallback에서는 `node dist/bin.js ...`
## 빠른 확인
날짜가 있는 요청은 Asia/Seoul 기준 `YYYYMMDD` 로 정규화하고 `--playDate <YYYYMMDD>` 를 항상 붙인다. 예를 들어 오늘을 물으면 KST 오늘 날짜를 계산해서 넣는다.
```bash
npx --yes daiso health
npx --yes daiso get /api/cgv/theaters --keyword 강남 --limit 5 --json
npx --yes daiso get /api/cgv/movies --keyword 강남 --playDate <YYYYMMDD> --json
npx --yes daiso get /api/cgv/timetable --keyword 강남 --playDate <YYYYMMDD> --json
npx --yes daiso get /api/megabox/theaters --keyword 코엑스 --limit 5 --json
npx --yes daiso get /api/megabox/movies --keyword 코엑스 --playDate <YYYYMMDD> --json
- ChatGPT·Claude·Gemini 등이 쓴 "AI 티 나는" 한국어 글을 자연스러운 사람 글로 윤문
- 번역체, AI 상투어, 과도한 명사화·피동, 3의 법칙, 과장된 의의 부여, 마무리 상투구, 챗봇 잔재, 줄표·이모지·곡선따옴표 같은 흔적을 **심각도(S1/S2/S3)** 로 분류해 탐지
- "이 글에서 AI 흔적 찾아줘"처럼 고치지 않고 진단만 (탐지 리포트 + 심각도)
- 목표 글자수 지정 시(`length=1000`, "1000자로") ±5% 안으로 분량 조정, 공백 포함/제외 글자수 보고
- 사용자 글 샘플을 주면 그 말투(voice)로 재작성
## 왜 별도 스킬이 필요한가
- 영어권 humanizer(QuillBot·Undetectable AI 등)는 한국어에 약하다. 한국어 AI 글의 티는 대부분 **영어 번역투**와 격식을 가장한 **상투어**에서 나온다.
- 단순 맞춤법 교정(`korean-spell-check`)이나 유행어 입히기(`korean-slang-writing`)와 달리, 이 스킬은 의미를 보존하면서 **문체·리듬·표현**만 사람답게 되돌린다.
- 과교정을 막기 위해 4대 철칙(의미 불변 · 근거 기반 · 장르 유지 · 과윤문 금지)과 변경률 가드(30% 경고, 50% 중단)를 둔다.
## 먼저 필요한 것
- 추가 설치·API 키 없음. 이 스킬은 프롬프트/지식 기반이며 외부 호출이나 스크립트가 없다.
- (선택) 정확한 글자수 카운팅이 필요하면 `korean-character-count` 스킬과 연동된다.
## 기본 흐름 (탐지 → 윤문 → 감사 → 등급)
1. **트리아지** — 흔적이 무더기인지, 서식만 문제인지, 산문까지 다시 써야 하는지 먼저 정한다.
2. **탐지** — A~J 분류 카탈로그로 흔적을 span·심각도로 표시한다. S1부터 본다.
3. **윤문** — 흔적을 자연스러운 표현으로 교체한다. 의미·사실·고유명사·수치는 100% 보존한다.
4. **감사** — "왜 아직 AI 같은가?"를 다시 묻고, 자가검증 6항과 변경률을 점검한다. 위반이면 롤백 후 재윤문.
5. **등급** — A~D로 자가 채점한다. C·D면 추가 윤문이나 사람 검토를 권한다.
전체 패턴 표(A~J, 60+ 서브 패턴)는 스킬 디렉터리의 [`references/ai-tell-taxonomy.md`](../../korean-humanizer/references/ai-tell-taxonomy.md)에 있다.
## 사용 예시
```text
이 글 AI 티 안 나게 자연스럽게 다듬어줘:
[ChatGPT/Claude 초안 붙여넣기]
```
```text
이 글에서 AI 흔적만 찾아줘 (고치지 말고 심각도까지)
```
```text
1000자로 맞춰서 번역체 고쳐줘
```
## 제한사항
- 문체만 고친다. 사실관계 확인·출처 보강은 하지 않는다(필요하면 별도 리서치).
- 원문에 없는 내용을 창작해 채우지 않는다(의미 보존이 원칙).
- 변경률이 50%를 넘으면 작업을 중단하고 사람 검토를 권한다.
## 감사의 말 (Acknowledgments)
이 스킬은 두 기여 위에 만들어졌다.
- **[happy-nut](https://github.com/happy-nut) (Hyungsun Song)** 님이 PR [#311](https://github.com/NomaDamas/k-skill/pull/311)로 최초 `korean-humanizer` 스킬과 33개 한국어 패턴 카탈로그·예문, triage/length-control 설계를 기여했다. 이 가이드와 v2 스킬의 토대다.
- **[epoko77-ai/im-not-ai](https://github.com/epoko77-ai/im-not-ai)** (Humanize KR, MIT)의 방법론을 중심으로 v2를 재구성했다. A~J 분류 체계, S1/S2/S3 심각도, 4대 철칙, 변경률 30%/50% 가드, 품질 등급(A~D), 그리고 A-16(그/그녀 강박)·A-18(관계절 좌향 수식)·A-19(이중 조사)·C-11(연결어미 뒤 쉼표)·E-7(경어법 일관성) 같은 한국어 고유 패턴이 여기서 왔다.
원형은 영어권 [blader/humanizer](https://github.com/blader/humanizer)와 [Wikipedia: Signs of AI writing](https://en.wikipedia.org/wiki/Wikipedia:Signs_of_AI_writing)이다. 두 프로젝트와 happy-nut 님의 기여에 감사한다.
한국 법령 관련 검색/조회는 기본 hosted proxy(`k-skill-proxy.nomadamas.org`)의 `/v1/korean-law/...` endpoint로 처리합니다. 사용자 쪽 `LAW_OC` 가 불필요합니다. 별도 repo package, 별도 python package, 임의 크롤러를 새로 만들지 않습니다.
이 endpoint는 법제처(국가법령정보센터) 공식 Open API(`open.law.go.kr` 의 DRF `lawSearch.do`/`lawService.do`)를 감싼 것이고, read-only 도구 표면 설계는 `chrisryugj/korean-law-mcp` 를 참고했습니다.
로컬 설치가 막히면 먼저 `https://korean-law-mcp.fly.dev/mcp` remote endpoint를 사용한다. 그 경로도 응답하지 않거나 서비스 장애가 나면 `법망`(`https://api.beopmang.org`) MCP/REST를 fallback으로 사용한다.
## MCP 연결 예시
```json
{
"mcpServers": {
"korean-law": {
"command": "korean-law-mcp",
"env": {
"LAW_OC": "your-api-key"
}
}
}
}
```
remote endpoint 예시:
```json
{
"mcpServers": {
"korean-law": {
"url": "https://korean-law-mcp.fly.dev/mcp"
}
}
}
```
위 remote 예시는 upstream 문서 기준으로 사용자 `LAW_OC` 를 따로 넣지 않는다. 사용자 쪽에서 준비할 것은 `url` 등록뿐이다.
7. fallback 검색 결과가 0건이어도 바로 "관련 규범이 없다"고 단정하지 말고 검색어와 범주를 다시 확인한다.
2. 법령명만 찾으면 `target=law` 로 `search` 한다.
3. 특정 조문이 필요하면 `search` 로 식별자(`MST`/`ID`)를 확인한 뒤 `detail` 을 호출한다.
4. 판례는 `target=prec`, 유권해석은 `target=expc`, 자치법규는 `target=ordin` 로 조회한다.
5. 범주가 애매하면 `target=law` 부터 시작한다.
6. 검색 결과가 0건이어도 바로 "관련 규범이 없다"고 단정하지 말고 검색어와 범주를 다시 확인한다.
## CLI 예시
## 실패 모드
```bash
korean-law search_law --query "관세법"
korean-law get_law_text --mst 160001 --jo "제38조"
korean-law search_precedents --query "부당해고"
```
- `target` 이 없거나 허용되지 않은 값이면 400 응답
- 검색어/식별자가 없으면 400 응답
- 프록시 서버에 `LAW_OC` 가 없으면 503 응답
- 법제처 API가 사용자 검증 실패를 반환하면 502 + `law_user_verification_failed` (운영자가 서버 OC/UA/Referer 점검)
- 법제처 API가 일시적으로 빈/HTML 응답이면 proxy가 재시도 후 502 + `upstream_unstable`
- 일부 출처는 본문을 제공하지 않을 수 있다. 본문을 못 가져오면 목록 메타데이터(사건번호·법원·선고일자·출처·요지)까지만 제공하고 본문이 없다는 점을 명시한다.
## 운영 팁
- `화관법` 같은 약칭은 `search_law` / `search_all` 로 정식 법령명을 먼저 확인한다.
- 조문 번호가 헷갈리면 `get_law_text` 전에 법령 식별자부터 다시 확인한다.
- 로컬 CLI/MCP 경로를 쓰는데 `LAW_OC` 가 없으면 credential resolution order에 따라 확보를 안내한다.
- remote MCP endpoint를 쓰면 사용자 `LAW_OC` 없이 `url` 등록 상태만 확인한다.
- 기존 `korean-law-mcp` 경로가 실패하면 `https://api.beopmang.org/mcp` 또는 `/api/v4/law?action=search` 경로를 fallback으로 쓴다.
- `화관법` 같은 약칭은 `target=law` 로 정식 법령명을 먼저 확인한다.
- 조문 번호가 헷갈리면 `detail` 전에 법령 식별자부터 다시 확인한다.
- 요약은 할 수 있지만 법률 자문처럼 단정적으로 결론을 내리지는 않는다.
## 라이브 확인 메모
## 출처
2026-04-01 기준 smoke test 에서 아래 명령은 실제로 정상 동작했다.
- `korean-law list`
- `korean-law help search_law`
즉, `korean-law-mcp` CLI 설치와 기본 명령 진입은 검증했다. 실제 법령 검색은 로컬 CLI/MCP 경로라면 `LAW_OC` 가 준비된 환경에서 바로 이어서 사용할 수 있고, remote MCP endpoint는 사용자 `LAW_OC` 없이 URL 등록만으로 붙일 수 있다. 기존 경로 장애 시에는 `법망` fallback을 사용할 수 있다.
- 설계 참고(upstream): `https://github.com/chrisryugj/korean-law-mcp`
- 공식 데이터 출처: 법제처 국가법령정보 공동활용 (`https://open.law.go.kr`, DRF `lawSearch.do`/`lawService.do`)
- ODsay Server API Key 발급 및 호출 IP 화이트리스트 등록: https://lab.odsay.com
- Kakao Local geocoding은 기본 hosted `k-skill-proxy` 경유. 사용자 쪽 Kakao 키는 불필요하며, self-host proxy 운영자만 Kakao REST API Key를 발급해 서버에 설정한다: https://developers.kakao.com
## 필요한 환경변수
- `ODSAY_API_KEY` — ODsay LIVE API Server 키
`ODSAY_API_KEY` 를 `~/.config/k-skill/secrets.env` 에 저장하거나 환경변수로 주입한다. 별도 self-host proxy를 쓰는 경우에만 `KSKILL_PROXY_BASE_URL` 을 설정한다.
- 1회 응답 40,000셀 한도. 초과하면 코드 `31`/`41` 반환 → 쿼리 분할 또는 `bigdata` 사용.
- `bigdata` 의 `userStatsId` 는 KOSIS 웹에서 사용자가 직접 등록해야 하며, 이 helper로 자동 등록하지 않는다. `openapisample/...` 같은 타인 등록 ID는 인증되지 않아 코드 `11` 을 반환한다.
- 2026-03-05 이후 HTTPS 전용. 모든 URL은 `https://`.
- KOSIS는 가끔 따옴표 없는 키의 비표준 JSON을 반환한다. helper가 자동 보정한다.
## 흔한 문제 해결
- `missing required environment variable: KSKILL_KOSIS_API_KEY`: `bigdata` 또는 `--direct` 호출에서만 발생한다. 환경변수가 현재 shell에 주입됐는지 확인한다. 없다면 https://kosis.kr/openapi/ 에서 발급한다.
- `KOSIS error 10` (인증키 누락) / `11` (만료): 키를 재확인하거나 갱신한다. `bigdata` 호출에서 `11` 이 뜨면 해당 `userStatsId` 가 본인 KOSIS 계정에 등록되어 있지 않을 가능성이 높다.
- `KOSIS error 20` (필수 분류 누락): 표마다 필수 차원 수가 다르다. `meta --table-id <ID> --meta-type OBJ` 로 차원 수를 확인하고(OBJ가 비어 있으면 `--meta-type ITM`), `--obj-l 1=<코드> --obj-l 2=<코드>` 형태로 모두 지정한 뒤 재호출한다. 예: `data --table-id DT_1J22001 --prd-se M --start 202401 --end 202401 --obj-l 1=ALL` → 코드 20 → meta 확인 → `--obj-l 1=T10 --obj-l 2=0` 추가 → 성공.
- `KOSIS error 21` (잘못된 요청 변수): `org_id`/`tbl_id`/`prdSe`/`startPrdDe` 형식과 분류 인덱스를 재확인한다. 표에 존재하지 않는 `objL3=ALL` 같은 인덱스는 거부된다. tblId 의심 시 `search --query <키워드>` 로 정확한 ID를 다시 찾는다.
- `KOSIS error 30` (결과 없음): 키워드 또는 기간 필터를 완화한다.
- `KOSIS error 31` / `41` (한도 초과): 기간을 좁히거나(예: 5년 → 1년) 분류 ALL 을 특정 코드로 바꾼다(예: `--obj-l 1=ALL` → `--obj-l 1=11` 서울만). 그래도 부족하면 `bigdata` 로 전환한다. 행정구역 코드는 시도 2자리, 시군구 5자리 관례.
`announcements` 가 가장 활용도 높다. 지역·대상·기간·모집 진행 여부로 필터링해 답변할 공고 후보를 좁히고, 자세한 신청 절차는 응답의 `detl_pg_url` 로 사용자가 K-Startup 사이트에서 직접 확인한다.
> **주의**: `supt_regin`은 라이브 호출에서 upstream이 서버 측에서 적용하지 않는 사례가 관측됐다 (서울만 요청해도 타 지역 공고가 섞여 돌아온다). 지역 필터가 중요한 답변이라면 helper가 받은 응답 JSON을 client에서 `supt_regin` 으로 한 번 더 거른다.
## 사용자 시크릿
- 일반 조회는 hosted proxy(`https://k-skill-proxy.nomadamas.org`)가 K-Startup 인증키를 서버 측에서 주입한다. 사용자에게 키를 요구하지 않는다.
- `--direct` 사용 시에만 `KSKILL_KSTARTUP_API_KEY` (또는 `DATA_GO_KR_API_KEY` fallback) 가 필요하다.
- 자세한 credential resolution order 는 [공통 설정 가이드](../setup.md) 와 [보안/시크릿 정책](../security-and-secrets.md) 참고.
- `502 upstream_error`: data.go.kr이 `resultCode != "00"` 또는 `errMsg` 를 반환 (API 키 미등록·만료·IP 미등록·요청 초과 등).
- 빈 `data` 배열: 필터에 맞는 공고나 콘텐츠가 없는 경우 → 키워드·지역·대상 범위를 완화한다.
- 데이터 갱신 주기: 공식 서비스설계서는 **일 1회**, 공공데이터포털 dataset 메타데이터에는 "실시간" 으로 표기돼 있다. 두 표면이 일치하지 않으니 분 단위 마감 시계열에는 쓰지 말고, 최종 마감·접수 상태는 응답의 `detl_pg_url` 에서 직접 확인한다.
## 한도와 출처
- 일 호출 한도: 개발계정 10,000, 운영계정 활용사례 등록 시 증가 가능.
- 라이선스: 이용허락범위 제한 없음 (data.go.kr 명시).
- 공식 표면: `https://www.data.go.kr/data/15125364/openapi.do`
- 서비스 URL: `https://apis.data.go.kr/B552735/kisedKstartupService01`
`local-election-candidate-search`는 중앙선거관리위원회 선거통계시스템(`info.nec.go.kr`)의 공개 **통합검색** HTML 표면을 직접 조회하는 read-only 스킬이다. upstream이 인증/키 없이 열려 있는 공개 표면이므로 `k-skill-proxy`를 사용하지 않는다.
사용자 로컬 시크릿은 필요 없다. upstream `DATA_GO_KR_API_KEY`는 프록시 서버에만 둔다.
self-host 프록시를 쓰는 경우에만 `KSKILL_PROXY_BASE_URL`을 설정한다. 비우면 hosted proxy(`https://k-skill-proxy.nomadamas.org`)를 사용한다.
## 진위확인 개인정보 경로
`/v1/nts-business/validate`는 대표자명(`p_nm`), 개업일자(`start_dt`), 선택 주소/상호 메타데이터를 hosted proxy와 공공데이터포털 upstream으로 전송한다. proxy는 validate 성공 응답을 캐시하지 않고(`status` 조회만 성공 캐시), 응답에 normalized `query`를 echo하지 않으며, upstream 응답이 요청값을 되돌려도 민감 필드를 제거한다.
기본 proxy 서버는 Fastify request logging을 켜지 않는다. self-host 운영자가 별도 요청 로깅을 활성화했다면 validate 요청 본문이 저장되지 않도록 로그 정책을 확인해야 한다. hosted proxy 대신 자체 운영 경로가 필요하면 `KSKILL_PROXY_BASE_URL`로 self-host proxy를 지정한다.
## 예시
```bash
python3 nts-business-registration/scripts/nts_business_registration.py status \