Skill: sw326/law-search (version 2.1.0)

--- name: law-search description: Korean law/case search via law.go.kr + data.go.kr APIs. Use for legal questions, statute lookup, court cases, and everyday legal info. version: 2.1.0 author: chumjibot created: 2026-02-13 updated: 2026-02-14 tags: [law, legal, korea, openapi, data.go.kr, law.go.kr] connectors: [~~law, ~~docs, ~~notify] --- # Law & Case Search Skill Korea Legislation Research Institute (MOLEG) law search, statute detail, case search. Inspired by Cowork Legal plugin architecture. ## Overview | Key | Value | |-----|-------| | Provider | MOLEG (Ministry of Government Legislation) | | API Source | law.go.kr (primary) + data.go.kr (fallback) | | Auth (law.go.kr) | `~/.config/law-go-kr/credentials.json` → OC code | | Auth (data.go.kr) | `~/.config/data-go-kr/api_key` | | Daily limit | 10,000 calls | | Playbook | `playbook.md` | ## Scripts ``` scripts/ ├── law_search.sh → Search laws by keyword ├── law_detail.sh → Statute text by law ID ├── case_search.sh → Court case search └── life_law.sh → Everyday legal info search ``` ## Workflow ### Step 1: Analyze question - Extract legal keywords from user question - Match against `playbook.md` interest areas - Decide strategy: statute-focused? case-focused? combined? ### Step 2: Search statutes - `law_search.sh "keyword"` → list of relevant laws ### Step 3: Fetch statute text (if needed) - `law_detail.sh [law_id]` → specific articles ### Step 4: Search cases - `case_search.sh "keyword"` → relevant court decisions ### Step 5: Everyday legal info (optional) - `life_law.sh "keyword"` → plain-language legal guide ### Step 6: Structured response using template ## Output Template Action-first structure: lead with what to DO, then back it up with law. ```markdown ## ⚖️ [Topic] ### 📋 Core Answer [1-2 sentence conclusion — what the user needs to know] ### 🚶 What To Do (practical steps) 1. [Concrete action step] — with conditions/branches if needed 2. [Next step] 3. [Final step] ※ [Situation-specific branch: "If X, then A; if Y, then B"] ### 📖 Legal Basis **[Law Name]** Art. X (Effective YYYY.MM.DD) > ① Original text... > → Plain language: [simplified explanation] **[Related Law]** Art. Y (if applicable) > ... ### ⚖️ Related Case (if relevant) **[Case Number]** ([Court], YYYY.MM.DD) > Key point: [one-line holding] ### 🔗 References - [Statute](https://www.law.go.kr/...) ⚠️ Reference only — not legal advice. Consult an attorney for important decisions. ``` **Principles:** 1. User's action = main content; statutes = supporting evidence 2. Branch by situation (listed vs unlisted company, etc.) 3. Cite specific articles, not just law names 4. Ask follow-up if context is needed for better advice ## API Endpoints ### law.go.kr (Primary) | Target | Description | URL | |--------|-------------|-----| | law | Statute search/detail | `https://www.law.go.kr/DRF/lawSearch.do?OC={oc}&target=law&type=JSON` | | prec | Court cases | `...&target=prec&type=JSON` | | detc | Interpretation examples | `...&target=detc&type=JSON` | | admrul | Administrative rules | `...&target=admrul&type=JSON` | ### data.go.kr (Fallback) | API | Data ID | Endpoint | |-----|---------|----------| | Statute search | 15000115 | `http://apis.data.go.kr/1170000/law/lawSearchList.do` | | Everyday law | 15000215 | `http://apis.data.go.kr/1170000/lifeLawSearch/lifeLawSearchList.do` | | Case text | 15057123 | (linked to law.go.kr) | ## Connectors | Placeholder | Purpose | Current Tool | |-------------|---------|-------------| | `~~law` | Law/case API | law.go.kr, data.go.kr | | `~~docs` | Save results | Notion | | `~~notify` | Alerts | Telegram | | `~~search` | Supplementary search | Brave Search | ## Notes 1. **Disclaimer**: API info is reference only. Not legal advice. 2. **Currency**: Based on current law, but recent amendments may have delay. 3. **Format**: law.go.kr returns JSON; data.go.kr returns XML → each needs parser. 4. **Priority**: law.go.kr JSON first → fallback to data.go.kr XML. 5. **Encoding**: URL-encode query parameters. --- *Cowork Legal architecture v2.1 — 🦞 chumjibot (2026-02-14)* ## 🔧 Setup ### 법제처 국가법령정보 API (주 API) 1. [open.law.go.kr](https://open.law.go.kr) 회원가입 2. OC 코드 발급 (이메일 @ 앞부분) 3. `mkdir -p ~/.config/law-go-kr && echo '{"oc":"YOUR_OC"}' > ~/.config/law-go-kr/credentials.json` ### data.go.kr 판례 API (보조) 1. [data.go.kr](https://www.data.go.kr) 회원가입 → 인증키 복사 2. `mkdir -p ~/.config/data-go-kr && echo "YOUR_KEY" > ~/.config/data-go-kr/api_key` > 법제처 API 미등록 시에도 `web_search` 폴백으로 법령 기본 검색 가능합니다.

