본문으로 건너뛰기

JunkBox libs/shared-ai 가이드

1. 문서 목적

  • 이 문서는 JunkBox 전체에서 공통으로 사용하는 AI 런타임 구조와 현재 체스 착수, Jeevtrap AI 기능, 개발 문서 벡터 캐시 지원 구조를 설명하는 가이드임.
  • 본 문서는 계획 문서가 아니라 현재 런타임 기준의 구조, 책임 경계, API 계약, 제약 사항을 이해하기 위한 기준 문서임.

2. 라이브러리 역할

  • shared-ai는 모든 앱이 공통으로 사용하는 AI 연계 라이브러리임.
  • provider 어댑터, 기능별 모델 라우팅, usage log 기록, AI 통신 오류 정규화, direct/graph runner, prompt registry, graph registry, 체스 착수 그래프 실행을 담당함.
  • workout 앱의 운동 계획 생성은 현재 shared-ai 런타임 책임에 포함되지 않음.
  • 현재 체스 샌드박스 기능은 chess-api가 이 라이브러리의 graph runner를 사용해 실행함.
  • 현재 jeevtrap는 이 라이브러리의 direct runner를 사용해 /md, /북마크, 평문 명령 의도 판별 기능을 실행함.

3. 상위 구조

  • 체스 착수 공개 진입점은 chess-api의 Lichess challenge API와 WebSocket 로그 화면임.
  • 체스 착수 실행 주체는 chess-api임.
  • 그래프 실행, provider 호출, structured output, usage log 기록은 libs/shared-ai가 담당함.
  • workout 앱의 운동 계획 생성, 운동 기록 조회, 운동 계획 저장은 workout-api의 규칙 엔진과 Workout SQLite 저장소가 담당함.

4. 패키지 구조

junkbox_shared_ai/
├── dev_doc_sync.py
├── graph_registry.py
├── providers/
├── prompts/
├── registry.py
├── runners.py
├── models.py
├── routing.py
├── service.py
├── chess_agent.py
└── __init__.py

5. 공개 API와 내부 API

5.1 공개 API

  • POST /api/chess/challenges
  • GET /api/chess/games
  • POST /api/chess/games/{game_id}/resign
  • GET /api/chess/game-history
  • GET /api/chess/games/{game_id}/logs
  • GET /api/chess/ws/logs

5.2 내부 API

  • GET /api/internal/master-data/codes/{code_type_code}
  • GET /api/internal/master-data/metas/by-key/{meta_key}

5.3 호출 방향

  • chess-api는 Lichess Bot API와 연동하며, 체스 착수 AI 호출은 shared-ai graph runner와 LiteLLM proxy를 통해 수행함.
  • jeevtrap는 Discord 이벤트와 command service에서 direct runner를 호출함.
  • workout 앱은 운동 계획 생성을 위해 shared-ai 또는 hub-api AI 엔드포인트를 호출하지 않음.

6. 주요 책임

6.1 provider 어댑터

  • LiteLlmProvider는 LiteLLM proxy의 OpenAI 호환 chat completions API 호출을 담당함.
  • 현재 런타임 provider는 litellm 단일 provider만 사용함.
  • provider 구현은 AiProviderRequest, AiProviderResponse 계약을 공유함.
  • 어떤 provider 어댑터를 사용할지는 model이 아니라 라우팅 YAML의 provider 값이 결정함.

