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.
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
* 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>
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.
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.
* 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.
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>
- 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.
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.
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.
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
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
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.
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.
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.
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.