path: playbook.md
content: # 법률 검색 플레이북 — 개인 설정

## 관심 분야
- 근로법 (퇴직금, 해고, 연차)
- 임대차 (전세, 보증금, 계약)
- 개인정보보호 (동의, 처리, 유출)
- 관세/통상 (수출입, 관세율, 보복관세)
- 부동산 (매매, 등기, 세금)
- 일상 법률 (교통사고, 소비자보호, 층간소음)

## 출력 선호
- 조문 원문 + 쉬운 해설 병행
- 판례는 항상 1-2개 포함
- 실용적 조언 (구체적으로 뭘 할 수 있는지)
- 한국어 (법률 용어는 괄호 안에 쉬운 설명 추가)

## 검색 우선순위
1. 현행법령 (가장 최신)
2. 판례 (실제 적용 사례)
3. 법령해석례 (공식 해석)
4. 생활법령 (쉬운 해설)

## 출력 시 주의사항
- 폐지/개정된 법은 명확히 표시
- 시행일 미래인 법은 "아직 시행 전" 표시
- 민감한 사안은 "변호사 상담 추천" 명시


path: scripts/case_search.sh
content: #!/bin/bash
# 판례 검색 - 키워드로 관련 판례 조회
# Usage: case_search.sh <검색어> [결과수]
# Output: JSON

set -euo pipefail

QUERY="${1:?검색어를 입력하세요}"
NUM="${2:-5}"
API_KEY=$(cat ~/.config/data-go-kr/api_key)

ENCODED_QUERY=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$QUERY'))")

RESPONSE=$(curl -s "http://apis.data.go.kr/1170000/caseSearch/caseSearchList.do?serviceKey=${API_KEY}&numOfRows=${NUM}&pageNo=1&type=XML&query=${ENCODED_QUERY}")

echo "$RESPONSE" | python3 -c "
import xml.etree.ElementTree as ET
import json, sys, re

xml_str = sys.stdin.read()

try:
    root = ET.fromstring(xml_str)
except ET.ParseError:
    if 'Forbidden' in xml_str:
        print(json.dumps({'error': 'API 접근 거부. 판례 API 활용신청 필요.', 'api_url': 'https://www.data.go.kr/data/15057123/openapi.do'}, ensure_ascii=False))
    else:
        print(json.dumps({'error': '파싱 실패', 'raw': xml_str[:500]}, ensure_ascii=False))
    sys.exit(0)

result = {'totalCount': 0, 'cases': []}

total_el = root.find('.//totalCnt')
if total_el is not None and total_el.text:
    result['totalCount'] = int(total_el.text)

for case in root.findall('.//prec'):
    item = {}
    for child in case:
        tag = child.tag
        text = (child.text or '').strip()
        if text:
            text = re.sub(r'<[^>]+>', '', text)
            item[tag] = text
    if item:
        # 판례 상세 URL 추가
        case_id = item.get('판례일련번호', '')
        if case_id:
            item['url'] = f'https://www.law.go.kr/판례/{case_id}'
        result['cases'].append(item)

print(json.dumps(result, ensure_ascii=False, indent=2))
"


path: scripts/law_detail.sh
content: #!/bin/bash
# 법령 상세(조문) 조회 - 법령ID로 조문 본문 조회
# Usage: law_detail.sh <법령ID> [조문번호]
# Output: JSON

set -euo pipefail

LAW_ID="${1:?법령ID를 입력하세요}"
ARTICLE="${2:-}"
API_KEY=$(cat ~/.config/data-go-kr/api_key)

# 법령 상세 조회
RESPONSE=$(curl -s "http://apis.data.go.kr/1170000/law/lawServiceDtl.do?serviceKey=${API_KEY}&target=law&MST=${LAW_ID}&type=XML")

echo "$RESPONSE" | python3 -c "
import xml.etree.ElementTree as ET
import json, sys, re

xml_str = sys.stdin.read()
article_filter = '$ARTICLE'

try:
    root = ET.fromstring(xml_str)
except ET.ParseError:
    if 'Forbidden' in xml_str:
        print(json.dumps({'error': 'API 접근 거부. 활용신청 필요.'}, ensure_ascii=False))
    else:
        print(json.dumps({'error': '파싱 실패', 'raw': xml_str[:500]}, ensure_ascii=False))
    sys.exit(0)

result = {'법령명': '', '시행일자': '', '조문': []}

# 기본 정보
for tag in ['법령명_한글', '시행일자', '소관부처명', '법령구분명']:
    el = root.find(f'.//{tag}')
    if el is not None and el.text:
        result[tag] = el.text.strip()