6.2 개발 문서 벡터 캐시 지원

  • dev_doc_sync.pyvault/docs/**/*.md 문서를 vault 스키마의 문서 벡터 캐시로 동기화하기 위한 공통 유틸을 제공함.
  • 이 모듈은 애플리케이션 런타임 요청 처리 경로가 아니라 수동 빌드/인프라 타임 스크립트에서 사용됨.
  • 문서의 Source of Truth는 vault/docs/**/*.md 파일이며, JunkBox 개발 가이드의 Source of Truth는 vault/docs/junkbox/**/*.md 파일임.
  • 벡터 DB는 AI 에이전트의 검색 효율과 컨텍스트 선별을 위한 파생 캐시임.
  • scripts/sync_dev_docs.pydev_doc_sync.py의 동기화 진입점임.
  • scripts/search_dev_docs.py는 동일한 임베딩 설정으로 query를 벡터화하고 vault.doc_embeddings, vault.doc_chunks, vault.docs에서 관련 chunk를 검색하는 진입점임.
  • scripts/fetch_master_data.py는 AI 에이전트가 hub-api의 통합 master-data snapshot을 조회하는 진입점임.
  • 이 스크립트들은 애플리케이션 런타임 env인 .env.local, .env.prod, .env.shared를 로드하지 않음.
  • 이 스크립트들은 scripts/.env.ai만 설정 입력으로 사용함.
  • scripts/.env.ai.template는 개발 문서 동기화/검색과 AI master-data 조회에 필요한 환경 변수 예시를 제공함.
  • scripts/.env.ai는 Git 추적 대상이 아닌 작업자별 비밀 설정 파일임.
  • master-data 조회에는 FASTAPI_BASE_URLAI_ACCESS_TOKEN을 사용함.
  • AI_ACCESS_TOKENROLE_AI JWT이며 scripts/fetch_master_data.py가 Bearer 토큰으로 전달함.
  • 문서 동기화와 검색은 VAULT_DATABASE_URL, AI_EMBEDDING_BASE_URL, AI_EMBEDDING_API_KEY, VAULT_EMBEDDING_MODEL을 사용함.
  • AI_EMBEDDING_BASE_URL은 LiteLLM proxy 같은 OpenAI 호환 embeddings API의 /v1 base URL임.
  • VAULT_EMBEDDING_MODEL은 LiteLLM에 등록된 embedding model id 또는 alias임.
  • 저장 벡터 차원은 1536임.
  • vault.docs.doc_namespace는 문서 최상위 분류를 나타내며 JunkBox 개발 가이드는 junkbox 값을 사용함.
  • vault.docs.doc_kind는 JunkBox 문서에서 project_common_guide, project_lib_guide, project_app_guide로 세분화됨.
  • vault.docs.project_key는 JunkBox 개발 가이드에서 junkbox 값을 사용함.

6.3 기능별 라우팅

  • 기능별 provider/model 선택은 AiRoutingStore가 담당함.
  • shared-ai는 라우팅 파일을 해석하는 라이브러리이며 실제 라우팅 정책 파일을 소유하지 않음.
  • 라우팅 Git 원본 소스 오브 트루스는 apps/hub-api/var/master-data/ai-routing.yml 계열 YAML 파일임.
  • 컨테이너 런타임 경로는 /app/var/master-data/ai-routing.yml이며 hub-api, jeevtrap가 같은 파일을 공유함.
  • 운영 배포는 호스트 공통 경로 /sorc001/junkbox/master-data/ai-routing.yml에 파일을 동기화한 뒤 컨테이너 볼륨으로 마운트하는 구조임.
  • route_key 단위로 provider, model, temperature, top_p, max_output_tokens를 결정함.
  • structured_output_mode는 route별 구조화 응답 요청 전략을 결정함.
  • 현재 지원 값은 auto, json_object, json_schema, prompt_json임.
  • auto는 shared-ai provider adapter가 모델 계열에 맞는 기본 전략을 선택함.
  • 체스 sleepzz-bot 착수 생성과 두 줄 thought 작성은 CHESS_MOVE route key를 사용함.
  • 모든 AI route의 provider 값은 litellm을 사용함.
  • JEEVTRAP_MD는 기본 모델로 openrouter/deepseek/deepseek-v4-flash를 사용함.
  • JEEVTRAP_BOOKMARK는 기본 모델로 openrouter/deepseek/deepseek-v4-flash를 사용함.
  • JEEVTRAP_COMMAND_INTENT는 기본 모델로 openrouter/qwen/qwen-2.5-7b-instruct를 사용하며 prompt_json 전략, 320 output token 예산, 낮은 temperature와 top_p를 사용함.
  • CHESS_MOVE는 기본 모델로 openrouter/qwen/qwen-2.5-7b-instruct를 사용하며 prompt_json 전략, 320 output token 예산, 낮은 temperature를 사용함.
  • CHESS_MOVE는 짧은 구조화 응답을 전제로 프롬프트 기반 JSON 출력과 낮은 temperature를 사용함.
  • CHESS_MOVEmax_output_tokens는 짧은 두 줄 thought와 단일 choice 번호 응답을 안정적으로 담을 수 있는 범위로 유지함.
  • APP_PROFILE 값이 있으면 ai-routing.{profile}.yml을 우선 선택하고, 없으면 기본 ai-routing.yml을 사용함.
  • 라우팅 결과의 providermodel은 usage log 저장 시 provider:model 형식의 full model id로 조합됨.

