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,451 @@
1
+ """
2
+ AST 기반 코드 구조 분석 도구.
3
+
4
+ Python ast 모듈을 사용하여 코드 구조를 정확하게 파싱합니다.
5
+ AutoCodeRover의 계층적 검색 API 패턴을 참조하여 설계:
6
+ - 시그니처만 반환 (본문 제외) → 토큰 절약
7
+ - 결과 수 제한 (50개 cap) → 에이전트 혼란 방지
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import ast
13
+ import json
14
+ from pathlib import Path
15
+ from typing import Optional
16
+
17
+ from codebuff_core.directory_policy import AST_EXCLUDED_DIRS as _EXCLUDE_DIRS
18
+ from langchain_core.tools import BaseTool, tool
19
+
20
+ _MAX_FILE_SIZE = 10 * 1024 * 1024 # 10MB
21
+ _MAX_RESULTS = 50
22
+
23
+
24
+ def _format_annotation(node: Optional[ast.expr]) -> Optional[str]:
25
+ """AST annotation 노드를 문자열로 변환."""
26
+ if node is None:
27
+ return None
28
+ return ast.unparse(node)
29
+
30
+
31
+ def _extract_args(args: ast.arguments) -> list[str]:
32
+ """함수 인자 리스트를 문자열로 추출."""
33
+ result = []
34
+ # positional args (defaults는 뒤에서부터 매칭)
35
+ n_defaults = len(args.defaults)
36
+ n_args = len(args.args)
37
+ for i, arg in enumerate(args.args):
38
+ s = arg.arg
39
+ if arg.annotation:
40
+ s += f": {ast.unparse(arg.annotation)}"
41
+ default_idx = i - (n_args - n_defaults)
42
+ if default_idx >= 0:
43
+ s += f" = {ast.unparse(args.defaults[default_idx])}"
44
+ result.append(s)
45
+ # *args
46
+ if args.vararg:
47
+ s = f"*{args.vararg.arg}"
48
+ if args.vararg.annotation:
49
+ s += f": {ast.unparse(args.vararg.annotation)}"
50
+ result.append(s)
51
+ # keyword-only args
52
+ for i, arg in enumerate(args.kwonlyargs):
53
+ s = arg.arg
54
+ if arg.annotation:
55
+ s += f": {ast.unparse(arg.annotation)}"
56
+ if i < len(args.kw_defaults) and args.kw_defaults[i] is not None:
57
+ s += f" = {ast.unparse(args.kw_defaults[i])}"
58
+ result.append(s)
59
+ # **kwargs
60
+ if args.kwarg:
61
+ s = f"**{args.kwarg.arg}"
62
+ if args.kwarg.annotation:
63
+ s += f": {ast.unparse(args.kwarg.annotation)}"
64
+ result.append(s)
65
+ return result
66
+
67
+
68
+ def _parse_file_safe(file_path: Path) -> Optional[ast.Module]:
69
+ """파일을 안전하게 AST 파싱. 실패 시 None 반환."""
70
+ try:
71
+ if file_path.stat().st_size > _MAX_FILE_SIZE:
72
+ return None
73
+ source = file_path.read_text(encoding="utf-8", errors="ignore")
74
+ return ast.parse(source, filename=str(file_path))
75
+ except (SyntaxError, UnicodeDecodeError, OSError):
76
+ return None
77
+
78
+
79
+ def _should_skip_dir(name: str) -> bool:
80
+ """디렉토리를 건너뛸지 판단."""
81
+ return name.startswith(".") or name in _EXCLUDE_DIRS
82
+
83
+
84
+ def _collect_py_files(root: Path, scope: Optional[Path] = None) -> list[Path]:
85
+ """워크스페이스에서 Python 파일 수집."""
86
+ target = scope or root
87
+ if target.is_file():
88
+ return [target] if target.suffix == ".py" else []
89
+
90
+ files = []
91
+ for p in target.rglob("*.py"):
92
+ if any(_should_skip_dir(part) for part in p.relative_to(target).parts):
93
+ continue
94
+ if p.is_file():
95
+ files.append(p)
96
+ return files
97
+
98
+
99
+ def _build_signature(
100
+ name: str,
101
+ args: ast.arguments,
102
+ returns: Optional[ast.expr],
103
+ is_async: bool = False,
104
+ ) -> str:
105
+ """함수/메서드 시그니처 문자열 생성."""
106
+ prefix = "async def" if is_async else "def"
107
+ arg_strs = _extract_args(args)
108
+ ret = f" -> {ast.unparse(returns)}" if returns else ""
109
+ return f"{prefix} {name}({', '.join(arg_strs)}){ret}"
110
+
111
+
112
+ # =============================================================================
113
+ # Tool 1: analyze_file_structure
114
+ # =============================================================================
115
+
116
+
117
+ def create_analyze_file_structure_tool(workspace_root: str) -> BaseTool:
118
+ """파일 구조 분석용 LangChain 도구를 생성합니다."""
119
+ base_path = Path(workspace_root)
120
+
121
+ @tool
122
+ def analyze_file_structure(path: str) -> str:
123
+ """파일의 코드 구조를 AST로 분석합니다. 클래스, 함수, import 목록을 시그니처와 함께 반환합니다.
124
+ 본문은 포함하지 않아 토큰을 절약합니다.
125
+
126
+ 이 도구의 용도:
127
+ - 파일의 전체 구조 빠르게 파악 (클래스, 함수, import)
128
+ - 클래스의 메서드 목록과 시그니처 확인
129
+ - grep보다 정확한 구조적 분석 (주석/문자열 내 매칭 제외)
130
+
131
+ Args:
132
+ path: 분석할 Python 파일 경로 (workspace 상대 경로)
133
+
134
+ Returns:
135
+ JSON: classes, functions, imports, top_level_variables, total_lines
136
+ """
137
+ target = base_path / path
138
+ if not target.is_file():
139
+ return json.dumps(
140
+ {"success": False, "error": f"파일을 찾을 수 없음: {path}"}
141
+ )
142
+ if target.suffix != ".py":
143
+ return json.dumps(
144
+ {"success": False, "error": "Python 파일(.py)만 지원합니다"}
145
+ )
146
+
147
+ tree = _parse_file_safe(target)
148
+ if tree is None:
149
+ return json.dumps(
150
+ {
151
+ "success": True,
152
+ "data": {
153
+ "path": path,
154
+ "classes": [],
155
+ "functions": [],
156
+ "imports": [],
157
+ "top_level_variables": [],
158
+ "total_lines": 0,
159
+ "parse_error": True,
160
+ },
161
+ }
162
+ )
163
+
164
+ classes = []
165
+ functions = []
166
+ imports = []
167
+ variables = []
168
+
169
+ for node in ast.iter_child_nodes(tree):
170
+ if isinstance(node, ast.ClassDef):
171
+ bases = [ast.unparse(b) for b in node.bases]
172
+ methods = []
173
+ for item in node.body:
174
+ if isinstance(item, (ast.FunctionDef, ast.AsyncFunctionDef)):
175
+ methods.append(
176
+ {
177
+ "name": item.name,
178
+ "line": item.lineno,
179
+ "args": _extract_args(item.args),
180
+ "returns": _format_annotation(item.returns),
181
+ "is_async": isinstance(item, ast.AsyncFunctionDef),
182
+ }
183
+ )
184
+ classes.append(
185
+ {
186
+ "name": node.name,
187
+ "line": node.lineno,
188
+ "bases": bases,
189
+ "methods": methods,
190
+ }
191
+ )
192
+
193
+ elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
194
+ functions.append(
195
+ {
196
+ "name": node.name,
197
+ "line": node.lineno,
198
+ "args": _extract_args(node.args),
199
+ "returns": _format_annotation(node.returns),
200
+ "is_async": isinstance(node, ast.AsyncFunctionDef),
201
+ }
202
+ )
203
+
204
+ elif isinstance(node, ast.Import):
205
+ for alias in node.names:
206
+ if alias.asname:
207
+ imports.append(f"import {alias.name} as {alias.asname}")
208
+ else:
209
+ imports.append(f"import {alias.name}")
210
+
211
+ elif isinstance(node, ast.ImportFrom):
212
+ module = node.module or ""
213
+ names = ", ".join(
214
+ f"{a.name} as {a.asname}" if a.asname else a.name
215
+ for a in node.names
216
+ )
217
+ imports.append(f"from {module} import {names}")
218
+
219
+ elif isinstance(node, ast.Assign):
220
+ for target_node in node.targets:
221
+ if isinstance(target_node, ast.Name):
222
+ variables.append(target_node.id)
223
+
224
+ elif isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name):
225
+ variables.append(node.target.id)
226
+
227
+ # total_lines
228
+ try:
229
+ source = target.read_text(encoding="utf-8", errors="ignore")
230
+ total_lines = len(source.splitlines())
231
+ except OSError:
232
+ total_lines = 0
233
+
234
+ return json.dumps(
235
+ {
236
+ "success": True,
237
+ "data": {
238
+ "path": path,
239
+ "classes": classes,
240
+ "functions": functions,
241
+ "imports": imports,
242
+ "top_level_variables": variables,
243
+ "total_lines": total_lines,
244
+ },
245
+ }
246
+ )
247
+
248
+ return analyze_file_structure
249
+
250
+
251
+ # =============================================================================
252
+ # Tool 2: search_symbol_definition
253
+ # =============================================================================
254
+
255
+
256
+ def create_search_symbol_definition_tool(workspace_root: str) -> BaseTool:
257
+ """심볼 정의 검색용 LangChain 도구를 생성합니다."""
258
+ base_path = Path(workspace_root)
259
+
260
+ @tool
261
+ def search_symbol_definition(
262
+ symbol_name: str,
263
+ symbol_type: Optional[str] = None,
264
+ path: Optional[str] = None,
265
+ ) -> str:
266
+ """워크스페이스에서 클래스, 함수, 메서드의 정의 위치를 AST로 검색합니다.
267
+ grep과 달리 주석이나 문자열 안의 매칭을 제외하고 실제 정의만 찾습니다.
268
+
269
+ 이 도구의 용도:
270
+ - 클래스/함수가 어디에 정의되어 있는지 정확히 찾기
271
+ - 동일 이름의 함수가 여러 파일에 있을 때 모두 찾기
272
+ - 메서드가 어떤 클래스에 속하는지 확인
273
+
274
+ Args:
275
+ symbol_name: 검색할 심볼 이름 (예: "DataProcessor", "process")
276
+ symbol_type: 선택적 필터 ("class", "function", "method"). 미지정시 모두 검색.
277
+ path: 선택적 검색 범위 제한 (디렉토리 또는 파일의 workspace 상대 경로)
278
+
279
+ Returns:
280
+ JSON: 매칭되는 심볼 정의 목록 (파일, 라인, 시그니처, 소속 클래스)
281
+ """
282
+ if not symbol_name:
283
+ return json.dumps({"success": False, "error": "심볼 이름이 필요합니다"})
284
+
285
+ scope = base_path / path if path else None
286
+ if scope and not scope.exists():
287
+ return json.dumps(
288
+ {"success": False, "error": f"경로를 찾을 수 없음: {path}"}
289
+ )
290
+
291
+ py_files = _collect_py_files(base_path, scope)
292
+ definitions: list[dict] = []
293
+
294
+ for file_path in py_files:
295
+ tree = _parse_file_safe(file_path)
296
+ if tree is None:
297
+ continue
298
+
299
+ rel_path = str(file_path.relative_to(base_path))
300
+
301
+ # 탑레벨 함수/클래스 탐색
302
+ for node in ast.iter_child_nodes(tree):
303
+ if isinstance(node, ast.ClassDef):
304
+ if node.name == symbol_name and symbol_type in (None, "class"):
305
+ bases = [ast.unparse(b) for b in node.bases]
306
+ base_str = f"({', '.join(bases)})" if bases else ""
307
+ definitions.append(
308
+ {
309
+ "file": rel_path,
310
+ "line": node.lineno,
311
+ "type": "class",
312
+ "class": None,
313
+ "signature": f"class {node.name}{base_str}",
314
+ }
315
+ )
316
+ # 클래스 내 메서드 탐색
317
+ if symbol_type in (None, "method", "function"):
318
+ for item in node.body:
319
+ if (
320
+ isinstance(
321
+ item, (ast.FunctionDef, ast.AsyncFunctionDef)
322
+ )
323
+ and item.name == symbol_name
324
+ ):
325
+ sig = _build_signature(
326
+ item.name,
327
+ item.args,
328
+ item.returns,
329
+ isinstance(item, ast.AsyncFunctionDef),
330
+ )
331
+ definitions.append(
332
+ {
333
+ "file": rel_path,
334
+ "line": item.lineno,
335
+ "type": "method",
336
+ "class": node.name,
337
+ "signature": sig,
338
+ }
339
+ )
340
+
341
+ elif (
342
+ isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
343
+ and node.name == symbol_name
344
+ ):
345
+ if symbol_type in (None, "function"):
346
+ sig = _build_signature(
347
+ node.name,
348
+ node.args,
349
+ node.returns,
350
+ isinstance(node, ast.AsyncFunctionDef),
351
+ )
352
+ definitions.append(
353
+ {
354
+ "file": rel_path,
355
+ "line": node.lineno,
356
+ "type": "function",
357
+ "class": None,
358
+ "signature": sig,
359
+ }
360
+ )
361
+
362
+ if len(definitions) >= _MAX_RESULTS:
363
+ break
364
+
365
+ truncated = len(definitions) > _MAX_RESULTS
366
+ definitions = definitions[:_MAX_RESULTS]
367
+
368
+ return json.dumps(
369
+ {
370
+ "success": True,
371
+ "data": {
372
+ "symbol": symbol_name,
373
+ "definitions": definitions,
374
+ "total_found": len(definitions),
375
+ "truncated": truncated,
376
+ },
377
+ }
378
+ )
379
+
380
+ return search_symbol_definition
381
+
382
+
383
+ # =============================================================================
384
+ # Tool 3: analyze_dependencies
385
+ # =============================================================================
386
+
387
+
388
+ def create_analyze_dependencies_tool(workspace_root: str) -> BaseTool:
389
+ """파일 의존성 분석용 LangChain 도구를 생성합니다."""
390
+ base_path = Path(workspace_root)
391
+
392
+ @tool
393
+ def analyze_dependencies(path: str) -> str:
394
+ """파일의 import 의존성과 변수 사용을 AST로 분석합니다.
395
+
396
+ 이 도구의 용도:
397
+ - 파일이 어떤 모듈을 import하는지 정확히 파악
398
+ - 정의된 이름과 사용된 이름 비교로 미정의 변수 탐지
399
+ - 코드 리팩토링 전 의존성 관계 파악
400
+
401
+ Args:
402
+ path: 분석할 Python 파일 경로 (workspace 상대 경로)
403
+
404
+ Returns:
405
+ JSON: imports, from_imports, defined_names, used_names, undefined_names
406
+ """
407
+ target = base_path / path
408
+ if not target.is_file():
409
+ return json.dumps(
410
+ {"success": False, "error": f"파일을 찾을 수 없음: {path}"}
411
+ )
412
+ if target.suffix != ".py":
413
+ return json.dumps(
414
+ {"success": False, "error": "Python 파일(.py)만 지원합니다"}
415
+ )
416
+
417
+ try:
418
+ source = target.read_text(encoding="utf-8", errors="ignore")
419
+ except OSError as e:
420
+ return json.dumps({"success": False, "error": f"파일 읽기 오류: {e}"})
421
+
422
+ from codebuff_core.core.code_validator import CodeValidator
423
+
424
+ validator = CodeValidator()
425
+ deps = validator.analyze_dependencies(source)
426
+ undefined_issues = validator.check_undefined_names(source)
427
+
428
+ undefined = [
429
+ {
430
+ "name": issue.message.split("'")[1],
431
+ "line": issue.line,
432
+ "severity": issue.severity.value,
433
+ }
434
+ for issue in undefined_issues
435
+ ]
436
+
437
+ return json.dumps(
438
+ {
439
+ "success": True,
440
+ "data": {
441
+ "path": path,
442
+ "imports": deps.imports,
443
+ "from_imports": deps.from_imports,
444
+ "defined_names": sorted(deps.defined_names),
445
+ "used_names": sorted(deps.used_names),
446
+ "undefined_names": undefined,
447
+ },
448
+ }
449
+ )
450
+
451
+ return analyze_dependencies