Commit graph

38 commits

Author SHA1 Message Date
Jeffrey (Dongkyu) Kim
49bf262bb9 Route shared key APIs through the proxy
Move KOSIS general lookups and Kakao Local geocoding behind k-skill-proxy so users do not need to manage those API keys for common skill flows. Keep KOSIS bigdata/direct calls user-keyed because userStatsId is account-specific.

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

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

Confidence: high

Scope-risk: moderate

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

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

Not-tested: Production proxy deployment after main merge/cron update.
2026-05-13 16:31:29 +09:00
Jeffrey (Dongkyu) Kim
667e2e1347
Feature/#211 (#222)
* Add public marathon schedule lookup

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

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

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

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

Confidence: high

Scope-risk: moderate

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

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

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

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

* Keep marathon locations authoritative

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

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

* Bound marathon schedule crawling to trusted sources

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

* Honor explicit public crawl budgets

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

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

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

Confidence: high

Scope-risk: narrow

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

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

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

---------

Co-authored-by: OmX <omx@oh-my-codex.dev>
2026-05-12 18:49:06 +09:00
TaeyoungPark
91eeaf607a
feat: add SH notice search skill (#218)
* feat: add SH notice search skill

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

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

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

---------

Co-authored-by: Jeffrey (Dongkyu) Kim <vkehfdl1@gmail.com>
2026-05-09 00:09:45 +09:00
github-actions[bot]
96f0d810ef
chore: version packages (#164)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-04-24 13:48:33 +09:00
Jeffrey (Dongkyu) Kim
4fc01391ce
Release: Merge dev into main (#163)
* Add a guided Hola Poke Yeoksam skill without widening repo scope

Issue #120 only needs a repository skill payload, discoverability docs,
and regression coverage. This change adds the new skill, wires it into
existing docs surfaces, and locks the remote-MCP-only contract in tests
so future edits keep the phone-only event flow and verbatim message
relay behavior.

Constraint: The upstream Hola Poke flow lives on a remote MCP server, so this repo should not add proxy/runtime code
Constraint: Tests must be written before refining the new docs/skill wording
Rejected: Add local package or proxy support for Hola Poke | would over-scope a docs-only skill addition
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep this skill limited to 올라포케 역삼점 and treat the MCP response message as the event source of truth
Tested: node --test scripts/skill-docs.test.js --test-name-pattern='hola-poke-yeoksam'
Tested: npm run ci
Tested: Live MCP initialize/tools/list/get_menu/get_shop_info/enter_event(phone_format) smoke checks against https://hola-poke-yeoksam-skill.onrender.com/mcp
Not-tested: Successful live event entry with a real phone number

* Help users find nearby public restrooms from Korean location queries

This adds a new public-restroom-nearby skill and reusable package that resolves a user-provided location, narrows the official 공중화장실정보 dataset by region when possible, and ranks nearby restroom results with opening-time hints and map links.

Constraint: Must use free official/open surfaces without introducing new dependencies
Constraint: Must follow TDD and keep release/docs metadata aligned in the same change
Rejected: Add a proxy route first | direct official CSV access already works and keeps scope narrower
Rejected: Use nationwide-only ranking without regional narrowing | too much noisy data for dense urban anchors
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: If Kakao place-panel or localdata CSV schema changes, update parser fixtures before broad logic changes
Tested: npm run ci; live smoke via searchNearbyPublicRestroomsByLocationQuery('광화문', { limit: 3 }); architect review APPROVED
Not-tested: Non-Seoul live smoke across every regional orgCode

* Pin the Hola Poke MCP contract in repo-owned regression fixtures

The earlier issue #120 regression only matched prose, so this follow-up records the verified remote MCP tool/result snapshot in a checked-in fixture and makes both docs surfaces byte-align to it. That keeps the discoverability docs honest while turning the review claim into a real contract lock for tools/list, get_menu, get_shop_info, and the invalid-phone event flow.

Constraint: The upstream remote MCP server can change independently of this repo
Rejected: Keep prose-only regex checks | would not catch contract drift
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Refresh the fixture, both JSON fences, and the live-smoke evidence together whenever the upstream contract changes
Tested: node --test scripts/skill-docs.test.js --test-name-pattern='hola-poke-yeoksam'; npm run ci; live MCP smoke check against https://hola-poke-yeoksam-skill.onrender.com/mcp (initialize, tools/list, get_menu, get_shop_info, invalid enter_event)
Not-tested: Successful enter_event with a real phone number (intentionally avoided to prevent live event participation)

* Keep nearby restroom lookups resilient to flaky Kakao place panels

The review caught two regressions in the new public-restroom-nearby package: a single broken Kakao panel aborted anchor resolution, and coordinate search dropped maxDistanceMeters before normalization. This change adds targeted regression coverage first, keeps per-candidate HTTP failures recoverable, and hardens request errors with explicit status/url metadata so fallback logic no longer depends on parsing error strings.

Constraint: Must preserve the published package surface and keep the fix scoped to PR #123 follow-up
Rejected: Swallow all panel errors | would hide non-HTTP failures like network faults
Rejected: Parse request error messages for status codes | brittle coupling to string formatting
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep recoverable Kakao panel handling aligned with request() error annotations if request() changes again
Tested: npm test --workspace public-restroom-nearby
Tested: npm run ci
Tested: live smoke searchNearbyPublicRestroomsByLocationQuery('광화문', { limit: 3 })
Tested: LSP diagnostics on packages/public-restroom-nearby/src/index.js and test/index.test.js
Not-tested: Live Kakao fallback against a real upstream 5xx place-panel response

* Keep the Hola Poke contract claims aligned with verified coverage

The reviewed fixture-based regression already locks the documented remote
snapshot, but the docs still implied the enter_event success path had
live proof. Narrow the docs and the regression so they explicitly say the
success fields are pinned by the recorded snapshot while the live smoke
only verifies the invalid-phone retry path.

Constraint: Live success-path verification would trigger a real event entry and is intentionally avoided
Rejected: Leave the broader wording in place | review feedback showed it overstated the live evidence
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If a safe non-mutating success-path probe becomes available, update the docs and fixture wording together
Tested: node --test scripts/skill-docs.test.js --test-name-pattern='hola-poke-yeoksam'; npm run ci; live MCP smoke against https://hola-poke-yeoksam-skill.onrender.com/mcp (initialize, tools/list, get_menu subset, get_shop_info subset, invalid enter_event)
Not-tested: Real enter_event success-path invocation

* Document the restroom distance-cap contract with regression coverage

The approved issue-117 code fix already restored maxDistanceMeters behavior, but the published docs did not lock or explain that contract. This follow-up adds a failing-first doc regression, then updates the feature guide and package README with the verified 100m example so users and future reviewers see the same behavior the package now ships.

Constraint: Must stay scoped to the existing PR #123 follow-up without reopening the implementation surface
Rejected: Leave the behavior implicit in code/tests only | published docs would lag the verified contract
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the public-restroom-nearby docs and skill-docs regression aligned with live maxDistanceMeters smoke evidence if the sample query changes
Tested: node --test scripts/skill-docs.test.js (red then green)
Tested: npm test --workspace public-restroom-nearby
Tested: npm run ci
Tested: live smoke searchNearbyPublicRestroomsByLocationQuery('광화문', { limit: 3 })
Tested: live smoke searchNearbyPublicRestroomsByLocationQuery('광화문', { limit: 3, maxDistanceMeters: 100 })
Tested: architect review APPROVED
Not-tested: Alternative landmark queries with a non-zero maxDistanceMeters hit set

* Expose KRX partial failures instead of misreporting stock lookups

The Korean stock proxy used to silently drop failed market snapshots during
search and could turn an empty holiday trade snapshot into a 502 by falling
back into base-info lookup.

This change surfaces degraded market metadata on partial search success,
short-circuits empty trade snapshots to not_found, and refreshes the user
docs to use a real trading day in examples.

Constraint: KOSPI base-info approval is granted separately from other KRX routes
Constraint: Healthy markets should still return usable search results during a partial outage
Rejected: Return 502 on every partial search failure | hides still-usable markets and breaks current clients unnecessarily
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep degraded search metadata when any market snapshot fetch fails so partial outages stay visible
Tested: npm test --workspace k-skill-proxy
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Not-tested: Live KOSPI base-info behavior after the new KRX permission is approved

* Adopt kordoc for the hwp skill workflow

Issue #119 replaces the previous HWP guidance with kordoc so the skill matches the newer agent-native document flow. The docs and regression tests now center the HWP skill on kordoc parsing, JSON extraction, diffing, form filling, and Markdown-to-HWPX round-tripping, while the install/source references stay in sync.

Constraint: The repository treats skill behavior as documentation contracts backed by regression tests
Constraint: The requested branch/PR flow must target dev with TDD and verified execution evidence
Rejected: Keep @ohah/hwpjs or hwp-mcp as fallback guidance | issue #119 explicitly approves replacing the prior stack with kordoc
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep future hwp skill/docs/tests aligned to a single kordoc-first contract unless a new issue explicitly reintroduces multi-backend routing
Tested: node --test scripts/skill-docs.test.js; npm run ci; temp-dir kordoc roundtrip via markdownToHwpx -> sample.hwpx -> kordoc CLI markdown output; architect review APPROVED
Not-tested: Live parsing of user-provided proprietary HWP/HWPX samples outside the generated roundtrip fixture

* Prevent degraded stock search outages from sticking in cache

Reviewer feedback showed that partial KRX market failures could be cached as full search answers, masking recovery on the next identical request. This change adds a regression that fails first, skips route-level caching for degraded search payloads, and keeps the trade-info empty-snapshot contract documented alongside the partial-failure response semantics.

Constraint: Existing PR #124 already targets dev and must remain the follow-up lane for issue #99
Constraint: Proxy behavior must stay read-only and dependency-free
Rejected: Cache degraded search payloads for a short TTL | still risks transient false negatives during the TTL window
Rejected: Broaden trade-info fallback behavior | empty snapshots should stay explicit not_found results
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep degraded search responses out of the long-lived route cache unless a future design adds explicit revalidation semantics
Tested: npm test --workspace k-skill-proxy; node --test scripts/skill-docs.test.js; npm run ci; explicit buildServer degraded-search recovery repro
Not-tested: Live KRX production endpoints from this branch

* Align HWP docs with the published kordoc surface

The issue #119 follow-up needs the repository contract to match what the
currently published kordoc package actually supports. This narrows the
HWP skill/docs/tests to the verified install requirement and supported
CLI/Node API surfaces, and removes unsupported fill/mcp claims.

Constraint: Published kordoc CLI fails at startup without pdfjs-dist
Constraint: Docs/tests must reflect the current npm package behavior, not intended future features
Rejected: Keep fill/mcp examples with caveats | still documents unsupported entrypoints
Confidence: high
Scope-risk: narrow
Directive: Reintroduce fill/mcp docs only after verifying the published package exposes them in both CLI and Node API
Tested: node --test scripts/skill-docs.test.js; npm run ci; temp-dir clean install smoke; temp-dir kordoc+pdfjs-dist watch/parse/extractFormFields/compare/markdownToHwpx/roundtrip smoke; Claude architect review
Not-tested: Real-world HWPX template that produces non-empty extractFormFields output

* Keep HWP docs runnable against the published kordoc package

The follow-up closes the last runnable-contract gaps from review by documenting the working one-shot npx form and separating Node API examples into a local project install path. The regression suite now locks both install notes so future edits do not drift back to broken command shapes.

Constraint: Published kordoc CLI still requires pdfjs-dist at startup
Constraint: Global NODE_PATH does not make ESM imports from kordoc resolvable in the documented examples
Rejected: Keep bare `npx kordoc` examples | fails in a clean environment
Rejected: Keep global-install Node API guidance | ESM import remains unresolved
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep HWP docs aligned to verified published kordoc surfaces until the package contract changes upstream
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: temp-dir local npm install kordoc pdfjs-dist plus markdownToHwpx -> sample.hwpx -> one-shot kordoc roundtrip smoke
Not-tested: upstream unpublished kordoc features beyond the verified CLI and Node API surfaces

* Add Korean scholarship search skill and reporting workflow (#116)

* Add nationwide scholarship search skill workflow

* Rename scholarship skill to 장학금 주세요 쮜에발

* Fix scholarship skill validation in CI

* Trigger GitHub PR diff refresh after dev rebase on main

* Fix scholarship helper status handling and test coverage

* Use KST as scholarship helper default date basis

* Rename scholarship skill display name

---------

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

* Feature/#121 (#127)

* Recover KakaoTalk mac skill auth when upstream user_id detection fails

Issue #121 reproduces on a real MacBook because `kakaocli auth` can fail even when the encrypted hex-named DB exists. This change adds a thin repo-owned helper that recovers the active user_id from plist revision hashes, caches the validated DB/key tuple, and reuses it for read-only `kakaocli` commands. The skill and feature docs now steer users to the helper when upstream auto-detection stops at candidate key mismatch, and regression tests lock the recovery flow before the implementation.

Constraint: Must stay a thin adapter around upstream kakaocli rather than forking the CLI
Constraint: Must verify on a real local macOS KakaoTalk install where issue #121 reproduces
Rejected: Full kakaocli reimplementation inside k-skill | too broad for the user_id/key-derivation failure scope
Rejected: Docs-only workaround | does not actually fix the broken auth path for users
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep this helper limited to auth/key recovery and read-only passthrough unless upstream gaps widen materially
Tested: python3 -m unittest scripts.test_kakaotalk_mac
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: python3 scripts/kakaotalk_mac.py auth --refresh --max-user-id 800000000 --workers 8 --chunk-size 2000000
Tested: python3 scripts/kakaotalk_mac.py chats --limit 1 --json
Not-tested: Other kakaocli subcommands beyond auth/chats/messages/search/query/schema

* Protect the KakaoTalk helper's safe recovery path

Address the PR follow-up by treating malformed auth cache files as cache misses,
removing write-capable passthrough from the wrapper surface, and redacting
human-readable auth output so the cached SQLCipher key is not echoed back into
terminal history. The docs and regression suite now describe and enforce the
read-only contract that the helper is meant to preserve.

Constraint: Helper must remain a read-only recovery wrapper around local kakaocli access
Rejected: Keep query support with SQL validation | still leaves a risky write-capable escape hatch
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Do not re-expose arbitrary SQL passthrough or print the SQLCipher key in default text output
Tested: python3 -m unittest scripts.test_kakaotalk_mac; node --test scripts/skill-docs.test.js; npm run ci; python3 scripts/kakaotalk_mac.py auth --refresh --max-user-id 800000000 --workers 8 --chunk-size 2000000; python3 scripts/kakaotalk_mac.py chats --limit 1 --json; python3 scripts/kakaotalk_mac.py auth --cache-path <bad-json>; python3 scripts/kakaotalk_mac.py query --help
Not-tested: External automation consumers that depend on shell/json auth output beyond the documented helper flows

* Lock the helper CLI surface against accidental regressions

The approved issue #121 fixes already hardened the KakaoTalk Mac helper, but the test suite still only exercised the passthrough validator directly. Add an explicit parser-level regression so the public CLI contract stays read-only and `query` cannot quietly reappear in future edits.

Constraint: Follow-up is on the existing feature/#121 PR branch and must stay minimal
Rejected: Re-open helper implementation changes | current code already satisfies the approved review findings
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep parser exposure tests aligned with READ_ONLY_COMMANDS whenever helper subcommands change
Tested: python3 -m unittest scripts.test_kakaotalk_mac; node --test scripts/skill-docs.test.js; npm run ci; python3 scripts/kakaotalk_mac.py auth --refresh --max-user-id 800000000 --workers 8 --chunk-size 2000000; python3 scripts/kakaotalk_mac.py chats --limit 1 --json; python3 scripts/kakaotalk_mac.py auth --cache-path <bad-json>
Not-tested: No new production code paths changed in this follow-up

* Honor explicit Kakao auth recovery overrides

The helper now treats manual auth overrides as a cache-bypassing recovery request and rejects invalid brute-force tuning flags at the CLI boundary so users get deterministic behavior instead of stale cached tuples or Python tracebacks. Regression coverage locks both paths before the PR follow-up lands.

Constraint: The helper must remain a thin read-only wrapper around kakaocli auth recovery
Rejected: Require --refresh whenever --user-id/--uuid is passed | worse UX than honoring overrides directly
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep explicit auth overrides ahead of cache reuse unless the CLI contract is redesigned and documented
Tested: python3 -m unittest scripts.test_kakaotalk_mac; node --test scripts/skill-docs.test.js; npm run ci; python3 scripts/kakaotalk_mac.py auth --refresh --max-user-id 800000000 --workers 8 --chunk-size 2000000; python3 scripts/kakaotalk_mac.py chats --limit 1 --json; python3 scripts/kakaotalk_mac.py auth --cache-path <bad-json>; python3 scripts/kakaotalk_mac.py auth --refresh --max-user-id -1; python3 scripts/kakaotalk_mac.py auth --refresh --workers 2 --chunk-size 0 --max-user-id 10; python3 scripts/kakaotalk_mac.py auth --cache-path <temp-cache> --user-id 999; python3 scripts/kakaotalk_mac.py auth --cache-path <temp-cache> --uuid <live-uuid>
Not-tested: Manual override success with a truly alternate valid user_id/uuid pair on a multi-account local install

* Feature/#129 (#131)

* Add official KBL results support so basketball queries use live league data

Issue #129 needs a read-only skill and reusable package for KBL schedules, results, and standings. The implementation follows the existing sports package pattern and uses the league's live JSON APIs after verifying they respond successfully in real requests.

Constraint: Must use official KBL JSON surfaces before considering scraping
Constraint: Packaging changes must pass npm run ci and include docs plus Changesets updates
Rejected: Browser scraping first | official api.kbl.or.kr endpoints are live and simpler to maintain
Rejected: Reuse KBO/K League package shapes verbatim | KBL payload and team/status fields differ materially
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep seasonGrade=1 as the default KBL path unless future docs/tests explicitly widen to D-League flows
Tested: npm run ci; npm run lint --workspace kbl-results; npm test --workspace kbl-results; live getKBLSummary("2026-04-01", { team: "KCC", includeStandings: true })
Not-tested: Historical standings snapshots for past seasons via alternative KBL endpoints

* Prevent optional standings lookups from over-fetching the KBL API

The new kbl-results summary helper exposes includeStandings=false, so the
regression suite now proves that path stays schedule-only and never calls
the standings endpoint when the caller opts out.

Constraint: The KBL package should preserve the caller's no-standings contract
Rejected: Rely on manual inspection of the helper options | a targeted test is cheaper and safer
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep includeStandings=false side-effect free unless the public API contract changes explicitly
Tested: npm test --workspace kbl-results; npm run lint --workspace kbl-results
Not-tested: Full-repo CI before stacking this commit onto the rebased branch

* Add Naver Shopping price comparison skill

* Use Naver Shopping BFF fallback

* Fix naver shopping BFF page and sort fallback

* Clarify Naver OpenAPI review sort fallback

* Add library book search skill

* Add Data4Library route regression coverage

* Fix Data4Library book-exists ISBN-10 handling

* Refactor Coupang skill to retention MCP layer

* Add Coupang MCP wrapper follow-up coverage

* Clarify Coupang wrapper init guidance

* Document Coupang MCP init examples

* Add parking lot search skill

* Add korean-privacy-terms skill regression tests

* Add korean-privacy-terms thin-wrapper skill

* Document korean-privacy-terms skill across repo docs

* Bundle Apache-2.0 LICENSE with korean-privacy-terms wrapper

Addresses PR #149 review SHOULD FIX: ship the Apache-2.0 LICENSE text
alongside the thin wrapper so Apache License 2.0 §4(a) ('give any other
recipients of the Work or Derivative Works a copy of this License') is
satisfied even before `install.sh` fetches the upstream payload.

- Copy upstream LICENSE verbatim to `korean-privacy-terms/LICENSE.upstream`
  (byte-for-byte identical to upstream at pinned SHA
  e390f7b9feb825e368c26726363ea5ce11a34083; SHA256
  35ef947614c2f14df01c5fc553f987f644f0c9f6b011adda397bd788a87f1510).
- Update SKILL.md Notes to link LICENSE.upstream, clarify that repo-root
  LICENSE (MIT) is k-skill's own license not this skill's, and document
  that nested upstream SKILL.md is not discovered by agent platforms.
- Document the home-path `bash ~/.claude/skills/.../install.sh` variant in
  SKILL.md so users who pulled the wrapper via `npx skills add --skill` can
  install without a repo checkout (installer already resolves
  ${BASH_SOURCE[0]} absolutely).
- Update docs/features/korean-privacy-terms.md to document LICENSE.upstream
  and the §4(a) rationale.
- Strengthen skill-docs regression tests (NICE TO HAVE items from review):
  * Reject placeholder pins (all-zero / all-f 40-char strings).
  * Assert the literal upstream clone URL
    (https://github.com/kimlawtech/korean-privacy-terms.git).
  * Assert `git clone --filter=blob:none` is used for blobless fetches.
  * Add new regression test that verifies LICENSE.upstream exists, matches
    the Apache-2.0 preamble / §4 / APPENDIX structure, and is referenced
    from both SKILL.md and the feature doc.

* Assert APPENDIX anchor in korean-privacy-terms LICENSE.upstream

Close Round 3 NICE TO HAVE from PR #149. The LICENSE.upstream
regression block asserted preamble, Version 2.0, Redistribution,
END OF TERMS, and Copyright 2026 kimlawtech but not the APPENDIX
anchor at LICENSE.upstream:179, even though the Round 1 follow-up
and Round 2 review collectively described 'APPENDIX structure
verification'. Adding this one assertion closes that claim/test
parity gap and acts as tamper-detection if upstream reformats
LICENSE later.

Verified with TDD: temporarily stripped APPENDIX line from
LICENSE.upstream, confirmed test 108 FAILS with the expected
regex mismatch, then restored and re-confirmed 109/109 GREEN.
Byte-for-byte identity with upstream LICENSE still holds
(SHA256 35ef947614c2f14df01c5fc553f987f644f0c9f6b011adda397bd788a87f1510).

npm run ci exit 0 with 357 ok subtests (unchanged baseline,
additive assertion within existing test block).

* Fix extractDataGoItems to handle current data.go.kr JSON shapes

The MFDS data.go.kr drug and food endpoints now return body.items as a
flat array (DrbEasyDrugInfoService, SafeStadDrugService) or an array of
{item: {...}} wrappers (PrsecImproptFoodInfoService03), instead of the
legacy {items: {item: [...]}} XML→JSON auto-convert shape.

Our extractDataGoItems was still looking for body.items.item, so it
returned [] for every entry, silently breaking:
  - /v1/mfds/drug-safety/lookup
  - /v1/mfds/food-safety/search (improperFood portion)

Update extractDataGoItems to accept all three shapes and refresh the
mock fixtures in server.test.js to match what upstream actually returns,
while adding a backward-compat test for the legacy shape.

Note: this does not resolve the remaining FOODSAFETYKOREA_API_KEY being
rejected by upstream (issue #148 core symptom) - that is a separate
operational key rotation on the proxy server.

* Make proxy cache failure-aware and require route-prefixed cache keys

Two related issues surfaced while investigating issue #148:

1. Transient upstream failures were being cached for the full 5-minute
   TTL because every route handler called cache.set() unconditionally
   with whatever payload came back - including empty items + warnings
   from a flaky upstream like openapi.foodsafetykorea.go.kr. The user
   would then see "empty + warning" for 5 minutes even after upstream
   recovered.

2. makeCacheKey(payload) hashes the whole payload, but fine-dust/report
   was the only route calling it without a "route" prefix
   (makeCacheKey(normalized) instead of
   makeCacheKey({ route: "fine-dust-report", ...normalized })).
   Different routes with the same normalized shape could collide.

Fix both globally in the cache layer so every current and future route
benefits without per-route edits:

- createMemoryCache.set rejects any payload that isFailureResponse
  considers a failure (explicit error field, upstream.degraded flag,
  or empty items alongside warnings). Returns false on reject, true
  on accept, so callers can observe the decision if needed.
- makeCacheKey now throws if payload.route is missing or empty. This
  catches the fine-dust inconsistency and prevents new routes from
  reintroducing it.
- fine-dust/report now passes `route: "fine-dust-report"` like every
  other route.

New tests:
- makeCacheKey asserts distinct routes produce distinct keys and throws
  without a route.
- isFailureResponse covers all failure signatures plus graceful-
  fallback cases (items present alongside warnings) that must stay
  cacheable.
- createMemoryCache.set refuses each failure shape and still stores
  healthy payloads.
- End-to-end: food-safety/search with a flaky recall upstream serves
  the upstream failure, retries live when upstream recovers, and only
  caches once the payload is healthy.

TTL itself is unchanged - the value still protects upstream rate
limits; it just no longer amplifies transient errors.

* Document Coupang hosted fallback contract and affiliate disclosure

retention-corp/coupang_partners#1 is merged, so upstream now transparently falls back to the Retention Corp hosted backend at https://a.retn.kr/v1/public/assist when Coupang Partners API credentials are missing. The k-skill wrapper already passes environment variables through unchanged, so this commit lines up the documented contract with the actual two-path behavior without changing runtime logic.

- SKILL.md and docs/features/coupang-product-search.md describe both execution paths (operator local HMAC vs credentialless hosted fallback), the honored OPENCLAW_SHOPPING_* env vars, the allowlist client-id convention including the k-skill-specific coupang-mcp-fallback value, and the mandatory affiliate disclosure when a.retn.kr/s/ shortlinks or lptag=AF deeplinks appear in responses.
- docs/sources.md adds the hosted assist endpoint and the merged upstream PR so the source surface stays truthful.
- README.md reflects the 선택사항 semantics for the 쿠팡 상품 검색 row and extends the column legend so 선택사항 is distinct from 불필요.
- coupang_partners_mcp.py expands its --help epilog so operators discover the honored upstream env vars without reading the wrapper source; no runtime behavior change.
- scripts/test_coupang_partners_mcp_wrapper.py locks env pass-through as a regression, asserts the new --help contract, and adds an opt-in K_SKILL_COUPANG_SMOKE=1 live smoke test that verifies the credentialless hosted path returns a Coupang deeplink.
- scripts/skill-docs.test.js extends the docs regression to require the hosted assist URL, OPENCLAW_SHOPPING_* env prefix, affiliate disclosure wording, and hosted fallback concept while keeping the yuju777 HF Space negative assertion.

Verified: npm run ci exits 0, live smoke test (K_SKILL_COUPANG_SMOKE=1) returns a.retn.kr/s/ shortlinks via credentialless wrapper, and manual env -u COUPANG_ACCESS_KEY -u COUPANG_SECRET_KEY call returns isRocket+lptag=AF3727577 responses through the hosted fallback.

Refs: #134

* Drop non-allowlisted coupang-mcp-fallback recommendation from hosted fallback docs

Direct probes against https://a.retn.kr/v1/public/assist confirmed that
X-OpenClaw-Client-Id: coupang-mcp-fallback returns HTTP 403 Client is not
allowlisted, while the upstream default openclaw-skill returns HTTP 200.
The default wrapper path already works because upstream falls back to
openclaw-skill, but the explicit recommendation in SKILL.md and the
feature doc was luring users to a 403 path.

Remove the dead recommendation and lock in the working configuration:

- Docs describe openclaw-skill as the upstream-allowlisted default and
  note that k-skill does not override OPENCLAW_SHOPPING_CLIENT_ID.
- Wrapper --help epilog drops the Suggested k-skill value line and
  documents openclaw-skill as the allowlist value in play.
- New skill-docs regression asserts coupang-mcp-fallback is absent from
  SKILL.md, the feature doc, the wrapper, and docs/sources.md while
  openclaw-skill is documented across all three narrative surfaces.
- New Python wrapper regression asserts --help drops the dead value and
  surfaces openclaw-skill so the constraint stays locked.
- Existing env-forwarding test uses openclaw-skill as the pass-through
  sentinel so the repo no longer ships the non-allowlisted string at all.

* Add lh-notice-search skill and /v1/lh-notice/{search,detail} proxy routes

Wraps the official data.go.kr LH (Korea Land & Housing Corporation) 청약
공고 Open API (B552555/lhLeaseNoticeInfo1/*) so agents can look up LH
임대/분양/주거복지/토지/상가 공고 by region, status, category, keyword,
and notice ID without asking users for a ServiceKey. Reuses the shared
DATA_GO_KR_API_KEY the proxy already manages; users see '불필요'.

Adapter handles both the LH-specific [CMN, dsList] JSON envelope and the
standard data.go.kr <OpenAPI_ServiceResponse> XML error envelope; refuses
to cache failure responses so transient upstream errors self-heal.

Closes #145.

* Document LH extractNoticeEnvelope success-code accept-list as deliberate

Per review note #4 on PR #158, extractNoticeEnvelope accepts four upstream
CMN.CODE values ("SUCCESS", "0", "00", "000") and three header.resultCode
values ("0", "00", "000") as success. This is deliberate: the data.go.kr
platform has surfaced different forms across catalog eras, and a future
normalization that flips SUCCESS to a numeric form must not regress into
502'ing otherwise-valid responses.

- Add an inline comment above the array-envelope success-code check in
  src/lh-notice.js explaining why the accept-list is NOT redundant.
- Add regression tests in test/lh-notice.test.js that explicitly exercise
  each accepted success code (SUCCESS/0/00/000 for array envelope; 0/00/000
  for object envelope) so a future refactor cannot silently collapse the
  accept-list.
- Add a paired rejection test that numeric-looking non-success codes like
  "22" and "10" still raise as upstream_error, disambiguating the
  accept-list from a blanket 'any numeric string passes' rule.

Test count: lh-notice.test.js 30 -> 38 (all pass); npm run ci exits 0.

* Pin LH /v1/lh-notice/detail failure-not-cached contract with regression test

Round 2 review noted that /v1/lh-notice/detail failure-not-cached
behavior was only verified via manual QA, while /search had an
explicit automated regression test.

This adds an equivalent automated test for /detail that:
- fails upstream once (XML SERVICE_KEY error, upstream_code=30)
- confirms first call returns 502 with cache.hit=false
- switches upstream to success and retries the same URL
- confirms second call returns 200 with cache.hit=false (failure was
  NOT cached, retry hit upstream again)
- sabotages upstream back to failing and verifies the third call
  serves the previously-cached success (cache.hit=true, no new fetch)

Verified the test genuinely catches regressions by temporarily
monkey-patching the detail route to cache error payloads — the test
correctly fails in that sabotaged state and passes when the route is
correct. Full server.test.js suite goes from 95 to 96 tests, all pass.

* Document LH /detail test pins both cache-protection layers

Adds a 12-line header comment to the 'lh-notice detail does not cache
upstream XML auth errors so retries self-heal' test in server.test.js
naming the two cache-protection layers it pins:

  (a) the early-return catch block in the route handler (no cache.set
      on upstream failure), and
  (b) the isFailureResponse() guard inside cache.set (refuses any
      payload with .error set).

Points future maintainers to the independent sabotage audit in PR #158
Round 3 review that proved bypassing either layer alone makes the
State 2 self-heal assertion fail, and cross-links the sibling /search
failure-not-cached test for symmetric coverage.

Addresses the Round 3 non-blocking observation #2 nice-to-have.
Test-only, comment-only: +12 lines, 0 source changes, 0 behavior
changes, 0 doc changes, 0 changeset changes. server.test.js remains
96/96, lh-notice.test.js remains 38/38, full proxy workspace 184/184.

* Add naver-news-search skill and /v1/naver-news/search proxy route

Closes #143. Proxies the official Naver Search Open API news endpoint
(openapi.naver.com/v1/search/news.json) through k-skill-proxy so users do
not need to issue their own Naver Client ID/Secret. Reuses the existing
NAVER_SEARCH_CLIENT_ID/NAVER_SEARCH_CLIENT_SECRET that naver-shopping already
consumes, since the Naver Developer application enables the 'Search' scope
covering both news and shopping.

Implementation details:
- src/naver-news.js normalizes q/display/start/sort, builds the official URL,
  calls upstream with X-Naver-Client-Id/Secret headers, and parses the JSON
  response into rank/title/description/link/original_link/pub_date items.
- Strips <b> highlight tags and decodes HTML entities in title/description
  using zero-width replacement so compound Korean words like '주식형' are
  preserved (not split into '주식 형').
- Parses RFC822 pubDate into pub_date_iso (ISO-8601 UTC) for clients.
- Deduplicates items by normalized link; drops entries missing title/link.
- Returns 503 upstream_not_configured when proxy keys are absent (no public
  BFF fallback exists for news like it does for shopping, so keys are
  required).
- Failure responses are not cached (failure-aware cache layer).
- Exposes naverNewsApiConfigured on /health.

14 new tests in test/naver-news.test.js cover query validation, URL
building, payload normalization (HTML stripping, entity decoding,
deduplication, missing-field tolerance), plus Fastify integration tests
for 200/400/401/429/500/503 paths, cache hit/miss, header wiring, and
the health flag.

* Add rhwp-edit and rhwp-advanced skills with k-skill-rhwp CLI

Splits HWP handling into three focused skills per issue #155:

- hwp (kept): kordoc-based read/convert (Markdown, JSON, diffing, form
  fields, Markdown->HWPX). Description narrowed to 'read-only' to make
  the routing policy explicit.
- rhwp-edit (new): HWP binary editing via new k-skill-rhwp npm package
  that wraps the @rhwp/core WASM bindings as CLI subcommands: info,
  list-paragraphs, search, insert-text, delete-text, replace-all,
  create-table, set-cell-text, create-blank, and render.
- rhwp-advanced (new): guidance for the upstream Rust rhwp CLI
  (export-svg --debug-overlay, dump, dump-pages, ir-diff, thumbnail,
  convert) for layout debugging, IR inspection, version comparison,
  and read-only-document unlocking.

The new k-skill-rhwp package under packages/ ships a Node.js 18+ CLI
and library that round-trips HWP 5.x documents entirely in-process; no
Rust toolchain is required. It auto-installs the WASM-required
globalThis.measureTextWidth shim for headless Node, and all editing
subcommands always write to a distinct output path so the source file
is never mutated. HWPX save remains disabled per the upstream rhwp
#196 data-safety gate; HWPX input is accepted but output is written as
HWP 5.x.

Includes 24 node:test cases covering init, round-trip insertText,
replaceAll, createTable + setCellText, deleteText, searchText,
listParagraphs, renderPage (SVG/HTML), and full CLI arg-parse +
end-to-end round-trip through the CLI layer.

Wires README feature table (3 rows for hwp / rhwp-edit / rhwp-advanced),
docs/install.md optional-install list, docs/roadmap.md (marks HWP
advanced editing as shipped while keeping Windows/security-module
automation out of scope), docs/sources.md (adds rhwp upstream, CLI
source, @rhwp/core, @rhwp/editor, and rhwp #196 references), and the
root pack:dry-run script. Adds a Changesets entry for k-skill-rhwp
minor.

Closes #155.

*  feat: add k-dart skill for DART OpenAPI financial disclosures (#147)

*  feat: add k-dart skill for DART OpenAPI financial disclosures

금감원 전자공시시스템(DART) 14개 endpoint 조회 스킬 추가.
공시검색, 기업개황, 재무제표, 배당, 증자/감자, 전환사채, 소송 등.
API_K_DART 환경변수로 직접 호출하며 프록시 불필요.

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

* 📝 docs(k-dart): remove redundant korean-stock-search dependency

corpCode.xml 자체에 회사명·종목코드·고유번호가 모두 포함되어 있으므로
korean-stock-search 스킬 연계 절차 제거

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

* 📝 docs: add k-dart to README feature table

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

* 📝 docs: add k-dart feature guide and fix README link format

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

* 🐛 fix(k-dart): correct status code 013, remove invalid corp_name filter, update daily limit

3개 critical 정확성 오류 수정:

1. 상태코드 013은 "조회된 데이터 없음"이며 "접근 권한 없음"이 아님 (012=접근 불가 IP).
   상태코드 표를 공식 명세 기준으로 재정리하고 누락된 014/021 코드 추가.
2. list.json은 corp_name 파라미터를 검색 필터로 지원하지 않음. SKILL.md의
   잘못된 진술과 corp_name을 사용한 misleading example을 제거하고, corp_code
   확보 절차를 거치도록 명시.
3. DART 일일 한도는 키당 10,000건이 아닌 20,000건이며 분당 약 1,000회
   throttle도 별도로 존재함. SKILL.md 및 docs/features/k-dart.md 모두 정정.

추가로 status: "013" 발생 시 사용자 안내 정책을 Response policy에 추가하고,
오픈API 이용현황 페이지 링크를 Notes에 추가함.

* 🐛 fix(k-dart): correct pifricDecsn endpoint, list.json corp_code optional, add empSttus, soften throttle claim

Codex adversarial review에서 식별된 4건의 추가 정확성 이슈 수정:

1. endpoint #8 유무상증자 결정이 잘못된 API에 연결됨. piicDecsn.json은
   유상증자 결정 (apiId=2020023)이며, 유무상증자 결정은 pifricDecsn.json
   (apiId=2020025)이 맞음. endpoint를 정정하고 piicDecsn (유상증자) 및
   fricDecsn (무상증자)와의 차이를 주의문으로 추가.

2. list.json의 corp_code 는 사실 선택사항이며, 미지정 시 검색 기간이
   3개월 이내로 제한될 뿐임. 이전 commit의 "corp_code 필수" 표현을
   정정하고, 두 가지 호출 패턴(corp_code 지정/미지정)을 Example
   requests에 모두 추가.

3. "분당 약 1,000회 throttle"은 공식 공개 가이드에 근거 없음
   (apiUsageStatusView.do 는 로그인 게이트). 공식 가이드가 명시한
   "일반적으로 20,000건 이상 요청 시 020 발생"만 유지하고 분당
   throttle 주장을 제거. 상태코드 표·Response policy도 일관되게 정리.

4. docs/features/k-dart.md가 "직원 현황" 기능을 광고하지만 SKILL.md
   에는 endpoint가 누락됨. empSttus.json (apiGrpCd=DS002,
   apiId=2019011)을 endpoint #8로 추가하고 example도 함께 등록.
   기존 endpoint 9~14는 10~15로 재번호.

* 🐛 fix(k-dart): align list.json signature and 020 caveat with official spec

Codex 2nd-round review에서 식별된 정확성 이슈 2건 수정:

1) list.json 요청 인자 signature가 공식 가이드(DS001/2019001)와 정확히
   일치하도록 재작성. crtfc_key 외 모든 파라미터가 선택사항임을 분명히
   하고, 각 파라미터의 default 동작과 pblntf_ty 값(A/B/C/D/E)도 명시.
   "corp_code 지정 시 기간 제한 없음" 표현은 공식 가이드가 보장하지
   않으므로 제거. corp_name이 공식 파라미터에 "존재하지 않는다"는
   사실로 수정 (이전: "지원하지 않는다").
   "corp_code 미지정 시 3개월 제한"은 외부 사용 사례에서 관찰된
   동작으로 약화 (공식 가이드에 별도 명시 없음).

2) 020 (요청 제한 초과) 안내가 일일 20,000건 cap 으로 너무 단정적
   해석되던 표현을 공식 메시지 그대로 보존: "일반적으로 20,000건
   이상 요청 시 발생하며, 키별로 별도 한도가 설정된 경우 다른
   임계치에서도 동일 코드가 반환될 수 있음". 상태코드 표·Response
   policy·Notes·docs/features/k-dart.md 모두 일관되게 정정.

* 🐛 fix(k-dart): mirror official Korean DS001/2019001 list.json spec exactly

Codex 3rd-round review에서 식별된 잔존 정확성 이슈 수정.

영어 가이드(DE001/AE00001)와 한국어 가이드(DS001/2019001)가 list.json
필수여부에서 다르게 표기되어 있어 이전 commit이 영어 가이드를 따랐으나,
한국어 공식 가이드를 직접 확인한 결과(opendart.fss.or.kr/guide/detail.do
?apiGrpCd=DS001&apiId=2019001) 다음이 한국어 공식 spec임을 확인:

- bgn_de, end_de는 Y(필수) (기본값은 명시되어 있으나 표기상 필수)
- corp_code 미지정 시 검색기간 3개월 제한은 공식 spec에 명시된 룰
  (외부 사용 사례 관찰이 아님)
- pblntf_ty는 A~J 전체 enum (정기공시/주요사항보고/발행공시/지분공시/
  기타공시/외부감사관련/펀드공시/자산유동화/거래소공시/공정위공시)
- page_count 기본값 10, 최대값 100
- corp_cls 복수 조건 불가
- last_reprt_at, sort, sort_mth 각 default 동작 명시

list.json 섹션을 공식 가이드 표와 1:1 일치하는 마크다운 표로 재작성.
3개월 제한 표현을 "외부 사례"에서 "공식 spec"으로 정정. Response policy
에 잔존하던 corp_name "지원하지 않는다" 표현도 "공식 파라미터에 존재하지
않는다"로 통일하여 #1 endpoint 섹션과 일관성 확보. docs/features/k-dart.md
도 동일하게 정정.

* 🐛 fix(k-dart): make list.json table 1:1 mirror of DS001/2019001 + unify corp_name wording

Codex 4th-round review가 식별한 잔존 이슈 2건 마무리.

1) list.json 파라미터 표를 공식 가이드 행 순서 그대로(crtfc_key,
   corp_code, bgn_de, end_de, last_reprt_at, pblntf_ty,
   pblntf_detail_ty, corp_cls, sort, sort_mth, page_no, page_count)
   재정리하고 공식 표의 모든 컬럼(요청키/명칭/타입/필수여부/값설명)을
   포함. page_no(1~n) / page_count(1~100, 기본10, 최대100) 범위
   값을 공식 표 그대로 표기. pblntf_detail_ty 값설명도 공식 표
   그대로 "(※ 상세 유형 참조: pblntf_detail_ty)"로 두고, 자주 쓰는
   코드 예시(A001/B001/F001/D001)는 표 아래 별도 단락으로 분리해
   표의 1:1 mirror 성격을 유지.

2) corp_name 관련 canonical 문장 "공식 요청 파라미터 표에
   corp_name 은 존재하지 않는다" 를 다음 3곳 모두 verbatim 일치
   시킴 (이전 commit에서 SKILL.md는 '않는다', docs/features는
   '않음' 으로 어미 차이가 잔존했음):
   - k-dart/SKILL.md #1 endpoint 섹션 주의문
   - k-dart/SKILL.md Response policy
   - docs/features/k-dart.md 에러/제약 섹션

* 🐛 fix(k-dart): unify corp_name canonical sentence verbatim + soften list.json table claim

Codex 5th-round review가 식별한 fine-grained 이슈 마무리.

1) corp_name canonical 문장을 self-contained 형태로 재작성하여
   3곳 모두 byte-for-byte 동일하게 통일:
   "DART OpenAPI list.json 의 공식 요청 파라미터 표에 corp_name 은
   존재하지 않는다."
   - SKILL.md #1 endpoint 섹션 주의문
   - SKILL.md Response policy
   - docs/features/k-dart.md 에러/제약 섹션
   이전에는 SKILL.md는 "위 공식 요청 파라미터 표에"로 docs/features는
   "list.json 공식 요청 파라미터 표에" 로 prefix가 달라 verbatim
   일치하지 않았음.

2) list.json 표 헤더 문구를 "공식 가이드 표를 그대로 옮긴 것"에서
   "공식 가이드 요청 인자 정리 (필수여부·기본값·허용값은 공식 표
   기준, 식별자는 코드 폰트로 표기)"로 약화. 마크다운 backtick 등
   포매팅 차이가 "1:1 mirror" 약속과 모순되지 않게 정확히 표현.

---------

Co-authored-by: hon2be <hon2be>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Jeffrey (Dongkyu) Kim <vkehfdl1@gmail.com>

* WIP korean-slang-writing (#133): scaffold slang_search.py

* WIP korean-slang-writing (#133): add http + lookup scripts

* WIP korean-slang-writing (#133): add seed index of 30 curated trending slang

* WIP korean-slang-writing (#133): add test suite

* korean-slang-writing (#133): fix module-loader sys.modules registration

* korean-slang-writing (#133): add SKILL.md

* korean-slang-writing (#133): add feature doc

* korean-slang-writing (#133): register skill in README and root lint/test pipeline

* Revert out-of-scope HWP README edits to unblock CI

The prior commit 4c7877a on this branch renamed the HWP feature row to
'HWP 문서 조회/변환' and added two new rows ('HWP 문서 편집',
'HWP 레이아웃·IR 디버깅') pointing at docs/features/rhwp-edit.md and
docs/features/rhwp-advanced.md. Those docs do not exist on any branch
in this repo, and the rename violates scripts/skill-docs.test.js
assertions at lines 210, 223, 224, which caused the CI 'validate' job
to fail.

Those changes belong to a separate rhwp-edit/rhwp-advanced feature
effort (tracked elsewhere), not to issue #143 'naver-news-search'.
Revert README.md in both the feature table and the list section so the
only additions in this PR relative to origin/dev are the two
in-scope naver-news-search entries.

Verified by running 'npm run ci' locally (EXIT=0). skill-docs.test.js
now passes 110/110 (previously failed 2/110) and the full
k-skill-proxy suite remains 198/198 including the 14 naver-news tests.

* Update skill-docs tests to cover rhwp-edit, rhwp-advanced, and the k-skill-rhwp package

Pins the HWP table row rename to 'HWP 문서 조회/변환', asserts the new
'HWP 문서 편집' and 'HWP 레이아웃·IR 디버깅' README rows and their linked
feature docs, pins the new SKILL.md routing policy for rhwp-edit and
rhwp-advanced (k-skill-rhwp CLI + @rhwp/core for editing vs upstream
Rust CLI for layout/IR debugging), and asserts the k-skill-rhwp
package.json wiring (bin mapping, @rhwp/core dependency, Node 18+
engines, wasm-init shim + CLI bin files).

Per AGENTS.md rule, no assertion is added on the presence of any
.changeset/*.md file so the changeset release flow can consume the
rhwp-edit-skill.md entry without breaking CI at version-bump time.

Also captures the package-lock.json delta introduced by adding the
k-skill-rhwp workspace (pulls @rhwp/core@0.7.3 and its WASM binary).

Refs #155.

* Polish naver-news: preflight, link canonicalization, /health docs (#143)

Address the three non-blocking items flagged in the round 1/2 reviews. All
were explicitly deferred by the reviewer as "follow-up if the maintainer
wants" — picking them up now so the feature lands with a tighter surface.

1) Preflight 400 for start + display - 1 > 1000
   Naver's official news endpoint only exposes the first 1000 items
   (start 1..1000, display 1..100). Asking for start=1000 & display=100
   would send a request that silently returns no usable items, wasting
   an upstream quota call. Reject the combination before calling upstream
   with a 400 bad_request and a message that tells the caller which item
   the request would have needed and what the cap is. Boundary values
   (start + display - 1 === 1000) are still accepted.

2) Canonical link dedup
   The previous dedup key was link.toLowerCase(), which failed to merge
   the same article when Naver's redirect URLs differed only by query-param
   order, trailing slash, host-name casing, or fragment. Added
   canonicalizeLinkForDedup() which parses the URL, sorts search params by
   key, strips a single trailing pathname slash, drops the fragment, and
   lowercases the result — conservative on purpose so different paths or
   different query values stay as distinct articles. The visible
   items[].link value is still the original URL returned by Naver; only
   the dedup key is canonicalized.

3) Clarify the naverSearchApiConfigured vs naverNewsApiConfigured split
   The two flags currently evaluate the same boolean, but their semantic
   contracts differ: naverSearchApiConfigured reports "are the Naver
   Open API keys configured" (which is advisory for the shopping route
   since shopping has a BFF fallback), while naverNewsApiConfigured
   reports "is the news route operational end-to-end" (no fallback — 503
   when false). Hoist the shared expression into a local, and add a
   `/health 업스트림 플래그 의미` section to packages/k-skill-proxy/README.md
   documenting the split. Also update naver-news-search SKILL.md and
   docs/features/naver-news-search.md to mention the new preflight and
   the canonical-link dedup behavior.

TDD verification: added 4 new node:test cases exercising the boundary,
overflow, and URL-dedup paths; ran the full k-skill-proxy workspace
suite (202/202 pass) plus the root `npm run ci` (exit 0). Manual QA on
a proxy started from this commit reproduces every round-1 case plus the
new preflight: start=1000 & display=100 → 400 bad_request before
upstream; start=1000 & display=1 and start=901 & display=100 → 503 (or
200/401 depending on keys), confirming the boundary passes preflight.

* korean-slang-writing (#133): fix broken seed namuwiki URLs + add encoding invariant test

Reviewer flagged 4/30 seed namuwiki_url values returning HTTP 404 on live
Namu Wiki. These URLs are part of the documented response contract and get
surfaced directly to agents, so broken links are a functional bug, not a
cosmetic one.

Root causes per entry:
- 중꺾마: wrong 꺾 codepoint (U+AFFA 꿺 instead of U+AEBE 꺾).
- 아아: typo in aliased title (아이스 아메리칸노 instead of 아메리카노).
- 어쩔티비: missing 받침 (어쩌티비 instead of 어쩔티비).
- 당모치: encoding correct but no live Namu Wiki article exists; dropped.

Also fixes two separately-broken 중꺾마 example URLs in SKILL.md
(U+AFBE 꾾 instead of U+AEBE 꺾) — these were discovered while auditing
the seed and would have surfaced as 404 to agents following the example
snippets.

Adds two regression tests:
- test_each_seed_url_decodes_to_term_or_alias: decodes every seed URL's
  path segment and asserts it equals the term or one of its aliases.
  Catches Hangul-codepoint typos offline (no network dependency) and
  would have caught all 3 encoding bugs in this PR.
- test_no_seed_entry_points_at_known_missing_namuwiki_page: locks the
  당모치 drop so nobody re-adds an entry pointing at a page that does
  not exist on Namu Wiki.

Fixes the existing LookupNetworkTest assertion that was hard-coding the
broken URL — it now derives the expected URL via build_namuwiki_url()
so the test cannot drift out of sync with the helper again.

Verification:
- PYTHONPATH=.:scripts python3 -m unittest scripts.test_korean_slang_writing -> 40/40 pass
- Live GET with browser headers against all 29 remaining seed URLs -> 29/29 return 200
- npm run ci -> exit 0
- Manual QA: slang_search on 중꺾마, 어쩔티비, 아이스 아메리카노 returns
  correct URLs; slang_lookup live-fetches 중꺾마 and extracts the
  canonical title '중요한 것은 꺾이지 않는 마음'.

* korean-slang-writing (#133): extract summaries via h2 section anchor + og:description fallback

Namu Wiki's current HTML layout uses build-time-obfuscated CSS class
names (e.g. _36R8DWTn, OZVChh+l) and has no <article>/<main>/<section>
tags, so all six MAIN_CONTENT_CLASSES anchors fail to match and
extract_summary() returned empty with a 'Main content region not
detected' warning on every live page.

Replace the single class-based strategy with a three-tier fallback
chain that pins to progressively weaker but more structurally stable
anchors:

  1. First h2 section boundary. Namu Wiki articles consistently open
     with '<h2>1. 개요[편집]</h2>' and mark subsequent sections with
     numbered h2 headings. Extracting text between the first and
     second h2 reliably captures the overview section on every page
     sampled (중꺾마, 갓생, 럭키비키, 어쩔티비).
  2. MAIN_CONTENT_CLASSES / <article> - kept as a legacy fallback
     for older Namu Wiki layouts and for third-party fixtures.
  3. og:description meta tag - final safety net before returning
     empty, gives the agent at least a ~64-char preview when the
     article has unusual structure.

Strip '[편집]' edit-affordance markers and numbered section prefixes
(e.g. '1.2.') from the extracted text so headings don't leak through
as noise.

Live verification (text format):
  slang_lookup.py 중꺾마   -> Title + 286-char summary
  slang_lookup.py 갓생     -> Title + 96-char summary
  slang_lookup.py 럭키비키 -> Title + 59-char summary
  slang_lookup.py 어쩔티비 -> Title + 20-char summary

All previously-empty. Not-found / blocked / upstream-error paths and
exit codes are unchanged.

* korean-slang-writing (#133): harden extractor with numbered-h2 gate + category-nav strip

Implements the three non-blocking observations from PR #161 round-3 review:

1. Numbered-h2 gate (reviewer-flagged fragility):
   Refactored _extract_first_section_between_h2 to extract h2 inner text
   (stripping nested tags) and filter by '^\\s*\\d+(?:\\.\\d+)*\\.\\s+\\S'.
   Sidebar widgets like <h2>관련 문서</h2> or <h2>외부 링크</h2> can no longer
   anchor the extractor - only numbered section headers (1., 1.2., 2.3.4.) do.
   Handles live Namu Wiki structure where the number sits inside an <a> tag
   (<a>1.</a> <span>개요</span>), which the round-3 suggested regex-only gate
   missed. All 29 seed pages continue to produce valid summaries on live
   fetches.

2. Category-nav template strip (reviewer-flagged long-page noise):
   a. CATEGORY_NAV_RE strips the inline '[펼치기 · 접기]' marker plus its
      same-line aftermath (the category list items on the same line).
   b. DETAILS_PELCHIGI_RE strips the entire <details> block whose <summary>
      contains 펼치기. Namu Wiki today wraps category nav in exactly this
      structure, so the strip removes the full noise block (not just the
      marker line).
   꿀잼 summary drops from 3482 chars of category dump to 562 chars
   starting with the real definition '무언가가 매우 재미있다는 의미의 인터넷
   유행어'. Non-category <details> blocks (spoilers, footnotes) are
   preserved.

3. TDD + mutation coverage:
   6 new tests total: 2 numbered-h2 gate tests, 2 inline category-nav tests,
   1 <details>-block strip test, 1 <details>-keep test (negative case).
   All 6 were written first and confirmed RED against the round-2 baseline,
   then made GREEN after the implementation landed. Each fix path was also
   mutation-tested (revert regex, remove .sub line) to confirm the tests
   genuinely catch the target bug class.

Suite grows from 45 to 51 tests. All pass. npm run ci exits 0.

* rhwp-edit (#155): fix replace-all silent no-op and document body-only scope

Upstream @rhwp/core HwpDocument.replaceAll returns {ok:true, count:N} but
does not persist the mutation into exportHwp() serialization, so the output
bytes are byte-identical to the input. This is confirmed against
@rhwp/core@0.7.3 with SHA diffing and round-trip searchText.

Rewrite the Node wrapper replaceAll to compose engine primitives that do
persist: for each body paragraph, read the full text via getTextRange,
compute all non-overlapping match offsets in JS, then apply replaceText
right-to-left so earlier offsets are unaffected by length changes. This
restores the documented '2025 → 2026 일괄 치환' headline workflow.

Guard rails in the new replaceAll:
- Reject replacements containing newline or paragraph-break characters
  (\n, \r, U+2028, U+2029) with a descriptive error. Splitting a paragraph
  via replaceText would invalidate subsequent offsets.
- Non-overlapping semantics against the original text, so
  --query a --replacement aa against 'aaa' yields 'aaaaaa' (3 replacements)
  instead of looping on the freshly inserted 'a' characters.

Tighten the regression tests to assert content, not just length:
- Same-length replacement: output SHA must differ from input, searchText
  must find the replacement and must NOT find the original query.
- Longer-length replacement: paragraph length must grow by the correct
  amount and output SHA must differ.
- Shorter-length replacement: paragraph length must shrink by the correct
  amount and output SHA must differ.
- Empty replacement: deletes every match and output no longer contains
  the query.
- Replacement contains query (a→aa on aaa): expects count 3 and length 6.
- Zero matches: count 0, output still written.
- Case-sensitive flag skips mismatched case.
- Newline replacement is rejected synchronously.

Document the body-only scope of search and replace-all in the SKILL.md
routing policy, failure-modes, CLI USAGE text, feature doc, and package
README so users know to use set-cell-text for cell content. This matches
the upstream searchText contract, which does not descend into table cells,
headers, footers, or footnotes.

Add a matching regression assertion to scripts/skill-docs.test.js so the
body-only scope note cannot be silently removed from SKILL.md or the
feature doc.

Closes review round 1 for PR #162.

* rhwp-edit (#155): guard replace-all case-insensitive path against UTF-16 length-drift

Round 2 review flagged a latent Unicode safety bug: when replaceAll's
caseSensitive=false branch encounters characters whose toLowerCase()
changes UTF-16 length (e.g. Turkish İ U+0130 → i + U+0307 combining dot
above), offsets taken in the lowercased haystack drift by the expansion
delta for every subsequent match and silently corrupt the document.
Reviewer repro: 'ABCİABCİXYZ' + case-insensitive İ→Z reported
{ok:true,count:2} but rendered 'ABCZABCİZYZ' instead of 'ABCZABCZXYZ'
(the X at index 8 was corrupted while the second İ survived).

Surface a descriptive error rather than silently drift:
- findAllMatchOffsets: in the case-insensitive branch, verify that the
  paragraph text and the query each preserve UTF-16 length under
  toLowerCase; otherwise throw with an actionable message pointing the
  user to --case-sensitive or input normalization.
- This is strictly a safety guard: the 2025→2026 headline workflow,
  ASCII, Hangul, and every existing test are unaffected.

Tests (TDD red → green, net +4 in packages/k-skill-rhwp):
- 'replaceAll refuses case-insensitive matching when source text
  contains case-folding length-changing chars (e.g. Turkish İ U+0130)'
  reproduces the exact reviewer input and asserts rejection + no output
  file
- 'replaceAll refuses case-insensitive matching when the query itself
  contains case-folding length-changing chars' covers the query-side path
- 'replaceAll with --case-sensitive succeeds on inputs containing İ'
  confirms the guard only fires in the case-insensitive path and that
  case-sensitive produces ABCZABCZXYZ with no X corruption
- 'replaceAll case-insensitive still works for normal ASCII/Hangul'
  regression-guards against the fix over-rejecting the common case

Doc disclosure in all 4 surfaces called out by the reviewer:
- rhwp-edit/SKILL.md: new failure-mode bullet naming U+0130 specifically
- docs/features/rhwp-edit.md: Unicode 대소문자 무시 주의 paragraph
  under scenario 3 (replace-all)
- packages/k-skill-rhwp/README.md: extended Scope section
- packages/k-skill-rhwp/src/cli.js: USAGE 'Scope note' appended
- scripts/skill-docs.test.js: 2 new assertions locking the SKILL.md and
  feature-doc disclosure so they can't be silently removed
- .changeset: note the guard in the pending v0.1.0 release notes

Manual QA (end-to-end via the published CLI):
  $ k-skill-rhwp replace-all … --query İ --replacement Z
  → exit 1 + 'case-insensitive matching is unsafe because case folding
    changes the UTF-16 length …'
  → no output file written
  $ k-skill-rhwp replace-all … --query İ --replacement Z --case-sensitive
  → {ok:true,count:2}, render shows 'ABCZABCZXYZ', search İ ⇒ found:false
  $ replace-all '2025'→'2026' on '2025 2025 2025' ⇒ {ok:true,count:3}
  $ replace-all 'hello'→'hi' (case-insens.) on 'hello WORLD 안녕 HELLO'
    ⇒ {ok:true,count:2}

Verification:
- npm test --workspace k-skill-rhwp: 35 pass / 0 fail (+4 vs Round 2)
- node --test scripts/skill-docs.test.js: 114 pass / 0 fail
- npm run ci: exit 0 (lint + typecheck + all workspace tests +
  pack:dry-run + validate-skills.sh all green)

Refs PR #162 Round 2 review 'Non-blocking residual risk — Unicode
case-insensitive offset drift'.

* Document preflight 400 and full canonical dedup contract in naver-news feature doc

Round-3 review flagged two non-blocking doc-completeness nits in docs/features/naver-news-search.md:

- 실패 모드의 `400 bad_request` 항목이 preflight 케이스(`start + display - 1 > 1000`)를 누락하고 있었음. SKILL.md line 94 와 본문 line 128 의 '운영 팁' 과 대칭이 되도록 업데이트.
- 운영 팁의 canonical dedup 설명이 쿼리 파라미터 순서와 trailing slash 만 언급해서, 실제 구현(`canonicalizeLinkForDedup`)이 같이 정규화하는 host 대소문자와 URL fragment 를 빠뜨리고 있었음. test/naver-news.test.js line 273 이 네 가지 모두 검증하고 있으므로 공개 문서를 구현과 테스트에 맞춰 정정.

* feat: add catchtable-sniper skill (#146)

* feat: add catchtable-sniper skill

* Make the Catchtable skill loadable and discoverable

The submitted skill landed under skills/ without YAML frontmatter, which broke the repo's auto-discovery contract and Codex skill loading. Move it to the root-level skill layout, add the required metadata block, and document the feature in the main README plus a dedicated guide so the PR ships in a usable state.

Constraint: This repository auto-discovers skills from root-level directories only
Constraint: Skill manifests must start with YAML frontmatter for Codex to load them
Rejected: Keep the nested skills/catchtable-sniper layout | validate-skills and the repo's documented convention reject it
Rejected: Add only README links without a feature guide | would create a broken documentation target
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Future skill PRs should follow docs/adding-a-skill.md and place each skill in its own root directory
Tested: node --test scripts/skill-docs.test.js
Tested: ./scripts/validate-skills.sh
Tested: git diff --check
Not-tested: End-to-end Catchtable reservation completion on a logged-in account

---------

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

---------

Co-authored-by: minsing-jin <ironman0722@naver.com>
Co-authored-by: hon2be <saysun34@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: choihyun-1110 <74152226+choihyun-1110@users.noreply.github.com>
2026-04-24 10:41:21 +09:00
Jeffrey (Dongkyu) Kim
e1656541a4
Fix parking lot lookups: force HTTPS, cache full dataset, normalize provider fields (#156)
Data.go.kr 이 tn_pubr_prkplce_info_api 를 HTTPS 로만 서비스하고 HTTP 요청은 301 로 리다이렉트하기 때문에 Node fetch 가 `response.ok=false` 로 떨어져 기능이 전체 실패하고 있었다. 이 커밋은 HTTPS 로 직접 호출하도록 수정하면서, 업스트림의 주소/지역 필터가 실제로는 동작하지 않고 페이지당 응답이 1000rows 기준 26s 에 달해 20s fetch timeout 에 꾸준히 걸리던 문제까지 함께 해결한다.

## What changed

- packages/k-skill-proxy/src/parking-lots.js
  - PARKING_LOT_API_URL 을 `http://` → `https://` 로 고정 (root cause).
  - 업스트림 address/geo 필터가 신뢰 불가하므로 full-dataset 을 한 번 로드해 프로세스 메모리에 6시간 TTL 로 캐시하고, 동시 호출자는 in-flight promise 를 공유하도록 한다. nearby 쿼리는 캐시된 행을 좌표 거리로 필터링해 서비스한다.
  - DATASET_PAGE_SIZE=300, fetch timeout 30s 로 페이지당 응답이 20s 를 넘기지 않도록 맞췄다.
- packages/k-skill-proxy/src/server.js
  - 더 이상 의미 없어진 numOfRows / maxPages 쿼리 파라미터를 라우트에서 제거하고, 응답 payload 의 query echo 도 정리했다.
- packages/k-skill-proxy/test/server.test.js
  - 새 캐시 기반 동작을 검증하는 테스트로 교체: (1) full dataset load + 좌표 필터 + 프록시 응답 캐시 재사용, (2) public_only 기본값 및 해제 시 동작, (3) 좌표 검증 실패 400, (4) 업스트림 키 미설정 시 503.
- packages/parking-lot-search/src/index.js
  - OFFICIAL_API_URL 도 HTTPS 로 맞춰 직접 호출 모드 사용자도 같은 버그를 밟지 않게 한다.
- packages/parking-lot-search/src/parse.js
  - 업스트림이 `insttCode` / `insttNm` (camelCase) 를 돌려주는데 parser 가 snake_case (`instt_code`, `instt_nm`) 만 인식해 providerCode/providerName 이 비어 있던 문제를 수정.
- packages/parking-lot-search/test/* 및 fixtures
  - HTTPS URL 매칭으로 업데이트하고, insttCode/insttNm 회귀 테스트를 fixture/assertion 에 추가.
- docs/features/parking-lot-search.md, parking-lot-search/SKILL.md, packages/parking-lot-search/README.md
  - 공식 endpoint 표기를 HTTPS 로 통일.
- .changeset/parking-lot-https-fix.md
  - parking-lot-search 패키지 patch 릴리즈 노트 추가.

## How it was verified

- `npm run ci` (lint + typecheck + tests + pack:dry-run) 통과.
- 로컬에서 실제 `DATA_GO_KR_API_KEY` 로 k-skill-proxy 를 기동해 live 호출 검증:
  - 광화문 (37.573713, 126.978338) cold cache: 30s 내 전체 18,868 rows 로드, 2km 내 47개 공영주차장 반환 (세종로 414m, 서린노외 456m 등).
  - 강남역 (37.497952, 127.027621) warm cache: 31ms 응답, 1.5km 내 13개 반환 (역삼문화공원 380m, 역삼푸른솔도서관 421m 등).
- 업스트림 직접 HTTPS 호출로 `resultCode=00 NORMAL_SERVICE` 정상 동작 확인.
2026-04-22 10:52:57 +09:00
Jeffrey (Dongkyu) Kim
c002561f34
Sync dev → main: MFDS proxy fixes, cache hardening, HWP kordoc, KRX degraded handling + new skills (#152)
* Add a guided Hola Poke Yeoksam skill without widening repo scope

Issue #120 only needs a repository skill payload, discoverability docs,
and regression coverage. This change adds the new skill, wires it into
existing docs surfaces, and locks the remote-MCP-only contract in tests
so future edits keep the phone-only event flow and verbatim message
relay behavior.

Constraint: The upstream Hola Poke flow lives on a remote MCP server, so this repo should not add proxy/runtime code
Constraint: Tests must be written before refining the new docs/skill wording
Rejected: Add local package or proxy support for Hola Poke | would over-scope a docs-only skill addition
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep this skill limited to 올라포케 역삼점 and treat the MCP response message as the event source of truth
Tested: node --test scripts/skill-docs.test.js --test-name-pattern='hola-poke-yeoksam'
Tested: npm run ci
Tested: Live MCP initialize/tools/list/get_menu/get_shop_info/enter_event(phone_format) smoke checks against https://hola-poke-yeoksam-skill.onrender.com/mcp
Not-tested: Successful live event entry with a real phone number

* Help users find nearby public restrooms from Korean location queries

This adds a new public-restroom-nearby skill and reusable package that resolves a user-provided location, narrows the official 공중화장실정보 dataset by region when possible, and ranks nearby restroom results with opening-time hints and map links.

Constraint: Must use free official/open surfaces without introducing new dependencies
Constraint: Must follow TDD and keep release/docs metadata aligned in the same change
Rejected: Add a proxy route first | direct official CSV access already works and keeps scope narrower
Rejected: Use nationwide-only ranking without regional narrowing | too much noisy data for dense urban anchors
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: If Kakao place-panel or localdata CSV schema changes, update parser fixtures before broad logic changes
Tested: npm run ci; live smoke via searchNearbyPublicRestroomsByLocationQuery('광화문', { limit: 3 }); architect review APPROVED
Not-tested: Non-Seoul live smoke across every regional orgCode

* Pin the Hola Poke MCP contract in repo-owned regression fixtures

The earlier issue #120 regression only matched prose, so this follow-up records the verified remote MCP tool/result snapshot in a checked-in fixture and makes both docs surfaces byte-align to it. That keeps the discoverability docs honest while turning the review claim into a real contract lock for tools/list, get_menu, get_shop_info, and the invalid-phone event flow.

Constraint: The upstream remote MCP server can change independently of this repo
Rejected: Keep prose-only regex checks | would not catch contract drift
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Refresh the fixture, both JSON fences, and the live-smoke evidence together whenever the upstream contract changes
Tested: node --test scripts/skill-docs.test.js --test-name-pattern='hola-poke-yeoksam'; npm run ci; live MCP smoke check against https://hola-poke-yeoksam-skill.onrender.com/mcp (initialize, tools/list, get_menu, get_shop_info, invalid enter_event)
Not-tested: Successful enter_event with a real phone number (intentionally avoided to prevent live event participation)

* Keep nearby restroom lookups resilient to flaky Kakao place panels

The review caught two regressions in the new public-restroom-nearby package: a single broken Kakao panel aborted anchor resolution, and coordinate search dropped maxDistanceMeters before normalization. This change adds targeted regression coverage first, keeps per-candidate HTTP failures recoverable, and hardens request errors with explicit status/url metadata so fallback logic no longer depends on parsing error strings.

Constraint: Must preserve the published package surface and keep the fix scoped to PR #123 follow-up
Rejected: Swallow all panel errors | would hide non-HTTP failures like network faults
Rejected: Parse request error messages for status codes | brittle coupling to string formatting
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep recoverable Kakao panel handling aligned with request() error annotations if request() changes again
Tested: npm test --workspace public-restroom-nearby
Tested: npm run ci
Tested: live smoke searchNearbyPublicRestroomsByLocationQuery('광화문', { limit: 3 })
Tested: LSP diagnostics on packages/public-restroom-nearby/src/index.js and test/index.test.js
Not-tested: Live Kakao fallback against a real upstream 5xx place-panel response

* Keep the Hola Poke contract claims aligned with verified coverage

The reviewed fixture-based regression already locks the documented remote
snapshot, but the docs still implied the enter_event success path had
live proof. Narrow the docs and the regression so they explicitly say the
success fields are pinned by the recorded snapshot while the live smoke
only verifies the invalid-phone retry path.

Constraint: Live success-path verification would trigger a real event entry and is intentionally avoided
Rejected: Leave the broader wording in place | review feedback showed it overstated the live evidence
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If a safe non-mutating success-path probe becomes available, update the docs and fixture wording together
Tested: node --test scripts/skill-docs.test.js --test-name-pattern='hola-poke-yeoksam'; npm run ci; live MCP smoke against https://hola-poke-yeoksam-skill.onrender.com/mcp (initialize, tools/list, get_menu subset, get_shop_info subset, invalid enter_event)
Not-tested: Real enter_event success-path invocation

* Document the restroom distance-cap contract with regression coverage

The approved issue-117 code fix already restored maxDistanceMeters behavior, but the published docs did not lock or explain that contract. This follow-up adds a failing-first doc regression, then updates the feature guide and package README with the verified 100m example so users and future reviewers see the same behavior the package now ships.

Constraint: Must stay scoped to the existing PR #123 follow-up without reopening the implementation surface
Rejected: Leave the behavior implicit in code/tests only | published docs would lag the verified contract
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the public-restroom-nearby docs and skill-docs regression aligned with live maxDistanceMeters smoke evidence if the sample query changes
Tested: node --test scripts/skill-docs.test.js (red then green)
Tested: npm test --workspace public-restroom-nearby
Tested: npm run ci
Tested: live smoke searchNearbyPublicRestroomsByLocationQuery('광화문', { limit: 3 })
Tested: live smoke searchNearbyPublicRestroomsByLocationQuery('광화문', { limit: 3, maxDistanceMeters: 100 })
Tested: architect review APPROVED
Not-tested: Alternative landmark queries with a non-zero maxDistanceMeters hit set

* Expose KRX partial failures instead of misreporting stock lookups

The Korean stock proxy used to silently drop failed market snapshots during
search and could turn an empty holiday trade snapshot into a 502 by falling
back into base-info lookup.

This change surfaces degraded market metadata on partial search success,
short-circuits empty trade snapshots to not_found, and refreshes the user
docs to use a real trading day in examples.

Constraint: KOSPI base-info approval is granted separately from other KRX routes
Constraint: Healthy markets should still return usable search results during a partial outage
Rejected: Return 502 on every partial search failure | hides still-usable markets and breaks current clients unnecessarily
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep degraded search metadata when any market snapshot fetch fails so partial outages stay visible
Tested: npm test --workspace k-skill-proxy
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Not-tested: Live KOSPI base-info behavior after the new KRX permission is approved

* Adopt kordoc for the hwp skill workflow

Issue #119 replaces the previous HWP guidance with kordoc so the skill matches the newer agent-native document flow. The docs and regression tests now center the HWP skill on kordoc parsing, JSON extraction, diffing, form filling, and Markdown-to-HWPX round-tripping, while the install/source references stay in sync.

Constraint: The repository treats skill behavior as documentation contracts backed by regression tests
Constraint: The requested branch/PR flow must target dev with TDD and verified execution evidence
Rejected: Keep @ohah/hwpjs or hwp-mcp as fallback guidance | issue #119 explicitly approves replacing the prior stack with kordoc
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep future hwp skill/docs/tests aligned to a single kordoc-first contract unless a new issue explicitly reintroduces multi-backend routing
Tested: node --test scripts/skill-docs.test.js; npm run ci; temp-dir kordoc roundtrip via markdownToHwpx -> sample.hwpx -> kordoc CLI markdown output; architect review APPROVED
Not-tested: Live parsing of user-provided proprietary HWP/HWPX samples outside the generated roundtrip fixture

* Prevent degraded stock search outages from sticking in cache

Reviewer feedback showed that partial KRX market failures could be cached as full search answers, masking recovery on the next identical request. This change adds a regression that fails first, skips route-level caching for degraded search payloads, and keeps the trade-info empty-snapshot contract documented alongside the partial-failure response semantics.

Constraint: Existing PR #124 already targets dev and must remain the follow-up lane for issue #99
Constraint: Proxy behavior must stay read-only and dependency-free
Rejected: Cache degraded search payloads for a short TTL | still risks transient false negatives during the TTL window
Rejected: Broaden trade-info fallback behavior | empty snapshots should stay explicit not_found results
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep degraded search responses out of the long-lived route cache unless a future design adds explicit revalidation semantics
Tested: npm test --workspace k-skill-proxy; node --test scripts/skill-docs.test.js; npm run ci; explicit buildServer degraded-search recovery repro
Not-tested: Live KRX production endpoints from this branch

* Align HWP docs with the published kordoc surface

The issue #119 follow-up needs the repository contract to match what the
currently published kordoc package actually supports. This narrows the
HWP skill/docs/tests to the verified install requirement and supported
CLI/Node API surfaces, and removes unsupported fill/mcp claims.

Constraint: Published kordoc CLI fails at startup without pdfjs-dist
Constraint: Docs/tests must reflect the current npm package behavior, not intended future features
Rejected: Keep fill/mcp examples with caveats | still documents unsupported entrypoints
Confidence: high
Scope-risk: narrow
Directive: Reintroduce fill/mcp docs only after verifying the published package exposes them in both CLI and Node API
Tested: node --test scripts/skill-docs.test.js; npm run ci; temp-dir clean install smoke; temp-dir kordoc+pdfjs-dist watch/parse/extractFormFields/compare/markdownToHwpx/roundtrip smoke; Claude architect review
Not-tested: Real-world HWPX template that produces non-empty extractFormFields output

* Keep HWP docs runnable against the published kordoc package

The follow-up closes the last runnable-contract gaps from review by documenting the working one-shot npx form and separating Node API examples into a local project install path. The regression suite now locks both install notes so future edits do not drift back to broken command shapes.

Constraint: Published kordoc CLI still requires pdfjs-dist at startup
Constraint: Global NODE_PATH does not make ESM imports from kordoc resolvable in the documented examples
Rejected: Keep bare `npx kordoc` examples | fails in a clean environment
Rejected: Keep global-install Node API guidance | ESM import remains unresolved
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep HWP docs aligned to verified published kordoc surfaces until the package contract changes upstream
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: temp-dir local npm install kordoc pdfjs-dist plus markdownToHwpx -> sample.hwpx -> one-shot kordoc roundtrip smoke
Not-tested: upstream unpublished kordoc features beyond the verified CLI and Node API surfaces

* Add Korean scholarship search skill and reporting workflow (#116)

* Add nationwide scholarship search skill workflow

* Rename scholarship skill to 장학금 주세요 쮜에발

* Fix scholarship skill validation in CI

* Trigger GitHub PR diff refresh after dev rebase on main

* Fix scholarship helper status handling and test coverage

* Use KST as scholarship helper default date basis

* Rename scholarship skill display name

---------

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

* Feature/#121 (#127)

* Recover KakaoTalk mac skill auth when upstream user_id detection fails

Issue #121 reproduces on a real MacBook because `kakaocli auth` can fail even when the encrypted hex-named DB exists. This change adds a thin repo-owned helper that recovers the active user_id from plist revision hashes, caches the validated DB/key tuple, and reuses it for read-only `kakaocli` commands. The skill and feature docs now steer users to the helper when upstream auto-detection stops at candidate key mismatch, and regression tests lock the recovery flow before the implementation.

Constraint: Must stay a thin adapter around upstream kakaocli rather than forking the CLI
Constraint: Must verify on a real local macOS KakaoTalk install where issue #121 reproduces
Rejected: Full kakaocli reimplementation inside k-skill | too broad for the user_id/key-derivation failure scope
Rejected: Docs-only workaround | does not actually fix the broken auth path for users
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep this helper limited to auth/key recovery and read-only passthrough unless upstream gaps widen materially
Tested: python3 -m unittest scripts.test_kakaotalk_mac
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: python3 scripts/kakaotalk_mac.py auth --refresh --max-user-id 800000000 --workers 8 --chunk-size 2000000
Tested: python3 scripts/kakaotalk_mac.py chats --limit 1 --json
Not-tested: Other kakaocli subcommands beyond auth/chats/messages/search/query/schema

* Protect the KakaoTalk helper's safe recovery path

Address the PR follow-up by treating malformed auth cache files as cache misses,
removing write-capable passthrough from the wrapper surface, and redacting
human-readable auth output so the cached SQLCipher key is not echoed back into
terminal history. The docs and regression suite now describe and enforce the
read-only contract that the helper is meant to preserve.

Constraint: Helper must remain a read-only recovery wrapper around local kakaocli access
Rejected: Keep query support with SQL validation | still leaves a risky write-capable escape hatch
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Do not re-expose arbitrary SQL passthrough or print the SQLCipher key in default text output
Tested: python3 -m unittest scripts.test_kakaotalk_mac; node --test scripts/skill-docs.test.js; npm run ci; python3 scripts/kakaotalk_mac.py auth --refresh --max-user-id 800000000 --workers 8 --chunk-size 2000000; python3 scripts/kakaotalk_mac.py chats --limit 1 --json; python3 scripts/kakaotalk_mac.py auth --cache-path <bad-json>; python3 scripts/kakaotalk_mac.py query --help
Not-tested: External automation consumers that depend on shell/json auth output beyond the documented helper flows

* Lock the helper CLI surface against accidental regressions

The approved issue #121 fixes already hardened the KakaoTalk Mac helper, but the test suite still only exercised the passthrough validator directly. Add an explicit parser-level regression so the public CLI contract stays read-only and `query` cannot quietly reappear in future edits.

Constraint: Follow-up is on the existing feature/#121 PR branch and must stay minimal
Rejected: Re-open helper implementation changes | current code already satisfies the approved review findings
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep parser exposure tests aligned with READ_ONLY_COMMANDS whenever helper subcommands change
Tested: python3 -m unittest scripts.test_kakaotalk_mac; node --test scripts/skill-docs.test.js; npm run ci; python3 scripts/kakaotalk_mac.py auth --refresh --max-user-id 800000000 --workers 8 --chunk-size 2000000; python3 scripts/kakaotalk_mac.py chats --limit 1 --json; python3 scripts/kakaotalk_mac.py auth --cache-path <bad-json>
Not-tested: No new production code paths changed in this follow-up

* Honor explicit Kakao auth recovery overrides

The helper now treats manual auth overrides as a cache-bypassing recovery request and rejects invalid brute-force tuning flags at the CLI boundary so users get deterministic behavior instead of stale cached tuples or Python tracebacks. Regression coverage locks both paths before the PR follow-up lands.

Constraint: The helper must remain a thin read-only wrapper around kakaocli auth recovery
Rejected: Require --refresh whenever --user-id/--uuid is passed | worse UX than honoring overrides directly
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep explicit auth overrides ahead of cache reuse unless the CLI contract is redesigned and documented
Tested: python3 -m unittest scripts.test_kakaotalk_mac; node --test scripts/skill-docs.test.js; npm run ci; python3 scripts/kakaotalk_mac.py auth --refresh --max-user-id 800000000 --workers 8 --chunk-size 2000000; python3 scripts/kakaotalk_mac.py chats --limit 1 --json; python3 scripts/kakaotalk_mac.py auth --cache-path <bad-json>; python3 scripts/kakaotalk_mac.py auth --refresh --max-user-id -1; python3 scripts/kakaotalk_mac.py auth --refresh --workers 2 --chunk-size 0 --max-user-id 10; python3 scripts/kakaotalk_mac.py auth --cache-path <temp-cache> --user-id 999; python3 scripts/kakaotalk_mac.py auth --cache-path <temp-cache> --uuid <live-uuid>
Not-tested: Manual override success with a truly alternate valid user_id/uuid pair on a multi-account local install

* Feature/#129 (#131)

* Add official KBL results support so basketball queries use live league data

Issue #129 needs a read-only skill and reusable package for KBL schedules, results, and standings. The implementation follows the existing sports package pattern and uses the league's live JSON APIs after verifying they respond successfully in real requests.

Constraint: Must use official KBL JSON surfaces before considering scraping
Constraint: Packaging changes must pass npm run ci and include docs plus Changesets updates
Rejected: Browser scraping first | official api.kbl.or.kr endpoints are live and simpler to maintain
Rejected: Reuse KBO/K League package shapes verbatim | KBL payload and team/status fields differ materially
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep seasonGrade=1 as the default KBL path unless future docs/tests explicitly widen to D-League flows
Tested: npm run ci; npm run lint --workspace kbl-results; npm test --workspace kbl-results; live getKBLSummary("2026-04-01", { team: "KCC", includeStandings: true })
Not-tested: Historical standings snapshots for past seasons via alternative KBL endpoints

* Prevent optional standings lookups from over-fetching the KBL API

The new kbl-results summary helper exposes includeStandings=false, so the
regression suite now proves that path stays schedule-only and never calls
the standings endpoint when the caller opts out.

Constraint: The KBL package should preserve the caller's no-standings contract
Rejected: Rely on manual inspection of the helper options | a targeted test is cheaper and safer
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep includeStandings=false side-effect free unless the public API contract changes explicitly
Tested: npm test --workspace kbl-results; npm run lint --workspace kbl-results
Not-tested: Full-repo CI before stacking this commit onto the rebased branch

* Add Naver Shopping price comparison skill

* Use Naver Shopping BFF fallback

* Fix naver shopping BFF page and sort fallback

* Clarify Naver OpenAPI review sort fallback

* Add library book search skill

* Add Data4Library route regression coverage

* Fix Data4Library book-exists ISBN-10 handling

* Refactor Coupang skill to retention MCP layer

* Add Coupang MCP wrapper follow-up coverage

* Clarify Coupang wrapper init guidance

* Document Coupang MCP init examples

* Add parking lot search skill

* Add korean-privacy-terms skill regression tests

* Add korean-privacy-terms thin-wrapper skill

* Document korean-privacy-terms skill across repo docs

* Bundle Apache-2.0 LICENSE with korean-privacy-terms wrapper

Addresses PR #149 review SHOULD FIX: ship the Apache-2.0 LICENSE text
alongside the thin wrapper so Apache License 2.0 §4(a) ('give any other
recipients of the Work or Derivative Works a copy of this License') is
satisfied even before `install.sh` fetches the upstream payload.

- Copy upstream LICENSE verbatim to `korean-privacy-terms/LICENSE.upstream`
  (byte-for-byte identical to upstream at pinned SHA
  e390f7b9feb825e368c26726363ea5ce11a34083; SHA256
  35ef947614c2f14df01c5fc553f987f644f0c9f6b011adda397bd788a87f1510).
- Update SKILL.md Notes to link LICENSE.upstream, clarify that repo-root
  LICENSE (MIT) is k-skill's own license not this skill's, and document
  that nested upstream SKILL.md is not discovered by agent platforms.
- Document the home-path `bash ~/.claude/skills/.../install.sh` variant in
  SKILL.md so users who pulled the wrapper via `npx skills add --skill` can
  install without a repo checkout (installer already resolves
  ${BASH_SOURCE[0]} absolutely).
- Update docs/features/korean-privacy-terms.md to document LICENSE.upstream
  and the §4(a) rationale.
- Strengthen skill-docs regression tests (NICE TO HAVE items from review):
  * Reject placeholder pins (all-zero / all-f 40-char strings).
  * Assert the literal upstream clone URL
    (https://github.com/kimlawtech/korean-privacy-terms.git).
  * Assert `git clone --filter=blob:none` is used for blobless fetches.
  * Add new regression test that verifies LICENSE.upstream exists, matches
    the Apache-2.0 preamble / §4 / APPENDIX structure, and is referenced
    from both SKILL.md and the feature doc.

* Assert APPENDIX anchor in korean-privacy-terms LICENSE.upstream

Close Round 3 NICE TO HAVE from PR #149. The LICENSE.upstream
regression block asserted preamble, Version 2.0, Redistribution,
END OF TERMS, and Copyright 2026 kimlawtech but not the APPENDIX
anchor at LICENSE.upstream:179, even though the Round 1 follow-up
and Round 2 review collectively described 'APPENDIX structure
verification'. Adding this one assertion closes that claim/test
parity gap and acts as tamper-detection if upstream reformats
LICENSE later.

Verified with TDD: temporarily stripped APPENDIX line from
LICENSE.upstream, confirmed test 108 FAILS with the expected
regex mismatch, then restored and re-confirmed 109/109 GREEN.
Byte-for-byte identity with upstream LICENSE still holds
(SHA256 35ef947614c2f14df01c5fc553f987f644f0c9f6b011adda397bd788a87f1510).

npm run ci exit 0 with 357 ok subtests (unchanged baseline,
additive assertion within existing test block).

* Fix extractDataGoItems to handle current data.go.kr JSON shapes

The MFDS data.go.kr drug and food endpoints now return body.items as a
flat array (DrbEasyDrugInfoService, SafeStadDrugService) or an array of
{item: {...}} wrappers (PrsecImproptFoodInfoService03), instead of the
legacy {items: {item: [...]}} XML→JSON auto-convert shape.

Our extractDataGoItems was still looking for body.items.item, so it
returned [] for every entry, silently breaking:
  - /v1/mfds/drug-safety/lookup
  - /v1/mfds/food-safety/search (improperFood portion)

Update extractDataGoItems to accept all three shapes and refresh the
mock fixtures in server.test.js to match what upstream actually returns,
while adding a backward-compat test for the legacy shape.

Note: this does not resolve the remaining FOODSAFETYKOREA_API_KEY being
rejected by upstream (issue #148 core symptom) - that is a separate
operational key rotation on the proxy server.

* Make proxy cache failure-aware and require route-prefixed cache keys

Two related issues surfaced while investigating issue #148:

1. Transient upstream failures were being cached for the full 5-minute
   TTL because every route handler called cache.set() unconditionally
   with whatever payload came back - including empty items + warnings
   from a flaky upstream like openapi.foodsafetykorea.go.kr. The user
   would then see "empty + warning" for 5 minutes even after upstream
   recovered.

2. makeCacheKey(payload) hashes the whole payload, but fine-dust/report
   was the only route calling it without a "route" prefix
   (makeCacheKey(normalized) instead of
   makeCacheKey({ route: "fine-dust-report", ...normalized })).
   Different routes with the same normalized shape could collide.

Fix both globally in the cache layer so every current and future route
benefits without per-route edits:

- createMemoryCache.set rejects any payload that isFailureResponse
  considers a failure (explicit error field, upstream.degraded flag,
  or empty items alongside warnings). Returns false on reject, true
  on accept, so callers can observe the decision if needed.
- makeCacheKey now throws if payload.route is missing or empty. This
  catches the fine-dust inconsistency and prevents new routes from
  reintroducing it.
- fine-dust/report now passes `route: "fine-dust-report"` like every
  other route.

New tests:
- makeCacheKey asserts distinct routes produce distinct keys and throws
  without a route.
- isFailureResponse covers all failure signatures plus graceful-
  fallback cases (items present alongside warnings) that must stay
  cacheable.
- createMemoryCache.set refuses each failure shape and still stores
  healthy payloads.
- End-to-end: food-safety/search with a flaky recall upstream serves
  the upstream failure, retries live when upstream recovers, and only
  caches once the payload is healthy.

TTL itself is unchanged - the value still protects upstream rate
limits; it just no longer amplifies transient errors.

* Document Coupang hosted fallback contract and affiliate disclosure

retention-corp/coupang_partners#1 is merged, so upstream now transparently falls back to the Retention Corp hosted backend at https://a.retn.kr/v1/public/assist when Coupang Partners API credentials are missing. The k-skill wrapper already passes environment variables through unchanged, so this commit lines up the documented contract with the actual two-path behavior without changing runtime logic.

- SKILL.md and docs/features/coupang-product-search.md describe both execution paths (operator local HMAC vs credentialless hosted fallback), the honored OPENCLAW_SHOPPING_* env vars, the allowlist client-id convention including the k-skill-specific coupang-mcp-fallback value, and the mandatory affiliate disclosure when a.retn.kr/s/ shortlinks or lptag=AF deeplinks appear in responses.
- docs/sources.md adds the hosted assist endpoint and the merged upstream PR so the source surface stays truthful.
- README.md reflects the 선택사항 semantics for the 쿠팡 상품 검색 row and extends the column legend so 선택사항 is distinct from 불필요.
- coupang_partners_mcp.py expands its --help epilog so operators discover the honored upstream env vars without reading the wrapper source; no runtime behavior change.
- scripts/test_coupang_partners_mcp_wrapper.py locks env pass-through as a regression, asserts the new --help contract, and adds an opt-in K_SKILL_COUPANG_SMOKE=1 live smoke test that verifies the credentialless hosted path returns a Coupang deeplink.
- scripts/skill-docs.test.js extends the docs regression to require the hosted assist URL, OPENCLAW_SHOPPING_* env prefix, affiliate disclosure wording, and hosted fallback concept while keeping the yuju777 HF Space negative assertion.

Verified: npm run ci exits 0, live smoke test (K_SKILL_COUPANG_SMOKE=1) returns a.retn.kr/s/ shortlinks via credentialless wrapper, and manual env -u COUPANG_ACCESS_KEY -u COUPANG_SECRET_KEY call returns isRocket+lptag=AF3727577 responses through the hosted fallback.

Refs: #134

* Drop non-allowlisted coupang-mcp-fallback recommendation from hosted fallback docs

Direct probes against https://a.retn.kr/v1/public/assist confirmed that
X-OpenClaw-Client-Id: coupang-mcp-fallback returns HTTP 403 Client is not
allowlisted, while the upstream default openclaw-skill returns HTTP 200.
The default wrapper path already works because upstream falls back to
openclaw-skill, but the explicit recommendation in SKILL.md and the
feature doc was luring users to a 403 path.

Remove the dead recommendation and lock in the working configuration:

- Docs describe openclaw-skill as the upstream-allowlisted default and
  note that k-skill does not override OPENCLAW_SHOPPING_CLIENT_ID.
- Wrapper --help epilog drops the Suggested k-skill value line and
  documents openclaw-skill as the allowlist value in play.
- New skill-docs regression asserts coupang-mcp-fallback is absent from
  SKILL.md, the feature doc, the wrapper, and docs/sources.md while
  openclaw-skill is documented across all three narrative surfaces.
- New Python wrapper regression asserts --help drops the dead value and
  surfaces openclaw-skill so the constraint stays locked.
- Existing env-forwarding test uses openclaw-skill as the pass-through
  sentinel so the repo no longer ships the non-allowlisted string at all.

---------

Co-authored-by: minsing-jin <ironman0722@naver.com>
2026-04-21 09:53:03 +09:00
Jeffrey (Dongkyu) Kim
68e6829052
Sync dev → main: scholarship, public restroom, KBL, Hola Poke + HWP/stock proxy upgrades (#136)
* Add a guided Hola Poke Yeoksam skill without widening repo scope

Issue #120 only needs a repository skill payload, discoverability docs,
and regression coverage. This change adds the new skill, wires it into
existing docs surfaces, and locks the remote-MCP-only contract in tests
so future edits keep the phone-only event flow and verbatim message
relay behavior.

Constraint: The upstream Hola Poke flow lives on a remote MCP server, so this repo should not add proxy/runtime code
Constraint: Tests must be written before refining the new docs/skill wording
Rejected: Add local package or proxy support for Hola Poke | would over-scope a docs-only skill addition
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep this skill limited to 올라포케 역삼점 and treat the MCP response message as the event source of truth
Tested: node --test scripts/skill-docs.test.js --test-name-pattern='hola-poke-yeoksam'
Tested: npm run ci
Tested: Live MCP initialize/tools/list/get_menu/get_shop_info/enter_event(phone_format) smoke checks against https://hola-poke-yeoksam-skill.onrender.com/mcp
Not-tested: Successful live event entry with a real phone number

* Help users find nearby public restrooms from Korean location queries

This adds a new public-restroom-nearby skill and reusable package that resolves a user-provided location, narrows the official 공중화장실정보 dataset by region when possible, and ranks nearby restroom results with opening-time hints and map links.

Constraint: Must use free official/open surfaces without introducing new dependencies
Constraint: Must follow TDD and keep release/docs metadata aligned in the same change
Rejected: Add a proxy route first | direct official CSV access already works and keeps scope narrower
Rejected: Use nationwide-only ranking without regional narrowing | too much noisy data for dense urban anchors
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: If Kakao place-panel or localdata CSV schema changes, update parser fixtures before broad logic changes
Tested: npm run ci; live smoke via searchNearbyPublicRestroomsByLocationQuery('광화문', { limit: 3 }); architect review APPROVED
Not-tested: Non-Seoul live smoke across every regional orgCode

* Pin the Hola Poke MCP contract in repo-owned regression fixtures

The earlier issue #120 regression only matched prose, so this follow-up records the verified remote MCP tool/result snapshot in a checked-in fixture and makes both docs surfaces byte-align to it. That keeps the discoverability docs honest while turning the review claim into a real contract lock for tools/list, get_menu, get_shop_info, and the invalid-phone event flow.

Constraint: The upstream remote MCP server can change independently of this repo
Rejected: Keep prose-only regex checks | would not catch contract drift
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Refresh the fixture, both JSON fences, and the live-smoke evidence together whenever the upstream contract changes
Tested: node --test scripts/skill-docs.test.js --test-name-pattern='hola-poke-yeoksam'; npm run ci; live MCP smoke check against https://hola-poke-yeoksam-skill.onrender.com/mcp (initialize, tools/list, get_menu, get_shop_info, invalid enter_event)
Not-tested: Successful enter_event with a real phone number (intentionally avoided to prevent live event participation)

* Keep nearby restroom lookups resilient to flaky Kakao place panels

The review caught two regressions in the new public-restroom-nearby package: a single broken Kakao panel aborted anchor resolution, and coordinate search dropped maxDistanceMeters before normalization. This change adds targeted regression coverage first, keeps per-candidate HTTP failures recoverable, and hardens request errors with explicit status/url metadata so fallback logic no longer depends on parsing error strings.

Constraint: Must preserve the published package surface and keep the fix scoped to PR #123 follow-up
Rejected: Swallow all panel errors | would hide non-HTTP failures like network faults
Rejected: Parse request error messages for status codes | brittle coupling to string formatting
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep recoverable Kakao panel handling aligned with request() error annotations if request() changes again
Tested: npm test --workspace public-restroom-nearby
Tested: npm run ci
Tested: live smoke searchNearbyPublicRestroomsByLocationQuery('광화문', { limit: 3 })
Tested: LSP diagnostics on packages/public-restroom-nearby/src/index.js and test/index.test.js
Not-tested: Live Kakao fallback against a real upstream 5xx place-panel response

* Keep the Hola Poke contract claims aligned with verified coverage

The reviewed fixture-based regression already locks the documented remote
snapshot, but the docs still implied the enter_event success path had
live proof. Narrow the docs and the regression so they explicitly say the
success fields are pinned by the recorded snapshot while the live smoke
only verifies the invalid-phone retry path.

Constraint: Live success-path verification would trigger a real event entry and is intentionally avoided
Rejected: Leave the broader wording in place | review feedback showed it overstated the live evidence
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If a safe non-mutating success-path probe becomes available, update the docs and fixture wording together
Tested: node --test scripts/skill-docs.test.js --test-name-pattern='hola-poke-yeoksam'; npm run ci; live MCP smoke against https://hola-poke-yeoksam-skill.onrender.com/mcp (initialize, tools/list, get_menu subset, get_shop_info subset, invalid enter_event)
Not-tested: Real enter_event success-path invocation

* Document the restroom distance-cap contract with regression coverage

The approved issue-117 code fix already restored maxDistanceMeters behavior, but the published docs did not lock or explain that contract. This follow-up adds a failing-first doc regression, then updates the feature guide and package README with the verified 100m example so users and future reviewers see the same behavior the package now ships.

Constraint: Must stay scoped to the existing PR #123 follow-up without reopening the implementation surface
Rejected: Leave the behavior implicit in code/tests only | published docs would lag the verified contract
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the public-restroom-nearby docs and skill-docs regression aligned with live maxDistanceMeters smoke evidence if the sample query changes
Tested: node --test scripts/skill-docs.test.js (red then green)
Tested: npm test --workspace public-restroom-nearby
Tested: npm run ci
Tested: live smoke searchNearbyPublicRestroomsByLocationQuery('광화문', { limit: 3 })
Tested: live smoke searchNearbyPublicRestroomsByLocationQuery('광화문', { limit: 3, maxDistanceMeters: 100 })
Tested: architect review APPROVED
Not-tested: Alternative landmark queries with a non-zero maxDistanceMeters hit set

* Expose KRX partial failures instead of misreporting stock lookups

The Korean stock proxy used to silently drop failed market snapshots during
search and could turn an empty holiday trade snapshot into a 502 by falling
back into base-info lookup.

This change surfaces degraded market metadata on partial search success,
short-circuits empty trade snapshots to not_found, and refreshes the user
docs to use a real trading day in examples.

Constraint: KOSPI base-info approval is granted separately from other KRX routes
Constraint: Healthy markets should still return usable search results during a partial outage
Rejected: Return 502 on every partial search failure | hides still-usable markets and breaks current clients unnecessarily
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep degraded search metadata when any market snapshot fetch fails so partial outages stay visible
Tested: npm test --workspace k-skill-proxy
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Not-tested: Live KOSPI base-info behavior after the new KRX permission is approved

* Adopt kordoc for the hwp skill workflow

Issue #119 replaces the previous HWP guidance with kordoc so the skill matches the newer agent-native document flow. The docs and regression tests now center the HWP skill on kordoc parsing, JSON extraction, diffing, form filling, and Markdown-to-HWPX round-tripping, while the install/source references stay in sync.

Constraint: The repository treats skill behavior as documentation contracts backed by regression tests
Constraint: The requested branch/PR flow must target dev with TDD and verified execution evidence
Rejected: Keep @ohah/hwpjs or hwp-mcp as fallback guidance | issue #119 explicitly approves replacing the prior stack with kordoc
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep future hwp skill/docs/tests aligned to a single kordoc-first contract unless a new issue explicitly reintroduces multi-backend routing
Tested: node --test scripts/skill-docs.test.js; npm run ci; temp-dir kordoc roundtrip via markdownToHwpx -> sample.hwpx -> kordoc CLI markdown output; architect review APPROVED
Not-tested: Live parsing of user-provided proprietary HWP/HWPX samples outside the generated roundtrip fixture

* Prevent degraded stock search outages from sticking in cache

Reviewer feedback showed that partial KRX market failures could be cached as full search answers, masking recovery on the next identical request. This change adds a regression that fails first, skips route-level caching for degraded search payloads, and keeps the trade-info empty-snapshot contract documented alongside the partial-failure response semantics.

Constraint: Existing PR #124 already targets dev and must remain the follow-up lane for issue #99
Constraint: Proxy behavior must stay read-only and dependency-free
Rejected: Cache degraded search payloads for a short TTL | still risks transient false negatives during the TTL window
Rejected: Broaden trade-info fallback behavior | empty snapshots should stay explicit not_found results
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep degraded search responses out of the long-lived route cache unless a future design adds explicit revalidation semantics
Tested: npm test --workspace k-skill-proxy; node --test scripts/skill-docs.test.js; npm run ci; explicit buildServer degraded-search recovery repro
Not-tested: Live KRX production endpoints from this branch

* Align HWP docs with the published kordoc surface

The issue #119 follow-up needs the repository contract to match what the
currently published kordoc package actually supports. This narrows the
HWP skill/docs/tests to the verified install requirement and supported
CLI/Node API surfaces, and removes unsupported fill/mcp claims.

Constraint: Published kordoc CLI fails at startup without pdfjs-dist
Constraint: Docs/tests must reflect the current npm package behavior, not intended future features
Rejected: Keep fill/mcp examples with caveats | still documents unsupported entrypoints
Confidence: high
Scope-risk: narrow
Directive: Reintroduce fill/mcp docs only after verifying the published package exposes them in both CLI and Node API
Tested: node --test scripts/skill-docs.test.js; npm run ci; temp-dir clean install smoke; temp-dir kordoc+pdfjs-dist watch/parse/extractFormFields/compare/markdownToHwpx/roundtrip smoke; Claude architect review
Not-tested: Real-world HWPX template that produces non-empty extractFormFields output

* Keep HWP docs runnable against the published kordoc package

The follow-up closes the last runnable-contract gaps from review by documenting the working one-shot npx form and separating Node API examples into a local project install path. The regression suite now locks both install notes so future edits do not drift back to broken command shapes.

Constraint: Published kordoc CLI still requires pdfjs-dist at startup
Constraint: Global NODE_PATH does not make ESM imports from kordoc resolvable in the documented examples
Rejected: Keep bare `npx kordoc` examples | fails in a clean environment
Rejected: Keep global-install Node API guidance | ESM import remains unresolved
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep HWP docs aligned to verified published kordoc surfaces until the package contract changes upstream
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: temp-dir local npm install kordoc pdfjs-dist plus markdownToHwpx -> sample.hwpx -> one-shot kordoc roundtrip smoke
Not-tested: upstream unpublished kordoc features beyond the verified CLI and Node API surfaces

* Add Korean scholarship search skill and reporting workflow (#116)

* Add nationwide scholarship search skill workflow

* Rename scholarship skill to 장학금 주세요 쮜에발

* Fix scholarship skill validation in CI

* Trigger GitHub PR diff refresh after dev rebase on main

* Fix scholarship helper status handling and test coverage

* Use KST as scholarship helper default date basis

* Rename scholarship skill display name

---------

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

* Feature/#121 (#127)

* Recover KakaoTalk mac skill auth when upstream user_id detection fails

Issue #121 reproduces on a real MacBook because `kakaocli auth` can fail even when the encrypted hex-named DB exists. This change adds a thin repo-owned helper that recovers the active user_id from plist revision hashes, caches the validated DB/key tuple, and reuses it for read-only `kakaocli` commands. The skill and feature docs now steer users to the helper when upstream auto-detection stops at candidate key mismatch, and regression tests lock the recovery flow before the implementation.

Constraint: Must stay a thin adapter around upstream kakaocli rather than forking the CLI
Constraint: Must verify on a real local macOS KakaoTalk install where issue #121 reproduces
Rejected: Full kakaocli reimplementation inside k-skill | too broad for the user_id/key-derivation failure scope
Rejected: Docs-only workaround | does not actually fix the broken auth path for users
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep this helper limited to auth/key recovery and read-only passthrough unless upstream gaps widen materially
Tested: python3 -m unittest scripts.test_kakaotalk_mac
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: python3 scripts/kakaotalk_mac.py auth --refresh --max-user-id 800000000 --workers 8 --chunk-size 2000000
Tested: python3 scripts/kakaotalk_mac.py chats --limit 1 --json
Not-tested: Other kakaocli subcommands beyond auth/chats/messages/search/query/schema

* Protect the KakaoTalk helper's safe recovery path

Address the PR follow-up by treating malformed auth cache files as cache misses,
removing write-capable passthrough from the wrapper surface, and redacting
human-readable auth output so the cached SQLCipher key is not echoed back into
terminal history. The docs and regression suite now describe and enforce the
read-only contract that the helper is meant to preserve.

Constraint: Helper must remain a read-only recovery wrapper around local kakaocli access
Rejected: Keep query support with SQL validation | still leaves a risky write-capable escape hatch
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Do not re-expose arbitrary SQL passthrough or print the SQLCipher key in default text output
Tested: python3 -m unittest scripts.test_kakaotalk_mac; node --test scripts/skill-docs.test.js; npm run ci; python3 scripts/kakaotalk_mac.py auth --refresh --max-user-id 800000000 --workers 8 --chunk-size 2000000; python3 scripts/kakaotalk_mac.py chats --limit 1 --json; python3 scripts/kakaotalk_mac.py auth --cache-path <bad-json>; python3 scripts/kakaotalk_mac.py query --help
Not-tested: External automation consumers that depend on shell/json auth output beyond the documented helper flows

* Lock the helper CLI surface against accidental regressions

The approved issue #121 fixes already hardened the KakaoTalk Mac helper, but the test suite still only exercised the passthrough validator directly. Add an explicit parser-level regression so the public CLI contract stays read-only and `query` cannot quietly reappear in future edits.

Constraint: Follow-up is on the existing feature/#121 PR branch and must stay minimal
Rejected: Re-open helper implementation changes | current code already satisfies the approved review findings
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep parser exposure tests aligned with READ_ONLY_COMMANDS whenever helper subcommands change
Tested: python3 -m unittest scripts.test_kakaotalk_mac; node --test scripts/skill-docs.test.js; npm run ci; python3 scripts/kakaotalk_mac.py auth --refresh --max-user-id 800000000 --workers 8 --chunk-size 2000000; python3 scripts/kakaotalk_mac.py chats --limit 1 --json; python3 scripts/kakaotalk_mac.py auth --cache-path <bad-json>
Not-tested: No new production code paths changed in this follow-up

* Honor explicit Kakao auth recovery overrides

The helper now treats manual auth overrides as a cache-bypassing recovery request and rejects invalid brute-force tuning flags at the CLI boundary so users get deterministic behavior instead of stale cached tuples or Python tracebacks. Regression coverage locks both paths before the PR follow-up lands.

Constraint: The helper must remain a thin read-only wrapper around kakaocli auth recovery
Rejected: Require --refresh whenever --user-id/--uuid is passed | worse UX than honoring overrides directly
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep explicit auth overrides ahead of cache reuse unless the CLI contract is redesigned and documented
Tested: python3 -m unittest scripts.test_kakaotalk_mac; node --test scripts/skill-docs.test.js; npm run ci; python3 scripts/kakaotalk_mac.py auth --refresh --max-user-id 800000000 --workers 8 --chunk-size 2000000; python3 scripts/kakaotalk_mac.py chats --limit 1 --json; python3 scripts/kakaotalk_mac.py auth --cache-path <bad-json>; python3 scripts/kakaotalk_mac.py auth --refresh --max-user-id -1; python3 scripts/kakaotalk_mac.py auth --refresh --workers 2 --chunk-size 0 --max-user-id 10; python3 scripts/kakaotalk_mac.py auth --cache-path <temp-cache> --user-id 999; python3 scripts/kakaotalk_mac.py auth --cache-path <temp-cache> --uuid <live-uuid>
Not-tested: Manual override success with a truly alternate valid user_id/uuid pair on a multi-account local install

* Feature/#129 (#131)

* Add official KBL results support so basketball queries use live league data

Issue #129 needs a read-only skill and reusable package for KBL schedules, results, and standings. The implementation follows the existing sports package pattern and uses the league's live JSON APIs after verifying they respond successfully in real requests.

Constraint: Must use official KBL JSON surfaces before considering scraping
Constraint: Packaging changes must pass npm run ci and include docs plus Changesets updates
Rejected: Browser scraping first | official api.kbl.or.kr endpoints are live and simpler to maintain
Rejected: Reuse KBO/K League package shapes verbatim | KBL payload and team/status fields differ materially
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep seasonGrade=1 as the default KBL path unless future docs/tests explicitly widen to D-League flows
Tested: npm run ci; npm run lint --workspace kbl-results; npm test --workspace kbl-results; live getKBLSummary("2026-04-01", { team: "KCC", includeStandings: true })
Not-tested: Historical standings snapshots for past seasons via alternative KBL endpoints

* Prevent optional standings lookups from over-fetching the KBL API

The new kbl-results summary helper exposes includeStandings=false, so the
regression suite now proves that path stays schedule-only and never calls
the standings endpoint when the caller opts out.

Constraint: The KBL package should preserve the caller's no-standings contract
Rejected: Rely on manual inspection of the helper options | a targeted test is cheaper and safer
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep includeStandings=false side-effect free unless the public API contract changes explicitly
Tested: npm test --workspace kbl-results; npm run lint --workspace kbl-results
Not-tested: Full-repo CI before stacking this commit onto the rebased branch

---------

Co-authored-by: minsing-jin <ironman0722@naver.com>
2026-04-18 11:50:47 +09:00
Jeffrey (Dongkyu) Kim
4c1989fdb2 Add product-report proxy route for health food manufacturing data (I0030)
The existing health-food-ingredient route only covers individually-approved
ingredients (I-0040/I-0050), missing gazetted ingredients like psyllium husk
(차전자피). I0030 (건강기능식품 품목제조 신고사항) has 44k+ registered
products with raw materials, functionality, intake precautions, and standards.

The new /v1/mfds/food-safety/product-report route queries I0030 with
server-side PRDLST_NM and RAWMTRL_NM filters in parallel, deduplicates
by report number, and returns normalized results. Live-verified with
FOODSAFETYKOREA_API_KEY returning 차전자피 products with full precautions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 02:44:25 +09:00
Jeffrey (Dongkyu) Kim
9dd5438f5f Add health-food ingredient and inspection-fail proxy routes for food-safety
The existing food-safety skill only queried recall/sales-suspension records
(I0490), which was too edge-case for "can I eat this?" questions. This adds
two new foodsafetykorea.go.kr surfaces through k-skill-proxy:

- /v1/mfds/food-safety/health-food-ingredient (I-0040 + I-0050):
  official health food ingredient recognition, daily intake, precautions
- /v1/mfds/food-safety/inspection-fail (I2620):
  domestic inspection failure records with test results and standards

All three routes share the same FOODSAFETYKOREA_API_KEY with sample-feed
fallback. Python helper gains health-food-ingredient and inspection-fail
subcommands. SKILL.md updated with new surfaces, workflow, and CLI examples.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 02:22:50 +09:00
Jeffrey (Dongkyu) Kim
2550d19974 Route MFDS drug-safety and food-safety lookups through k-skill-proxy
Move direct API calls out of the skill Python scripts and into shared
proxy routes so end-users no longer need their own DATA_GO_KR_API_KEY
for MFDS surfaces. Adds mfds.js helper, proxy tests, and updates all
docs and setup guidance to reflect the hosted proxy workflow.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 00:07:18 +09:00
Jeffrey (Dongkyu) Kim
5e34ee041b Normalize NEIS proxy failures and repair the selective install command
The GeekNews follow-up review found two merge blockers outside the new helper itself: rejected NEIS upstream fetches escaped as Fastify 500s, and the documented selective install block split into two shell commands. This change adds regression coverage first, catches rejected NEIS fetches at the route boundary with the established proxy_error/502 contract, and locks the multiline install snippet so copy-paste works.

Constraint: Keep NEIS proxy failures aligned with existing public proxy error handling
Constraint: Preserve the published GeekNews verification commands and current PR scope
Rejected: Wrapping fetch inside the NEIS helper functions | route-level handling matches the surrounding Fastify endpoint pattern
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep public proxy routes fail-closed on rejected upstream fetches and keep multiline install snippets copy-pasteable
Tested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_geeknews_search; node --test scripts/skill-docs.test.js; node --test packages/k-skill-proxy/test/server.test.js; python3 scripts/geeknews_search.py list --limit 3; python3 scripts/geeknews_search.py search --query Claude --limit 3; python3 scripts/geeknews_search.py detail --id 28439; node NEIS rejected-fetch repro; npm run ci
Not-tested: Live NEIS upstream success responses against the real public API in this follow-up
2026-04-13 00:28:34 +09:00
hyeongr
d3d048e822 📝 docs: 생활쓰레기·급식 hosted proxy 문서 정합성 및 skill-docs 회귀 테스트
- setup.md·k-skill-setup: 생활쓰레기/급식을 기본 hosted·unset base URL 문구에 포함, 서버 키(DATA_GO_KR·KEDU) 명시
- 표·다음 문서: 생활쓰레기 행·가이드 링크, pageNo/numOfRows 필수
- proxy README·feature doc·household-waste 스킬: 키·페이지네이션·400 계약 보강
- skill-docs.test.js: hosted 생활쓰레기/급식 흐름 회귀 테스트, 예시 secrets에 KEDU_INFO_KEY 금지

Made-with: Cursor
2026-04-11 03:34:11 +09:00
hyeongr
23dd424dd7 feat(k-skill-proxy): 생활쓰레기 페이지네이션 검증 및 문서 보강
- /v1/household-waste/info에 pageNo·numOfRows 필수, 값은 1·100만 허용(미충족·비정수 문자열은 400)
- validateHouseholdWastePaginationQuery 오동작 수정
- validate-skills.sh에서 .cursor·.vscode 디렉터리 제외
- household-waste·k-skill-proxy 문서, 스킬, 패키지 README에 NEIS·생활쓰레기 curl 예시 정리

Made-with: Cursor
2026-04-11 03:02:09 +09:00
hyeongr
0ceff05e20 fix: PR #102 관련 잘못된 머지 충돌 처리 및 API 키 누락 방지를 위한 proxy 서버에 설명 추가 2026-04-10 19:31:50 +09:00
hon2be
1e89bace8b
Merge branch 'dev' into feature/k-schoollunch-menu 2026-04-10 13:53:51 +09:00
hyeongr
eacd087359 🔀 origin/main 병합 및 NEIS·문서 충돌 정리
- upstream 생활쓰레기 프록시/스킬·skill-docs 변경 반영
- README 표에 학교 급식 행 복원, security에 KEDU_INFO_KEY·household 라우트 문구 정리
- NEIS 프록시 단위 테스트 블록 복원

Made-with: Cursor
2026-04-10 13:25:02 +09:00
hyeongr
fb9a5c6f0d NEIS 급식·학교검색 프록시, k-schoollunch-menu 스킬 및 문서
- KEDU_INFO_KEY로 /v1/neis/school-search, /v1/neis/school-meal 중계
- 시도교육청 자연어 해석(neis-office-codes.js)
- k-schoollunch-menu 스킬, README·설치/설정/보안·프록시 문서 반영
- docs/adding-a-skill.md 스킬 추가 가이드

Made-with: Cursor
2026-04-10 13:14:15 +09:00
Jeffrey (Dongkyu) Kim
7710281ade Merge main into dev: sync household-waste-info and README trim/auth-collapse from PR #82 2026-04-08 16:18:23 +09:00
hon2be
73f995b343
feat: 생활쓰레기 조회 스킬 및 프록시 라우트 구현 (#82)
*  feat: 생활쓰레기 조회 스킬 및 프록시 라우트 구현

생활쓰레기 조회 스킬 문서와 기능 가이드를 추가하고, 프록시 라우트를 구현해 조회 흐름을 완성했다. 설치/설정 문서도 스킬 사용 흐름에 맞게 정리했다.

Made-with: Cursor

* fix(household-waste-info): force returnType=json, add proxy tests, fix SKILL.md newline

- Drop user-supplied returnType and force "json" upstream so the cache key
  (which omits returnType) cannot be poisoned by alternate response shapes.
- Add server tests covering: missing SGG_NM (400), missing API key (503),
  serviceKey injection + cache hit on second call, returnType=xml override
  ignored, upstream non-200 surfaced as 502.
- Add trailing newline to household-waste-info/SKILL.md.

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

* docs(household-waste-info): align skill contract with proxy reality

vkehfdl1's review on PR #82: skill/docs claimed support for
cond[DAT_CRTR_YMD::*] / cond[DAT_UPDT_PNT::*] filters and an optional
returnType, but the proxy only forwards pageNo, numOfRows, and
cond[SGG_NM::LIKE], and forces returnType=json. Typical user queries
("강남구 쓰레기 배출 요일") only need 시군구 검색, so shrink the
documented contract to match the proxy instead of widening pass-through.

- household-waste-info/SKILL.md: list only proxy-supported params, note
  returnType is server-forced, fix failure modes.
- docs/features/household-waste-info.md: switch base example to the
  proxy route, drop the bare upstream curl, call out unsupported
  filters explicitly.
- docs/install.md, docs/security-and-secrets.md, k-skill-setup/SKILL.md:
  describe the skill as calling the proxy /v1/household-waste/info
  route rather than the raw upstream endpoint.

* docs(readme): collapse auth column to user-login binary

The 인증/시크릿 column mixed user-side credentials, proxy URL hints,
and "use this fallback" notes — confusing for end users who only need
to know "do I have to log in or not?". Operator-managed secrets that
ship in k-skill-proxy are not the user's problem.

- Rename column to "사용자 로그인" with a one-line preface explaining
  the new contract.
- Reclassify proxy-fronted skills (서울 지하철, 한강 수위, 부동산,
  생활쓰레기, 가장 싼 주유소, 한국 법령 remote endpoint) to 불필요.
- Only SRT, KTX, 토스증권 keep 필요 (real per-user account login).
- Tighten the household-waste-info row to use the proxy-route phrasing
  consistent with the rest of the docs in this PR.
- Update skill-docs tests to assert the new binary classification for
  서울 지하철 and 한국 법령 rows.

* docs(readme): trim feature table descriptions to user-facing capability

The 설명 column was leaking implementation details — k-skill-proxy
routing notes, upstream package names, anti-bot helper mentions —
that don't help a user decide whether the skill does what they want.

Rewrite each row to state only "what this skill does for the user",
dropping references to k-skill-proxy, upstream library names
(real-estate-mcp, kakaocli, daiso-mcp, coupang-mcp, tossctl,
korean-law-mcp, Dynapath helper, Kakao Map anchor, Opinet, etc.) and
proxy route paths. The 사용자 로그인 column already captures the
"do I need credentials?" question, so the description is free to
focus on the capability itself.

---------

Co-authored-by: hyeongr <honeybee@hyeongrui-MacBookPro.local>
Co-authored-by: Jeffrey (Dongkyu) Kim <vkehfdl1@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:08:24 +09:00
Jeffrey (Dongkyu) Kim
baea3f2c24 fix(household-waste-info): force returnType=json, add proxy tests, fix SKILL.md newline
- Drop user-supplied returnType and force "json" upstream so the cache key
  (which omits returnType) cannot be poisoned by alternate response shapes.
- Add server tests covering: missing SGG_NM (400), missing API key (503),
  serviceKey injection + cache hit on second call, returnType=xml override
  ignored, upstream non-200 surfaced as 502.
- Add trailing newline to household-waste-info/SKILL.md.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 15:22:48 +09:00
hyeongr
bcd32ac7cc feat: 생활쓰레기 조회 스킬 및 프록시 라우트 구현
생활쓰레기 조회 스킬 문서와 기능 가이드를 추가하고, 프록시 라우트를 구현해 조회 흐름을 완성했다. 설치/설정 문서도 스킬 사용 흐름에 맞게 정리했다.

Made-with: Cursor
2026-04-07 19:35:01 +09:00
Jeffrey (Dongkyu) Kim
d5034daaaf
Feature/#57 (#69)
* Hide KMA forecast keys behind the public proxy

Add a Korea weather skill plus a new k-skill-proxy route for the
KMA short-term forecast API. The route accepts grid coordinates or
lat/lon, defaults to the latest safe base time, and keeps the user
workflow keyless while the proxy owns the upstream credential.

Constraint: KMA short-term forecast requires a service key and 5km grid parameters
Constraint: Must keep the client flow dependency-free and free of user API issuance
Rejected: Direct client-side KMA calls | would force end users to issue and store their own keys
Rejected: Add a third-party geocoder dependency | unnecessary for v1 when lat/lon-to-grid conversion is enough
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep future KMA integrations on explicit allowlisted proxy routes and avoid documenting upstream keys for clients
Tested: npm run ci; local HTTP smoke test for /v1/korea-weather/forecast with mocked upstream fetch; npx tsc --noEmit --pretty false --project /Users/jeffrey/Projects/k-skill/tsconfig.json
Not-tested: Live KMA upstream call with a production service key

* Reject invalid weather coordinates before proxying

The korea-weather forecast route already normalized valid lat/lon requests, but
out-of-range inputs could still project to NaN grid values and consume upstream
quota. This change rejects invalid latitude/longitude pairs at the boundary and
locks the behavior with a regression that proves invalid coordinates never
reach fetch.

Constraint: Public proxy endpoints must reject malformed caller input before upstream calls
Rejected: Allow upstream KMA failures to handle invalid coordinates | wastes quota and returns opaque errors
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep coordinate validation local to query normalization so cache keys and upstream URLs only see finite grid values
Tested: node --test packages/k-skill-proxy/test/server.test.js; local app.inject smoke for /v1/korea-weather/forecast with mocked fetch; npx tsc --noEmit --pretty false --project /Users/jeffrey/Projects/k-skill/tsconfig.json; npm run ci
Not-tested: Live KMA upstream behavior for invalid coordinates (intentionally blocked before proxying)

* Protect proxied public-data API keys with HTTPS transport

The new korea-weather route already hid the KMA service key behind the proxy, but
it was still forwarding that key over plaintext HTTP. Switching the shared
data.go.kr proxy base URL to HTTPS keeps the existing request contract intact
while closing the transport exposure. Regression coverage now locks the weather
route and direct KMA proxy helper to HTTPS so the downgrade does not regress.

Constraint: The follow-up had to stay minimal on the existing PR branch
Rejected: Add a KMA-only base URL constant | unnecessary divergence from the shared data.go.kr transport setting
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep proxied upstreams on HTTPS whenever the provider supports it, especially when operator-managed keys are injected server-side
Tested: node --test packages/k-skill-proxy/test/server.test.js; local buildServer().inject smoke with mocked fetch; npx tsc --noEmit --pretty false --project /Users/jeffrey/Projects/k-skill/tsconfig.json; npm run ci
Not-tested: Live network call to KMA upstream with a real service key

* Merge dev into main: new skills & proxy enhancements (#72)

* Remove client-side Seoul subway key setup

Route Seoul subway arrival lookups through k-skill-proxy so the
hosted proxy owns the Seoul Open Data upstream key and end users
only need the proxy base URL. Add proxy route coverage, update
skill/docs guidance, and align setup materials with the hosted
proxy flow used for fine dust.

Constraint: Must keep the proxy public, read-only, and dependency-free
Constraint: Must satisfy TDD-first verification and ship on feature/#35 targeting dev
Rejected: Add a separate client helper package | unnecessary extra layer for a single proxy route
Rejected: Keep SEOUL_OPEN_API_KEY as an end-user requirement | defeats the issue goal
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep the Seoul subway proxy surface limited to station-arrival passthrough unless tests/docs expand the contract
Tested: npm run ci; local proxy runtime on 127.0.0.1:4120 for /health and /v1/seoul-subway/arrival on 2026-03-31 with an invalid upstream key
Not-tested: Live success response with a valid Seoul Open API key
Related: #35

* Prevent broken Seoul subway proxy defaults before hosted rollout

The Seoul subway proxy endpoint code is present locally, but the hosted public route is not live yet. This change turns the user-facing subway docs back into an explicit proxy configuration flow, replaces the misleading hosted default in setup examples, and keeps subway proxy examples on self-host/local URLs until rollout is verified.

Constraint: Hosted k-skill-proxy.nomadamas.org/v1/seoul-subway/arrival is not live yet
Rejected: Keep the hosted Seoul subway URL as the default path | would send default users to a 404 route
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Do not restore a hosted Seoul subway default until the public proxy route is deployed and smoke-verified
Tested: node --test scripts/skill-docs.test.js; npm run ci; local proxy smoke on 127.0.0.1:4120 with stubbed Seoul upstream (GET /health, GET /v1/seoul-subway/arrival?stationName=강남)
Not-tested: Live hosted proxy smoke after deployment

* Reduce Seoul subway proxy upstream pressure with request caching

The public subway arrival route already normalized caller input but still re-fetched the Seoul upstream for every identical poll. This change adds a short-TTL cache keyed from the normalized subway query, annotates JSON responses with cache metadata, and locks the repeated-read collapse with a regression that proves alias/default normalization still reuses the cached result.

Constraint: The endpoint must stay public/no-auth while protecting a shared server-side SEOUL_OPEN_API_KEY from unnecessary repeated upstream hits
Rejected: Add a new shared cache abstraction for proxy metadata | unnecessary for this narrow fix and would enlarge the diff
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep subway cache keys tied to normalizeSeoulSubwayQuery output so alias/default query forms continue collapsing to one upstream request
Tested: node --test packages/k-skill-proxy/test/server.test.js
Tested: npm run ci
Tested: Local proxy runtime on 127.0.0.1:4120 with SEOUL_OPEN_API_KEY=test-seoul-key and stubbed fetch for /health plus repeated /v1/seoul-subway/arrival requests
Not-tested: Live hosted proxy rollout state

* Document OpenClaw support in the public compatibility list

The approved scope for issue #29 was narrowed to README support messaging,
so this change adds OpenClaw/ClawHub to the supported-client line and locks
that wording with a regression test in the docs suite.

Constraint: Issue #29 was approved as a README-only compatibility/docs change
Rejected: Broader install-doc updates | out of approved scope for this issue
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep OpenClaw support wording aligned with the supported-client README line and its docs regression test
Tested: Focused Node docs regression, npm run lint, npm run typecheck, npm run build, npm test
Not-tested: Live OpenClaw or ClawHub install flow (issue scope was documentation-only)

* Protect README client-support claims from ClawHub regressions

The README already advertises OpenClaw/ClawHub, but the docs
regression only matched OpenClaw. Tighten the assertion to the
exact supported-client fragment so a future edit cannot silently
remove ClawHub while keeping the issue verification command stable.

Constraint: PR #39 already publishes a verification command keyed to the current test name
Rejected: Rename the test to mention ClawHub explicitly | would drift from the published verification command
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep this regression synchronized with the README supported-client line whenever that copy changes
Tested: node --test scripts/skill-docs.test.js --test-name-pattern 'README advertises OpenClaw among the supported coding agents'
Tested: npm run lint
Tested: npm run typecheck
Tested: npm run build
Tested: npm test
Tested: lsp diagnostics for scripts/skill-docs.test.js (0 errors)
Not-tested: N/A

* Make Coupang shopper research usable without pretending scraping is stable

Coupang blocks unattended shopper queries in this environment, so the new package focuses on the durable pieces we can ship honestly: official URL builders, browser-captured HTML parsers, and explicit automation probes. The docs and skill now explain the seller-API limitation, the verified anti-bot behavior, and the browser-capture fallback expected by callers.

Constraint: No general shopper Open API surfaced in Coupang's official developer docs

Constraint: Headless/direct retrieval is anti-bot blocked in verified local probes

Rejected: Bundle a scraping bypass or hidden browser dependency | violates repo policy and would over-claim reliability

Confidence: high

Scope-risk: moderate

Reversibility: clean

Directive: Keep probe/docs behavior aligned with fresh live verification before claiming unattended Coupang access works

Tested: npm run ci; live probeAutomation(query=생수) with direct fetch + Playwright-core browserFetchHtml; LSP diagnostics on changed JS/test files

Not-tested: Headed/manual browser sessions with a human-authenticated Coupang context

* Keep the Coupang workspace installable and its entrypoint honest

Review follow-up found two contract gaps in the first issue #36 rollout: fresh installs were missing the new workspace link in package-lock, and the package README documented parser helpers that were not exported from the package entrypoint. Refreshing the lockfile and re-exporting the parsers keeps npm ci and consumer imports aligned with the published API.

Constraint: npm ci must succeed from a clean checkout
Rejected: Narrow the README API list to only client helpers | would hide useful parsers that the package already ships and tests
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: When adding a new workspace, refresh package-lock and verify the package entrypoint matches README public API claims
Tested: npm run ci; workspace coupang-product-search tests; LSP diagnostics on coupang-product-search JS/test files
Not-tested: published npm install from registry (local pack dry-run only)

* Keep Coupang anti-bot docs honest as probe results drift

A same-day PR rerun showed the published mobile probe snapshot was already stale, so the follow-up locks the 403/access-denied mobile path with regression coverage and softens the docs/skill contract to describe blocked outcome classes instead of one frozen signature. The published guidance now also explains that browser results only appear when browserFetchHtml is injected, matching what a clean checkout can actually reproduce.

Constraint: Coupang anti-bot responses vary by edge/challenge between reruns on the same day
Rejected: Freeze one exact mobile probe snapshot | same-day reruns already diverged
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Refresh Coupang anti-bot docs only with same-day live probe evidence, and prefer blocked outcome classes over a single exact signature when reruns disagree
Tested: npm run lint; npm run typecheck; npm test; npm run ci; live probeAutomation("생수") with direct fetch + Playwright-core browser fetch
Not-tested: Non-Chrome Playwright-core executables

* Keep the Coupang skill honest about clean-checkout browser probes

The approved follow-up already documented that clean-checkout probeAutomation runs leave browser unset unless a browser fetcher is injected, but the skill text itself had not made that null contract explicit. This commit adds a regression test that locks the browser-null wording across the published docs surfaces and updates the skill so the stated behavior matches the package implementation and clean-checkout reality.

Constraint: probeAutomation() only populates browser when browserFetchHtml is supplied by the caller
Rejected: Leave the skill wording implicit and rely on README examples alone | the skill could drift away from the shipped package contract again
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the README, feature doc, and skill aligned whenever the probeAutomation browser contract changes
Tested: node --test packages/coupang-product-search/test/index.test.js; npm run lint; npm run typecheck; npm test; npm run ci; live probeAutomation("생수") with direct fetch + Playwright-core browser fetch; codex exec review --uncommitted
Not-tested: Alternative browser engines beyond local Google Chrome for the manual browserFetchHtml probe

* Keep Coupang browser probe docs aligned with manual verification paths

The approved PR #40 follow-up still had one wording gap: the skill said when browser results appear, but it did not explicitly say those populated results come from an injected manual/external browserFetchHtml path. This change locks that contract with a failing regression first, then updates the skill text to match the already-shipped README/feature-doc guidance and runtime behavior.

Constraint: clean-checkout probeAutomation() must keep browser null unless browserFetchHtml is supplied
Rejected: change runtime browser probe behavior | approved follow-up was docs/test only and runtime contract is already correct
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: keep Coupang probe docs aligned with the shipped browserFetchHtml injection contract; do not imply built-in browser probing in clean checkouts
Tested: node --test packages/coupang-product-search/test/index.test.js; npm run lint; npm run typecheck; npm test; npm run ci; live probeAutomation("생수") with direct fetch + Playwright-core browser fetch; lsp diagnostics on changed files; architect review approved
Not-tested: alternate browser engines beyond local Google Chrome + playwright-core manual runner

* Route Korean law lookups through korean-law-mcp

Add a documentation-first korean-law-search skill and lock the repo docs around the rule that Korean law queries must go through korean-law-mcp rather than a new in-repo package.

The change updates install/setup/security guidance, publishes the new feature doc, and adds regression tests so future edits keep the LAW_OC + korean-law-mcp contract intact.

Constraint: Issue #41 requires korean-law-mcp for every Korean law lookup
Constraint: Must not add a new npm or python package in this repository
Rejected: Add a repo-local law package | violates the no-new-package requirement and duplicates upstream MCP work
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep Korean law lookup guidance pinned to korean-law-mcp unless issue requirements explicitly change
Tested: node --test scripts/skill-docs.test.js
Tested: npm install -g korean-law-mcp && korean-law list && korean-law help search_law
Tested: npm run ci
Tested: npx tsc --noEmit --pretty false --project /Users/jeffrey/Projects/k-skill/tsconfig.json
Not-tested: live search_law/get_law_text against a real LAW_OC credential

* Clarify Korean law setup modes to avoid credential confusion

A review found the new korean-law-search docs treated LAW_OC as an unconditional prerequisite even though the upstream remote MCP endpoint is configured separately from the local CLI/server path. This update makes the docs and regression tests mode-specific: local CLI/MCP uses LAW_OC, while the remote endpoint stays a korean-law-mcp-only url fallback without a user-supplied credential.

Constraint: Upstream korean-law-mcp uses LAW_OC on the local CLI/server path while the documented remote MCP endpoint is configured with url only
Rejected: Keep LAW_OC mandatory for every korean-law-mcp mode | contradicts upstream docs and reviewer evidence
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep README/setup/skill docs and regression tests aligned on the local-vs-remote korean-law-mcp contract
Tested: node --test scripts/skill-docs.test.js; npm install -g korean-law-mcp && korean-law list && korean-law help search_law; npx tsc --noEmit --pretty false --project /Users/jeffrey/Projects/k-skill/tsconfig.json; npm run ci; lsp_diagnostics scripts/skill-docs.test.js
Not-tested: Live remote MCP endpoint connection against https://korean-law-mcp.fly.dev/mcp

* Record issue #41 follow-up after approved verification

The approved korean-law-search change was already present on feature/#41, so this follow-up records a fresh verification pass and updates the PR without introducing unnecessary code churn.

Constraint: Existing branch already contained the approved implementation
Rejected: Touch repo files without need | would create noise without improving behavior
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the korean-law-mcp-only + mode-specific LAW_OC contract aligned with upstream docs before changing these surfaces again
Tested: node --test scripts/skill-docs.test.js; npx tsc --noEmit --pretty false --project /Users/jeffrey/Projects/k-skill/tsconfig.json; npm install -g korean-law-mcp && korean-law list && korean-law help search_law; npm run ci
Not-tested: Live korean-law queries that require a real LAW_OC or remote endpoint session

* Keep the Korean law feature diff merge-ready

Verification uncovered trailing whitespace in the new korean-law feature guide when checking the branch diff. I added a regression to the skill docs suite and removed the whitespace so the approved documentation contract stays clean and reviewable.

Constraint: Preserve the existing korean-law-mcp + mode-specific LAW_OC contract without widening scope
Rejected: Leave the whitespace issue as-is | git diff --check stayed dirty on the branch
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep docs/features/korean-law-search.md free of trailing whitespace so diff hygiene stays enforced by the regression
Tested: node --test scripts/skill-docs.test.js; npx tsc --noEmit --pretty false --project /Users/jeffrey/Projects/k-skill/tsconfig.json; npm install -g korean-law-mcp && korean-law list && korean-law help search_law; npm run ci; git diff --check
Not-tested: Live credentialed LAW_OC search execution against the upstream API

* Keep Korean law skill guidance aligned with supported lookups

The approved issue #41 feature already worked, but the shipped skill text still under-described ordinance and interpretation lookups compared with the documented capability set. This follow-up tightens the skill copy and locks that contract with a doc regression so the branch stays merge-ready without changing the underlying korean-law-mcp routing rules.

Constraint: Must preserve the existing korean-law-mcp-only and mode-specific LAW_OC setup contract
Rejected: Leave the skill wording as-is | drift from the documented lookup surface would remain
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the skill examples and doc regression aligned whenever supported korean-law-mcp search surfaces change
Tested: node --test scripts/skill-docs.test.js
Not-tested: live LAW_OC-backed upstream API queries

* Keep Korean law completion guidance in sync with enforced lookups

The previous follow-up aligned the main skill examples, but the done-criteria text still left interpretation and ordinance routing implicit. This commit makes the completion checklist explicit and extends the doc regression so future edits cannot silently drop those lookup paths.

Constraint: Must stay within the existing issue #41 korean-law-mcp-only contract
Rejected: Leave the done checklist implicit | reviewers and future edits could drift from the enforced lookup set
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: When the supported Korean-law lookup set changes, update the done checklist and regression together
Tested: node --test scripts/skill-docs.test.js
Not-tested: full CI before commit

* Enable repeatable used-car price lookups from a rental-company source

Issue #46 required surveying major Korean rental companies before implementation and then choosing the easiest stable provider. SK렌터카 다이렉트 exposes 타고BUY inventory in public Next.js page data, so the feature stays dependency-free while still supporting live repeated lookups and documented provider rationale.

Constraint: Must compare major Korean rental companies before implementation
Constraint: Must verify 10+ live lookups against a real provider surface
Rejected: 롯데오토옥션 as v1 provider | public list contract was unstable and legacy .do flows returned inconsistent or 404 pages
Rejected: 레드캡렌터카 as v1 provider | no public used-car inventory or API surface was found
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep the v1 provider read-only and inventory-snapshot-based unless a stable documented public API is confirmed
Tested: npm run ci
Tested: Live 10-query run against https://www.skdirect.co.kr/tb at 2026-04-02T07:22:46Z
Tested: LSP diagnostics on affected files
Not-tested: Seller-specific detail drilldowns or non-SK providers
Related: #46

* Keep used-car-price-search releasable after merge

Review feedback found that the new used-car workspace would not ship because it lacked a changeset, and the fallback install docs still omitted the runtime package. This follow-up adds regression coverage first, then restores both release and install-path coverage with the smallest possible diff.

Constraint: New publishable workspaces must be wired through Changesets to reach npm release automation
Constraint: Fallback install docs must list runtime packages users need when skill files are present but global packages are missing
Rejected: Fix only the changeset gap | would leave the documented fallback install path incomplete
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep used-car-price-search in both the fallback global install example and a Changesets entry whenever release wiring changes
Tested: node --test scripts/skill-docs.test.js; npx changeset status; live 10-query used-car run against SK direct at 2026-04-02T07:38:44.949Z; npm run ci; LSP diagnostics on used-car package files and scripts/skill-docs.test.js; architect verification
Not-tested: No additional live provider permutations beyond the verified 10-query smoke run

* Keep used-car verification docs resilient to live inventory churn

A fresh rerun showed the SK direct inventory total continues to move during the day, so the feature doc now records the verified smoke-run timestamp without freezing a brittle exact count. The regression suite was updated first so the docs stay variability-aware and diff-clean in future follow-ups.

Constraint: Live SK direct inventory totals change over time even when the parsing contract stays stable
Rejected: Keep documenting a fixed total from one smoke run | it immediately drifted and made the docs stale
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the used-car live verification note timestamped and variability-aware instead of pinning an exact inventory total
Tested: node --test scripts/skill-docs.test.js; git diff --check; npm run ci; npx changeset status; live 10-query run against https://www.skdirect.co.kr/tb at 2026-04-02T07:59:41.391Z; LSP diagnostics on scripts/skill-docs.test.js; architect verification
Not-tested: No additional content changes outside the used-car feature doc

* Keep used-car query summaries honest when results are limited

The review found that query-level stats were being computed from the
post-limit slice, so common searches like 아반떼 under-reported both
match counts and price ranges. This change locks the regression first,
then computes the full filtered set before slicing only the returned
items.

Constraint: PR #48 must preserve the existing API shape while fixing the review-blocking stats bug
Rejected: Expanding the response with separate limited/full summary fields | unnecessary API churn for a targeted bug fix
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Query-level summary and matchedCount must stay derived from the full filtered set, not the display limit slice
Tested: npm test --workspace used-car-price-search; npm run ci; git diff --check; npx changeset status; live SK direct smoke run on 2026-04-02T08:23:59Z; LSP diagnostics on used-car-price-search source and test files; architect verification APPROVED
Not-tested: limit=0 semantics remain unchanged and still coerce to the default limit
Related: PR #48

* Keep korean-law-search available during upstream outages (#45)

Issue #44 adds Beopmang as the documented fallback when the primary korean-law-mcp path is unavailable, and locks the new routing into the doc regression suite so future edits do not silently revert the policy.

Constraint: Existing guidance must still prefer korean-law-mcp and keep LAW_OC scoped to the local CLI/MCP path
Rejected: Add a repo-local Beopmang client package | issue only requires fallback registration, not a new implementation surface
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the primary-first rule intact; Beopmang is an outage fallback, not the default path
Tested: node --test scripts/skill-docs.test.js; npm run ci; korean-law list; python3 live Beopmang search smoke for 관세법
Not-tested: Live Beopmang MCP handshake from a GUI MCP client

* Replace coupang scraping package with coupang-mcp server integration

Drop the browser-based scraping package (packages/coupang-product-search)
and switch to the uju777/coupang-mcp MCP server for all Coupang product
searches. This removes the anti-bot workaround complexity and provides
8 ready-to-use tools via MCP Streamable HTTP with no API key required.

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

* Add disclaimer to used-car-price-search README

Clarify that the package queries SK렌터카 타고BUY public data with
no affiliation or sponsorship, and that ad/partnership inquiries
are welcome.

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

* Refactor used-car-price-search into provider-based architecture

SK 타고BUY 전용 로직을 src/providers/sk-tagobuy.js로 분리하고,
공급자를 수평 확장할 수 있는 registry 구조로 전환한다.

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

* Ship LCK analytics inside k-skill's managed release flow

Adapt jerjangmin's upstream lck-analytics skill/package into a new
workspace and skill pack, wire docs/install/release surfaces, and add
regression fixtures/tests plus script smoke coverage so the feature is
verifiable before publish.

Constraint: Upstream package is not published to npm yet
Constraint: Must preserve attribution to original source and author in shipped docs
Rejected: Keep the upstream lck-results install wording in k-skill | conflicts with repo workspace/package naming
Rejected: Ship only the npm package without the local skill scripts | issue explicitly requested the skill as well
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep Changesets as the only version-bump path for lck-analytics; do not hand-edit versions for release
Tested: node --test packages/lck-analytics/test/index.test.js scripts/skill-docs.test.js
Tested: npm run lint --workspace lck-analytics
Tested: npm test --workspace lck-analytics
Tested: live getLckSummary('2026-04-01', { team: '한화', includeStandings: true })
Tested: node lck-analytics/scripts/sync-oracle.js --csv lck-analytics/samples/oracle-lck-sample.csv --cache .tmp/lck-cache && node lck-analytics/scripts/build-match-report.js --date 2026-04-01 --team 한화 --cache .tmp/lck-cache && node lck-analytics/scripts/analyze-live-game.js --game game-1 --window packages/lck-analytics/test/fixtures/live-window-game-1.json --details packages/lck-analytics/test/fixtures/live-details-game-1.json --cache .tmp/lck-cache
Tested: npm run ci
Tested: npx tsc --noEmit --project /Users/jeffrey/Projects/k-skill/tsconfig.json
Not-tested: Riot live feed behavior for arbitrary future game ids outside the fixture-backed smoke path

* Enable policy-aware Korean spell checking from the official Nara surface

Add a skill guide and Python helper that use the approved old_speller HTML flow, chunk long text conservatively, and report original/suggestion/reason deltas. The docs also record the public-site limits, Cloudflare behavior, and non-commercial usage policy so agents do not overreach the free surface.

Constraint: Public site is HTML-only and may return 403 to non-browser clients
Constraint: Must not add new dependencies or high-volume crawling behavior
Rejected: Node fetch client | Cloudflare returned 403 in this environment
Rejected: Paid API integration | no public contract or credentials were available for this task
Confidence: medium
Scope-risk: moderate
Reversibility: clean
Directive: Keep usage low-rate and non-commercial unless supplier-approved API terms are added
Tested: npm run lint
Tested: npm run typecheck
Tested: npm test
Tested: npm run build
Tested: python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json
Tested: python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json
Not-tested: Paid API/order flow
Not-tested: High-volume commercial workloads
Not-tested: Non-UTF-8 file inputs

* Preserve korean spell-check layout in corrected output

The Nara payload can collapse paragraph separators into normalized page text, so the helper now maps corrections back onto the original chunk before rebuilding corrected_text. The CLI also rejects non-positive --max-chars values, and regression tests cover both the layout-preservation path and invalid argument handling.

Constraint: Nara result pages can normalize blank lines and sentence spacing before exposing errInfo offsets
Rejected: Narrow docs away from file/Markdown proofreading | preserving original chunk separators keeps the documented workflow intact
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep multiline separator preservation tied to the original chunk whenever a suggestion only changes whitespace across collapsed boundaries
Tested: python3 -m unittest scripts.test_korean_spell_check; npm run lint; npm run typecheck; npm test; npm run build; python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json; python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json; python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n왠지 않되요.' --format json; python3 scripts/korean_spell_check.py --text 테스트 --max-chars 0 --format json
Not-tested: Live multi-page Nara payloads with separator-sensitive corrections across multiple returned pages

* Preserve file-proofreading layout in Korean spell check output

The spell-check helper now keeps exact blank-line runs and paragraph
indentation when chunking and reassembling file-style input, while still
allowing the official service's spacing corrections to flow through.
Regression coverage now locks the collapsed-layout, triple-blank-line,
and cross-boundary spacing cases that triggered the PR review.

Constraint: Official Nara/PNU payloads can collapse layout and sometimes merge corrections across preserved paragraph boundaries
Rejected: Narrow docs away from file-level proofreading | the existing feature scope explicitly supports file and Markdown checks
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Preserve exact layout tokens only at original newline boundaries; do not reintroduce global strip/join normalization without real file-mode verification
Tested: npm run lint; npm run typecheck; npm test; npm run build; python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json; python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json; python3 scripts/korean_spell_check.py --file <tmpfile> --format json; python3 scripts/korean_spell_check.py --text 테스트 --max-chars 0 --format json
Not-tested: Live upstream behavior for extremely long leading/trailing-whitespace-only files
Related: PR #60

* Keep layout-preserving chunk splits from tripping on separator-boundary fixes

The follow-up layout preservation pass renamed the working unit variable, but one separator-length guard still referenced the old paragraph name. This commit finishes the refactor so exact-boundary paragraph chunks keep the preserved-separator logic reachable and the new regression coverage stays green.

Constraint: Must preserve the existing feature/#47 branch and PR #60 flow while fixing the approved review follow-up
Rejected: Revert the layout-preserving chunking refactor | would discard the verified file-layout fix and its regressions
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep chunk reassembly lossless; new chunking changes should continue to round-trip original separators byte-for-byte
Tested: npm run lint; npm run typecheck; npm test; npm run build; python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json; python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json; python3 scripts/korean_spell_check.py --file <tempfile-with-triple-blank-lines> --format json; python3 scripts/korean_spell_check.py --text 테스트 --max-chars 0 --format json (expected argument error)
Not-tested: Live multi-page service responses beyond the local smoke cases

* Preserve spell-check chunk separators when units overflow

The file-layout follow-up already preserved blank runs and indentation,
but the overlong-unit path still checked the wrong variable when
extracting a trailing separator. That could strand separators in
a standalone chunk and break exact reassembly for tight max-char
limits.

This commit fixes the separator extraction guard and locks the
behavior with a regression that proves chunk concatenation still
matches the original text when an overlong paragraph is followed
by a blank-line separator.

Constraint: File-mode reconstruction must preserve exact layout while chunking long input
Rejected: Broader chunking rewrite | existing structure only needed the overflow guard corrected
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep split_text_into_chunks chunk concatenation identical to the original input for layout-sensitive file checks
Tested: npm run lint; npm run typecheck; npm test; npm run build; python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json; python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json; python3 scripts/korean_spell_check.py --text 테스트 --max-chars 0 --format json; manual --file smoke with triple blank lines and indentation
Not-tested: Live-service failure modes such as Cloudflare/browser challenges

* Prevent clean spell-check chunks from crashing mixed file runs

The Nara/PNU surface can return a plain 'no issues found' HTML page
without the embedded result payload when a chunk is already clean.
Chunked file and markdown runs could hit that response on earlier
chunks, raise a ValueError, and never reach later chunks that still
needed corrections. Treat the no-issues page as an empty result set
and lock the behavior with narrow regression coverage.

Constraint: Upstream no-issue responses omit the JavaScript payload entirely and can split the status message across HTML whitespace
Rejected: Fabricate a synthetic result page | empty-page handling already preserves the original clean chunk
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep empty-result detection aligned with the upstream no-issues message unless the service publishes a structured empty payload contract
Tested: python3 -m unittest scripts.test_korean_spell_check
Tested: npm run lint
Tested: npm run typecheck
Tested: npm test
Tested: npm run build
Tested: python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json
Tested: python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json
Tested: python3 scripts/korean_spell_check.py --text 테스트 --max-chars 0 --format json
Tested: python3 scripts/korean_spell_check.py --file <tmpfile> --format json
Tested: python3 scripts/korean_spell_check.py --file <tmpfile> --max-chars 18 --format json
Not-tested: Other upstream empty-result templates beyond the current no-issues HTML wording

* Make Joseon Sillok lookups reproducible from the official site

Add a joseon-sillok-search skill and a Python helper that scrape the
official Joseon Annals search/detail pages. The helper normalizes
king/year metadata, fetches detail excerpts, and locks the repository
docs plus regression coverage around the shipped workflow.

Constraint: v1 must stay on official public HTML surfaces only
Constraint: Must avoid adding new dependencies for a simple scraping helper
Constraint: Shell connectivity to sillok.history.go.kr became intermittent during final live reruns
Rejected: Ship a new npm workspace | repo skill/docs pattern is enough for v1
Rejected: Add BeautifulSoup or another parser dependency | unnecessary for the bounded HTML patterns
Confidence: medium
Scope-risk: narrow
Reversibility: clean
Directive: Keep year filtering Gregorian and derived from official regnal metadata unless the upstream site exposes a better structured contract
Tested: npm run ci
Tested: Earlier live POST/detail probes against search/searchResultList.do and /id/kda_12512030_002 during implementation
Tested: Live official article inspection for kda_12512030_002 via the public site
Not-tested: Final end-to-end CLI live run after the last refactor, because the shell hit transient TCP timeouts to sillok.history.go.kr
Related: #59

* Prevent sillok follow-up fixes from missing filtered results or weakening trust

This follow-up addresses the blocking PR review items on the Joseon Sillok helper. The search loop now derives total pages from the first live page size so king/year filtering can reach later pages, TLS verification stays enabled on both requests and urllib paths, detail parsing accepts the live classification brackets, and repo docs stop linking to missing Korean spell-check assets on this branch.

Constraint: Must preserve the existing joseon-sillok-search surface and avoid new dependencies
Rejected: Keep verify=False behind an implicit fallback | still weakens authenticity for the default path
Rejected: Infer pagination from the hardcoded 50-row default | misses valid later-page matches when the site serves smaller pages
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If the official site changes page size or pagination markup again, update the first-page pagination regression before altering search_sillok pagination math
Tested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_sillok_search
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: python3 scripts/sillok_search.py --query '훈민정음' --king 세종 --year 1443 --limit 1 --timeout 20
Tested: python3 - <<'PY' ... fetch_detail_page(..., article_id='kda_12512030_002', timeout=20) ... PY
Not-tested: Successful live sillok.history.go.kr responses in this environment (POST and detail GET both timed out at 20s)
Related: PR #62

* Make joseon-sillok-search installs work outside the repo

The skill installer only ships the skill directory, so the sillok helper has to live inside that payload. This moves the authoritative helper into joseon-sillok-search/scripts/, keeps the repo-root script as a thin shim for tests and docs, and trims footer metadata from parsed article bodies so excerpts match the published examples.

Constraint: skills add exposes only the installed skill payload, not repo-root helpers

Rejected: Keep the helper only under scripts/ | installed skill commands fail after copy-only installs

Confidence: high

Scope-risk: narrow

Directive: Keep the repo-root sillok wrapper thin and update the bundled helper first when behavior changes

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

Tested: node --test scripts/skill-docs.test.js

Tested: temp installed-skill smoke run of python3 scripts/sillok_search.py --help plus footer-cleaning parse_detail_page check

Tested: npm run ci

Not-tested: live sillok.history.go.kr shell requests still hit connect timeouts in this environment

* Restore the sillok CLI's verified stdlib transport fallback

The helper already shipped a stdlib urllib opener that can reach the
official search endpoint in environments where requests/urllib3 aborts.
This change keeps that opener available even when requests imports
successfully and falls back to it on retryable requests transport
failures. Added regression coverage for opener availability and the
requests-to-urllib fallback so the default CLI path matches the live
verified behavior.

Constraint: Official sillok detail GETs can still time out transiently in this environment
Constraint: Keep TLS verification enabled and preserve the documented CLI entrypoints
Rejected: Force urllib for every request | keep the existing requests fast path when it succeeds
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Preserve the stdlib fallback tests whenever the transport layer changes
Tested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_sillok_search
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: live forced-fallback probe against searchResultList.do with requests.post patched to OSError(22)
Not-tested: full live CLI completion through the detail GET in this environment

* Document the approved real-estate MCP skill without vendoring upstream

Issue #53 is intentionally doc-only, so this change adds the
real-estate-search skill, feature guide, setup/security notes,
and regression coverage around the upstream real-estate-mcp
integration instead of importing server code.

The new docs keep the original MCP link, cover Codex/Claude
registration, and spell out the self-host + Cloudflare Tunnel +
launchd path for environments where no fixed hosted endpoint is
available.

Constraint: Must use tae0y/real-estate-mcp without copying its source into this repository
Constraint: Must include the original MCP link and a stable self-host fallback when no hosted endpoint is available
Rejected: Vendor the upstream MCP source | issue explicitly requires skill docs only
Rejected: Assume a public hosted MCP endpoint exists | upstream docs did not publish one
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep this integration doc-only; do not add a workspace or vendored server without revisiting issue #53 constraints
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: upstream bootstrap smoke (`uv sync`, `uv run real-estate-mcp --help`, HTTP initialize on 127.0.0.1:8017)
Not-tested: live property data queries with a valid DATA_GO_KR_API_KEY

* Prevent misleading real-estate self-host instructions

Tighten the real-estate skill docs so the launchd fallback stays operational
and the Onbid bid-result tools are described with the same WIP caveat the
upstream project still publishes.

Constraint: Upstream Docker compose already uses restart: unless-stopped while `docker compose ... up -d` daemonizes immediately
Rejected: Keep a separate server LaunchAgent with RunAtLoad + KeepAlive | launchd would restart-loop on the exiting compose command
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Mirror upstream capability caveats in k-skill docs and do not wrap daemonized server commands in launchd KeepAlive jobs
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: uv sync
Tested: uv run real-estate-mcp --help
Tested: DATA_GO_KR_API_KEY=dummy uv run real-estate-mcp --transport http --host 127.0.0.1 --port 8017
Tested: curl initialize on http://127.0.0.1:8017/mcp returned protocolVersion 2024-11-05
Not-tested: Live 거래 조회 with a real DATA_GO_KR_API_KEY

* Keep install docs from reintroducing broken launchd guidance

The install guide had drifted from the real-estate skill and feature docs and still implied that macOS launchd should auto-run both the server and tunnel. This narrows the guidance back to tunnel-only launchd ownership and extends regression coverage so docs/install.md cannot silently reintroduce the server-side loop wording.

Constraint: Upstream docker compose already uses restart: unless-stopped
Constraint: Review-round-2 scope is limited to docs and regression coverage
Rejected: Leave docs/install.md under a generic launchd presence check | it missed the exact server/터널 regression
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep docs/install.md aligned with the detailed real-estate feature guide when self-host guidance changes
Tested: node --test scripts/skill-docs.test.js; npm run ci; fresh-clone upstream smoke with uv sync, uv run real-estate-mcp --help, and HTTP initialize on 127.0.0.1:8017/mcp
Not-tested: None

* Allow Han River water-level lookups without user API keys

The proxy now resolves HRFCO water-level stations via waterlevel/info and
fetches the latest 10-minute measurement via waterlevel/list, exposing a
public summary route plus a hosted skill/docs path.

Constraint: HRFCO requires a ServiceKey and station-code-based latest lookup
Rejected: Raw passthrough only | forces users to manage upstream details and misses the approved no-key UX
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep this route summary-only and public; expand rainfall/dam/bo/fldfct in separate issues
Tested: npm run ci; local server smoke test with HRFCO sample key against /v1/han-river/water-level; tsc diagnostics on packages/k-skill-proxy/src/server.js and src/hrfco.js
Not-tested: Hosted production proxy rollout

* Expose official nearby fuel prices for location-based gas station lookups

Add the first cheap-gas-nearby skill/package pair so nearby gas-price
queries can resolve a user-supplied location, translate it into Opinet's
KATEC search contract, and return the cheapest nearby stations with
address and facility detail. The docs and setup surfaces now advertise
the new skill and its Opinet API key requirement.

Constraint: Nearby fuel prices must come from the official KNOC Opinet API when available
Constraint: No new external dependencies were allowed for coordinate conversion or location resolution
Rejected: Map-only gas price scraping | official Opinet Open API exists and is the preferred source
Rejected: Require lat/lng input only | poorer UX than supporting landmark/station queries through anchor resolution
Confidence: medium
Scope-risk: moderate
Reversibility: clean
Directive: Keep `OPINET_API_KEY` as the only supported official-price credential unless the repo adopts an Opinet proxy later
Tested: npm run ci; node --test packages/cheap-gas-nearby/test/index.test.js; offline fixture smoke via searchCheapGasStationsByLocationQuery('서울역', ...)
Not-tested: Live Opinet API call with a real `OPINET_API_KEY` (no non-placeholder key was configured locally)
Related: #54

* Explain Blue Ribbon premium gating instead of failing opaquely

Blue Ribbon's nearby endpoint now returns PREMIUM_REQUIRED for public
requests, so the package now upgrades that response into a stable domain
error and updates user-facing docs to describe the degraded nearby state.
Regression tests cover both location-query and coordinate-query entrypoints
while keeping the existing happy path intact.

Constraint: Must not attempt to bypass Blue Ribbon premium access controls
Constraint: Package releases must use Changesets metadata
Rejected: Silently return empty results on PREMIUM_REQUIRED | would hide the upstream contract change from callers
Rejected: Keep generic 403 error and docs unchanged | leaves the main failure mode opaque and misleading
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If Blue Ribbon reopens a public nearby surface later, update the docs and replace the premium_required remap only after fresh live verification
Tested: npm test --workspace blue-ribbon-nearby; npm run lint --workspace blue-ribbon-nearby; npm run ci; live node repro now returns premium_required metadata
Not-tested: Official premium-authenticated nearby flow, because no approved premium credentials are available

* Reduce duplicate premium-required test assertions

The implementation diff was already verified, so this follow-up keeps the
new regression coverage easier to read by sharing one assertion helper
instead of repeating the same predicate twice.

Constraint: Must preserve the verified PREMIUM_REQUIRED regression coverage exactly
Rejected: Leave duplicated inline predicates in both tests | repeats the same contract and adds noise to future edits
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep location-query and coordinate-query regressions aligned when the premium_required error contract changes
Tested: npm test --workspace blue-ribbon-nearby; npm run lint --workspace blue-ribbon-nearby; npm run ci
Not-tested: Additional live Blue Ribbon requests beyond the previously verified premium_required repro

* Document an Olive Young search skill around the upstream daiso CLI

Issue #61 asked for an Olive Young lookup workflow without vendoring the upstream daiso-mcp server into k-skill, so this change adds a docs-only skill, threads it through repository docs, and locks the guidance with regression assertions.

The new guidance prefers CLI-first verification (`npx daiso`) and a clone fallback (`git clone https://github.com/hmmhmmhm/daiso-mcp.git`) instead of requiring direct Claude Code MCP installation. The existing daiso-product-search skill remains untouched.

Constraint: Must mention the original https://github.com/hmmhmmhm/daiso-mcp repo
Constraint: Must avoid changing the existing daiso-product-search skill
Constraint: Must prefer upstream CLI/package usage over direct Claude Code MCP installation
Rejected: Vendor upstream Olive Young code into k-skill | contradicts the minimal-addition design request
Rejected: Make Claude Code MCP setup the default path | issue explicitly asked for CLI-first usage
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the Olive Young docs aligned with upstream `daiso` CLI behavior and note public endpoint instability when live verification shows it
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: 2026-04-05 live /tmp/daiso-mcp CLI checks for health, /api/oliveyoung/stores, /api/oliveyoung/products, /api/oliveyoung/inventory
Not-tested: Authenticated/private Olive Young flows or ordering/payment paths

* Keep Han River docs honest until hosted rollout lands

The local HRFCO proxy route already works, but the deployed public proxy still
lacks /v1/han-river/water-level as of 2026-04-05. This follow-up changes the
user-facing docs to require a self-hosted or deployment-verified proxy URL,
keeps the intended hosted path documented as rollout-pending, and locks that
caveat with a regression test.

Constraint: Hosted k-skill-proxy deployment still returns 404 for /v1/han-river/water-level on 2026-04-05
Rejected: Keep advertising the hosted route as the default live path | current deployment is not live yet
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Restore hosted-default wording only after the public proxy route and HRFCO configuration are verified live
Tested: node --test scripts/skill-docs.test.js; npm run ci; local mocked smoke via node packages/k-skill-proxy/src/server.js + GET /v1/han-river/water-level?stationName=한강대교
Not-tested: Live hosted k-skill-proxy behavior after deployment

* Keep cheap gas lookups from failing on recoverable Kakao and input issues

The lookup now walks ranked Kakao anchor candidates until one returns usable coordinates, and it rejects invalid limit inputs instead of silently collapsing non-empty results into an empty list. The regression suite now locks both review repros plus the null-coordinate normalization edge case that surfaced while implementing the fallback.

Constraint: Kakao place panels can be partially populated or return 404 for otherwise valid search candidates
Rejected: Default invalid limit/detailLimit values silently | still masks caller bugs and can look like no results nearby
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep anchor resolution tolerant of unusable Kakao panels before failing the whole lookup
Tested: node --test packages/cheap-gas-nearby/test/index.test.js; npm run ci; offline fixture smoke for searchCheapGasStationsByLocationQuery('서울역', ...)
Not-tested: Live Opinet/Kakao network path without a real OPINET_API_KEY

* Keep Olive Young install guidance aligned with live retry behavior

Issue #61's initial branch already shipped the olive-young-search skill/docs set, but the install guide did not have a regression lock for the public endpoint instability note captured during live verification. This follow-up adds that test first and updates the install quickstart so retry-or-clone fallback guidance stays in sync with the verified daiso CLI workflow.

Constraint: Must keep the follow-up scoped to the approved issue #61 docs behavior
Constraint: Public olive-young endpoint can intermittently return 5xx/503 during live verification
Rejected: Re-open the broader feature docs set | the existing branch already covered the main feature scope
Rejected: Leave the retry guidance untested in install docs | risk of future doc drift across surfaces
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If live olive-young verification changes again, update install docs and the regression together so retry/fallback guidance stays honest
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: cd /tmp/daiso-mcp && npx daiso health
Tested: cd /tmp/daiso-mcp && npx daiso get /api/oliveyoung/stores --keyword 명동 --limit 3 --json
Tested: cd /tmp/daiso-mcp && npx daiso get /api/oliveyoung/products --keyword 선크림 --size 3 --json
Tested: cd /tmp/daiso-mcp && npx daiso get /api/oliveyoung/inventory --keyword 선크림 --storeKeyword 명동 --size 2 --json
Not-tested: Authenticated Olive Young flows or sustained-rate retry behavior beyond the documented smoke checks

* Preserve ranked Kakao anchor fallbacks after panel failures

The resolver already retried later Kakao panels, but after the best panel failed it
walked the remaining candidates in raw HTML order. Centralizing the full
anchor ranking in parse.js keeps fallback iteration aligned with the
existing scoring rules and locks the review repro with a regression test
for 강남역 ordering.

Constraint: Kakao place panels can 404 or omit coordinates even when later ranked candidates are usable
Rejected: Re-rank only after a failed panel | duplicates scoring logic and drifts from selectAnchorCandidate
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep resolveAnchor fallback order tied to the shared anchor scoring logic rather than raw Kakao result order
Tested: node --test packages/cheap-gas-nearby/test/index.test.js; npm run ci; offline fixture smoke for searchCheapGasStationsByLocationQuery('서울역', ...); lsp diagnostics on affected files
Not-tested: Live Kakao/Opinet network calls with a non-placeholder OPINET_API_KEY

* Clarify that Blue Ribbon coordinate lookups fail the same way

The premium gate now affects both location-query and coordinate-entry nearby flows. Record that parity in the skill and docs so the live 2026-04-05 repro evidence stays aligned with the shipped public contract.

Constraint: /restaurants/map remains premium-gated for public requests as of 2026-04-05
Rejected: Add more code changes without a behavior gap | would add churn after the approved fix already landed
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the docs/examples aligned with live repro evidence whenever Blue Ribbon changes nearby access behavior
Tested: npm test --workspace blue-ribbon-nearby; npm run lint --workspace blue-ribbon-nearby; npm run ci; live node repro for location + coordinates premium_required contract
Not-tested: New upstream success-path live nearby payloads, because public access is still premium-gated

* Document a runnable Olive Young clone fallback

The follow-up review found that clone-local `npx daiso` commands do not run from a built `hmmhmmhm/daiso-mcp` checkout because the generated bin file is not executable. This updates the olive-young skill and docs to use the verified `node dist/bin.js ...` path for clone fallback, and locks that behavior with regression tests while keeping the public CLI-first path unchanged.\n\nConstraint: Upstream clone checkouts can fail with `Permission denied` when invoked through clone-local `npx daiso`\nConstraint: Keep the existing daiso-product-search skill untouched\nRejected: Leave install docs unchanged | PR body and docs would keep a broken clone fallback path\nRejected: Vendor or patch upstream daiso-mcp inside k-skill | issue explicitly requires documenting upstream flow instead\nConfidence: high\nScope-risk: narrow\nReversibility: clean\nDirective: Keep clone-fallback docs and PR verification commands aligned with the actually runnable local invocation\nTested: node --test scripts/skill-docs.test.js\nTested: npm run ci\nTested: cd /tmp/daiso-mcp && npm install && npm run build && node dist/bin.js health\nTested: cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/stores --keyword 명동 --limit 3 --json\nTested: cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/products --keyword 선크림 --size 3 --json\nTested: cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/inventory --keyword 선크림 --storeKeyword 명동 --size 2 --json\nNot-tested: Public npx endpoint stability beyond the verified smoke-test window

* Record fresh verification for the approved cheap-gas follow-up

The ranked Kakao fallback-order and invalid-limit fixes are already present on feature/#54, so no further code edits were necessary. This empty follow-up commit records the requested rerun verification for PR #67 before posting the implementation update.\n\nConstraint: Existing branch head already contains the approved cheap-gas-nearby follow-up\nRejected: Invent another code/doc change just to force a non-empty diff | unnecessary risk after approval\nConfidence: high\nScope-risk: narrow\nReversibility: clean\nDirective: Keep Kakao anchor fallback iteration tied to the shared ranking helper and preserve finite-number validation for limit inputs\nTested: node --test packages/cheap-gas-nearby/test/index.test.js; npm run ci; offline fixture smoke for searchCheapGasStationsByLocationQuery('서울역', ...); lsp diagnostics on affected files\nNot-tested: Live Kakao/Opinet network calls with a non-placeholder OPINET_API_KEY

* Record verified delivery for approved cheap-gas-nearby rollout

The approved Issue #54 implementation was already present on feature/#54 when this follow-up began, so this checkpoint records fresh verification evidence and keeps PR #67 moving without introducing extra code churn.

Constraint: Existing approved fixes were already on the branch and the follow-up still required a pushed update plus implementation comment
Rejected: Add additional code or docs churn | approved scope was already satisfied and green locally
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Preserve the Kakao fallback-order and invalid-count regression coverage unless equivalent runtime repros replace it
Tested: node --test packages/cheap-gas-nearby/test/index.test.js; offline fixture smoke for searchCheapGasStationsByLocationQuery('서울역', ...); npm run ci; LSP diagnostics on affected cheap-gas-nearby files
Not-tested: Live Kakao/Opinet network path without a non-placeholder OPINET_API_KEY

* Keep Blue Ribbon premium-gate remapping explicitly bounded

The issue #63 fix already remaps PREMIUM_REQUIRED for nearby lookups. This follow-up adds a regression that proves non-premium /restaurants/map failures still surface as generic request errors, and clarifies that boundary in the package and feature docs.

Constraint: PR #68 already carries the shipped behavior change and must stay aligned with current live premium-gated upstream behavior
Rejected: Broaden domain remapping to all 403 nearby errors | would hide distinct upstream failure modes
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep only PREMIUM_REQUIRED on /restaurants/map mapped to premium_required unless a new verified upstream contract is documented
Tested: npm test --workspace blue-ribbon-nearby; npm run lint --workspace blue-ribbon-nearby; npm run ci; live node repro for location+coordinate nearby lookups on 2026-04-06; LSP diagnostics on src/test
Not-tested: Alternative non-premium upstream status codes beyond the mocked 403 ACCESS_DENIED path

* Keep Olive Young clone fallback docs runnable from a fresh clone

Round 3 review found that the inline fallback shorthand skipped `cd daiso-mcp`,
which made `npm install` run outside the cloned upstream repo even though the
fenced examples were already correct. This tightens the two published shorthand
snippets and adds regression coverage so both docs surfaces reject the broken
chain in future edits.

Constraint: Existing node dist/bin.js clone fallback examples were already verified and had to stay aligned
Constraint: The follow-up had to stay scoped to docs/test coverage without touching the existing daiso-product-search skill
Rejected: Convert the shorthand to prose only | the review specifically requested a runnable inline form or an explicit removal
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep inline clone fallback snippets synchronized with the fenced node dist/bin.js examples and require `cd daiso-mcp` before install/build
Tested: node --test scripts/skill-docs.test.js; npm run ci; cd /tmp/daiso-mcp && npm install && npm run build; cd /tmp/daiso-mcp && node dist/bin.js health; cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/stores --keyword 명동 --limit 3 --json; cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/products --keyword 선크림 --size 3 --json; cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/inventory --keyword 선크림 --storeKeyword 명동 --size 2 --json
Not-tested: Public npx path in this follow-up round (previous PR verification already covered it)
Related: PR #71

* Bundle korean-spell-check script inside skill directory for packageless installs

The Python helper lived only in the repo-root scripts/ folder, so
`skills add` never shipped it. Move the real implementation into
korean-spell-check/scripts/ (mirroring joseon-sillok-search) and
replace the root copy with a thin re-export wrapper so lint/test
still resolve from the repo root.

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

* Exclude .claude directory from skill validation to fix CI in worktrees

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

* real-estate-search: MCP self-host → k-skill-proxy HTTP 전환

사용자가 직접 real-estate-mcp를 clone/self-host하는 대신
k-skill-proxy에 MOLIT 실거래가 API route를 추가해서
다른 스킬(한강수위, 미세먼지 등)과 동일한 패턴으로 사용 가능하게 함.

- GET /v1/real-estate/region-code — 지역코드 검색
- GET /v1/real-estate/:assetType/:dealType — 9개 거래 유형 조회
- molit.js: XML 파싱, 필드 정규화, 취소거래 필터링, 요약통계
- region-lookup.js: 284개 법정동 5자리 코드 토큰 매칭
- SKILL.md/docs를 HTTP proxy 기반으로 전면 재작성
- DATA_GO_KR_API_KEY를 사용자 필수항목에서 프록시 운영자 전용으로 이동

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

* cheap-gas-nearby: proxy 경유로 전환 + 프로덕션 배포 구조 문서화

- cheap-gas-nearby: OPINET_API_KEY 없이 k-skill-proxy 경유로 동작하도록 변경
  - fetchAroundStations/fetchDetailById에 proxy fallback 추가
  - SKILL.md, feature doc에서 사용자 API key 요구 제거
- AGENTS.md, CLAUDE.md: proxy 개발/배포 워크플로우 문서화
- docs/features/k-skill-proxy.md: opinet route 추가, 프로덕션 자동 배포 구조 설명

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

* blue-ribbon-nearby: proxy 경유로 전환 + 프리미엄 세션 프록시 라우트

Blue Ribbon /restaurants/map이 프리미엄 전용으로 전환되어,
k-skill-proxy에 BLUE_RIBBON_SESSION_ID 기반 프록시 라우트를 추가하고
blue-ribbon-nearby 패키지가 기본적으로 프록시를 경유하도록 변경.

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

---------

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

* chore: version packages (#74)

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

* fix(ci): remove changeset assertion that breaks CI after version bump (#78)

The used-car-price-search changeset assertion fails after `changeset version`
consumes .changeset/*.md files, breaking both CI and Release workflows on main.

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-04-07 04:35:50 +09:00
Jeffrey (Dongkyu) Kim
7699a7bdbf Close the remaining Korean stock proxy gaps before public rollout
The KRX search route trusted spoofable direct-request IP headers and
failed whole-search fanout when one market snapshot errored. This
change keys rate limits off Fastify's resolved request.ip, reuses
per-market base snapshots across same-date queries, and keeps healthy
search results available when one market fails while new regressions
lock the behavior.

Constraint: Public proxy routes stay unauthenticated, so abuse controls must not depend on untrusted client headers
Constraint: Search must still fail loudly when every market fetch fails
Rejected: Keep trusting cf-connecting-ip on direct requests | direct clients can rotate the header to reset the bucket
Rejected: Promise.all all-or-nothing market fanout | one failing market hid healthy KOSPI results
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep Korean stock rate limiting keyed to Fastify-resolved client IP unless trust-proxy behavior is explicitly audited end-to-end
Tested: node --test packages/k-skill-proxy/test/server.test.js scripts/skill-docs.test.js; npm run ci; local proxy smoke on 127.0.0.1:4120; targeted inject script for spoofed-header rate limiting and partial-market failure
Not-tested: Live KRX success with a real KRX_API_KEY
2026-04-06 21:02:16 +09:00
Jeffrey (Dongkyu) Kim
e4c43d2c7f Protect KRX proxy responses from insecure or mismatched upstream data
Review follow-up switches KRX endpoints to HTTPS and removes the single-row trade fallback that could mislabel an unrelated stock as the requested code. Regression tests now assert HTTPS transport for search/base/trade flows and 404 behavior for unmatched single-row trade responses.

Constraint: KRX key must never leave the proxy over plaintext transport
Constraint: No local KRX_API_KEY is available for live upstream success verification
Rejected: Keep the single-row trade fallback with a synthetic code rewrite | can fabricate mismatched stock responses
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Do not reintroduce inferred short-code fallbacks without validating them against matching base-info first
Tested: node --test packages/k-skill-proxy/test/server.test.js scripts/skill-docs.test.js; npm run ci; local proxy smoke on 127.0.0.1:4120 (/health 200, /v1/korean-stock/search?q=삼성전자&bas_dd=20260404 503 without KRX_API_KEY); lsp diagnostics on changed files
Not-tested: live KRX upstream success path with a real KRX_API_KEY
2026-04-06 20:31:58 +09:00
Jeffrey (Dongkyu) Kim
6270ec64b3 Shield Korean stock lookups behind the proxy so users avoid KRX key setup
Use the local k-skill proxy as the public integration surface for Korea Exchange stock data. The new skill/docs flow starts with stock search, then narrows into base-info and trade-info lookups, while the proxy injects KRX credentials server-side only.

Constraint: KRX_API_KEY must remain server-side under the repo's free read-only proxy policy
Rejected: Expose upstream korea-stock-mcp setup to end users | would force user-issued KRX keys and a heavier install path
Confidence: high
Scope-risk: moderate
Directive: Keep Korean stock access proxy-first and read-only; do not move KRX_API_KEY into client setup docs
Tested: node --test packages/k-skill-proxy/test/server.test.js scripts/skill-docs.test.js; npm run ci; local proxy smoke on port 4120 (/health 200, /v1/korean-stock/search 503 without KRX_API_KEY)
Not-tested: Live upstream success path with a real local KRX_API_KEY
2026-04-06 20:06:08 +09:00
Jeffrey (Dongkyu) Kim
9f29ed5714
Merge dev into main: new skills & proxy enhancements (#72)
* Remove client-side Seoul subway key setup

Route Seoul subway arrival lookups through k-skill-proxy so the
hosted proxy owns the Seoul Open Data upstream key and end users
only need the proxy base URL. Add proxy route coverage, update
skill/docs guidance, and align setup materials with the hosted
proxy flow used for fine dust.

Constraint: Must keep the proxy public, read-only, and dependency-free
Constraint: Must satisfy TDD-first verification and ship on feature/#35 targeting dev
Rejected: Add a separate client helper package | unnecessary extra layer for a single proxy route
Rejected: Keep SEOUL_OPEN_API_KEY as an end-user requirement | defeats the issue goal
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep the Seoul subway proxy surface limited to station-arrival passthrough unless tests/docs expand the contract
Tested: npm run ci; local proxy runtime on 127.0.0.1:4120 for /health and /v1/seoul-subway/arrival on 2026-03-31 with an invalid upstream key
Not-tested: Live success response with a valid Seoul Open API key
Related: #35

* Prevent broken Seoul subway proxy defaults before hosted rollout

The Seoul subway proxy endpoint code is present locally, but the hosted public route is not live yet. This change turns the user-facing subway docs back into an explicit proxy configuration flow, replaces the misleading hosted default in setup examples, and keeps subway proxy examples on self-host/local URLs until rollout is verified.

Constraint: Hosted k-skill-proxy.nomadamas.org/v1/seoul-subway/arrival is not live yet
Rejected: Keep the hosted Seoul subway URL as the default path | would send default users to a 404 route
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Do not restore a hosted Seoul subway default until the public proxy route is deployed and smoke-verified
Tested: node --test scripts/skill-docs.test.js; npm run ci; local proxy smoke on 127.0.0.1:4120 with stubbed Seoul upstream (GET /health, GET /v1/seoul-subway/arrival?stationName=강남)
Not-tested: Live hosted proxy smoke after deployment

* Reduce Seoul subway proxy upstream pressure with request caching

The public subway arrival route already normalized caller input but still re-fetched the Seoul upstream for every identical poll. This change adds a short-TTL cache keyed from the normalized subway query, annotates JSON responses with cache metadata, and locks the repeated-read collapse with a regression that proves alias/default normalization still reuses the cached result.

Constraint: The endpoint must stay public/no-auth while protecting a shared server-side SEOUL_OPEN_API_KEY from unnecessary repeated upstream hits
Rejected: Add a new shared cache abstraction for proxy metadata | unnecessary for this narrow fix and would enlarge the diff
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep subway cache keys tied to normalizeSeoulSubwayQuery output so alias/default query forms continue collapsing to one upstream request
Tested: node --test packages/k-skill-proxy/test/server.test.js
Tested: npm run ci
Tested: Local proxy runtime on 127.0.0.1:4120 with SEOUL_OPEN_API_KEY=test-seoul-key and stubbed fetch for /health plus repeated /v1/seoul-subway/arrival requests
Not-tested: Live hosted proxy rollout state

* Document OpenClaw support in the public compatibility list

The approved scope for issue #29 was narrowed to README support messaging,
so this change adds OpenClaw/ClawHub to the supported-client line and locks
that wording with a regression test in the docs suite.

Constraint: Issue #29 was approved as a README-only compatibility/docs change
Rejected: Broader install-doc updates | out of approved scope for this issue
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep OpenClaw support wording aligned with the supported-client README line and its docs regression test
Tested: Focused Node docs regression, npm run lint, npm run typecheck, npm run build, npm test
Not-tested: Live OpenClaw or ClawHub install flow (issue scope was documentation-only)

* Protect README client-support claims from ClawHub regressions

The README already advertises OpenClaw/ClawHub, but the docs
regression only matched OpenClaw. Tighten the assertion to the
exact supported-client fragment so a future edit cannot silently
remove ClawHub while keeping the issue verification command stable.

Constraint: PR #39 already publishes a verification command keyed to the current test name
Rejected: Rename the test to mention ClawHub explicitly | would drift from the published verification command
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep this regression synchronized with the README supported-client line whenever that copy changes
Tested: node --test scripts/skill-docs.test.js --test-name-pattern 'README advertises OpenClaw among the supported coding agents'
Tested: npm run lint
Tested: npm run typecheck
Tested: npm run build
Tested: npm test
Tested: lsp diagnostics for scripts/skill-docs.test.js (0 errors)
Not-tested: N/A

* Make Coupang shopper research usable without pretending scraping is stable

Coupang blocks unattended shopper queries in this environment, so the new package focuses on the durable pieces we can ship honestly: official URL builders, browser-captured HTML parsers, and explicit automation probes. The docs and skill now explain the seller-API limitation, the verified anti-bot behavior, and the browser-capture fallback expected by callers.

Constraint: No general shopper Open API surfaced in Coupang's official developer docs

Constraint: Headless/direct retrieval is anti-bot blocked in verified local probes

Rejected: Bundle a scraping bypass or hidden browser dependency | violates repo policy and would over-claim reliability

Confidence: high

Scope-risk: moderate

Reversibility: clean

Directive: Keep probe/docs behavior aligned with fresh live verification before claiming unattended Coupang access works

Tested: npm run ci; live probeAutomation(query=생수) with direct fetch + Playwright-core browserFetchHtml; LSP diagnostics on changed JS/test files

Not-tested: Headed/manual browser sessions with a human-authenticated Coupang context

* Keep the Coupang workspace installable and its entrypoint honest

Review follow-up found two contract gaps in the first issue #36 rollout: fresh installs were missing the new workspace link in package-lock, and the package README documented parser helpers that were not exported from the package entrypoint. Refreshing the lockfile and re-exporting the parsers keeps npm ci and consumer imports aligned with the published API.

Constraint: npm ci must succeed from a clean checkout
Rejected: Narrow the README API list to only client helpers | would hide useful parsers that the package already ships and tests
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: When adding a new workspace, refresh package-lock and verify the package entrypoint matches README public API claims
Tested: npm run ci; workspace coupang-product-search tests; LSP diagnostics on coupang-product-search JS/test files
Not-tested: published npm install from registry (local pack dry-run only)

* Keep Coupang anti-bot docs honest as probe results drift

A same-day PR rerun showed the published mobile probe snapshot was already stale, so the follow-up locks the 403/access-denied mobile path with regression coverage and softens the docs/skill contract to describe blocked outcome classes instead of one frozen signature. The published guidance now also explains that browser results only appear when browserFetchHtml is injected, matching what a clean checkout can actually reproduce.

Constraint: Coupang anti-bot responses vary by edge/challenge between reruns on the same day
Rejected: Freeze one exact mobile probe snapshot | same-day reruns already diverged
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Refresh Coupang anti-bot docs only with same-day live probe evidence, and prefer blocked outcome classes over a single exact signature when reruns disagree
Tested: npm run lint; npm run typecheck; npm test; npm run ci; live probeAutomation("생수") with direct fetch + Playwright-core browser fetch
Not-tested: Non-Chrome Playwright-core executables

* Keep the Coupang skill honest about clean-checkout browser probes

The approved follow-up already documented that clean-checkout probeAutomation runs leave browser unset unless a browser fetcher is injected, but the skill text itself had not made that null contract explicit. This commit adds a regression test that locks the browser-null wording across the published docs surfaces and updates the skill so the stated behavior matches the package implementation and clean-checkout reality.

Constraint: probeAutomation() only populates browser when browserFetchHtml is supplied by the caller
Rejected: Leave the skill wording implicit and rely on README examples alone | the skill could drift away from the shipped package contract again
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the README, feature doc, and skill aligned whenever the probeAutomation browser contract changes
Tested: node --test packages/coupang-product-search/test/index.test.js; npm run lint; npm run typecheck; npm test; npm run ci; live probeAutomation("생수") with direct fetch + Playwright-core browser fetch; codex exec review --uncommitted
Not-tested: Alternative browser engines beyond local Google Chrome for the manual browserFetchHtml probe

* Keep Coupang browser probe docs aligned with manual verification paths

The approved PR #40 follow-up still had one wording gap: the skill said when browser results appear, but it did not explicitly say those populated results come from an injected manual/external browserFetchHtml path. This change locks that contract with a failing regression first, then updates the skill text to match the already-shipped README/feature-doc guidance and runtime behavior.

Constraint: clean-checkout probeAutomation() must keep browser null unless browserFetchHtml is supplied
Rejected: change runtime browser probe behavior | approved follow-up was docs/test only and runtime contract is already correct
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: keep Coupang probe docs aligned with the shipped browserFetchHtml injection contract; do not imply built-in browser probing in clean checkouts
Tested: node --test packages/coupang-product-search/test/index.test.js; npm run lint; npm run typecheck; npm test; npm run ci; live probeAutomation("생수") with direct fetch + Playwright-core browser fetch; lsp diagnostics on changed files; architect review approved
Not-tested: alternate browser engines beyond local Google Chrome + playwright-core manual runner

* Route Korean law lookups through korean-law-mcp

Add a documentation-first korean-law-search skill and lock the repo docs around the rule that Korean law queries must go through korean-law-mcp rather than a new in-repo package.

The change updates install/setup/security guidance, publishes the new feature doc, and adds regression tests so future edits keep the LAW_OC + korean-law-mcp contract intact.

Constraint: Issue #41 requires korean-law-mcp for every Korean law lookup
Constraint: Must not add a new npm or python package in this repository
Rejected: Add a repo-local law package | violates the no-new-package requirement and duplicates upstream MCP work
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep Korean law lookup guidance pinned to korean-law-mcp unless issue requirements explicitly change
Tested: node --test scripts/skill-docs.test.js
Tested: npm install -g korean-law-mcp && korean-law list && korean-law help search_law
Tested: npm run ci
Tested: npx tsc --noEmit --pretty false --project /Users/jeffrey/Projects/k-skill/tsconfig.json
Not-tested: live search_law/get_law_text against a real LAW_OC credential

* Clarify Korean law setup modes to avoid credential confusion

A review found the new korean-law-search docs treated LAW_OC as an unconditional prerequisite even though the upstream remote MCP endpoint is configured separately from the local CLI/server path. This update makes the docs and regression tests mode-specific: local CLI/MCP uses LAW_OC, while the remote endpoint stays a korean-law-mcp-only url fallback without a user-supplied credential.

Constraint: Upstream korean-law-mcp uses LAW_OC on the local CLI/server path while the documented remote MCP endpoint is configured with url only
Rejected: Keep LAW_OC mandatory for every korean-law-mcp mode | contradicts upstream docs and reviewer evidence
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep README/setup/skill docs and regression tests aligned on the local-vs-remote korean-law-mcp contract
Tested: node --test scripts/skill-docs.test.js; npm install -g korean-law-mcp && korean-law list && korean-law help search_law; npx tsc --noEmit --pretty false --project /Users/jeffrey/Projects/k-skill/tsconfig.json; npm run ci; lsp_diagnostics scripts/skill-docs.test.js
Not-tested: Live remote MCP endpoint connection against https://korean-law-mcp.fly.dev/mcp

* Record issue #41 follow-up after approved verification

The approved korean-law-search change was already present on feature/#41, so this follow-up records a fresh verification pass and updates the PR without introducing unnecessary code churn.

Constraint: Existing branch already contained the approved implementation
Rejected: Touch repo files without need | would create noise without improving behavior
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the korean-law-mcp-only + mode-specific LAW_OC contract aligned with upstream docs before changing these surfaces again
Tested: node --test scripts/skill-docs.test.js; npx tsc --noEmit --pretty false --project /Users/jeffrey/Projects/k-skill/tsconfig.json; npm install -g korean-law-mcp && korean-law list && korean-law help search_law; npm run ci
Not-tested: Live korean-law queries that require a real LAW_OC or remote endpoint session

* Keep the Korean law feature diff merge-ready

Verification uncovered trailing whitespace in the new korean-law feature guide when checking the branch diff. I added a regression to the skill docs suite and removed the whitespace so the approved documentation contract stays clean and reviewable.

Constraint: Preserve the existing korean-law-mcp + mode-specific LAW_OC contract without widening scope
Rejected: Leave the whitespace issue as-is | git diff --check stayed dirty on the branch
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep docs/features/korean-law-search.md free of trailing whitespace so diff hygiene stays enforced by the regression
Tested: node --test scripts/skill-docs.test.js; npx tsc --noEmit --pretty false --project /Users/jeffrey/Projects/k-skill/tsconfig.json; npm install -g korean-law-mcp && korean-law list && korean-law help search_law; npm run ci; git diff --check
Not-tested: Live credentialed LAW_OC search execution against the upstream API

* Keep Korean law skill guidance aligned with supported lookups

The approved issue #41 feature already worked, but the shipped skill text still under-described ordinance and interpretation lookups compared with the documented capability set. This follow-up tightens the skill copy and locks that contract with a doc regression so the branch stays merge-ready without changing the underlying korean-law-mcp routing rules.

Constraint: Must preserve the existing korean-law-mcp-only and mode-specific LAW_OC setup contract
Rejected: Leave the skill wording as-is | drift from the documented lookup surface would remain
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the skill examples and doc regression aligned whenever supported korean-law-mcp search surfaces change
Tested: node --test scripts/skill-docs.test.js
Not-tested: live LAW_OC-backed upstream API queries

* Keep Korean law completion guidance in sync with enforced lookups

The previous follow-up aligned the main skill examples, but the done-criteria text still left interpretation and ordinance routing implicit. This commit makes the completion checklist explicit and extends the doc regression so future edits cannot silently drop those lookup paths.

Constraint: Must stay within the existing issue #41 korean-law-mcp-only contract
Rejected: Leave the done checklist implicit | reviewers and future edits could drift from the enforced lookup set
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: When the supported Korean-law lookup set changes, update the done checklist and regression together
Tested: node --test scripts/skill-docs.test.js
Not-tested: full CI before commit

* Enable repeatable used-car price lookups from a rental-company source

Issue #46 required surveying major Korean rental companies before implementation and then choosing the easiest stable provider. SK렌터카 다이렉트 exposes 타고BUY inventory in public Next.js page data, so the feature stays dependency-free while still supporting live repeated lookups and documented provider rationale.

Constraint: Must compare major Korean rental companies before implementation
Constraint: Must verify 10+ live lookups against a real provider surface
Rejected: 롯데오토옥션 as v1 provider | public list contract was unstable and legacy .do flows returned inconsistent or 404 pages
Rejected: 레드캡렌터카 as v1 provider | no public used-car inventory or API surface was found
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep the v1 provider read-only and inventory-snapshot-based unless a stable documented public API is confirmed
Tested: npm run ci
Tested: Live 10-query run against https://www.skdirect.co.kr/tb at 2026-04-02T07:22:46Z
Tested: LSP diagnostics on affected files
Not-tested: Seller-specific detail drilldowns or non-SK providers
Related: #46

* Keep used-car-price-search releasable after merge

Review feedback found that the new used-car workspace would not ship because it lacked a changeset, and the fallback install docs still omitted the runtime package. This follow-up adds regression coverage first, then restores both release and install-path coverage with the smallest possible diff.

Constraint: New publishable workspaces must be wired through Changesets to reach npm release automation
Constraint: Fallback install docs must list runtime packages users need when skill files are present but global packages are missing
Rejected: Fix only the changeset gap | would leave the documented fallback install path incomplete
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep used-car-price-search in both the fallback global install example and a Changesets entry whenever release wiring changes
Tested: node --test scripts/skill-docs.test.js; npx changeset status; live 10-query used-car run against SK direct at 2026-04-02T07:38:44.949Z; npm run ci; LSP diagnostics on used-car package files and scripts/skill-docs.test.js; architect verification
Not-tested: No additional live provider permutations beyond the verified 10-query smoke run

* Keep used-car verification docs resilient to live inventory churn

A fresh rerun showed the SK direct inventory total continues to move during the day, so the feature doc now records the verified smoke-run timestamp without freezing a brittle exact count. The regression suite was updated first so the docs stay variability-aware and diff-clean in future follow-ups.

Constraint: Live SK direct inventory totals change over time even when the parsing contract stays stable
Rejected: Keep documenting a fixed total from one smoke run | it immediately drifted and made the docs stale
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the used-car live verification note timestamped and variability-aware instead of pinning an exact inventory total
Tested: node --test scripts/skill-docs.test.js; git diff --check; npm run ci; npx changeset status; live 10-query run against https://www.skdirect.co.kr/tb at 2026-04-02T07:59:41.391Z; LSP diagnostics on scripts/skill-docs.test.js; architect verification
Not-tested: No additional content changes outside the used-car feature doc

* Keep used-car query summaries honest when results are limited

The review found that query-level stats were being computed from the
post-limit slice, so common searches like 아반떼 under-reported both
match counts and price ranges. This change locks the regression first,
then computes the full filtered set before slicing only the returned
items.

Constraint: PR #48 must preserve the existing API shape while fixing the review-blocking stats bug
Rejected: Expanding the response with separate limited/full summary fields | unnecessary API churn for a targeted bug fix
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Query-level summary and matchedCount must stay derived from the full filtered set, not the display limit slice
Tested: npm test --workspace used-car-price-search; npm run ci; git diff --check; npx changeset status; live SK direct smoke run on 2026-04-02T08:23:59Z; LSP diagnostics on used-car-price-search source and test files; architect verification APPROVED
Not-tested: limit=0 semantics remain unchanged and still coerce to the default limit
Related: PR #48

* Keep korean-law-search available during upstream outages (#45)

Issue #44 adds Beopmang as the documented fallback when the primary korean-law-mcp path is unavailable, and locks the new routing into the doc regression suite so future edits do not silently revert the policy.

Constraint: Existing guidance must still prefer korean-law-mcp and keep LAW_OC scoped to the local CLI/MCP path
Rejected: Add a repo-local Beopmang client package | issue only requires fallback registration, not a new implementation surface
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the primary-first rule intact; Beopmang is an outage fallback, not the default path
Tested: node --test scripts/skill-docs.test.js; npm run ci; korean-law list; python3 live Beopmang search smoke for 관세법
Not-tested: Live Beopmang MCP handshake from a GUI MCP client

* Replace coupang scraping package with coupang-mcp server integration

Drop the browser-based scraping package (packages/coupang-product-search)
and switch to the uju777/coupang-mcp MCP server for all Coupang product
searches. This removes the anti-bot workaround complexity and provides
8 ready-to-use tools via MCP Streamable HTTP with no API key required.

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

* Add disclaimer to used-car-price-search README

Clarify that the package queries SK렌터카 타고BUY public data with
no affiliation or sponsorship, and that ad/partnership inquiries
are welcome.

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

* Refactor used-car-price-search into provider-based architecture

SK 타고BUY 전용 로직을 src/providers/sk-tagobuy.js로 분리하고,
공급자를 수평 확장할 수 있는 registry 구조로 전환한다.

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

* Ship LCK analytics inside k-skill's managed release flow

Adapt jerjangmin's upstream lck-analytics skill/package into a new
workspace and skill pack, wire docs/install/release surfaces, and add
regression fixtures/tests plus script smoke coverage so the feature is
verifiable before publish.

Constraint: Upstream package is not published to npm yet
Constraint: Must preserve attribution to original source and author in shipped docs
Rejected: Keep the upstream lck-results install wording in k-skill | conflicts with repo workspace/package naming
Rejected: Ship only the npm package without the local skill scripts | issue explicitly requested the skill as well
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep Changesets as the only version-bump path for lck-analytics; do not hand-edit versions for release
Tested: node --test packages/lck-analytics/test/index.test.js scripts/skill-docs.test.js
Tested: npm run lint --workspace lck-analytics
Tested: npm test --workspace lck-analytics
Tested: live getLckSummary('2026-04-01', { team: '한화', includeStandings: true })
Tested: node lck-analytics/scripts/sync-oracle.js --csv lck-analytics/samples/oracle-lck-sample.csv --cache .tmp/lck-cache && node lck-analytics/scripts/build-match-report.js --date 2026-04-01 --team 한화 --cache .tmp/lck-cache && node lck-analytics/scripts/analyze-live-game.js --game game-1 --window packages/lck-analytics/test/fixtures/live-window-game-1.json --details packages/lck-analytics/test/fixtures/live-details-game-1.json --cache .tmp/lck-cache
Tested: npm run ci
Tested: npx tsc --noEmit --project /Users/jeffrey/Projects/k-skill/tsconfig.json
Not-tested: Riot live feed behavior for arbitrary future game ids outside the fixture-backed smoke path

* Enable policy-aware Korean spell checking from the official Nara surface

Add a skill guide and Python helper that use the approved old_speller HTML flow, chunk long text conservatively, and report original/suggestion/reason deltas. The docs also record the public-site limits, Cloudflare behavior, and non-commercial usage policy so agents do not overreach the free surface.

Constraint: Public site is HTML-only and may return 403 to non-browser clients
Constraint: Must not add new dependencies or high-volume crawling behavior
Rejected: Node fetch client | Cloudflare returned 403 in this environment
Rejected: Paid API integration | no public contract or credentials were available for this task
Confidence: medium
Scope-risk: moderate
Reversibility: clean
Directive: Keep usage low-rate and non-commercial unless supplier-approved API terms are added
Tested: npm run lint
Tested: npm run typecheck
Tested: npm test
Tested: npm run build
Tested: python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json
Tested: python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json
Not-tested: Paid API/order flow
Not-tested: High-volume commercial workloads
Not-tested: Non-UTF-8 file inputs

* Preserve korean spell-check layout in corrected output

The Nara payload can collapse paragraph separators into normalized page text, so the helper now maps corrections back onto the original chunk before rebuilding corrected_text. The CLI also rejects non-positive --max-chars values, and regression tests cover both the layout-preservation path and invalid argument handling.

Constraint: Nara result pages can normalize blank lines and sentence spacing before exposing errInfo offsets
Rejected: Narrow docs away from file/Markdown proofreading | preserving original chunk separators keeps the documented workflow intact
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep multiline separator preservation tied to the original chunk whenever a suggestion only changes whitespace across collapsed boundaries
Tested: python3 -m unittest scripts.test_korean_spell_check; npm run lint; npm run typecheck; npm test; npm run build; python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json; python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json; python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n왠지 않되요.' --format json; python3 scripts/korean_spell_check.py --text 테스트 --max-chars 0 --format json
Not-tested: Live multi-page Nara payloads with separator-sensitive corrections across multiple returned pages

* Preserve file-proofreading layout in Korean spell check output

The spell-check helper now keeps exact blank-line runs and paragraph
indentation when chunking and reassembling file-style input, while still
allowing the official service's spacing corrections to flow through.
Regression coverage now locks the collapsed-layout, triple-blank-line,
and cross-boundary spacing cases that triggered the PR review.

Constraint: Official Nara/PNU payloads can collapse layout and sometimes merge corrections across preserved paragraph boundaries
Rejected: Narrow docs away from file-level proofreading | the existing feature scope explicitly supports file and Markdown checks
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Preserve exact layout tokens only at original newline boundaries; do not reintroduce global strip/join normalization without real file-mode verification
Tested: npm run lint; npm run typecheck; npm test; npm run build; python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json; python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json; python3 scripts/korean_spell_check.py --file <tmpfile> --format json; python3 scripts/korean_spell_check.py --text 테스트 --max-chars 0 --format json
Not-tested: Live upstream behavior for extremely long leading/trailing-whitespace-only files
Related: PR #60

* Keep layout-preserving chunk splits from tripping on separator-boundary fixes

The follow-up layout preservation pass renamed the working unit variable, but one separator-length guard still referenced the old paragraph name. This commit finishes the refactor so exact-boundary paragraph chunks keep the preserved-separator logic reachable and the new regression coverage stays green.

Constraint: Must preserve the existing feature/#47 branch and PR #60 flow while fixing the approved review follow-up
Rejected: Revert the layout-preserving chunking refactor | would discard the verified file-layout fix and its regressions
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep chunk reassembly lossless; new chunking changes should continue to round-trip original separators byte-for-byte
Tested: npm run lint; npm run typecheck; npm test; npm run build; python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json; python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json; python3 scripts/korean_spell_check.py --file <tempfile-with-triple-blank-lines> --format json; python3 scripts/korean_spell_check.py --text 테스트 --max-chars 0 --format json (expected argument error)
Not-tested: Live multi-page service responses beyond the local smoke cases

* Preserve spell-check chunk separators when units overflow

The file-layout follow-up already preserved blank runs and indentation,
but the overlong-unit path still checked the wrong variable when
extracting a trailing separator. That could strand separators in
a standalone chunk and break exact reassembly for tight max-char
limits.

This commit fixes the separator extraction guard and locks the
behavior with a regression that proves chunk concatenation still
matches the original text when an overlong paragraph is followed
by a blank-line separator.

Constraint: File-mode reconstruction must preserve exact layout while chunking long input
Rejected: Broader chunking rewrite | existing structure only needed the overflow guard corrected
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep split_text_into_chunks chunk concatenation identical to the original input for layout-sensitive file checks
Tested: npm run lint; npm run typecheck; npm test; npm run build; python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json; python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json; python3 scripts/korean_spell_check.py --text 테스트 --max-chars 0 --format json; manual --file smoke with triple blank lines and indentation
Not-tested: Live-service failure modes such as Cloudflare/browser challenges

* Prevent clean spell-check chunks from crashing mixed file runs

The Nara/PNU surface can return a plain 'no issues found' HTML page
without the embedded result payload when a chunk is already clean.
Chunked file and markdown runs could hit that response on earlier
chunks, raise a ValueError, and never reach later chunks that still
needed corrections. Treat the no-issues page as an empty result set
and lock the behavior with narrow regression coverage.

Constraint: Upstream no-issue responses omit the JavaScript payload entirely and can split the status message across HTML whitespace
Rejected: Fabricate a synthetic result page | empty-page handling already preserves the original clean chunk
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep empty-result detection aligned with the upstream no-issues message unless the service publishes a structured empty payload contract
Tested: python3 -m unittest scripts.test_korean_spell_check
Tested: npm run lint
Tested: npm run typecheck
Tested: npm test
Tested: npm run build
Tested: python3 scripts/korean_spell_check.py --text '아버지가방에들어가신다.' --format json
Tested: python3 scripts/korean_spell_check.py --text $'아버지가방에들어가신다.\n\n아버지가방에들어가신다.' --max-chars 15 --format json
Tested: python3 scripts/korean_spell_check.py --text 테스트 --max-chars 0 --format json
Tested: python3 scripts/korean_spell_check.py --file <tmpfile> --format json
Tested: python3 scripts/korean_spell_check.py --file <tmpfile> --max-chars 18 --format json
Not-tested: Other upstream empty-result templates beyond the current no-issues HTML wording

* Make Joseon Sillok lookups reproducible from the official site

Add a joseon-sillok-search skill and a Python helper that scrape the
official Joseon Annals search/detail pages. The helper normalizes
king/year metadata, fetches detail excerpts, and locks the repository
docs plus regression coverage around the shipped workflow.

Constraint: v1 must stay on official public HTML surfaces only
Constraint: Must avoid adding new dependencies for a simple scraping helper
Constraint: Shell connectivity to sillok.history.go.kr became intermittent during final live reruns
Rejected: Ship a new npm workspace | repo skill/docs pattern is enough for v1
Rejected: Add BeautifulSoup or another parser dependency | unnecessary for the bounded HTML patterns
Confidence: medium
Scope-risk: narrow
Reversibility: clean
Directive: Keep year filtering Gregorian and derived from official regnal metadata unless the upstream site exposes a better structured contract
Tested: npm run ci
Tested: Earlier live POST/detail probes against search/searchResultList.do and /id/kda_12512030_002 during implementation
Tested: Live official article inspection for kda_12512030_002 via the public site
Not-tested: Final end-to-end CLI live run after the last refactor, because the shell hit transient TCP timeouts to sillok.history.go.kr
Related: #59

* Prevent sillok follow-up fixes from missing filtered results or weakening trust

This follow-up addresses the blocking PR review items on the Joseon Sillok helper. The search loop now derives total pages from the first live page size so king/year filtering can reach later pages, TLS verification stays enabled on both requests and urllib paths, detail parsing accepts the live classification brackets, and repo docs stop linking to missing Korean spell-check assets on this branch.

Constraint: Must preserve the existing joseon-sillok-search surface and avoid new dependencies
Rejected: Keep verify=False behind an implicit fallback | still weakens authenticity for the default path
Rejected: Infer pagination from the hardcoded 50-row default | misses valid later-page matches when the site serves smaller pages
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If the official site changes page size or pagination markup again, update the first-page pagination regression before altering search_sillok pagination math
Tested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_sillok_search
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: python3 scripts/sillok_search.py --query '훈민정음' --king 세종 --year 1443 --limit 1 --timeout 20
Tested: python3 - <<'PY' ... fetch_detail_page(..., article_id='kda_12512030_002', timeout=20) ... PY
Not-tested: Successful live sillok.history.go.kr responses in this environment (POST and detail GET both timed out at 20s)
Related: PR #62

* Make joseon-sillok-search installs work outside the repo

The skill installer only ships the skill directory, so the sillok helper has to live inside that payload. This moves the authoritative helper into joseon-sillok-search/scripts/, keeps the repo-root script as a thin shim for tests and docs, and trims footer metadata from parsed article bodies so excerpts match the published examples.

Constraint: skills add exposes only the installed skill payload, not repo-root helpers

Rejected: Keep the helper only under scripts/ | installed skill commands fail after copy-only installs

Confidence: high

Scope-risk: narrow

Directive: Keep the repo-root sillok wrapper thin and update the bundled helper first when behavior changes

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

Tested: node --test scripts/skill-docs.test.js

Tested: temp installed-skill smoke run of python3 scripts/sillok_search.py --help plus footer-cleaning parse_detail_page check

Tested: npm run ci

Not-tested: live sillok.history.go.kr shell requests still hit connect timeouts in this environment

* Restore the sillok CLI's verified stdlib transport fallback

The helper already shipped a stdlib urllib opener that can reach the
official search endpoint in environments where requests/urllib3 aborts.
This change keeps that opener available even when requests imports
successfully and falls back to it on retryable requests transport
failures. Added regression coverage for opener availability and the
requests-to-urllib fallback so the default CLI path matches the live
verified behavior.

Constraint: Official sillok detail GETs can still time out transiently in this environment
Constraint: Keep TLS verification enabled and preserve the documented CLI entrypoints
Rejected: Force urllib for every request | keep the existing requests fast path when it succeeds
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Preserve the stdlib fallback tests whenever the transport layer changes
Tested: PYTHONPATH=.:scripts python3 -m unittest scripts.test_sillok_search
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: live forced-fallback probe against searchResultList.do with requests.post patched to OSError(22)
Not-tested: full live CLI completion through the detail GET in this environment

* Document the approved real-estate MCP skill without vendoring upstream

Issue #53 is intentionally doc-only, so this change adds the
real-estate-search skill, feature guide, setup/security notes,
and regression coverage around the upstream real-estate-mcp
integration instead of importing server code.

The new docs keep the original MCP link, cover Codex/Claude
registration, and spell out the self-host + Cloudflare Tunnel +
launchd path for environments where no fixed hosted endpoint is
available.

Constraint: Must use tae0y/real-estate-mcp without copying its source into this repository
Constraint: Must include the original MCP link and a stable self-host fallback when no hosted endpoint is available
Rejected: Vendor the upstream MCP source | issue explicitly requires skill docs only
Rejected: Assume a public hosted MCP endpoint exists | upstream docs did not publish one
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep this integration doc-only; do not add a workspace or vendored server without revisiting issue #53 constraints
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: upstream bootstrap smoke (`uv sync`, `uv run real-estate-mcp --help`, HTTP initialize on 127.0.0.1:8017)
Not-tested: live property data queries with a valid DATA_GO_KR_API_KEY

* Prevent misleading real-estate self-host instructions

Tighten the real-estate skill docs so the launchd fallback stays operational
and the Onbid bid-result tools are described with the same WIP caveat the
upstream project still publishes.

Constraint: Upstream Docker compose already uses restart: unless-stopped while `docker compose ... up -d` daemonizes immediately
Rejected: Keep a separate server LaunchAgent with RunAtLoad + KeepAlive | launchd would restart-loop on the exiting compose command
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Mirror upstream capability caveats in k-skill docs and do not wrap daemonized server commands in launchd KeepAlive jobs
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: uv sync
Tested: uv run real-estate-mcp --help
Tested: DATA_GO_KR_API_KEY=dummy uv run real-estate-mcp --transport http --host 127.0.0.1 --port 8017
Tested: curl initialize on http://127.0.0.1:8017/mcp returned protocolVersion 2024-11-05
Not-tested: Live 거래 조회 with a real DATA_GO_KR_API_KEY

* Keep install docs from reintroducing broken launchd guidance

The install guide had drifted from the real-estate skill and feature docs and still implied that macOS launchd should auto-run both the server and tunnel. This narrows the guidance back to tunnel-only launchd ownership and extends regression coverage so docs/install.md cannot silently reintroduce the server-side loop wording.

Constraint: Upstream docker compose already uses restart: unless-stopped
Constraint: Review-round-2 scope is limited to docs and regression coverage
Rejected: Leave docs/install.md under a generic launchd presence check | it missed the exact server/터널 regression
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep docs/install.md aligned with the detailed real-estate feature guide when self-host guidance changes
Tested: node --test scripts/skill-docs.test.js; npm run ci; fresh-clone upstream smoke with uv sync, uv run real-estate-mcp --help, and HTTP initialize on 127.0.0.1:8017/mcp
Not-tested: None

* Allow Han River water-level lookups without user API keys

The proxy now resolves HRFCO water-level stations via waterlevel/info and
fetches the latest 10-minute measurement via waterlevel/list, exposing a
public summary route plus a hosted skill/docs path.

Constraint: HRFCO requires a ServiceKey and station-code-based latest lookup
Rejected: Raw passthrough only | forces users to manage upstream details and misses the approved no-key UX
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep this route summary-only and public; expand rainfall/dam/bo/fldfct in separate issues
Tested: npm run ci; local server smoke test with HRFCO sample key against /v1/han-river/water-level; tsc diagnostics on packages/k-skill-proxy/src/server.js and src/hrfco.js
Not-tested: Hosted production proxy rollout

* Expose official nearby fuel prices for location-based gas station lookups

Add the first cheap-gas-nearby skill/package pair so nearby gas-price
queries can resolve a user-supplied location, translate it into Opinet's
KATEC search contract, and return the cheapest nearby stations with
address and facility detail. The docs and setup surfaces now advertise
the new skill and its Opinet API key requirement.

Constraint: Nearby fuel prices must come from the official KNOC Opinet API when available
Constraint: No new external dependencies were allowed for coordinate conversion or location resolution
Rejected: Map-only gas price scraping | official Opinet Open API exists and is the preferred source
Rejected: Require lat/lng input only | poorer UX than supporting landmark/station queries through anchor resolution
Confidence: medium
Scope-risk: moderate
Reversibility: clean
Directive: Keep `OPINET_API_KEY` as the only supported official-price credential unless the repo adopts an Opinet proxy later
Tested: npm run ci; node --test packages/cheap-gas-nearby/test/index.test.js; offline fixture smoke via searchCheapGasStationsByLocationQuery('서울역', ...)
Not-tested: Live Opinet API call with a real `OPINET_API_KEY` (no non-placeholder key was configured locally)
Related: #54

* Explain Blue Ribbon premium gating instead of failing opaquely

Blue Ribbon's nearby endpoint now returns PREMIUM_REQUIRED for public
requests, so the package now upgrades that response into a stable domain
error and updates user-facing docs to describe the degraded nearby state.
Regression tests cover both location-query and coordinate-query entrypoints
while keeping the existing happy path intact.

Constraint: Must not attempt to bypass Blue Ribbon premium access controls
Constraint: Package releases must use Changesets metadata
Rejected: Silently return empty results on PREMIUM_REQUIRED | would hide the upstream contract change from callers
Rejected: Keep generic 403 error and docs unchanged | leaves the main failure mode opaque and misleading
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If Blue Ribbon reopens a public nearby surface later, update the docs and replace the premium_required remap only after fresh live verification
Tested: npm test --workspace blue-ribbon-nearby; npm run lint --workspace blue-ribbon-nearby; npm run ci; live node repro now returns premium_required metadata
Not-tested: Official premium-authenticated nearby flow, because no approved premium credentials are available

* Reduce duplicate premium-required test assertions

The implementation diff was already verified, so this follow-up keeps the
new regression coverage easier to read by sharing one assertion helper
instead of repeating the same predicate twice.

Constraint: Must preserve the verified PREMIUM_REQUIRED regression coverage exactly
Rejected: Leave duplicated inline predicates in both tests | repeats the same contract and adds noise to future edits
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep location-query and coordinate-query regressions aligned when the premium_required error contract changes
Tested: npm test --workspace blue-ribbon-nearby; npm run lint --workspace blue-ribbon-nearby; npm run ci
Not-tested: Additional live Blue Ribbon requests beyond the previously verified premium_required repro

* Document an Olive Young search skill around the upstream daiso CLI

Issue #61 asked for an Olive Young lookup workflow without vendoring the upstream daiso-mcp server into k-skill, so this change adds a docs-only skill, threads it through repository docs, and locks the guidance with regression assertions.

The new guidance prefers CLI-first verification (`npx daiso`) and a clone fallback (`git clone https://github.com/hmmhmmhm/daiso-mcp.git`) instead of requiring direct Claude Code MCP installation. The existing daiso-product-search skill remains untouched.

Constraint: Must mention the original https://github.com/hmmhmmhm/daiso-mcp repo
Constraint: Must avoid changing the existing daiso-product-search skill
Constraint: Must prefer upstream CLI/package usage over direct Claude Code MCP installation
Rejected: Vendor upstream Olive Young code into k-skill | contradicts the minimal-addition design request
Rejected: Make Claude Code MCP setup the default path | issue explicitly asked for CLI-first usage
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the Olive Young docs aligned with upstream `daiso` CLI behavior and note public endpoint instability when live verification shows it
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: 2026-04-05 live /tmp/daiso-mcp CLI checks for health, /api/oliveyoung/stores, /api/oliveyoung/products, /api/oliveyoung/inventory
Not-tested: Authenticated/private Olive Young flows or ordering/payment paths

* Keep Han River docs honest until hosted rollout lands

The local HRFCO proxy route already works, but the deployed public proxy still
lacks /v1/han-river/water-level as of 2026-04-05. This follow-up changes the
user-facing docs to require a self-hosted or deployment-verified proxy URL,
keeps the intended hosted path documented as rollout-pending, and locks that
caveat with a regression test.

Constraint: Hosted k-skill-proxy deployment still returns 404 for /v1/han-river/water-level on 2026-04-05
Rejected: Keep advertising the hosted route as the default live path | current deployment is not live yet
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Restore hosted-default wording only after the public proxy route and HRFCO configuration are verified live
Tested: node --test scripts/skill-docs.test.js; npm run ci; local mocked smoke via node packages/k-skill-proxy/src/server.js + GET /v1/han-river/water-level?stationName=한강대교
Not-tested: Live hosted k-skill-proxy behavior after deployment

* Keep cheap gas lookups from failing on recoverable Kakao and input issues

The lookup now walks ranked Kakao anchor candidates until one returns usable coordinates, and it rejects invalid limit inputs instead of silently collapsing non-empty results into an empty list. The regression suite now locks both review repros plus the null-coordinate normalization edge case that surfaced while implementing the fallback.

Constraint: Kakao place panels can be partially populated or return 404 for otherwise valid search candidates
Rejected: Default invalid limit/detailLimit values silently | still masks caller bugs and can look like no results nearby
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep anchor resolution tolerant of unusable Kakao panels before failing the whole lookup
Tested: node --test packages/cheap-gas-nearby/test/index.test.js; npm run ci; offline fixture smoke for searchCheapGasStationsByLocationQuery('서울역', ...)
Not-tested: Live Opinet/Kakao network path without a real OPINET_API_KEY

* Keep Olive Young install guidance aligned with live retry behavior

Issue #61's initial branch already shipped the olive-young-search skill/docs set, but the install guide did not have a regression lock for the public endpoint instability note captured during live verification. This follow-up adds that test first and updates the install quickstart so retry-or-clone fallback guidance stays in sync with the verified daiso CLI workflow.

Constraint: Must keep the follow-up scoped to the approved issue #61 docs behavior
Constraint: Public olive-young endpoint can intermittently return 5xx/503 during live verification
Rejected: Re-open the broader feature docs set | the existing branch already covered the main feature scope
Rejected: Leave the retry guidance untested in install docs | risk of future doc drift across surfaces
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If live olive-young verification changes again, update install docs and the regression together so retry/fallback guidance stays honest
Tested: node --test scripts/skill-docs.test.js
Tested: npm run ci
Tested: cd /tmp/daiso-mcp && npx daiso health
Tested: cd /tmp/daiso-mcp && npx daiso get /api/oliveyoung/stores --keyword 명동 --limit 3 --json
Tested: cd /tmp/daiso-mcp && npx daiso get /api/oliveyoung/products --keyword 선크림 --size 3 --json
Tested: cd /tmp/daiso-mcp && npx daiso get /api/oliveyoung/inventory --keyword 선크림 --storeKeyword 명동 --size 2 --json
Not-tested: Authenticated Olive Young flows or sustained-rate retry behavior beyond the documented smoke checks

* Preserve ranked Kakao anchor fallbacks after panel failures

The resolver already retried later Kakao panels, but after the best panel failed it
walked the remaining candidates in raw HTML order. Centralizing the full
anchor ranking in parse.js keeps fallback iteration aligned with the
existing scoring rules and locks the review repro with a regression test
for 강남역 ordering.

Constraint: Kakao place panels can 404 or omit coordinates even when later ranked candidates are usable
Rejected: Re-rank only after a failed panel | duplicates scoring logic and drifts from selectAnchorCandidate
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep resolveAnchor fallback order tied to the shared anchor scoring logic rather than raw Kakao result order
Tested: node --test packages/cheap-gas-nearby/test/index.test.js; npm run ci; offline fixture smoke for searchCheapGasStationsByLocationQuery('서울역', ...); lsp diagnostics on affected files
Not-tested: Live Kakao/Opinet network calls with a non-placeholder OPINET_API_KEY

* Clarify that Blue Ribbon coordinate lookups fail the same way

The premium gate now affects both location-query and coordinate-entry nearby flows. Record that parity in the skill and docs so the live 2026-04-05 repro evidence stays aligned with the shipped public contract.

Constraint: /restaurants/map remains premium-gated for public requests as of 2026-04-05
Rejected: Add more code changes without a behavior gap | would add churn after the approved fix already landed
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the docs/examples aligned with live repro evidence whenever Blue Ribbon changes nearby access behavior
Tested: npm test --workspace blue-ribbon-nearby; npm run lint --workspace blue-ribbon-nearby; npm run ci; live node repro for location + coordinates premium_required contract
Not-tested: New upstream success-path live nearby payloads, because public access is still premium-gated

* Document a runnable Olive Young clone fallback

The follow-up review found that clone-local `npx daiso` commands do not run from a built `hmmhmmhm/daiso-mcp` checkout because the generated bin file is not executable. This updates the olive-young skill and docs to use the verified `node dist/bin.js ...` path for clone fallback, and locks that behavior with regression tests while keeping the public CLI-first path unchanged.\n\nConstraint: Upstream clone checkouts can fail with `Permission denied` when invoked through clone-local `npx daiso`\nConstraint: Keep the existing daiso-product-search skill untouched\nRejected: Leave install docs unchanged | PR body and docs would keep a broken clone fallback path\nRejected: Vendor or patch upstream daiso-mcp inside k-skill | issue explicitly requires documenting upstream flow instead\nConfidence: high\nScope-risk: narrow\nReversibility: clean\nDirective: Keep clone-fallback docs and PR verification commands aligned with the actually runnable local invocation\nTested: node --test scripts/skill-docs.test.js\nTested: npm run ci\nTested: cd /tmp/daiso-mcp && npm install && npm run build && node dist/bin.js health\nTested: cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/stores --keyword 명동 --limit 3 --json\nTested: cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/products --keyword 선크림 --size 3 --json\nTested: cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/inventory --keyword 선크림 --storeKeyword 명동 --size 2 --json\nNot-tested: Public npx endpoint stability beyond the verified smoke-test window

* Record fresh verification for the approved cheap-gas follow-up

The ranked Kakao fallback-order and invalid-limit fixes are already present on feature/#54, so no further code edits were necessary. This empty follow-up commit records the requested rerun verification for PR #67 before posting the implementation update.\n\nConstraint: Existing branch head already contains the approved cheap-gas-nearby follow-up\nRejected: Invent another code/doc change just to force a non-empty diff | unnecessary risk after approval\nConfidence: high\nScope-risk: narrow\nReversibility: clean\nDirective: Keep Kakao anchor fallback iteration tied to the shared ranking helper and preserve finite-number validation for limit inputs\nTested: node --test packages/cheap-gas-nearby/test/index.test.js; npm run ci; offline fixture smoke for searchCheapGasStationsByLocationQuery('서울역', ...); lsp diagnostics on affected files\nNot-tested: Live Kakao/Opinet network calls with a non-placeholder OPINET_API_KEY

* Record verified delivery for approved cheap-gas-nearby rollout

The approved Issue #54 implementation was already present on feature/#54 when this follow-up began, so this checkpoint records fresh verification evidence and keeps PR #67 moving without introducing extra code churn.

Constraint: Existing approved fixes were already on the branch and the follow-up still required a pushed update plus implementation comment
Rejected: Add additional code or docs churn | approved scope was already satisfied and green locally
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Preserve the Kakao fallback-order and invalid-count regression coverage unless equivalent runtime repros replace it
Tested: node --test packages/cheap-gas-nearby/test/index.test.js; offline fixture smoke for searchCheapGasStationsByLocationQuery('서울역', ...); npm run ci; LSP diagnostics on affected cheap-gas-nearby files
Not-tested: Live Kakao/Opinet network path without a non-placeholder OPINET_API_KEY

* Keep Blue Ribbon premium-gate remapping explicitly bounded

The issue #63 fix already remaps PREMIUM_REQUIRED for nearby lookups. This follow-up adds a regression that proves non-premium /restaurants/map failures still surface as generic request errors, and clarifies that boundary in the package and feature docs.

Constraint: PR #68 already carries the shipped behavior change and must stay aligned with current live premium-gated upstream behavior
Rejected: Broaden domain remapping to all 403 nearby errors | would hide distinct upstream failure modes
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep only PREMIUM_REQUIRED on /restaurants/map mapped to premium_required unless a new verified upstream contract is documented
Tested: npm test --workspace blue-ribbon-nearby; npm run lint --workspace blue-ribbon-nearby; npm run ci; live node repro for location+coordinate nearby lookups on 2026-04-06; LSP diagnostics on src/test
Not-tested: Alternative non-premium upstream status codes beyond the mocked 403 ACCESS_DENIED path

* Keep Olive Young clone fallback docs runnable from a fresh clone

Round 3 review found that the inline fallback shorthand skipped `cd daiso-mcp`,
which made `npm install` run outside the cloned upstream repo even though the
fenced examples were already correct. This tightens the two published shorthand
snippets and adds regression coverage so both docs surfaces reject the broken
chain in future edits.

Constraint: Existing node dist/bin.js clone fallback examples were already verified and had to stay aligned
Constraint: The follow-up had to stay scoped to docs/test coverage without touching the existing daiso-product-search skill
Rejected: Convert the shorthand to prose only | the review specifically requested a runnable inline form or an explicit removal
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep inline clone fallback snippets synchronized with the fenced node dist/bin.js examples and require `cd daiso-mcp` before install/build
Tested: node --test scripts/skill-docs.test.js; npm run ci; cd /tmp/daiso-mcp && npm install && npm run build; cd /tmp/daiso-mcp && node dist/bin.js health; cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/stores --keyword 명동 --limit 3 --json; cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/products --keyword 선크림 --size 3 --json; cd /tmp/daiso-mcp && node dist/bin.js get /api/oliveyoung/inventory --keyword 선크림 --storeKeyword 명동 --size 2 --json
Not-tested: Public npx path in this follow-up round (previous PR verification already covered it)
Related: PR #71

* Bundle korean-spell-check script inside skill directory for packageless installs

The Python helper lived only in the repo-root scripts/ folder, so
`skills add` never shipped it. Move the real implementation into
korean-spell-check/scripts/ (mirroring joseon-sillok-search) and
replace the root copy with a thin re-export wrapper so lint/test
still resolve from the repo root.

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

* Exclude .claude directory from skill validation to fix CI in worktrees

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

* real-estate-search: MCP self-host → k-skill-proxy HTTP 전환

사용자가 직접 real-estate-mcp를 clone/self-host하는 대신
k-skill-proxy에 MOLIT 실거래가 API route를 추가해서
다른 스킬(한강수위, 미세먼지 등)과 동일한 패턴으로 사용 가능하게 함.

- GET /v1/real-estate/region-code — 지역코드 검색
- GET /v1/real-estate/:assetType/:dealType — 9개 거래 유형 조회
- molit.js: XML 파싱, 필드 정규화, 취소거래 필터링, 요약통계
- region-lookup.js: 284개 법정동 5자리 코드 토큰 매칭
- SKILL.md/docs를 HTTP proxy 기반으로 전면 재작성
- DATA_GO_KR_API_KEY를 사용자 필수항목에서 프록시 운영자 전용으로 이동

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

* cheap-gas-nearby: proxy 경유로 전환 + 프로덕션 배포 구조 문서화

- cheap-gas-nearby: OPINET_API_KEY 없이 k-skill-proxy 경유로 동작하도록 변경
  - fetchAroundStations/fetchDetailById에 proxy fallback 추가
  - SKILL.md, feature doc에서 사용자 API key 요구 제거
- AGENTS.md, CLAUDE.md: proxy 개발/배포 워크플로우 문서화
- docs/features/k-skill-proxy.md: opinet route 추가, 프로덕션 자동 배포 구조 설명

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

* blue-ribbon-nearby: proxy 경유로 전환 + 프리미엄 세션 프록시 라우트

Blue Ribbon /restaurants/map이 프리미엄 전용으로 전환되어,
k-skill-proxy에 BLUE_RIBBON_SESSION_ID 기반 프록시 라우트를 추가하고
blue-ribbon-nearby 패키지가 기본적으로 프록시를 경유하도록 변경.

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 17:44:23 +09:00
Jeffrey (Dongkyu) Kim
8dc6c9ea50 blue-ribbon-nearby: proxy 경유로 전환 + 프리미엄 세션 프록시 라우트
Blue Ribbon /restaurants/map이 프리미엄 전용으로 전환되어,
k-skill-proxy에 BLUE_RIBBON_SESSION_ID 기반 프록시 라우트를 추가하고
blue-ribbon-nearby 패키지가 기본적으로 프록시를 경유하도록 변경.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 17:36:26 +09:00
Jeffrey (Dongkyu) Kim
e81388f0d5 real-estate-search: MCP self-host → k-skill-proxy HTTP 전환
사용자가 직접 real-estate-mcp를 clone/self-host하는 대신
k-skill-proxy에 MOLIT 실거래가 API route를 추가해서
다른 스킬(한강수위, 미세먼지 등)과 동일한 패턴으로 사용 가능하게 함.

- GET /v1/real-estate/region-code — 지역코드 검색
- GET /v1/real-estate/:assetType/:dealType — 9개 거래 유형 조회
- molit.js: XML 파싱, 필드 정규화, 취소거래 필터링, 요약통계
- region-lookup.js: 284개 법정동 5자리 코드 토큰 매칭
- SKILL.md/docs를 HTTP proxy 기반으로 전면 재작성
- DATA_GO_KR_API_KEY를 사용자 필수항목에서 프록시 운영자 전용으로 이동

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 16:50:27 +09:00
Jeffrey (Dongkyu) Kim
d7a7a6de79 Merge main into dev and resolve 16-file conflicts
main accumulated release/hotfix PRs (#34, #43, #49, #50, kipris,
LCK, KMA forecast) while dev grew new skills (cheap-gas, han-river,
olive-young, joseon-sillok, korean-spell-check, real-estate, blue-
ribbon premium gate).  Both sides are fully preserved.

Also bump toss-securities test assertion to match the 0.2.0 version
that main already shipped.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 15:25:49 +09:00
Jeffrey (Dongkyu) Kim
afd7c6a996 Allow Han River water-level lookups without user API keys
The proxy now resolves HRFCO water-level stations via waterlevel/info and
fetches the latest 10-minute measurement via waterlevel/list, exposing a
public summary route plus a hosted skill/docs path.

Constraint: HRFCO requires a ServiceKey and station-code-based latest lookup
Rejected: Raw passthrough only | forces users to manage upstream details and misses the approved no-key UX
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep this route summary-only and public; expand rainfall/dam/bo/fldfct in separate issues
Tested: npm run ci; local server smoke test with HRFCO sample key against /v1/han-river/water-level; tsc diagnostics on packages/k-skill-proxy/src/server.js and src/hrfco.js
Not-tested: Hosted production proxy rollout
2026-04-05 19:26:06 +09:00
Jeffrey (Dongkyu) Kim
51464d1806
Release: coupang, korean-law, subway proxy improvements (#43)
* Remove client-side Seoul subway key setup

Route Seoul subway arrival lookups through k-skill-proxy so the
hosted proxy owns the Seoul Open Data upstream key and end users
only need the proxy base URL. Add proxy route coverage, update
skill/docs guidance, and align setup materials with the hosted
proxy flow used for fine dust.

Constraint: Must keep the proxy public, read-only, and dependency-free
Constraint: Must satisfy TDD-first verification and ship on feature/#35 targeting dev
Rejected: Add a separate client helper package | unnecessary extra layer for a single proxy route
Rejected: Keep SEOUL_OPEN_API_KEY as an end-user requirement | defeats the issue goal
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep the Seoul subway proxy surface limited to station-arrival passthrough unless tests/docs expand the contract
Tested: npm run ci; local proxy runtime on 127.0.0.1:4120 for /health and /v1/seoul-subway/arrival on 2026-03-31 with an invalid upstream key
Not-tested: Live success response with a valid Seoul Open API key
Related: #35

* Prevent broken Seoul subway proxy defaults before hosted rollout

The Seoul subway proxy endpoint code is present locally, but the hosted public route is not live yet. This change turns the user-facing subway docs back into an explicit proxy configuration flow, replaces the misleading hosted default in setup examples, and keeps subway proxy examples on self-host/local URLs until rollout is verified.

Constraint: Hosted k-skill-proxy.nomadamas.org/v1/seoul-subway/arrival is not live yet
Rejected: Keep the hosted Seoul subway URL as the default path | would send default users to a 404 route
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Do not restore a hosted Seoul subway default until the public proxy route is deployed and smoke-verified
Tested: node --test scripts/skill-docs.test.js; npm run ci; local proxy smoke on 127.0.0.1:4120 with stubbed Seoul upstream (GET /health, GET /v1/seoul-subway/arrival?stationName=강남)
Not-tested: Live hosted proxy smoke after deployment

* Reduce Seoul subway proxy upstream pressure with request caching

The public subway arrival route already normalized caller input but still re-fetched the Seoul upstream for every identical poll. This change adds a short-TTL cache keyed from the normalized subway query, annotates JSON responses with cache metadata, and locks the repeated-read collapse with a regression that proves alias/default normalization still reuses the cached result.

Constraint: The endpoint must stay public/no-auth while protecting a shared server-side SEOUL_OPEN_API_KEY from unnecessary repeated upstream hits
Rejected: Add a new shared cache abstraction for proxy metadata | unnecessary for this narrow fix and would enlarge the diff
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep subway cache keys tied to normalizeSeoulSubwayQuery output so alias/default query forms continue collapsing to one upstream request
Tested: node --test packages/k-skill-proxy/test/server.test.js
Tested: npm run ci
Tested: Local proxy runtime on 127.0.0.1:4120 with SEOUL_OPEN_API_KEY=test-seoul-key and stubbed fetch for /health plus repeated /v1/seoul-subway/arrival requests
Not-tested: Live hosted proxy rollout state

* Document OpenClaw support in the public compatibility list

The approved scope for issue #29 was narrowed to README support messaging,
so this change adds OpenClaw/ClawHub to the supported-client line and locks
that wording with a regression test in the docs suite.

Constraint: Issue #29 was approved as a README-only compatibility/docs change
Rejected: Broader install-doc updates | out of approved scope for this issue
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep OpenClaw support wording aligned with the supported-client README line and its docs regression test
Tested: Focused Node docs regression, npm run lint, npm run typecheck, npm run build, npm test
Not-tested: Live OpenClaw or ClawHub install flow (issue scope was documentation-only)

* Protect README client-support claims from ClawHub regressions

The README already advertises OpenClaw/ClawHub, but the docs
regression only matched OpenClaw. Tighten the assertion to the
exact supported-client fragment so a future edit cannot silently
remove ClawHub while keeping the issue verification command stable.

Constraint: PR #39 already publishes a verification command keyed to the current test name
Rejected: Rename the test to mention ClawHub explicitly | would drift from the published verification command
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep this regression synchronized with the README supported-client line whenever that copy changes
Tested: node --test scripts/skill-docs.test.js --test-name-pattern 'README advertises OpenClaw among the supported coding agents'
Tested: npm run lint
Tested: npm run typecheck
Tested: npm run build
Tested: npm test
Tested: lsp diagnostics for scripts/skill-docs.test.js (0 errors)
Not-tested: N/A

* Make Coupang shopper research usable without pretending scraping is stable

Coupang blocks unattended shopper queries in this environment, so the new package focuses on the durable pieces we can ship honestly: official URL builders, browser-captured HTML parsers, and explicit automation probes. The docs and skill now explain the seller-API limitation, the verified anti-bot behavior, and the browser-capture fallback expected by callers.

Constraint: No general shopper Open API surfaced in Coupang's official developer docs

Constraint: Headless/direct retrieval is anti-bot blocked in verified local probes

Rejected: Bundle a scraping bypass or hidden browser dependency | violates repo policy and would over-claim reliability

Confidence: high

Scope-risk: moderate

Reversibility: clean

Directive: Keep probe/docs behavior aligned with fresh live verification before claiming unattended Coupang access works

Tested: npm run ci; live probeAutomation(query=생수) with direct fetch + Playwright-core browserFetchHtml; LSP diagnostics on changed JS/test files

Not-tested: Headed/manual browser sessions with a human-authenticated Coupang context

* Keep the Coupang workspace installable and its entrypoint honest

Review follow-up found two contract gaps in the first issue #36 rollout: fresh installs were missing the new workspace link in package-lock, and the package README documented parser helpers that were not exported from the package entrypoint. Refreshing the lockfile and re-exporting the parsers keeps npm ci and consumer imports aligned with the published API.

Constraint: npm ci must succeed from a clean checkout
Rejected: Narrow the README API list to only client helpers | would hide useful parsers that the package already ships and tests
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: When adding a new workspace, refresh package-lock and verify the package entrypoint matches README public API claims
Tested: npm run ci; workspace coupang-product-search tests; LSP diagnostics on coupang-product-search JS/test files
Not-tested: published npm install from registry (local pack dry-run only)

* Keep Coupang anti-bot docs honest as probe results drift

A same-day PR rerun showed the published mobile probe snapshot was already stale, so the follow-up locks the 403/access-denied mobile path with regression coverage and softens the docs/skill contract to describe blocked outcome classes instead of one frozen signature. The published guidance now also explains that browser results only appear when browserFetchHtml is injected, matching what a clean checkout can actually reproduce.

Constraint: Coupang anti-bot responses vary by edge/challenge between reruns on the same day
Rejected: Freeze one exact mobile probe snapshot | same-day reruns already diverged
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Refresh Coupang anti-bot docs only with same-day live probe evidence, and prefer blocked outcome classes over a single exact signature when reruns disagree
Tested: npm run lint; npm run typecheck; npm test; npm run ci; live probeAutomation("생수") with direct fetch + Playwright-core browser fetch
Not-tested: Non-Chrome Playwright-core executables

* Keep the Coupang skill honest about clean-checkout browser probes

The approved follow-up already documented that clean-checkout probeAutomation runs leave browser unset unless a browser fetcher is injected, but the skill text itself had not made that null contract explicit. This commit adds a regression test that locks the browser-null wording across the published docs surfaces and updates the skill so the stated behavior matches the package implementation and clean-checkout reality.

Constraint: probeAutomation() only populates browser when browserFetchHtml is supplied by the caller
Rejected: Leave the skill wording implicit and rely on README examples alone | the skill could drift away from the shipped package contract again
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the README, feature doc, and skill aligned whenever the probeAutomation browser contract changes
Tested: node --test packages/coupang-product-search/test/index.test.js; npm run lint; npm run typecheck; npm test; npm run ci; live probeAutomation("생수") with direct fetch + Playwright-core browser fetch; codex exec review --uncommitted
Not-tested: Alternative browser engines beyond local Google Chrome for the manual browserFetchHtml probe

* Keep Coupang browser probe docs aligned with manual verification paths

The approved PR #40 follow-up still had one wording gap: the skill said when browser results appear, but it did not explicitly say those populated results come from an injected manual/external browserFetchHtml path. This change locks that contract with a failing regression first, then updates the skill text to match the already-shipped README/feature-doc guidance and runtime behavior.

Constraint: clean-checkout probeAutomation() must keep browser null unless browserFetchHtml is supplied
Rejected: change runtime browser probe behavior | approved follow-up was docs/test only and runtime contract is already correct
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: keep Coupang probe docs aligned with the shipped browserFetchHtml injection contract; do not imply built-in browser probing in clean checkouts
Tested: node --test packages/coupang-product-search/test/index.test.js; npm run lint; npm run typecheck; npm test; npm run ci; live probeAutomation("생수") with direct fetch + Playwright-core browser fetch; lsp diagnostics on changed files; architect review approved
Not-tested: alternate browser engines beyond local Google Chrome + playwright-core manual runner

* Route Korean law lookups through korean-law-mcp

Add a documentation-first korean-law-search skill and lock the repo docs around the rule that Korean law queries must go through korean-law-mcp rather than a new in-repo package.

The change updates install/setup/security guidance, publishes the new feature doc, and adds regression tests so future edits keep the LAW_OC + korean-law-mcp contract intact.

Constraint: Issue #41 requires korean-law-mcp for every Korean law lookup
Constraint: Must not add a new npm or python package in this repository
Rejected: Add a repo-local law package | violates the no-new-package requirement and duplicates upstream MCP work
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep Korean law lookup guidance pinned to korean-law-mcp unless issue requirements explicitly change
Tested: node --test scripts/skill-docs.test.js
Tested: npm install -g korean-law-mcp && korean-law list && korean-law help search_law
Tested: npm run ci
Tested: npx tsc --noEmit --pretty false --project /Users/jeffrey/Projects/k-skill/tsconfig.json
Not-tested: live search_law/get_law_text against a real LAW_OC credential

* Clarify Korean law setup modes to avoid credential confusion

A review found the new korean-law-search docs treated LAW_OC as an unconditional prerequisite even though the upstream remote MCP endpoint is configured separately from the local CLI/server path. This update makes the docs and regression tests mode-specific: local CLI/MCP uses LAW_OC, while the remote endpoint stays a korean-law-mcp-only url fallback without a user-supplied credential.

Constraint: Upstream korean-law-mcp uses LAW_OC on the local CLI/server path while the documented remote MCP endpoint is configured with url only
Rejected: Keep LAW_OC mandatory for every korean-law-mcp mode | contradicts upstream docs and reviewer evidence
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep README/setup/skill docs and regression tests aligned on the local-vs-remote korean-law-mcp contract
Tested: node --test scripts/skill-docs.test.js; npm install -g korean-law-mcp && korean-law list && korean-law help search_law; npx tsc --noEmit --pretty false --project /Users/jeffrey/Projects/k-skill/tsconfig.json; npm run ci; lsp_diagnostics scripts/skill-docs.test.js
Not-tested: Live remote MCP endpoint connection against https://korean-law-mcp.fly.dev/mcp

* Record issue #41 follow-up after approved verification

The approved korean-law-search change was already present on feature/#41, so this follow-up records a fresh verification pass and updates the PR without introducing unnecessary code churn.

Constraint: Existing branch already contained the approved implementation
Rejected: Touch repo files without need | would create noise without improving behavior
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the korean-law-mcp-only + mode-specific LAW_OC contract aligned with upstream docs before changing these surfaces again
Tested: node --test scripts/skill-docs.test.js; npx tsc --noEmit --pretty false --project /Users/jeffrey/Projects/k-skill/tsconfig.json; npm install -g korean-law-mcp && korean-law list && korean-law help search_law; npm run ci
Not-tested: Live korean-law queries that require a real LAW_OC or remote endpoint session

* Keep the Korean law feature diff merge-ready

Verification uncovered trailing whitespace in the new korean-law feature guide when checking the branch diff. I added a regression to the skill docs suite and removed the whitespace so the approved documentation contract stays clean and reviewable.

Constraint: Preserve the existing korean-law-mcp + mode-specific LAW_OC contract without widening scope
Rejected: Leave the whitespace issue as-is | git diff --check stayed dirty on the branch
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep docs/features/korean-law-search.md free of trailing whitespace so diff hygiene stays enforced by the regression
Tested: node --test scripts/skill-docs.test.js; npx tsc --noEmit --pretty false --project /Users/jeffrey/Projects/k-skill/tsconfig.json; npm install -g korean-law-mcp && korean-law list && korean-law help search_law; npm run ci; git diff --check
Not-tested: Live credentialed LAW_OC search execution against the upstream API

* Keep Korean law skill guidance aligned with supported lookups

The approved issue #41 feature already worked, but the shipped skill text still under-described ordinance and interpretation lookups compared with the documented capability set. This follow-up tightens the skill copy and locks that contract with a doc regression so the branch stays merge-ready without changing the underlying korean-law-mcp routing rules.

Constraint: Must preserve the existing korean-law-mcp-only and mode-specific LAW_OC setup contract
Rejected: Leave the skill wording as-is | drift from the documented lookup surface would remain
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the skill examples and doc regression aligned whenever supported korean-law-mcp search surfaces change
Tested: node --test scripts/skill-docs.test.js
Not-tested: live LAW_OC-backed upstream API queries

* Keep Korean law completion guidance in sync with enforced lookups

The previous follow-up aligned the main skill examples, but the done-criteria text still left interpretation and ordinance routing implicit. This commit makes the completion checklist explicit and extends the doc regression so future edits cannot silently drop those lookup paths.

Constraint: Must stay within the existing issue #41 korean-law-mcp-only contract
Rejected: Leave the done checklist implicit | reviewers and future edits could drift from the enforced lookup set
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: When the supported Korean-law lookup set changes, update the done checklist and regression together
Tested: node --test scripts/skill-docs.test.js
Not-tested: full CI before commit

* Enable repeatable used-car price lookups from a rental-company source

Issue #46 required surveying major Korean rental companies before implementation and then choosing the easiest stable provider. SK렌터카 다이렉트 exposes 타고BUY inventory in public Next.js page data, so the feature stays dependency-free while still supporting live repeated lookups and documented provider rationale.

Constraint: Must compare major Korean rental companies before implementation
Constraint: Must verify 10+ live lookups against a real provider surface
Rejected: 롯데오토옥션 as v1 provider | public list contract was unstable and legacy .do flows returned inconsistent or 404 pages
Rejected: 레드캡렌터카 as v1 provider | no public used-car inventory or API surface was found
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep the v1 provider read-only and inventory-snapshot-based unless a stable documented public API is confirmed
Tested: npm run ci
Tested: Live 10-query run against https://www.skdirect.co.kr/tb at 2026-04-02T07:22:46Z
Tested: LSP diagnostics on affected files
Not-tested: Seller-specific detail drilldowns or non-SK providers
Related: #46

* Keep used-car-price-search releasable after merge

Review feedback found that the new used-car workspace would not ship because it lacked a changeset, and the fallback install docs still omitted the runtime package. This follow-up adds regression coverage first, then restores both release and install-path coverage with the smallest possible diff.

Constraint: New publishable workspaces must be wired through Changesets to reach npm release automation
Constraint: Fallback install docs must list runtime packages users need when skill files are present but global packages are missing
Rejected: Fix only the changeset gap | would leave the documented fallback install path incomplete
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep used-car-price-search in both the fallback global install example and a Changesets entry whenever release wiring changes
Tested: node --test scripts/skill-docs.test.js; npx changeset status; live 10-query used-car run against SK direct at 2026-04-02T07:38:44.949Z; npm run ci; LSP diagnostics on used-car package files and scripts/skill-docs.test.js; architect verification
Not-tested: No additional live provider permutations beyond the verified 10-query smoke run

* Keep used-car verification docs resilient to live inventory churn

A fresh rerun showed the SK direct inventory total continues to move during the day, so the feature doc now records the verified smoke-run timestamp without freezing a brittle exact count. The regression suite was updated first so the docs stay variability-aware and diff-clean in future follow-ups.

Constraint: Live SK direct inventory totals change over time even when the parsing contract stays stable
Rejected: Keep documenting a fixed total from one smoke run | it immediately drifted and made the docs stale
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the used-car live verification note timestamped and variability-aware instead of pinning an exact inventory total
Tested: node --test scripts/skill-docs.test.js; git diff --check; npm run ci; npx changeset status; live 10-query run against https://www.skdirect.co.kr/tb at 2026-04-02T07:59:41.391Z; LSP diagnostics on scripts/skill-docs.test.js; architect verification
Not-tested: No additional content changes outside the used-car feature doc

* Keep used-car query summaries honest when results are limited

The review found that query-level stats were being computed from the
post-limit slice, so common searches like 아반떼 under-reported both
match counts and price ranges. This change locks the regression first,
then computes the full filtered set before slicing only the returned
items.

Constraint: PR #48 must preserve the existing API shape while fixing the review-blocking stats bug
Rejected: Expanding the response with separate limited/full summary fields | unnecessary API churn for a targeted bug fix
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Query-level summary and matchedCount must stay derived from the full filtered set, not the display limit slice
Tested: npm test --workspace used-car-price-search; npm run ci; git diff --check; npx changeset status; live SK direct smoke run on 2026-04-02T08:23:59Z; LSP diagnostics on used-car-price-search source and test files; architect verification APPROVED
Not-tested: limit=0 semantics remain unchanged and still coerce to the default limit
Related: PR #48

* Keep korean-law-search available during upstream outages (#45)

Issue #44 adds Beopmang as the documented fallback when the primary korean-law-mcp path is unavailable, and locks the new routing into the doc regression suite so future edits do not silently revert the policy.

Constraint: Existing guidance must still prefer korean-law-mcp and keep LAW_OC scoped to the local CLI/MCP path
Rejected: Add a repo-local Beopmang client package | issue only requires fallback registration, not a new implementation surface
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the primary-first rule intact; Beopmang is an outage fallback, not the default path
Tested: node --test scripts/skill-docs.test.js; npm run ci; korean-law list; python3 live Beopmang search smoke for 관세법
Not-tested: Live Beopmang MCP handshake from a GUI MCP client

* Replace coupang scraping package with coupang-mcp server integration

Drop the browser-based scraping package (packages/coupang-product-search)
and switch to the uju777/coupang-mcp MCP server for all Coupang product
searches. This removes the anti-bot workaround complexity and provides
8 ready-to-use tools via MCP Streamable HTTP with no API key required.

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

* Add disclaimer to used-car-price-search README

Clarify that the package queries SK렌터카 타고BUY public data with
no affiliation or sponsorship, and that ad/partnership inquiries
are welcome.

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

* Refactor used-car-price-search into provider-based architecture

SK 타고BUY 전용 로직을 src/providers/sk-tagobuy.js로 분리하고,
공급자를 수평 확장할 수 있는 registry 구조로 전환한다.

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 23:59:38 +09:00
Jeffrey (Dongkyu) Kim
24ca8feb5f Reduce Seoul subway proxy upstream pressure with request caching
The public subway arrival route already normalized caller input but still re-fetched the Seoul upstream for every identical poll. This change adds a short-TTL cache keyed from the normalized subway query, annotates JSON responses with cache metadata, and locks the repeated-read collapse with a regression that proves alias/default normalization still reuses the cached result.

Constraint: The endpoint must stay public/no-auth while protecting a shared server-side SEOUL_OPEN_API_KEY from unnecessary repeated upstream hits
Rejected: Add a new shared cache abstraction for proxy metadata | unnecessary for this narrow fix and would enlarge the diff
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep subway cache keys tied to normalizeSeoulSubwayQuery output so alias/default query forms continue collapsing to one upstream request
Tested: node --test packages/k-skill-proxy/test/server.test.js
Tested: npm run ci
Tested: Local proxy runtime on 127.0.0.1:4120 with SEOUL_OPEN_API_KEY=test-seoul-key and stubbed fetch for /health plus repeated /v1/seoul-subway/arrival requests
Not-tested: Live hosted proxy rollout state
2026-03-31 11:48:55 +09:00
Jeffrey (Dongkyu) Kim
d62e177a1b Prevent broken Seoul subway proxy defaults before hosted rollout
The Seoul subway proxy endpoint code is present locally, but the hosted public route is not live yet. This change turns the user-facing subway docs back into an explicit proxy configuration flow, replaces the misleading hosted default in setup examples, and keeps subway proxy examples on self-host/local URLs until rollout is verified.

Constraint: Hosted k-skill-proxy.nomadamas.org/v1/seoul-subway/arrival is not live yet
Rejected: Keep the hosted Seoul subway URL as the default path | would send default users to a 404 route
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Do not restore a hosted Seoul subway default until the public proxy route is deployed and smoke-verified
Tested: node --test scripts/skill-docs.test.js; npm run ci; local proxy smoke on 127.0.0.1:4120 with stubbed Seoul upstream (GET /health, GET /v1/seoul-subway/arrival?stationName=강남)
Not-tested: Live hosted proxy smoke after deployment
2026-03-31 11:18:00 +09:00
Jeffrey (Dongkyu) Kim
fbc004af9d Remove client-side Seoul subway key setup
Route Seoul subway arrival lookups through k-skill-proxy so the
hosted proxy owns the Seoul Open Data upstream key and end users
only need the proxy base URL. Add proxy route coverage, update
skill/docs guidance, and align setup materials with the hosted
proxy flow used for fine dust.

Constraint: Must keep the proxy public, read-only, and dependency-free
Constraint: Must satisfy TDD-first verification and ship on feature/#35 targeting dev
Rejected: Add a separate client helper package | unnecessary extra layer for a single proxy route
Rejected: Keep SEOUL_OPEN_API_KEY as an end-user requirement | defeats the issue goal
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep the Seoul subway proxy surface limited to station-arrival passthrough unless tests/docs expand the contract
Tested: npm run ci; local proxy runtime on 127.0.0.1:4120 for /health and /v1/seoul-subway/arrival on 2026-03-31 with an invalid upstream key
Not-tested: Live success response with a valid Seoul Open API key
Related: #35
2026-03-31 10:25:55 +09:00
Jeffrey (Dongkyu) Kim
ef2c69b81c Replace sops+age encryption with plain dotenv and agent-native credential resolution
Agent environments (OpenClaw, Claude Code, Codex) assume users delegate
credentials to the agent. sops+age added setup friction without real
security benefit since the agent decrypts on every call anyway.

New model: skills declare required env var names; how they are supplied
is up to the agent (own vault, shell env, or ~/.config/k-skill/secrets.env
as the default fallback with 0600 permissions).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 22:12:04 +09:00
Jeffrey (Dongkyu) Kim
8b36634e28 Stop guessing districts and require exact station retries for ambiguous fine-dust lookups
The fine-dust proxy now resolves natural-language region hints through
city-level station lists and only returns a report when a single station
can be justified. When the hint is ambiguous, the proxy returns a small
candidate list so callers can retry with an exact station name instead
of silently guessing.

The skill guidance was updated to match that runtime contract: region
hint first, then retry with stationName when candidate_stations are
returned. Coordinate-centric guidance was removed from the primary skill
surface so the default path stays lightweight and consistent with the
live proxy behavior.

Constraint: The current AirKorea key can access city-level and station-level measurement APIs but station-info lookups may still return 403
Constraint: Free-API proxy responses must stay safe to expose publicly, so ambiguous locations should not be auto-guessed
Rejected: Auto-pick the first city-level station for unmatched district hints | hides ambiguity and returns misleading air-quality data
Rejected: Keep coordinate-first language in the primary skill | no coordinate source exists in the default user flow
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Preserve the ambiguous_location contract; if you improve matching later, prefer evidence-backed narrowing over silent fallback guesses
Tested: node --test scripts/skill-docs.test.js; npm run test --workspace k-skill-proxy; python3 -m unittest discover -s scripts -p test_fine_dust.py; live curl for ambiguous regionHint=광주 광산구 and exact stationName=우산동(광주)
Not-tested: Broader region alias quality outside the manually checked examples
2026-03-28 23:28:12 +09:00
Jeffrey (Dongkyu) Kim
a3ef6ffac6 Make the fine-dust proxy easier to consume than the upstream APIs
The fine-dust lane now treats the public proxy as the default surface,
keeps a simple summarized report endpoint, and also exposes a narrow
AirKorea passthrough shape so callers can reuse upstream query patterns
without carrying service keys on the client side.

The skill instructions were trimmed down so the default path is obvious,
region-name guidance stays visible, and detailed implementation notes
move into feature docs instead of bloating the primary skill surface.

Constraint: Free-API proxy endpoints are intentionally public and must avoid embedding upstream secrets in the repo
Constraint: AirKorea station-info access can return 403 even when measurement access succeeds, so the report path needs a measurement-only fallback
Rejected: Keep proxy auth via shared token | contradicts the intended public free-API proxy policy
Rejected: Force all callers onto the summary endpoint only | passthrough compatibility is useful for direct HTTP consumers
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep the proxy allowlist narrow; if new upstream routes are exposed, document them explicitly rather than turning this into a generic open proxy
Tested: node --test scripts/skill-docs.test.js; npm run test --workspace k-skill-proxy; python3 -m unittest discover -s scripts -p test_fine_dust.py; live curls against /health, /v1/fine-dust/report, and /B552584/ArpltnInforInqireSvc/getMsrstnAcctoRltmMesureDnsty
Not-tested: Fresh reboot validation of PM2/cloudflared persistence after the latest code-only changes
2026-03-28 17:14:18 +09:00