6.4 usage log 기록

  • 공통 AI 사용 이력은 AiUsageLogEntity를 사용함.
  • 저장 대상은 Hub SQLite tb_ai_usage_logs_n임.
  • 성공 시 출력 텍스트, 토큰 수, USD 기준 추정 비용, 실행 시간, 입력 파라미터를 기록함.
  • 추정 비용 계산은 full model id와 AI_MODEL.common_code를 직접 비교해 단가 공통코드를 찾는 방식임.
  • AI_MODEL.common_codeprovider:model 형식이며, 단가 계산 계층은 공통코드 값을 : 기준으로 쪼개어 provider와 model을 별도 식별하지 않음.
  • AI_MODEL.attribute01은 입력 토큰 USD 단가, AI_MODEL.attribute02는 출력 토큰 USD 단가이며 둘 다 100만 토큰 기준 숫자값으로 해석함.
  • 실패 시 원본 예외와 사용자 메시지, 정규화 detail을 함께 기록함.
  • structured output 파싱이나 검증이 뒤에서 실패해도 provider 호출 시도 자체는 감사 로그로 남아야 하므로, generate_content()는 usage log row 생성 직후 성공/실패 로그를 즉시 commit함.
  • 앱 레벨 asyncio.wait_for timeout처럼 provider 호출 task가 취소되는 경우에도 실패 usage log를 남기기 위해 CancelledError를 별도로 기록하고 다시 전파함.

6.5 공통 오류 정규화

  • 앱은 provider별 예외를 직접 해석하지 않음.
  • SharedAiService가 timeout, connection, provider unauthorized, rate limit, provider unavailable, invalid response를 공통 CustomException으로 정규화함.
  • 사용자 노출 메시지는 호출 지점의 한국어 문구로 직접 작성하고, 문장형 메시지는 하십시오체와 마침표 규칙을 따름.

6.6 runner/registry 계층

  • SharedAiRuntimeFactory는 settings, routing store, master-data store, registry 인스턴스를 묶는 중앙 런타임 팩토리임.
  • DirectAiRunner는 단발성 프롬프트 호출 런타임임.
  • GraphAiRunner는 상태 기반 워크플로우 런타임임.
  • PromptRegistry는 프롬프트 키 단위 관리 지점을 제공함.
  • GraphRegistry는 graph key 단위 실행기 등록 지점을 제공함.
  • 앱은 provider 생성, usage log 저장, route lookup, 예외 정규화를 직접 수행하지 않고 runner를 통해 공통 처리함.

6.7 workout 계획 생성 비대상

  • workout 앱의 운동 계획 생성은 shared-ai graph runner 대상이 아님.
  • workout 계획 생성은 WORKOUT_PLAN AI route, LangGraph workflow, structured output, SSE trace, AI usage log 저장 경로를 사용하지 않음.
  • workout 계획 생성 규칙은 workout-api의 도메인 서비스와 Workout SQLite 저장소 계약으로 관리함.

7. direct runner 계약

7.1 입력 계약

  • 앱은 DirectAiRequest를 만들어 DirectAiRunner.run()을 호출함.
  • 주요 필드는 route_key, module_code, feature_code, system_prompt, user_prompt, output_model, sender_id, channel_id, input_params임.
  • output_model은 Pydantic 모델 기준 structured output 검증 계약임.

7.2 출력 계약

  • 반환 타입은 DirectAiRunResult[T]임.
  • data에는 검증된 Pydantic 출력 모델이 들어감.
  • generation에는 usage_log_id, text, full_model_id, prompt_tokens, output_tokens, total_tokens가 포함됨.

7.3 호출 흐름

  • route_key로 라우팅 설정 조회
  • provider 기준 provider 인스턴스 해석
  • provider 요청 실행
  • structured output 검증
  • usage log 저장
  • 앱 서비스로 검증된 결과 반환

8. graph runner 계약

8.1 graph registry 구조

  • graph registry는 graph_key -> workflow factory 매핑을 유지함.
  • 현재 CHESS_MOVE graph key가 등록되어 있음.
  • workflow factory는 GraphWorkflowContext를 받아 graph 실행 객체를 반환함.

