codebuff-agent 2.1.74__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (217) hide show
  1. codebuff_agent-2.1.74.data/data/etc/jupyter/jupyter_server_config.d/codebuff_agent.json +7 -0
  2. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/package.json +157 -0
  3. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/104.2b8f20f9d1ac2c9db01b.js +1 -0
  4. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/150.39fa98117cc8ca590465.js +1 -0
  5. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/166.386ff9b2c37ac865fdee.js +1 -0
  6. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/216.25cf81131bf7274dabf4.js +1 -0
  7. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/353.581bd1cb00746399b62e.js +1 -0
  8. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/367.575f9621b63c9a641aa9.js +1 -0
  9. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/421.11dac223a2c2da1b09f5.js +1 -0
  10. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/432.8513b4c6cc1d1232d165.js +2 -0
  11. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/432.8513b4c6cc1d1232d165.js.LICENSE.txt +19 -0
  12. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/439.ba5b5ef70ec6de214b8c.js +2 -0
  13. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/439.ba5b5ef70ec6de214b8c.js.LICENSE.txt +8 -0
  14. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/481.cc2dec9fa869d66f1407.js +1 -0
  15. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/487.0796e931d6e651482c14.js +1 -0
  16. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/49.d326f554b6f9ccde6580.js +1 -0
  17. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/541.26897ea32845659e36be.js +1 -0
  18. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/579.e569d3edd8391fd8fbb3.js +1 -0
  19. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/58.eb7cc679f7535cc8a2f1.js +2 -0
  20. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/58.eb7cc679f7535cc8a2f1.js.LICENSE.txt +8 -0
  21. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/666.d9bfdb23f98489d0a188.js +1 -0
  22. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/677.d266c39dbed33bc388b3.js +2 -0
  23. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/677.d266c39dbed33bc388b3.js.LICENSE.txt +8 -0
  24. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/68.26b6f59c8454adb17f20.js +204 -0
  25. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/694.4ac443814260057e68f7.js +1 -0
  26. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/791.61affb8d3480d3d60e0d.js +1 -0
  27. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/848.4d2db09cfbb09374b299.js +2 -0
  28. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/848.4d2db09cfbb09374b299.js.LICENSE.txt +9 -0
  29. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/868.5cfe7e1a348ec5af1b8c.js +1 -0
  30. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/97.ea559a5b8a9a09f17c6b.js +1 -0
  31. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/remoteEntry.5292fc9c6df7e7920cc8.js +1 -0
  32. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/style.js +4 -0
  33. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/third-party-licenses.json +724 -0
  34. codebuff_agent-2.1.74.dist-info/METADATA +142 -0
  35. codebuff_agent-2.1.74.dist-info/RECORD +217 -0
  36. codebuff_agent-2.1.74.dist-info/WHEEL +4 -0
  37. codebuff_agent-2.1.74.dist-info/licenses/LICENSE +21 -0
  38. codebuff_core/__init__.py +153 -0
  39. codebuff_core/codebuff_config_defaults.json +64 -0
  40. codebuff_core/core/__init__.py +35 -0
  41. codebuff_core/core/code_validator.py +1238 -0
  42. codebuff_core/core/error_classifier.py +579 -0
  43. codebuff_core/core/exceptions.py +49 -0
  44. codebuff_core/data/tiktoken_cache/9b5ad71b2ce5302211f9c61530b329a4922fc6a4 +100256 -0
  45. codebuff_core/directory_policy.py +32 -0
  46. codebuff_core/factory.py +155 -0
  47. codebuff_core/interfaces.py +197 -0
  48. codebuff_core/knowledge/__init__.py +31 -0
  49. codebuff_core/knowledge/chunking.py +375 -0
  50. codebuff_core/knowledge/loader.py +339 -0
  51. codebuff_core/knowledge/watchdog_service.py +354 -0
  52. codebuff_core/legacy_paths.py +69 -0
  53. codebuff_core/llm/__init__.py +13 -0
  54. codebuff_core/llm/service.py +407 -0
  55. codebuff_core/managers/__init__.py +22 -0
  56. codebuff_core/managers/config_manager.py +690 -0
  57. codebuff_core/managers/remote_config.py +673 -0
  58. codebuff_core/managers/remote_skills.py +539 -0
  59. codebuff_core/managers/session_manager.py +255 -0
  60. codebuff_core/middleware/__init__.py +3 -0
  61. codebuff_core/middleware/error_recovery.py +394 -0
  62. codebuff_core/middleware/logging_utils.py +33 -0
  63. codebuff_core/models/__init__.py +111 -0
  64. codebuff_core/models/agent.py +306 -0
  65. codebuff_core/models/chat.py +57 -0
  66. codebuff_core/models/common.py +82 -0
  67. codebuff_core/models/rag.py +300 -0
  68. codebuff_core/platform_rules.py +127 -0
  69. codebuff_core/prompts/__init__.py +44 -0
  70. codebuff_core/prompts/auto_agent_prompts.py +1278 -0
  71. codebuff_core/services/__init__.py +30 -0
  72. codebuff_core/services/agent_service.py +345 -0
  73. codebuff_core/services/analytics_aggregate.py +837 -0
  74. codebuff_core/services/chat_service.py +898 -0
  75. codebuff_core/services/context_condenser.py +306 -0
  76. codebuff_core/services/pii_guardrail.py +393 -0
  77. codebuff_core/services/rag_chat_agent.py +2993 -0
  78. codebuff_core/services/rag_collections.py +2740 -0
  79. codebuff_core/services/rag_manager.py +890 -0
  80. codebuff_core/services/rag_service.py +145 -0
  81. codebuff_core/services/retriever.py +260 -0
  82. codebuff_core/services/vllm_embedding_service.py +290 -0
  83. codebuff_core/tests/__init__.py +1 -0
  84. codebuff_core/tests/conftest.py +81 -0
  85. codebuff_core/tests/test_error_recovery.py +647 -0
  86. codebuff_core/tests/test_factory.py +135 -0
  87. codebuff_core/tests/test_services.py +150 -0
  88. jupyter_ext/__init__.py +266 -0
  89. jupyter_ext/_version.py +4 -0
  90. jupyter_ext/agent/__init__.py +49 -0
  91. jupyter_ext/agent/agent_factory.py +2270 -0
  92. jupyter_ext/agent/ast_tools.py +451 -0
  93. jupyter_ext/agent/autofix_middleware.py +481 -0
  94. jupyter_ext/agent/background_procs.py +372 -0
  95. jupyter_ext/agent/code_history_middleware.py +211 -0
  96. jupyter_ext/agent/codex_autofix.py +160 -0
  97. jupyter_ext/agent/codex_bridge.py +3339 -0
  98. jupyter_ext/agent/codex_hooks.py +320 -0
  99. jupyter_ext/agent/codex_slash.py +685 -0
  100. jupyter_ext/agent/codex_threads.py +114 -0
  101. jupyter_ext/agent/content_injection.py +377 -0
  102. jupyter_ext/agent/continuation_middleware.py +548 -0
  103. jupyter_ext/agent/custom_skills.py +335 -0
  104. jupyter_ext/agent/file_diffs.py +99 -0
  105. jupyter_ext/agent/git_history.py +188 -0
  106. jupyter_ext/agent/guardrail_middleware.py +421 -0
  107. jupyter_ext/agent/json_repair_utils.py +54 -0
  108. jupyter_ext/agent/langchain_tools.py +2673 -0
  109. jupyter_ext/agent/mermaid_fixer.py +289 -0
  110. jupyter_ext/agent/mermaid_middleware.py +135 -0
  111. jupyter_ext/agent/middleware.py +359 -0
  112. jupyter_ext/agent/middleware_prompts.py +53 -0
  113. jupyter_ext/agent/observability.py +334 -0
  114. jupyter_ext/agent/patch_tool_calls.py +57 -0
  115. jupyter_ext/agent/prompts.py +112 -0
  116. jupyter_ext/agent/skill_middleware.py +257 -0
  117. jupyter_ext/agent/skills/airflow_dag_development.md +36 -0
  118. jupyter_ext/agent/skills/anomaly_detection.md +12 -0
  119. jupyter_ext/agent/skills/athena_sql.md +156 -0
  120. jupyter_ext/agent/skills/bdp_table_search.md +33 -0
  121. jupyter_ext/agent/skills/bigquery.md +65 -0
  122. jupyter_ext/agent/skills/bigquery_oracle.md +231 -0
  123. jupyter_ext/agent/skills/bigquery_tuning.md +307 -0
  124. jupyter_ext/agent/skills/classification_evaluation.md +11 -0
  125. jupyter_ext/agent/skills/clustering_analysis.md +12 -0
  126. jupyter_ext/agent/skills/comprehensive_eda.md +11 -0
  127. jupyter_ext/agent/skills/data_analysis.md +236 -0
  128. jupyter_ext/agent/skills/data_cleaning_pipeline.md +12 -0
  129. jupyter_ext/agent/skills/data_loading.md +158 -0
  130. jupyter_ext/agent/skills/data_quality_report.md +13 -0
  131. jupyter_ext/agent/skills/dimensionality_reduction.md +12 -0
  132. jupyter_ext/agent/skills/emr_script_development.md +17 -0
  133. jupyter_ext/agent/skills/experiment_tracking.md +13 -0
  134. jupyter_ext/agent/skills/feature_importance_analysis.md +10 -0
  135. jupyter_ext/agent/skills/hyperparameter_tuning.md +12 -0
  136. jupyter_ext/agent/skills/hypothesis_testing.md +12 -0
  137. jupyter_ext/agent/skills/imbalanced_data_handling.md +12 -0
  138. jupyter_ext/agent/skills/inference.md +392 -0
  139. jupyter_ext/agent/skills/lambda_function_development.md +19 -0
  140. jupyter_ext/agent/skills/model_comparison.md +11 -0
  141. jupyter_ext/agent/skills/model_explainability.md +12 -0
  142. jupyter_ext/agent/skills/model_training.md +318 -0
  143. jupyter_ext/agent/skills/pyspark.md +352 -0
  144. jupyter_ext/agent/skills/python_code_quality.md +35 -0
  145. jupyter_ext/agent/skills/regression_evaluation.md +12 -0
  146. jupyter_ext/agent/skills/spring_java_development.md +35 -0
  147. jupyter_ext/agent/skills/text_preprocessing.md +12 -0
  148. jupyter_ext/agent/skills/time_series_analysis.md +13 -0
  149. jupyter_ext/agent/skills/unit_testing.md +203 -0
  150. jupyter_ext/agent/todo_prompts.py +57 -0
  151. jupyter_ext/agent/tool_report.py +45 -0
  152. jupyter_ext/analytics_ingest.py +599 -0
  153. jupyter_ext/etc/jupyter/jupyter_server_config.d/codebuff_agent.json +7 -0
  154. jupyter_ext/handlers/__init__.py +8 -0
  155. jupyter_ext/handlers/analytics_handler.py +170 -0
  156. jupyter_ext/handlers/benchmark_handler.py +794 -0
  157. jupyter_ext/handlers/eda_benchmark_handler.py +1328 -0
  158. jupyter_ext/handlers/rag_admin_handler.py +689 -0
  159. jupyter_ext/handlers/websocket.py +497 -0
  160. jupyter_ext/http_handlers.py +8744 -0
  161. jupyter_ext/idle_monitor.py +346 -0
  162. jupyter_ext/labextension/package.json +157 -0
  163. jupyter_ext/labextension/static/104.2b8f20f9d1ac2c9db01b.js +1 -0
  164. jupyter_ext/labextension/static/150.39fa98117cc8ca590465.js +1 -0
  165. jupyter_ext/labextension/static/166.386ff9b2c37ac865fdee.js +1 -0
  166. jupyter_ext/labextension/static/216.25cf81131bf7274dabf4.js +1 -0
  167. jupyter_ext/labextension/static/353.581bd1cb00746399b62e.js +1 -0
  168. jupyter_ext/labextension/static/367.575f9621b63c9a641aa9.js +1 -0
  169. jupyter_ext/labextension/static/421.11dac223a2c2da1b09f5.js +1 -0
  170. jupyter_ext/labextension/static/432.8513b4c6cc1d1232d165.js +2 -0
  171. jupyter_ext/labextension/static/432.8513b4c6cc1d1232d165.js.LICENSE.txt +19 -0
  172. jupyter_ext/labextension/static/439.ba5b5ef70ec6de214b8c.js +2 -0
  173. jupyter_ext/labextension/static/439.ba5b5ef70ec6de214b8c.js.LICENSE.txt +8 -0
  174. jupyter_ext/labextension/static/481.cc2dec9fa869d66f1407.js +1 -0
  175. jupyter_ext/labextension/static/487.0796e931d6e651482c14.js +1 -0
  176. jupyter_ext/labextension/static/49.d326f554b6f9ccde6580.js +1 -0
  177. jupyter_ext/labextension/static/541.26897ea32845659e36be.js +1 -0
  178. jupyter_ext/labextension/static/579.e569d3edd8391fd8fbb3.js +1 -0
  179. jupyter_ext/labextension/static/58.eb7cc679f7535cc8a2f1.js +2 -0
  180. jupyter_ext/labextension/static/58.eb7cc679f7535cc8a2f1.js.LICENSE.txt +8 -0
  181. jupyter_ext/labextension/static/666.d9bfdb23f98489d0a188.js +1 -0
  182. jupyter_ext/labextension/static/677.d266c39dbed33bc388b3.js +2 -0
  183. jupyter_ext/labextension/static/677.d266c39dbed33bc388b3.js.LICENSE.txt +8 -0
  184. jupyter_ext/labextension/static/68.26b6f59c8454adb17f20.js +204 -0
  185. jupyter_ext/labextension/static/694.4ac443814260057e68f7.js +1 -0
  186. jupyter_ext/labextension/static/791.61affb8d3480d3d60e0d.js +1 -0
  187. jupyter_ext/labextension/static/848.4d2db09cfbb09374b299.js +2 -0
  188. jupyter_ext/labextension/static/848.4d2db09cfbb09374b299.js.LICENSE.txt +9 -0
  189. jupyter_ext/labextension/static/868.5cfe7e1a348ec5af1b8c.js +1 -0
  190. jupyter_ext/labextension/static/97.ea559a5b8a9a09f17c6b.js +1 -0
  191. jupyter_ext/labextension/static/remoteEntry.5292fc9c6df7e7920cc8.js +1 -0
  192. jupyter_ext/labextension/static/style.js +4 -0
  193. jupyter_ext/labextension/static/third-party-licenses.json +724 -0
  194. jupyter_ext/log_paths.py +113 -0
  195. jupyter_ext/resource_usage.py +519 -0
  196. jupyter_ext/secrets_bootstrap.py +116 -0
  197. jupyter_ext/team_access.py +105 -0
  198. jupyter_ext/tests/__init__.py +1 -0
  199. jupyter_ext/tests/test_context.py +234 -0
  200. jupyter_ext/tests/test_factory.py +563 -0
  201. jupyter_ext/tests/test_file_finder.py +139 -0
  202. jupyter_ext/tests/test_handlers.py +58 -0
  203. jupyter_ext/tests/test_mcp_selftest.py +204 -0
  204. jupyter_ext/tests/test_mermaid_fixer.py +272 -0
  205. jupyter_ext/tests/test_resource_tiers.py +298 -0
  206. jupyter_ext/tools/__init__.py +15 -0
  207. jupyter_ext/tools/base.py +90 -0
  208. jupyter_ext/tools/context.py +268 -0
  209. jupyter_ext/tools/dir_complete.py +86 -0
  210. jupyter_ext/tools/file_finder.py +303 -0
  211. jupyter_ext/tools/file_tools.py +244 -0
  212. jupyter_ext/tools/jupyter_tools.py +296 -0
  213. jupyter_ext/tools/registry.py +159 -0
  214. jupyter_ext/tools/search_tools.py +230 -0
  215. jupyter_ext/tools/security_tools.py +657 -0
  216. jupyter_ext/tools/shell_tools.py +109 -0
  217. jupyter_ext/tools/user_tools.py +132 -0
