Imported from duc-ke/bsl-pyconkr (
AGENTS.md). Install upstream withnpx skills add duc-ke/bsl-pyconkr. Copyright stays with the author.
AGENTS.md
이 문서는 이 리포지토리에서 작업하는 코딩 에이전트가 따라야 할 최소 지침입니다.
더 구체적인 지침이 하위 디렉터리의 AGENTS.md, 프로젝트 설정, 기여 문서에
있다면 해당 지침을 우선합니다.
프로젝트 개요
- 이 프로젝트는 NEIS 공개 API를 활용해 학교 급식 메뉴를 조회하고 분석하는 웹 애플리케이션을 단계별로 구현하는 워크숍입니다.
- 현재 MVP는 React 프론트엔드, FastAPI 백엔드, 독립 MCP 서버, 독립 멀티 에이전트 서비스, 내부 OpenAPI 계약 및 Docker Compose 실행 환경으로 구현되어 있습니다.
- 사용자는 두 글자 이상의 학교명을 입력해 학교를 자동 검색하고, 학교와 날짜 범위를 선택해 중식 메뉴·열량·영양·원산지·급식 인원을 조회할 수 있습니다.
- 승인된 제품 요구사항은
PRD.md, 기술 요구사항은TRD.md를 기준으로 구현합니다. 두 문서가 충돌하거나 변경이 필요하면 코드를 먼저 바꾸지 말고 문서와 결정 사항을 함께 갱신합니다. - 요청된 범위에 필요한 파일만 변경하고 기존 동작, 문서 흐름, 공개 API 계약을 불필요하게 바꾸지 않습니다.
프로젝트 구조 및 API 계약
- 모든 애플리케이션과 테스트 코드는
src아래에 둡니다. - React 프론트엔드는
src/web, Python 백엔드는src/api, MCP 서버는src/mcp, E2E 테스트는src/e2e에서 관리합니다. - 프론트엔드의 화면 흐름은
src/web/src/App.tsx, 내부 API 호출은src/web/src/api/client.ts, 날짜 정책은src/web/src/utils/dates.ts에 있습니다. 컴포넌트에서fetch를 직접 호출하지 않습니다. - 백엔드는
api라우터,services유스케이스,clients외부 통신,mappers응답 변환,models경계 모델 및settings설정 계층을 유지합니다. - 내부 API는
GET /api/v1/schools,GET /api/v1/meals만 제공하며/health는 컨테이너 상태 확인에 사용합니다. src/openapi.json은 프론트엔드와 백엔드 사이의 내부 API 계약입니다. 엔드포인트나 페이로드를 변경하면 명세, 양쪽 구현 및 계약 테스트를 함께 갱신합니다.src/web/src/api/schema.d.ts는src/openapi.json에서 생성되는 파일입니다. 직접 수정하지 말고src/web에서npm run generate:api를 실행합니다.data/openapi.json은 백엔드와 NEIS 사이의 외부 API 계약입니다. 백엔드와 MCP 서버는 각각 이 계약을 근거로 NEIS를 호출하며, 프론트엔드는 이 명세로 NEIS를 직접 호출하지 않습니다.- MCP 서버는
/mcp에서 상태 비저장 Streamable HTTP를 제공하고/health를 상태 확인에 사용합니다.getSchoolInfo,getMealServiceDietInfo도구의 - Agent 서비스는
src/agent에서 기존 백엔드와 독립적으로 실행하며 MCP로 급식 데이터를 조회하고/ag-ui에서 AG-UI 스트림을 제공합니다.getSchoolInfo,getMealServiceDietInfo도구의 이름, 설명 및 입력 스키마는data/openapi.json에서 생성합니다. - 학교 검색어는 앞뒤 공백 제거 후 2~100자로 검증합니다.
- 프론트엔드는 유효한 검색어 입력 후 350ms 동안 추가 입력이 없으면 자동으로 검색하며, 백엔드에서도 같은 길이 제약을 다시 검증합니다.
- 급식 조회는 중식으로 한정합니다. 날짜는
Asia/Seoul을 기준으로 현재 달과 바로 이전 달만 허용하며 기본 범위는 오늘을 포함한 최근 7일입니다. - 검색 결과 없음과 급식 정보 없음은 정상적인 빈 결과로, 입력 오류와 NEIS 장애는 명시적인 오류로 구분합니다.
일반 작업 지침
- 작업 전에 관련
README,CONTRIBUTING.md, 설정 파일, 인접 코드와 문서를 확인하고 기존 규칙과 패턴을 우선합니다. - 문제의 근본 원인을 해결하되 요청과 무관한 리팩터링은 하지 않습니다.
- 가장 단순하고 명확한 구현을 선호하며, 필요성이 입증되지 않은 추상화, 프레임워크, 호환성 계층을 추가하지 않습니다.
- 기존 유틸리티와 타입을 재사용하고 중복 구현을 피합니다.
- 변경 범위가 모호하거나 데이터 손실, 호환성 저하, 공개 API 변경 가능성이 있으면 임의로 결정하지 말고 먼저 확인합니다.
- 생성 파일, 의존성 잠금 파일, 설정 파일은 해당 생태계의 관례와 기존 프로젝트 정책에 따라 함께 관리합니다.
Python 가이드라인
- Python 코드는 PEP 8을 기본 스타일 가이드로 따르되, 프로젝트에 설정된 포매터와 린터 규칙을 우선합니다.
- 공개 함수, 메서드와 데이터 모델에는 구체적인 타입을 사용하고 불필요한
Any, 무검증 타입 단언, 동적 속성 접근을 피합니다. - 외부 입력과 API 응답은 경계에서 검증하고 내부에서는 명확한 타입으로 다룹니다.
- 예외는 구체적으로 처리합니다. 오류를 삼키거나 성공처럼 보이는 기본값을 반환하지 말고, 호출자가 대응할 수 있도록 의미 있는 문맥과 함께 전달합니다.
- 표준 라이브러리와 기존 의존성을 우선합니다. 새 의존성은 명확한 필요성이 있을 때만 프로젝트의 기존 의존성 관리 방식으로 추가합니다.
- 작고 응집도 높은 함수, 명확한 이름, 테스트 가능한 구조를 유지하고 전역 가변 상태와 숨은 부작용을 피합니다.
- FastAPI 앱은
create_app팩터리에서 설정, HTTP 전송 및 기준 날짜를 주입할 수 있게 유지합니다. 테스트에서 실제 NEIS 네트워크를 호출하지 않습니다. - NEIS 호출은 연결 풀을 공유하는 HTTPX
AsyncClient를 사용합니다. 실제 NEIS 서버 호환성을 위해 IPv4 전송을 유지하고Accept: application/json헤더를 강제로 추가하지 않습니다. - 외부 NEIS 모델과 내부 API 모델을 재사용하지 않으며 변환은
mappers.py에서 수행합니다. - MCP 서버는 공식 Python MCP SDK 1.x와 Streamable HTTP를 사용하며 백엔드
API와 독립적으로 NEIS를 호출합니다. 도구 스키마는
data/openapi.json에서 생성하고 API 키를 도구 입력이나 응답에 노출하지 않습니다. - MCP 서버는 NEIS 인증키와
Type=json을 서버에서 주입하고 급식 도구의MMEAL_SC_CODE를 중식 코드2로 강제합니다.INFO-200은 정상적인 빈 결과로, 입력·외부 서비스 오류와 타임아웃은 MCP tool error로 반환합니다. - 멀티 에이전트는 GitHub Copilot SDK 브리지와 Microsoft Agent Framework 그래프를 사용합니다. 세 평가 Agent를 병렬 실행하고 점수와 승패는 Python 코드가 계산하며 최종 Agent가 이를 변경하지 못하게 합니다.
TypeScript 가이드라인
- TypeScript 코드는 Mozilla의 Coding style 지침을 참고하되, 프로젝트의 기존 스타일과 포매터 및 린터 설정을 우선합니다.
- 엄격한 타입 검사를 전제로 하며
any, 불필요한 타입 단언, non-null 단언을 피합니다. 알 수 없는 외부 값은unknown으로 받고 타입 가드로 좁힙니다. - API 응답, 사용자 입력과 환경 변수는 신뢰 경계에서 검증합니다.
- 오류를 빈
catch로 무시하거나 정상 값으로 위장하지 않습니다. 예상 가능한 실패를 구분해 사용자 또는 호출자에게 명확히 전달합니다. - 기존 컴포넌트, 훅, 유틸리티와 타입을 우선 재사용하며 상태와 부작용의 범위를 작게 유지합니다.
- 새 패키지는 기존 의존성으로 해결할 수 없는 경우에만 프로젝트의 패키지 관리자와 잠금 파일을 사용해 추가합니다.
- 서버 상태는 TanStack Query로 관리하고 입력·선택 상태와 분리합니다. 검색어가 바뀌면 이전 학교 선택과 급식 결과를 현재 조건의 결과처럼 표시하지 않습니다.
- 날짜는 API 경계에서만
YYYY-MM-DD로 직렬화하고, 기본 범위와 선택 가능 범위는Asia/Seoul기준으로 계산합니다. - NEIS 문자열은 React 텍스트로 렌더링하며
dangerouslySetInnerHTML을 사용하지 않습니다.
개발 및 실행 명령
- 전체 앱은 루트에서
.env.example을.env로 복사하고NEIS_API_KEY를 설정한 뒤 로컬 개발 시./run_app.sh, 컨테이너 통합 확인 시docker compose up --build로 실행합니다. 로컬 웹 주소는http://localhost:5173, Compose 웹 주소는http://localhost:8080입니다. - 프론트엔드는
src/web에서npm ci,npm run dev,npm run build,npm test,npm run typecheck를 사용합니다. - 백엔드는
src/api에서uv sync --locked --all-groups,uv run uvicorn app.main:app --reload,uv run --locked pytest를 사용합니다. - MCP 서버는
src/mcp에서uv sync --locked --all-groups,uv run uvicorn app.main:app --reload --port 8001,uv run --locked pytest를 사용합니다. - Agent 서비스는
src/agent에서uv sync --locked --all-groups,uv run uvicorn app.main:app --reload --port 8002,uv run --locked pytest,uv run python -m app.devui를 사용합니다. - MCP Inspector는
npx -y @modelcontextprotocol/inspector로 별도 실행하고Streamable HTTP방식의http://127.0.0.1:8001/mcp에 연결합니다.localhost는 IPv6로 해석될 수 있으므로 로컬 Inspector URL에 사용하지 않습니다. - E2E는
src/e2e에서npm ci,npx playwright install chromium,npm test를 사용합니다. 테스트는 Compose로 전체 앱을 실행하고src/e2e/fixtures의 결정적 NEIS 대역만 사용합니다. - 의존성을 변경하면 해당 패키지 관리자로
src/web/package-lock.json,src/e2e/package-lock.json,src/api/uv.lock또는src/mcp/uv.lock을 함께 갱신합니다.
테스트 및 유효성 검사
- 프론트엔드 통합 테스트는 Vitest, React Testing Library 및 MSW를 사용합니다.
- 백엔드 단위·통합 테스트는 pytest와 HTTPX AsyncClient를 사용합니다.
- 전체 사용자 흐름의 E2E 테스트는 Playwright를 사용합니다.
- 백엔드가 내보내는 OpenAPI와
src/openapi.json의 차이를 계약 검사로 탐지합니다. - 프론트엔드 테스트는 두 글자 자동 검색, 입력 검증, 학교 선택, 기본 날짜 범위, 결과·빈 상태를 사용자 행동 기준으로 검증합니다.
- 백엔드 테스트는 NEIS 오류 변환, 중식 코드 강제, 날짜 정책, 데이터 매핑 및 OpenAPI 계약을 검증합니다.
- MCP 테스트는 도구 목록·호출, Streamable HTTP 연결, OpenAPI 기반 입력 스키마, 중식 코드 강제 및 NEIS 오류·타임아웃 변환을 검증합니다.
- E2E는 브라우저에서 학교 자동 검색, 선택, 중식 조회를 데스크톱과 모바일
뷰포트로 검증합니다. 내부
/api/v1요청을 브라우저에서 가로채지 않습니다. - 동작을 변경하면 정상 경로, 실패 경로와 관련 경계 조건을 검증하는 테스트를 추가하거나 수정합니다.
- 변경한 구성 요소에 이미 설정된 테스트, 포맷팅, 린트, 타입 검사와 빌드를 가장 좁은 범위부터 실행합니다.
- 실행 명령은 실제 매니페스트, 스크립트와 CI 설정에서 확인합니다. 아직 구성되지 않은 도구를 설치하거나 명령을 임의로 만들지 않습니다.
- 검사 실패를 우회하거나 테스트를 약화하지 말고 원인을 해결합니다.
- 문서나 설정만 변경한 경우에도 링크, 예제, 구문과 설정 구조를 적절한 방법으로 확인합니다.
보안
- 비밀, 자격 증명, 개인 정보와 실제 환경 값을 코드, 테스트, 로그 또는
문서에 포함하지 않습니다. 특히
.env,.env.*(예제 파일 제외),*.pem,*.key, 자격 증명 파일과 로컬 비밀 설정 파일은 커밋하지 않습니다. - 외부 입력을 검증하고 출력, 쿼리, 명령 실행 등 사용 맥락에 맞게 안전하게 처리합니다.
- 최소 권한을 적용하고 인증서 검증이나 보안 검사를 비활성화하지 않습니다.
- 의존성을 추가할 때 유지보수 상태, 라이선스와 보안 영향을 확인합니다.
- 취약점은 공개 이슈에 작성하지 않고
SECURITY.md의 비공개 신고 절차를 따릅니다.
문서화
- 동작, 설정, 공개 API 또는 개발 절차가 바뀌면 관련 문서를 같은 변경에 포함합니다.
- 문서와 예제는 실제 구현과 일치하도록 유지하며 존재하지 않는 명령이나 기능을 설명하지 않습니다.
- 코드 자체로 의도가 분명하지 않은 경우에만 간결한 주석을 추가합니다.
주의사항 및 가드레일
- 사용자 변경 사항을 덮어쓰거나 요청 없이 파일을 삭제하지 않습니다.
- 파괴적 명령, 대규모 자동 변경, 데이터 마이그레이션은 영향과 복구 방법을 확인하기 전에는 실행하지 않습니다.
- 테스트 통과만을 위해 타입 검사, 보안 제어, 유효성 검사를 비활성화하지 않습니다.
- 하위 호환성을 깨는 변경, 공개 인터페이스 변경, 새로운 런타임 또는 서비스 도입은 명시적인 요구와 근거 없이 진행하지 않습니다.
- 자동 생성 파일은 원본 또는 생성 절차를 통해 수정하고 직접 편집하지 않습니다.
Git 커밋 및 Pull Request
- 커밋은 하나의 논리적 변경에 집중하고
CONTRIBUTING.md의 Conventional Commits 규칙을 따릅니다. - 관련 없는 변경, 생성 산출물, 비밀 정보와 디버깅 코드를 커밋하지 않습니다.
- Pull Request는
.github/PULL_REQUEST_TEMPLATE.md를 반드시 사용하고 변경 목적, 관련 이슈, 변경 유형, 실제로 수행한 검증을 정확히 기록합니다. - PR은 검토 가능한 크기로 유지하며 동작 변경에는 관련 테스트와 문서를 포함합니다.
- 검사하지 않은 항목을 완료로 표시하지 않고 CI 실패나 알려진 제한 사항을 숨기지 않습니다.