8.2 graph runner 구조

  • 앱은 GraphAiRunner.run(graph_key=..., payload=...) 또는 stream(...)을 통해 graph를 실행함.
  • runner는 graph key 해석, workflow 인스턴스 생성, 공통 context 주입을 담당함.
  • graph 내부 구현은 여전히 도메인별 실행기를 사용할 수 있지만, 앱은 이를 직접 생성하지 않음.

9. SharedAiService 계약

9.1 입력 계약

  • 앱은 AiRequestSpec을 만들어 generate_content()를 호출함.
  • 주요 필드는 route_key, module_code, feature_code, system_prompt, user_prompt, sender_id, input_params, response_schema임.

9.2 출력 계약

  • 반환 타입은 AiGenerationResult임.
  • usage_log_id, text, full_model_id, prompt_tokens, output_tokens, total_tokens를 포함함.

9.3 호출 흐름

  • route_key로 라우팅 설정 조회
  • provider 기준 provider 인스턴스 해석
  • provider 요청 실행
  • usage log 저장
  • 앱 또는 그래프 런타임으로 생성 결과 반환

10. Jeevtrap direct AI 흐름

10.1 /md

  • jeevtrap 서비스는 AIBOT_MD_INSTRUCTION, AIBOT_MD_USER_TEMPLATE 메타를 사용해 프롬프트를 조립함.
  • DirectAiRunnerJEEVTRAP_MD route key를 사용해 provider/model을 선택함.
  • 현재 기본 모델 매핑은 litellm:openrouter/deepseek/deepseek-v4-flash 계열임.
  • 반환 JSON은 문서 제목, 파일명, 마크다운 본문, 경로 추천 목록을 포함하는 Pydantic 모델로 검증됨.

10.2 /북마크

  • jeevtrap 서비스는 스크래핑 본문과 AIBOT_WEB_ANALYSIS_INSTRUCTION 메타를 사용해 프롬프트를 조립함.
  • DirectAiRunnerJEEVTRAP_BOOKMARK route key를 사용해 provider/model을 선택함.
  • 현재 기본 모델 매핑은 litellm:openrouter/deepseek/deepseek-v4-flash 계열임.
  • 반환 JSON은 원문 제목, 정제 제목, 요약, 태그 목록을 포함하는 Pydantic 모델로 검증됨.

10.3 평문 명령 의도 판별

  • jeevtrap 서비스는 봇이 멘션된 일반 Discord 메시지만 명령 의도 판별 대상으로 전달함.
  • jeevtrap 서비스는 활성 명령 목록, 사용자 요청 문장, AIBOT_COMMAND_INTENT_INSTRUCTION, AIBOT_COMMAND_INTENT_USER_TEMPLATE 메타를 사용해 프롬프트를 조립함.
  • DirectAiRunnerJEEVTRAP_COMMAND_INTENT route key를 사용해 provider/model을 선택함.
  • 현재 기본 모델 매핑은 litellm:openrouter/qwen/qwen-2.5-7b-instruct 계열임.
  • 반환 JSON은 매칭 여부, 명령 ID, 신뢰도, 판정 근거를 포함하는 Pydantic 모델로 검증됨.
  • 매칭 실패 결과는 명령 실행으로 이어지지 않으며, Jeevtrap 설정에 따라 디버그 안내 메시지만 전송할 수 있음.

11. Workout 앱과의 경계

  • workout 앱의 운동 계획 생성은 현재 AI 기능이 아님.
  • workout-api는 운동 계획 생성을 위해 shared-ai, hub-api AI 엔드포인트, LangGraph, LLM provider, structured output 재생성, usage log 기록을 호출하지 않음.
  • workout-ui는 운동 계획 생성 시 SSE trace, AI 조언, usage log id를 화면 계약으로 사용하지 않음.
  • workout 앱의 운동 계획 규칙, 기준 탑세트 산정, 계획 저장 구조는 docs/junkbox/apps/guide-apps-workout-api.md를 따름.

12. structured output 처리

12.1 공통 structured generation

  • SharedAiService.generate_structured_content()는 JSON 파싱, Pydantic 검증, JSON 재생성, usage log 누적을 공통 처리함.
  • Direct runner와 체스 착수 그래프는 같은 structured generation 경로를 사용할 수 있음.
  • 앱별 agent는 도메인 payload 정규화가 필요한 경우 normalizer만 전달하고, JSON 파싱/스키마 재시도 정책은 shared-ai에 위임함.
  • LangGraph validator 노드는 파싱이 끝난 도메인 객체의 업무 규칙 검증만 담당함.