@@ -0,0 +1,257 @@
1
+ """
2
+ SkillMiddleware
3
+
4
+ Middleware that provides progressive skill loading for code generation agents.
5
+ Based on LangChain Skills pattern for token-efficient context injection.
6
+
7
+ Key features:
8
+ - Injects skill metadata into system prompt (~250 tokens)
9
+ - Provides load_skill tool for on-demand full content loading
10
+ - Reads skill definitions from markdown files
11
+ """
12
+
13
+ import logging
14
+ from functools import lru_cache
15
+ from pathlib import Path
16
+ from typing import Any, Dict, List, Optional
17
+
18
+ import yaml
19
+ from langchain_core.tools import tool
20
+ from pydantic import BaseModel, Field
21
+
22
+ from . import custom_skills
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+ # Skills directory path
27
+ SKILLS_DIR = Path(__file__).parent / "skills"
28
+
29
+
30
+ def _parse_skill_metadata(file_path: Path) -> Optional[Dict[str, str]]:
31
+ """Parse skill metadata from markdown file's YAML frontmatter.
32
+
33
+ Expected format:
34
+ ---
35
+ name: skill-name
36
+ description: Skill description
37
+ ---
38
+ """
39
+ try:
40
+ content = file_path.read_text(encoding="utf-8")
41
+
42
+ if not content.startswith("---"):
43
+ logger.warning(f"Skill file missing YAML frontmatter: {file_path}")
44
+ return None
45
+
46
+ parts = content.split("---", 2)
47
+ if len(parts) < 3:
48
+ logger.warning(f"Invalid YAML frontmatter format: {file_path}")
49
+ return None
50
+
51
+ frontmatter = yaml.safe_load(parts[1])
52
+ if not frontmatter or "name" not in frontmatter:
53
+ logger.warning(f"Missing 'name' in frontmatter: {file_path}")
54
+ return None
55
+
56
+ return {
57
+ "name": frontmatter.get("name"),
58
+ "description": frontmatter.get("description", "No description"),
59
+ "path": str(file_path),
60
+ }
61
+ except Exception as e:
62
+ logger.error(f"Failed to parse skill file {file_path}: {e}")
63
+ return None
64
+
65
+
66
+ def _load_all_skills() -> Dict[str, Dict[str, str]]:
67
+ """Load all skill metadata from the skills directory."""
68
+ skills = {}
69
+
70
+ if not SKILLS_DIR.exists():
71
+ logger.warning(f"Skills directory not found: {SKILLS_DIR}")
72
+ return skills
73
+
74
+ for file_path in SKILLS_DIR.glob("*.md"):
75
+ metadata = _parse_skill_metadata(file_path)
76
+ if metadata:
77
+ skills[metadata["name"]] = metadata
78
+ logger.debug(f"Loaded skill: {metadata['name']}")
79
+
80
+ logger.info(f"Loaded {len(skills)} skills from {SKILLS_DIR}")
81
+ return skills
82
+
83
+
84
+ def _get_skill_content(skill_name: str, skills: Dict[str, Dict[str, str]]) -> str:
85
+ """Load full content of a skill file (excluding YAML frontmatter)."""
86
+ if skill_name not in skills:
87
+ # `_`/`-`·대소문자를 가리지 않는다 — 재정의 판정·codex 와 같은 `skill_key` (이슈 #220)
88
+ key = custom_skills.skill_key(skill_name)
89
+ for registered_name in skills:
90
+ if custom_skills.skill_key(registered_name) == key:
91
+ skill_name = registered_name
92
+ break
93
+ if skill_name not in skills:
94
+ available = ", ".join(skills.keys())
95
+ return f"Error: Unknown skill '{skill_name}'. Available skills: {available}"
96
+
97
+ try:
98
+ file_path = Path(skills[skill_name]["path"])
99
+ if skills[skill_name].get("custom"):
100
+ return custom_skills.read_body(file_path)
101
+ content = file_path.read_text(encoding="utf-8")
102
+
103
+ # Remove YAML frontmatter
104
+ if content.startswith("---"):
105
+ parts = content.split("---", 2)
106
+ if len(parts) >= 3:
107
+ content = parts[2].strip()
108
+
109
+ return content
110
+ except Exception as e:
111
+ return f"Error loading skill '{skill_name}': {str(e)}"
112
+
113
+
114
+ def _build_skills_prompt_section(skills: Dict[str, Dict[str, str]]) -> str:
115
+ """Build the Available Skills section for system prompt injection (~250 tokens)."""
116
+ if not skills:
117
+ return ""
118
+
119
+ lines = ["## Available Skills"]
120
+ lines.append("")
121
+ lines.append("### Context 리소스 정보")
122
+ lines.append(
123
+ "Main Agent가 task 호출 시 제공하는 Context에는 다음 정보가 포함됩니다:"
124
+ )
125
+ lines.append("- **파일 크기**: `file_size: 200MB`, `total_size: 1.5GB` 등")
126
+ lines.append("- **시스템 메모리**: `available_memory: 16GB`, `gpu_memory: 8GB` 등")
127
+ lines.append("- **데이터 행 수**: `row_count: 10000000` 등")
128
+ lines.append("")
129
+ lines.append("### 스킬 로드 규칙 (MANDATORY)")
130
+ lines.append(
131
+ "**분야 작업이면 설명이 맞는 스킬을 먼저 읽는다** — Athena·Airflow·EMR·Lambda·"
132
+ "Spring·BigQuery·EDA 같은 작업은 아래 목록에서 설명이 맞는 스킬을 "
133
+ "load_skill_tool로 읽고 그 규칙을 따른다."
134
+ )
135
+ # 로드 기준(파일 크기·행 수·GPU 등)은 각 스킬의 description 에 있다 — codex 도 같은
136
+ # 목록을 받으므로 여기에 따로 적지 않는다 (이슈 #228)
137
+ lines.append(
138
+ "**설명에 적힌 조건(파일 크기·행 수·GPU 등)에 해당하면 코드를 쓰기 전에"
139
+ " load_skill_tool tool을 먼저 사용하세요"
140
+ " (tool call로 호출, Python 코드로 작성하지 마세요).**"
141
+ )
142
+ lines.append(
143
+ "조건에 못 미치면(작은 파일·적은 행 수·단순 출력) 또는 Context 에 리소스 정보가"
144
+ " 없으면 최적화 스킬은 읽지 않는다."
145
+ )
146
+ lines.append("")
147
+
148
+ for name, metadata in sorted(skills.items()):
149
+ lines.append(f"- **{name}**: {metadata['description']}")
150
+
151
+ return "\n".join(lines)
152
+
153
+
154
+ def _create_load_skill_tool(skills: Dict[str, Dict[str, str]]):
155
+ """Create the load_skill_tool for on-demand skill loading."""
156
+ available_skills = ", ".join(sorted(skills.keys()))
157
+
158
+ class LoadSkillInput(BaseModel):
159
+ """Input schema for load_skill_tool"""
160
+
161
+ skill_name: str = Field(
162
+ description=f"Name of the skill to load. Available: {available_skills}"
163
+ )
164
+
165
+ @tool(args_schema=LoadSkillInput)
166
+ def load_skill_tool(skill_name: str) -> str:
167
+ """
168
+ Load the full guide of a skill listed in the Available Skills section.
169
+
170
+ Use this tool when the task matches a skill's description in that list.
171
+
172
+ Args:
173
+ skill_name: Name of the skill to load
174
+
175
+ Returns:
176
+ Full optimization guide with code patterns and best practices
177
+ """
178
+ logger.info(f"Loading skill: {skill_name}")
179
+ content = _get_skill_content(skill_name, skills)
180
+
181
+ if not content.startswith("Error"):
182
+ logger.info(f"Skill '{skill_name}' loaded: {len(content)} chars")
183
+
184
+ return content
185
+
186
+ return load_skill_tool
187
+
188
+
189
+ class SkillMiddleware:
190
+ """Middleware that adds skill loading capability to code generation agents.
191
+
192
+ This middleware:
193
+ 1. Injects Available Skills section into system prompt (~250 tokens)
194
+ 2. Adds the `load_skill` tool for on-demand content loading
195
+ 3. Enables token-efficient progressive disclosure of optimization guides
196
+
197
+ 워크스페이스를 주면 커스텀 스킬(`custom_skills`)을 합친다 — 이름이 겹치면 커스텀이
198
+ 이긴다 (이슈 #134). 없으면 내장 스킬만이라 프롬프트가 전과 같다.
199
+
200
+ Usage:
201
+ skill_mw = get_skill_middleware(workspace_root)
202
+ skills_section = skill_mw.get_prompt_section()
203
+ tools = skill_mw.get_tools()
204
+ """
205
+
206
+ def __init__(self, workspace_root: Optional[str] = None):
207
+ custom = custom_skills.discover(workspace_root) if workspace_root else []
208
+ # 철자만 다른 이름(`athena-sql`)도 같은 스킬이다 — codex 와 같은 판정 (이슈 #220)
209
+ custom_keys = {custom_skills.skill_key(s.name) for s in custom}
210
+ builtin = {
211
+ name: meta
212
+ for name, meta in _builtin_skills().items()
213
+ if custom_skills.skill_key(name) not in custom_keys
214
+ }
215
+ self.skills = dict(builtin)
216
+ for s in custom:
217
+ self.skills[s.name] = {
218
+ "name": s.name,
219
+ "description": s.description,
220
+ "path": str(s.path),
221
+ "custom": "1",
222
+ }
223
+ self.prompt_section = _build_skills_prompt_section(builtin)
224
+ custom_section = custom_skills.prompt_section(custom, "load_skill_tool")
225
+ if custom_section:
226
+ self.prompt_section += "\n\n" + custom_section
227
+ self.load_skill_tool = _create_load_skill_tool(self.skills)
228
+
229
+ logger.info(
230
+ f"SkillMiddleware initialized with {len(self.skills)} skills: "
231
+ f"{list(self.skills.keys())}"
232
+ )
233
+
234
+ def get_prompt_section(self) -> str:
235
+ """Get the Available Skills section for system prompt."""
236
+ return self.prompt_section
237
+
238
+ def get_tools(self) -> List[Any]:
239
+ """Get the tools provided by this middleware."""
240
+ return [self.load_skill_tool]
241
+
242
+
243
+ @lru_cache(maxsize=1)
244
+ def _builtin_skills() -> Dict[str, Dict[str, str]]:
245
+ """패키지 옆 내장 스킬은 코드와 함께 움직이므로 프로세스당 한 번 읽는다."""
246
+ return _load_all_skills()
247
+
248
+
249
+ def get_skill_middleware(workspace_root: Optional[str] = None) -> SkillMiddleware:
250
+ """에이전트를 만들 때마다 새로 만든다.
251
+
252
+ 커스텀 스킬은 워크스페이스마다 달라서 프로세스 싱글턴으로 둘 수 없다 (이슈 #134).
253
+ 내장 스킬 파싱은 `_builtin_skills` 가 캐시하므로 커스텀 스킬 디렉터리만 다시 훑는다.
254
+ HTTP 스트림은 에이전트 그래프를 캐시하지만 캐시 키에 스킬 목록이 들어가므로
255
+ (`http_handlers._skills_cache_key`), 기동 뒤 더한 스킬도 다음 요청부터 목록에 뜬다.
256
+ """
257
+ return SkillMiddleware(workspace_root)
@@ -0,0 +1,36 @@
1
+ ---
2
+ name: airflow_dag_development
3
+ description: 'Airflow Development & Operations Workflow. 이런 요청에 쓴다: airflow, dag, operator, taskflow, 에어플로우, mwaa, composer, cloud composer, 워크플로우 오케스트레이션, airflow 튜닝, airflow 에러, airflow 로그, task concurrency, parallelism, pool slot'
4
+ ---
5
+ ## Airflow Development & Operations Workflow
6
+ 1. **Structure**: default_args, DAG context, task dependencies
7
+ 2. **Tune**: parallelism, concurrency, pool slots, scheduler tuning
8
+ 3. **Debug**: task logs, scheduler logs, error tracing
9
+ 4. **Validate**: python -c "from airflow.models import DagBag; ..."
10
+ 5. **Deploy**: MWAA S3 sync / Composer GCS bucket upload
11
+
12
+ ## Guidelines
13
+ - DAG ID는 snake_case, 설명적 이름 사용
14
+ - default_args에 owner, retries, retry_delay 포함
15
+ - catchup=False를 기본으로 설정
16
+
17
+ ## 분석 작성 가이드
18
+ - DAG 구조 시각화: Mermaid graph로 task dependency 표현
19
+ - 병목 지점 식별: 성능 이슈 원인 분석
20
+ - 플랫폼별 차이: MWAA vs Composer 구분 명시
21
+ - 에러 패턴: 에러 메시지 → 원인 → 영향도 분석
22
+ - 로그 해석: 핵심 로그 라인 인용 및 의미 설명
23
+
24
+ ## Mermaid 다이어그램 컬러 코딩
25
+ - 정상/성공: `fill:#4CAF50,color:white` (green)
26
+ - 경고/주의: `fill:#FF9800,color:white` (orange)
27
+ - 에러/병목: `fill:#F44336,color:white` (red)
28
+ - 정보/중립: `fill:#2196F3,color:white` (blue)
29
+ - 대기/미실행: `fill:#9E9E9E,color:white` (gray)
30
+
31
+ ## 권고 작성 가이드
32
+ - 구체적 설정값 제시 (airflow.cfg 파라미터)
33
+ - MWAA와 Composer 각각의 설정 방법 명시
34
+ - 단계별 적용 가이드 (순서 중요한 경우)
35
+ - 검증 방법 포함
36
+ - 모든 설정 권고에 MWAA/Composer 플랫폼별 적용 방법 포함
@@ -0,0 +1,12 @@
1
+ ---
2
+ name: anomaly_detection
3
+ description: 'Anomaly Detection Template. 이런 요청에 쓴다: anomaly detection, isolation forest, outlier detection, 이상탐지, 이상치 탐지'
4
+ ---
5
+ ## Anomaly Detection Template
6
+ 1. **EDA**: Distribution plots, box plots to visually identify outliers
7
+ 2. **Statistical Methods**: Z-score (|z| > 3), IQR (< Q1-1.5*IQR or > Q3+1.5*IQR)
8
+ 3. **Isolation Forest**: `IsolationForest(contamination=0.05).fit_predict(X)`
9
+ 4. **Local Outlier Factor**: `LocalOutlierFactor(n_neighbors=20)`
10
+ 5. **Evaluation**: If labels available — precision, recall; otherwise visual inspection
11
+ 6. **Visualization**: Scatter plot highlighting anomalies, time-series anomaly overlay
12
+ 7. **Action**: Remove, cap, or flag anomalies based on domain context
@@ -0,0 +1,156 @@
1
+ ---
2
+ name: athena_sql
3
+ description: Athena(Trino) SQL 작성·튜닝. BDP 테이블 조회 쿼리 작성, 파티션 프루닝으로 스캔 비용 절감, EXPLAIN 오류 수정, Iceberg DML 제약 확인.
4
+ ---
5
+
6
+ # Athena SQL 가이드
7
+ BDP의 테이블은 Athena 쿼리로만 수행이 가능.
8
+ Athena는 Trino(Presto) 기반 서버리스 SQL 엔진. S3 데이터를 직접 쿼리. 과금: 스캔 데이터 $5/TB.
9
+
10
+ ## 엔진 특성
11
+
12
+ - DML: SELECT, INSERT INTO, UPDATE, DELETE, MERGE INTO (Iceberg 테이블만)
13
+ - DDL: CREATE TABLE, CREATE VIEW, ALTER TABLE, DROP TABLE, CTAS
14
+ - DDL은 HiveQL 기반 (백틱 `\`` 사용), DML은 Trino 기반 (큰따옴표 `"` 사용)
15
+
16
+ ## BDP에서 Athena 쿼리 수행 시 주의할 점
17
+ - 반드시 테이블 명 앞에 DB명을 명시하여 SQL을 생성 (예시: wdp_bdp_dlk_l1_msk_view.card_pchs)
18
+ - 숫자 컬럼 집계 시 NULL 처리 필수: SUM(COALESCE(col, 0)), AVG(COALESCE(col, 0))
19
+ - SELECT에 있는 비집계 컬럼은 GROUP BY에 빠짐없이 포함
20
+ - **Athena는 영문 컬럼 alias만 지원합니다.** `AS 한글명` 문법을 사용하지 마세요. 컬럼명을 한글화하지 마세요.
21
+ - 주요 로직(조인 조건, 필터, 집계 등)에 한글 주석을 간결하게 추가 (-- 형식)
22
+
23
+ ## 데이터 타입
24
+
25
+ | 타입 | 설명 | 예시 |
26
+ |------|------|------|
27
+ | `STRING` / `VARCHAR` | 문자열 | `'hello'` |
28
+ | `INT` / `BIGINT` | 정수 | `42` |
29
+ | `DOUBLE` | 실수 | `3.14` |
30
+ | `DECIMAL(p,s)` | 고정 소수점 | `DECIMAL(18,2)` |
31
+ | `DATE` | 날짜 | `DATE '2024-01-15'` |
32
+ | `TIMESTAMP` | 일시 | `TIMESTAMP '2024-01-15 10:30:00'` (공백 필수, T 사용 불가) |
33
+ | `BOOLEAN` | 불리언 | `TRUE` / `FALSE` |
34
+ | `ARRAY<T>` | 배열 | `ARRAY[1, 2, 3]` |
35
+ | `MAP<K,V>` | 맵 | `MAP(ARRAY['a'], ARRAY[1])` |
36
+ | `STRUCT` | 구조체 | `ROW(name VARCHAR, age INT)` |
37
+
38
+ ### 타입 주의사항
39
+ - TIMESTAMP 형식: `YYYY-MM-DD HH:MM:SS.SSS` (T 구분자 사용 불가)
40
+ - 파티션 키는 `STRING` 권장 (DATE 타입은 성능 저하)
41
+ - Long → Timestamp 암묵적 변환 불가 → `from_unixtime()` 사용
42
+ - `CONCAT()`은 최소 2개 인자 필요
43
+
44
+ ## 날짜/시간 함수
45
+
46
+ | 함수 | 설명 | 예시 |
47
+ |------|------|------|
48
+ | `current_date` | 오늘 날짜 | `WHERE dt = current_date` |
49
+ | `current_timestamp` | 현재 시각 | |
50
+ | `date_add('day', n, date)` | 날짜 더하기 | `date_add('day', -7, current_date)` |
51
+ | `date_diff('day', d1, d2)` | 날짜 차이 | `date_diff('day', start_dt, end_dt)` |
52
+ | `date_trunc('month', dt)` | 절삭 | `date_trunc('month', txn_date)` |
53
+ | `format_datetime(ts, fmt)` | 포맷 | `format_datetime(ts, 'yyyy-MM-dd')` |
54
+ | `date_parse(str, fmt)` | 파싱 | `date_parse('2024-01-15', '%Y-%m-%d')` |
55
+ | `from_unixtime(epoch)` | 에포크→타임스탬프 | `from_unixtime(1705276800)` |
56
+ | `to_unixtime(ts)` | 타임스탬프→에포크 | `to_unixtime(current_timestamp)` |
57
+ | `year(dt)` / `month(dt)` / `day(dt)` | 추출 | `year(txn_date) = 2024` |
58
+
59
+ **주의**: `date_format()` 대신 `format_datetime()` 권장 (동작이 더 직관적).
60
+
61
+ ## 문자열 함수
62
+
63
+ | 함수 | 설명 |
64
+ |------|------|
65
+ | `LOWER(s)` / `UPPER(s)` | 대소문자 |
66
+ | `TRIM(s)` / `LTRIM(s)` / `RTRIM(s)` | 공백 제거 |
67
+ | `LENGTH(s)` | 길이 |
68
+ | `SUBSTR(s, start, len)` | 부분 문자열 |
69
+ | `REPLACE(s, from, to)` | 치환 |
70
+ | `SPLIT(s, delim)` | 분할 → 배열 |
71
+ | `REGEXP_EXTRACT(s, pattern, group)` | 정규식 추출 |
72
+ | `REGEXP_LIKE(s, pattern)` | 정규식 매칭 |
73
+ | `CONCAT(s1, s2, ...)` | 연결 (최소 2개 인자) |
74
+ | `COALESCE(a, b, ...)` | NULL 대체 |
75
+ | `CAST(x AS type)` | 타입 변환 |
76
+
77
+ ## 윈도우 함수
78
+
79
+ ```sql
80
+ -- ROW_NUMBER: 순위
81
+ SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY txn_date DESC) rn
82
+ FROM db.txn;
83
+
84
+ -- LAG / LEAD: 이전/다음 행
85
+ SELECT txn_date, amount,
86
+ LAG(amount) OVER (ORDER BY txn_date) prev_amount,
87
+ LEAD(amount) OVER (ORDER BY txn_date) next_amount
88
+ FROM db.txn;
89
+
90
+ -- 누적합
91
+ SELECT txn_date, amount,
92
+ SUM(amount) OVER (ORDER BY txn_date ROWS UNBOUNDED PRECEDING) running_total
93
+ FROM db.txn;
94
+
95
+ -- FIRST_VALUE / LAST_VALUE
96
+ SELECT *, FIRST_VALUE(amount) OVER (PARTITION BY category ORDER BY txn_date) first_amt
97
+ FROM db.txn;
98
+ ```
99
+
100
+ **주의**: `IGNORE NULLS`는 `first_value`, `last_value`, `nth_value`, `lag`, `lead`에서만 사용 가능.
101
+
102
+ ## JOIN
103
+ **JOIN 최적화**: 작은 테이블을 오른쪽(또는 JOIN의 두 번째)에 배치.
104
+
105
+ ## CTAS (Create Table As Select)
106
+
107
+ ```sql
108
+ -- Parquet + Snappy 압축으로 결과 저장
109
+ CREATE TABLE db.result
110
+ WITH (format = 'PARQUET', write_compression = 'SNAPPY', external_location = 's3://bucket/result/')
111
+ AS SELECT * FROM db.source WHERE condition;
112
+
113
+ -- 파티셔닝 포함
114
+ CREATE TABLE db.result
115
+ WITH (format = 'PARQUET', partitioned_by = ARRAY['dt'])
116
+ AS SELECT col1, col2, dt FROM db.source;
117
+ ```
118
+
119
+ ## 성능 최적화
120
+
121
+ - Optimize for performance:
122
+ - Use appropriate aggregations
123
+
124
+ ### 1. 파티션 프루닝 (가장 중요)
125
+ - 반드시 테이블의 파티션키를 먼저 파악하고 해당 컬럼을 최대한 활용한다.
126
+ ```sql
127
+ -- 좋음: 파티션 키(dt)로 필터
128
+ SELECT * FROM db.txn WHERE dt = DATE '2024-01-15';
129
+
130
+ -- 나쁨: 파티션 키 없이 전체 스캔
131
+ SELECT * FROM db.txn WHERE amount > 1000;
132
+ ```
133
+
134
+ ### 2. 컬럼 프루닝
135
+ ```sql
136
+ -- 좋음: 필요한 컬럼만
137
+ SELECT id, name, amount FROM db.txn;
138
+
139
+ -- 나쁨: 전체 컬럼
140
+ SELECT * FROM db.txn;
141
+ ```
142
+
143
+ ### 4. LIMIT 활용
144
+ ```sql
145
+ -- 탐색적 쿼리에 LIMIT 필수
146
+ SELECT * FROM db.large_table LIMIT 100;
147
+ ```
148
+
149
+ ### 5. APPROX 함수 (대용량)
150
+ ```sql
151
+ -- 정확한 COUNT DISTINCT 대신
152
+ SELECT APPROX_DISTINCT(user_id) FROM db.txn;
153
+
154
+ -- 백분위수
155
+ SELECT APPROX_PERCENTILE(amount, 0.95) FROM db.txn;
156
+ ```
@@ -0,0 +1,33 @@
1
+ ---
2
+ name: bdp_table_search
3
+ description: 쿼리 도우미의 BDP 테이블 메타데이터 검색(rag_search·get_table_columns) 규칙. 두 도구가 있을 때만 쓴다 — SQL을 쓰기 전에 실제 테이블명·컬럼명·DB명을 검색 결과로 확인할 때.
4
+ ---
5
+ # BDP 테이블 검색
6
+
7
+ **[기존 SQL]이 존재할 때 반드시 정확한 테이블 정보 확인을 위해 rag_search를 호출한 후 응답을 생성하세요**
8
+
9
+ ## RAG 검색 도구
10
+
11
+ rag_search로 BDP 테이블 메타데이터를 검색할 수 있습니다.
12
+ [기존 SQL]이 제공된 경우에도, SQL에 사용된 테이블명을 match_text에 지정하여 반드시 rag_search를 호출하세요.
13
+ **절대로 검색 결과 없이 테이블명이나 컬럼명을 만들어내지 마세요.**
14
+
15
+ ### 컬렉션
16
+ - **bdp_tables**: BDP 테이블 메타데이터 (테이블명, 컬럼명, 타입, 스키마)
17
+
18
+ ### 도구 호출 규칙
19
+ 1. **`rag_search`**: queries 배열에 쿼리를 담아 **1회만 호출**. bdp_tables 컬렉션 사용.
20
+ - **query 작성법**: 반드시 `"필드명:값"` 형식을 사용하거나 자연어 키워드로 작성하세요.
21
+ 예: `"테이블명:card_pchs"`, `"테이블한글명:카드매출"`, `"컬럼명:brno"`, `"원천DB명:ADW"`
22
+ 모르는 정보만 있을 땐 자연어 키워드도 가능: `"카드매출 관련 테이블"`
23
+ - **match_text**: 정확한 테이블 영문명을 알 때 추가. 예: `"match_text": ["card_pchs"]`
24
+ 2. **`get_table_columns`**: 테이블의 전체 컬럼 목록을 조회. rag_search 결과에서 컬럼이 축약된 경우 사용.
25
+ - 예: `get_table_columns(table_name="card_pchs", collection="bdp_tables")`
26
+ 3. 결과가 0건일 때만 쿼리를 변경하여 1회 재시도를 허용합니다.
27
+
28
+ ### 검색 결과 사용 시 주의사항
29
+ - 테이블명, 컬럼명 등 고유명사는 **RAG 검색 결과에 있는 값을 그대로** 사용하세요.
30
+ - 검색 결과에 "[토큰 제한으로 컬럼 정보가 축약되었습니다]" 안내가 있으면 get_table_columns로 전체 컬럼을 조회하세요.
31
+
32
+ ### 도구 호출 시 주의사항
33
+ - 도구 호출(tool call)이 필요하면, content에는 "검색 중입니다"처럼 **한 줄 상태 메시지만** 넣으세요.
@@ -0,0 +1,65 @@
1
+ ---
2
+ name: bigquery
3
+ description: BigQuery 요청(쿼리 설명·오류 수정·성능 튜닝·비용 분석·Oracle→BQ 변환)이면 먼저 읽는 라우터. 요청 유형별 응답 구조와 Mermaid 시각화 규칙을 담고, 세부 가이드는 bigquery_tuning, bigquery_oracle 참조.
4
+ ---
5
+
6
+ # BigQuery Expert Guide
7
+
8
+ BQ agent의 3대 역할별 세부 가이드:
9
+
10
+ | 요청 유형 | 로드할 스킬 | 설명 |
11
+ |-----------|------------|------|
12
+ | 성능 튜닝 / 비용 최적화 / anti-pattern | `bigquery_tuning` | 900-slot 최적화, anti-pattern 10종, 비용 공식, INFORMATION_SCHEMA |
13
+ | Oracle → BQ 변환 | `bigquery_oracle` | 구문 매핑, 실행 모델 비교, Hint 대체, anti-pattern 체크 |
14
+ | 쿼리 오류 수정 | `bigquery_tuning` | anti-pattern 탐지 + 비용/성능 분석으로 오류 원인 진단 |
15
+
16
+ ## 사용법
17
+ 1. 요청 유형 판단
18
+ 2. `bigquery_tuning` 또는 `bigquery_oracle` 스킬 본문을 읽는다
19
+ 3. 두 가지 모두 필요하면 둘 다 읽는다
20
+
21
+ ## 응답 구조 가이드
22
+
23
+ ### Oracle→BQ 변환 시:
24
+ - **변환 개요**: 변환 배경, 핵심 변경사항 3~5줄 요약, Oracle 전용 구문 개수
25
+ - **SQL**: ```sql 블록에 변환된 SQL (인라인 주석으로 변경 포인트 설명)
26
+ - **구문 매핑 상세**: 각 Oracle→BQ 구문 변환의 동작 차이, 주의사항, 엣지 케이스를 매핑 테이블 + 서술로 설명
27
+ - **실행 흐름 비교**: Oracle vs BQ 실행 방식을 **Mermaid 다이어그램**으로 시각화 + 비교 테이블 (인덱스 vs 컬럼나, 비용 모델 등)
28
+ - **데이터 타입 주의사항**: 암묵적 형변환, 정밀도 차이, NULL 처리 차이
29
+ - **BQ 최적화 권고**: 파티셔닝, 클러스터링, 매터리얼라이즈드 뷰 등 구체적 제안
30
+
31
+ ### 성능 튜닝 시:
32
+ - **쿼리 분석 요약**: 쿼리 목적, 현재 성능 특성
33
+ - **SQL**: ```sql 블록에 최적화된 SQL
34
+ - **Anti-pattern 탐지**: 발견된 anti-pattern과 개선 방안 (코드 스니펫 포함)
35
+ - **실행 계획 분석**: 예상 슬롯 사용량, 병렬화 전략을 **Mermaid 다이어그램**으로 시각화
36
+ - **비용 분석**: 바이트 처리량, 비용 절감 방안
37
+ - **파티셔닝/클러스터링 권고**: 구체적 DDL 예시 포함
38
+
39
+ ### 오류 수정 시:
40
+ - **에러 원인 분석**: 에러 메시지 해석, 근본 원인
41
+ - **SQL**: ```sql 블록에 수정된 SQL
42
+ - **오류 흐름 시각화**: 에러 발생 흐름 → 수정 후 정상 흐름을 **Mermaid 다이어그램**으로 표현
43
+ - **수정 내용 상세**: 각 수정 포인트의 이유와 영향
44
+ - **유사 오류 방지 가이드**: 같은 실수를 피하는 패턴
45
+
46
+ ### 쿼리 설명 시:
47
+ - **쿼리 목적**: 비즈니스 로직과 데이터 흐름
48
+ - **SQL**: ```sql 블록에 원본 SQL (주석으로 각 절의 역할 설명)
49
+ - **실행 흐름 단계별 분석**: FROM → WHERE → GROUP BY → SELECT 순서로 데이터 흐름을 **Mermaid 다이어그램**으로 시각화 + 설명
50
+ - **성능 특성**: 예상 비용, 병목 구간
51
+ - **개선 포인트**: 성능/가독성 개선 제안
52
+
53
+ ### 복합 요청 시:
54
+ 해당하는 모든 섹션을 논리적 순서로 구성하세요.
55
+
56
+ ## 작성 원칙
57
+ - 각 섹션은 충분히 상세하게 서술하세요 (chat 모드 수준의 풍부함)
58
+ - 코드 스니펫은 마크다운 코드 블록으로 제시
59
+ - 매핑 테이블과 서술을 병행하여 이해도를 높이세요
60
+
61
+ ## ⚠️ 필수: Mermaid 시각화
62
+
63
+ **모든 BQ 응답에는 반드시 ```mermaid 다이어그램을 포함하세요.** 시각화 없는 응답은 불완전합니다.
64
+ - 쿼리 설명: 데이터 흐름을 단계별 Mermaid 다이어그램으로 시각화
65
+ - 튜닝·오류 수정·변환은 세부 스킬의 필수 출력 규칙을 따른다