# 조문 파싱
for jo in root.findall('.//조문단위'):
    item = {}
    for child in jo:
        tag = child.tag
        text = (child.text or '').strip()
        if text:
            # HTML 태그 제거
            text = re.sub(r'<[^>]+>', '', text)
            item[tag] = text

    if item:
        # 특정 조문 필터
        if article_filter:
            jo_num = item.get('조문번호', '')
            if article_filter not in jo_num:
                continue
        result['조문'].append(item)

# 조문이 너무 많으면 처음 20개만
if len(result['조문']) > 20 and not article_filter:
    result['note'] = f'전체 {len(result[\"조문\"])}개 조문 중 처음 20개만 표시'
    result['조문'] = result['조문'][:20]

print(json.dumps(result, ensure_ascii=False, indent=2))
"


path: scripts/law_search.sh
content: #!/bin/bash
# 법령 검색 - 키워드로 관련 법령 목록 조회
# Usage: law_search.sh <검색어> [결과수]
# Output: JSON

set -euo pipefail

QUERY="${1:?검색어를 입력하세요}"
NUM="${2:-5}"
API_KEY=$(cat ~/.config/data-go-kr/api_key)

ENCODED_QUERY=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$QUERY'))")

RESPONSE=$(curl -s "http://apis.data.go.kr/1170000/law/lawSearchList.do?serviceKey=${API_KEY}&target=law&query=${ENCODED_QUERY}&numOfRows=${NUM}&pageNo=1")

echo "$RESPONSE" | python3 -c "
import xml.etree.ElementTree as ET
import json, sys

xml_str = sys.stdin.read()

try:
    root = ET.fromstring(xml_str)
except ET.ParseError:
    if 'Forbidden' in xml_str or 'forbidden' in xml_str.lower():
        print(json.dumps({'error': 'API 접근 거부. 공공데이터포털에서 활용신청 필요.', 'api_url': 'https://www.data.go.kr/data/15000115/openapi.do'}, ensure_ascii=False))
    elif 'SERVICE_KEY' in xml_str:
        print(json.dumps({'error': 'API 키 미등록. 활용신청 필요.', 'api_url': 'https://www.data.go.kr/data/15000115/openapi.do'}, ensure_ascii=False))
    else:
        print(json.dumps({'error': '응답 파싱 실패', 'raw': xml_str[:500]}, ensure_ascii=False))
    sys.exit(0)

result = {'totalCount': 0, 'laws': []}
total_el = root.find('.//totalCnt')
if total_el is not None and total_el.text:
    result['totalCount'] = int(total_el.text)

for law in root.findall('.//law'):
    item = {}
    for child in law:
        tag = child.tag
        text = (child.text or '').strip()
        if text:
            item[tag] = text
    if item:
        # 법령 상세 URL 추가
        if '법령ID' in item:
            item['url'] = f'https://www.law.go.kr/법령/{item.get(\"법령명한글\", \"\")}'
        result['laws'].append(item)

print(json.dumps(result, ensure_ascii=False, indent=2))
"


path: scripts/life_law.sh
content: #!/bin/bash
# 생활법령 검색 - 일상 생활 관련 법령 정보 조회
# Usage: life_law.sh <검색어> [결과수]
# Output: JSON

set -euo pipefail

QUERY="${1:?검색어를 입력하세요}"
NUM="${2:-5}"
API_KEY=$(cat ~/.config/data-go-kr/api_key)

ENCODED_QUERY=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$QUERY'))")

RESPONSE=$(curl -s "http://apis.data.go.kr/1170000/lifeLawSearch/lifeLawSearchList.do?serviceKey=${API_KEY}&numOfRows=${NUM}&pageNo=1&type=XML&query=${ENCODED_QUERY}")

echo "$RESPONSE" | python3 -c "
import xml.etree.ElementTree as ET
import json, sys, re

xml_str = sys.stdin.read()

try:
    root = ET.fromstring(xml_str)
except ET.ParseError:
    if 'Forbidden' in xml_str:
        print(json.dumps({'error': 'API 접근 거부. 생활법령 API 활용신청 필요.', 'api_url': 'https://www.data.go.kr/data/15000215/openapi.do'}, ensure_ascii=False))
    else:
        print(json.dumps({'error': '파싱 실패', 'raw': xml_str[:500]}, ensure_ascii=False))
    sys.exit(0)

result = {'totalCount': 0, 'lifeLaws': []}

total_el = root.find('.//totalCnt')
if total_el is not None and total_el.text:
    result['totalCount'] = int(total_el.text)

# 생활법령 항목 파싱
for item_el in root.findall('.//*'):
    if item_el.tag in ['header', 'body']:
        continue
    item = {}
    for child in item_el:
        tag = child.tag
        text = (child.text or '').strip()
        if text:
            text = re.sub(r'<[^>]+>', '', text)
            item[tag] = text
    if item and len(item) > 1:
        result['lifeLaws'].append(item)

print(json.dumps(result, ensure_ascii=False, indent=2))
"


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: suspicious | SkillSpector: suspicious (CRITICAL)