12.2 LiteLLM adapter

  • LiteLLM proxy는 OpenAI 호환 chat completions 형식을 사용함.
  • response_schema가 있을 때는 route의 structured_output_mode와 모델 계열에 따라 response format을 선택함.
  • Gemini 계열 auto 전략은 response_format={"type":"json_object","response_schema":...}를 우선 사용함.
  • 비-Gemini 계열 auto 전략은 response_format=json_schema를 우선 사용함.
  • json_object, json_schema, prompt_json 전략은 route별로 명시할 수 있음.
  • LiteLLM 또는 하위 모델이 response format 옵션을 거부하면 JSON only 지시문을 강화한 fallback 요청을 1회 재시도함.
  • 따라서 ai-routing*.yml에서 provider/model을 교체해도 structured output 계약을 우선 유지하는 구조임.
  • LiteLLM 응답은 하위 모델에 따라 message content가 문자열이 아닌 배열 또는 구조체 형태로 올 수 있으므로, provider adapter는 응답 content를 안전하게 문자열로 정규화해야 함.
  • LiteLLM 경유 모델이 JSON을 중간에 끊어 반환하는 경우를 대비해 JSON 파싱/스키마 검증 실패 시 같은 프롬프트 문맥으로 JSON 재생성을 요청할 수 있음.

13. 체스 착수 그래프 구조

START
-> thinking_node
-> action_node
-> validation_node
-> END

13.1 입력 계약

  • ChessAgentRequestgame_id, turn_number, fen, legal_moves, board_ascii, pgn, side, opponent_id, invalid_moves를 포함함.
  • fen은 python-chess 기준 현재 보드 상태 문자열임.
  • legal_moves는 현재 보드에서 가능한 UCI 수 목록이며, 프롬프트에서는 choice 번호가 붙은 목록으로 렌더링됨.
  • invalid_moves는 같은 턴에서 sleepzz-bot이 이미 반환했다가 앱 계층 legal move 검증에 실패한 수 목록이며, 재시도 프롬프트의 forbidden move로 사용함.
  • board_ascii는 rank 8부터 rank 1까지 사람이 읽을 수 있는 8x8 보드 표현임.
  • pgn은 앱 계층에서 현재까지의 전체 기보를 전달하지만, 프롬프트 조립 단계에서는 최근 구간으로 축약될 수 있음.
  • legal moves, FEN, 축약 PGN, 축약 board ASCII, forbidden moves는 sleepzz-bot 착수와 thought 생성을 위한 핵심 프롬프트 입력임.

13.2 출력 계약

  • 체스 착수 출력은 thoughtchoice를 포함함.
  • thought는 실시간 대국 지연을 줄이기 위해 현재 상황:착수 근거: 두 줄의 짧은 해설을 기준으로 함.
  • choice는 legal moves choice 목록 안에 있는 정수여야 함.
  • 앱 계층은 choice를 UCI move로 매핑한 뒤 Lichess Bot API 제출 결정에 사용하며, 다시 legal moves 포함 여부를 검증함.
  • ChessAgentResult는 앱 계층이 UI 로그와 디버깅 payload에 원본 choice 번호를 보존할 수 있도록 choice 값을 함께 반환함.
  • JSON 파싱 실패, provider 오류, content 형식 오류가 발생하면 앱 레벨에서 fallback 합법 수 제출과 실패 로그를 처리함.
  • 체스 그래프는 실시간 대국 지연을 줄이기 위해 structured output repair 요청을 사용하지 않고, 단일 응답의 JSON 객체를 파싱함.

13.3 노드 책임

  • thinking_node는 현재 FEN, side, legal moves, 짧은 목표 문장을 바탕으로 sleepzz-bot 착수 문맥을 생성함.
  • action_node는 착수 문맥, legal moves choice 목록, 축약 board ASCII, 축약 PGN을 사용해 최종 {"thought": "...", "choice": 1} JSON 응답을 생성하고, choice를 UCI move로 매핑함.
  • validation_node는 매핑된 move가 legal_moves 안에 있는지 기록용 오류 메시지를 남김.
  • 최종 Lichess 제출 전 차단 검증은 앱 계층에서 다시 수행함.

13.4 프롬프트 자산

  • 체스 착수 기본 프롬프트는 libs/shared-ai/src/junkbox_shared_ai/prompts/chess_move.md에 둠.
  • 프롬프트는 FEN만 읽는 방식에 의존하지 않고 legal moves, 축약 ASCII 보드, 최근 PGN을 명확히 구분해 전달해야 함.
  • 프롬프트는 출력 안정성, 합법수 선택, 체스 선택 원칙, 설명 품질 규칙을 목적별로 구분해 관리함.
  • 프롬프트는 실시간 대국 사용성을 고려해 짧은 두 줄 thought와 단일 JSON 객체 출력을 요구함.
  • 체스 프롬프트는 앱 코드 안에 긴 문자열로 하드코딩하지 않고 shared-ai prompt 자산에서 관리함.

14. provider 환경 변수

  • provider 비밀값과 base URL은 .env.shared + ENV_FILE 조합으로 로딩되는 shared-core Settings를 통해 공급됨.
  • LiteLLM
    • LITELLM_API_KEY
    • LITELLM_BASE_URL
  • 공통 timeout
    • AI_HTTP_TIMEOUT_SECONDS

15. 현재 제약과 사용 원칙

  • 앱은 provider를 직접 생성하지 않고 get_ai_service(), direct runner, graph runner를 사용함.
  • 앱은 direct 호출과 graph 호출 중 어떤 runner를 쓸지만 선택하고, 실제 런타임 세부 구현은 shared-ai가 담당함.
  • 기능별 모델 선택은 앱 코드가 아니라 라우팅 YAML이 결정함.
  • provider별 예외 메시지를 앱마다 따로 분기하지 않음.
  • usage log 저장 책임을 앱 서비스로 분산하지 않음.
  • PromptRegistry는 현재 구조상 존재하지만 모든 프롬프트 텍스트를 완전히 중앙 등록하는 단계까지는 가지 않음.
  • OpenAI 호환 API가 아닌 새 공식 gateway를 추가할 때는 별도 provider 어댑터 구현이 필요함.
  • 현재 AI route는 모두 litellm provider를 사용하며, 모델 선택은 LiteLLM model id를 라우팅 YAML의 model 값으로 지정해 수행함.
  • usage log의 full model id는 litellm:model 형식을 유지해야 함.
  • 체스 수 제출 안정성이 중요하므로 앱 계층에서 sleepzz-bot choice를 UCI move로 매핑한 뒤 move in legal_moves 검증을 반드시 수행해야 함.
  • sleepzz-bot 착수 생성 실패 또는 1회 재시도 후 합법 수 검증 실패 시 fallback 합법 수를 제출함.
  • 개발 문서 벡터 캐시 동기화와 검색은 사용자 요청 처리용 AI runner 흐름과 분리됨.
  • 개발 문서 벡터 캐시 검색 결과는 후속 AI 코딩 작업의 컨텍스트 선별에 사용되며, 원본 문서 수정은 항상 vault/docs/junkbox/**/*.md 파일에서 수행함.
  • 개발 문서가 삭제되면 동기화 스크립트는 DB row를 hard delete하지 않고 vault.docs.is_deleted=true로 표시함.
  • 개발 문서가 수정되면 동기화 스크립트는 SHA-256 해시 차이를 기준으로 변경을 감지하고 해당 문서의 chunk와 embedding을 교체함.
  • 검색 스크립트는 기본적으로 cosine similarity 기준 상위 chunk를 반환하며, 출력에는 source, route, namespace, kind, project, heading, chunk, 유사도 점수, 본문 preview가 포함됨.

16. 관련 문서

  • 프로젝트 전체 구조는 docs/junkbox/common/guide-jb-core.md를 따름.
  • 공통 설정과 예외 구조는 docs/junkbox/libs/guide-lib-shared-core.md를 따름.
  • hub-api 역할은 docs/junkbox/apps/guide-apps-hub-api.md를 따름.
  • chess-api 역할은 docs/junkbox/apps/guide-apps-chess-api.md를 따름.
  • chess-ui 역할은 docs/junkbox/apps/guide-apps-chess-ui.md를 따름.
  • 개발 문서 원본과 지식 캐시 운영 원칙은 docs/junkbox/common/guide-jb-core.md를 따름.
  • jeevtrap 역할은 docs/junkbox/apps/guide-apps-jeevtrap.md를 따름.
  • workout-api 역할은 docs/junkbox/apps/guide-apps-workout-api.md를 따름.
  • workout-ui 역할은 docs/junkbox/apps/guide-apps-workout-ui.md를 따름.