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,375 @@
1
+ """
2
+ Document Chunking - Text splitters for different document formats.
3
+
4
+ Provides intelligent chunking strategies:
5
+ - Markdown: Split by headers for semantic boundaries
6
+ - Python: Split by class/function definitions
7
+ - Plain text: Character-based with overlap
8
+
9
+ Each strategy preserves context and adds relevant metadata.
10
+ """
11
+
12
+ import re
13
+ import logging
14
+ from typing import List, Dict, Any, Optional, TYPE_CHECKING
15
+ from pathlib import Path
16
+
17
+ if TYPE_CHECKING:
18
+ from codebuff_core.models.rag import ChunkingConfig
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ class DocumentChunker:
24
+ """
25
+ Intelligent document chunking with format-aware splitting.
26
+
27
+ Strategies:
28
+ - Markdown: Split by headers (##) for semantic boundaries
29
+ - Python: Split by class/function definitions
30
+ - Plain text: Character-based with sentence boundary respect
31
+
32
+ Usage:
33
+ chunker = DocumentChunker(config)
34
+ chunks = chunker.chunk_document(content, metadata={"source": "file.md"})
35
+ """
36
+
37
+ def __init__(self, config: Optional["ChunkingConfig"] = None):
38
+ from codebuff_core.models.rag import ChunkingConfig
39
+
40
+ self._config = config or ChunkingConfig()
41
+
42
+ def chunk_document(
43
+ self,
44
+ content: str,
45
+ metadata: Optional[Dict[str, Any]] = None,
46
+ file_type: Optional[str] = None,
47
+ ) -> List[Dict[str, Any]]:
48
+ """
49
+ Chunk document based on content type.
50
+
51
+ Args:
52
+ content: Document content
53
+ metadata: Base metadata for all chunks
54
+ file_type: Override file type detection ("markdown", "python", "text")
55
+
56
+ Returns:
57
+ List of chunks with content and metadata
58
+ """
59
+ metadata = metadata or {}
60
+
61
+ # Infer file type from metadata if not provided
62
+ if not file_type:
63
+ source = metadata.get("source", "")
64
+ file_type = self._infer_file_type(source)
65
+
66
+ # Route to appropriate chunker
67
+ if file_type == "markdown" and self._config.split_by_header:
68
+ chunks = self._chunk_markdown(content)
69
+ elif file_type == "python":
70
+ chunks = self._chunk_python(content)
71
+ else:
72
+ chunks = self._chunk_text(content)
73
+
74
+ # Filter by minimum size and add metadata
75
+ result = []
76
+ for chunk in chunks:
77
+ chunk_content = chunk["content"].strip()
78
+ if len(chunk_content) >= self._config.min_chunk_size:
79
+ result.append(
80
+ {
81
+ "content": chunk_content,
82
+ "metadata": {**metadata, **chunk.get("metadata", {})},
83
+ }
84
+ )
85
+
86
+ logger.debug(f"Chunked document into {len(result)} chunks (type={file_type})")
87
+ return result
88
+
89
+ def _infer_file_type(self, source: str) -> str:
90
+ """Infer file type from source path"""
91
+ source_lower = source.lower()
92
+ if source_lower.endswith(".md"):
93
+ return "markdown"
94
+ elif source_lower.endswith(".py"):
95
+ return "python"
96
+ elif source_lower.endswith((".txt", ".json", ".yaml", ".yml")):
97
+ return "text"
98
+ else:
99
+ return "text"
100
+
101
+ def _chunk_markdown(self, content: str) -> List[Dict[str, Any]]:
102
+ """
103
+ Split markdown by headers while preserving context.
104
+
105
+ Strategy:
106
+ - Split on header boundaries (# ## ### etc.)
107
+ - Track header hierarchy for section context
108
+ - Respect max chunk size with sub-splitting
109
+ """
110
+ # Pattern for markdown headers
111
+ header_pattern = r"^(#{1,6})\s+(.+)$"
112
+
113
+ lines = content.split("\n")
114
+ chunks = []
115
+ current_chunk_lines = []
116
+ current_headers = [] # Stack of (level, text)
117
+
118
+ for line in lines:
119
+ header_match = re.match(header_pattern, line)
120
+
121
+ if header_match:
122
+ # Save current chunk if it has content
123
+ if current_chunk_lines:
124
+ chunk_content = "\n".join(current_chunk_lines).strip()
125
+ if chunk_content:
126
+ section_path = (
127
+ " > ".join(h[1] for h in current_headers)
128
+ if current_headers
129
+ else "Introduction"
130
+ )
131
+ chunks.append(
132
+ {
133
+ "content": chunk_content,
134
+ "metadata": {"section": section_path},
135
+ }
136
+ )
137
+
138
+ # Update header stack
139
+ level = len(header_match.group(1))
140
+ header_text = header_match.group(2).strip()
141
+
142
+ # Pop headers of same or lower level
143
+ while current_headers and current_headers[-1][0] >= level:
144
+ current_headers.pop()
145
+
146
+ current_headers.append((level, header_text))
147
+ current_chunk_lines = [line]
148
+ else:
149
+ current_chunk_lines.append(line)
150
+
151
+ # Check chunk size limit
152
+ chunk_text = "\n".join(current_chunk_lines)
153
+ if len(chunk_text) >= self._config.max_chunk_size:
154
+ section_path = (
155
+ " > ".join(h[1] for h in current_headers)
156
+ if current_headers
157
+ else "Content"
158
+ )
159
+ chunks.append(
160
+ {
161
+ "content": chunk_text.strip(),
162
+ "metadata": {"section": section_path},
163
+ }
164
+ )
165
+ # Keep overlap for context continuity
166
+ overlap_lines = self._get_overlap_lines(current_chunk_lines)
167
+ current_chunk_lines = overlap_lines
168
+
169
+ # Save final chunk
170
+ if current_chunk_lines:
171
+ chunk_content = "\n".join(current_chunk_lines).strip()
172
+ if chunk_content:
173
+ section_path = (
174
+ " > ".join(h[1] for h in current_headers)
175
+ if current_headers
176
+ else "Content"
177
+ )
178
+ chunks.append(
179
+ {"content": chunk_content, "metadata": {"section": section_path}}
180
+ )
181
+
182
+ return chunks
183
+
184
+ def _chunk_python(self, content: str) -> List[Dict[str, Any]]:
185
+ """
186
+ Split Python code by class/function definitions.
187
+
188
+ Strategy:
189
+ - Identify top-level class and function definitions
190
+ - Keep each definition as a separate chunk
191
+ - Preserve import statements and module docstrings
192
+ """
193
+ # Pattern for class and function definitions (top-level only)
194
+ def_pattern = r"^(class|def|async\s+def)\s+(\w+)"
195
+
196
+ lines = content.split("\n")
197
+ chunks = []
198
+ current_chunk_lines = []
199
+ current_def = None
200
+ in_multiline_string = False
201
+
202
+ for i, line in enumerate(lines):
203
+ # Track multiline strings to avoid false positives
204
+ triple_quote_count = line.count('"""') + line.count("'''")
205
+ if triple_quote_count % 2 == 1:
206
+ in_multiline_string = not in_multiline_string
207
+
208
+ def_match = re.match(def_pattern, line)
209
+
210
+ # Check if this is a top-level definition (not indented)
211
+ if (
212
+ def_match
213
+ and not line.startswith((" ", "\t"))
214
+ and not in_multiline_string
215
+ ):
216
+ # Save current chunk
217
+ if current_chunk_lines:
218
+ chunk_content = "\n".join(current_chunk_lines).strip()
219
+ if chunk_content:
220
+ chunks.append(
221
+ {
222
+ "content": chunk_content,
223
+ "metadata": {"definition": current_def or "module"},
224
+ }
225
+ )
226
+
227
+ current_def = f"{def_match.group(1)} {def_match.group(2)}"
228
+ current_chunk_lines = [line]
229
+ else:
230
+ current_chunk_lines.append(line)
231
+
232
+ # Check max chunk size
233
+ if len("\n".join(current_chunk_lines)) >= self._config.max_chunk_size:
234
+ chunks.append(
235
+ {
236
+ "content": "\n".join(current_chunk_lines).strip(),
237
+ "metadata": {"definition": current_def or "module"},
238
+ }
239
+ )
240
+ overlap_lines = self._get_overlap_lines(current_chunk_lines)
241
+ current_chunk_lines = overlap_lines
242
+
243
+ # Save final chunk
244
+ if current_chunk_lines:
245
+ chunk_content = "\n".join(current_chunk_lines).strip()
246
+ if chunk_content:
247
+ chunks.append(
248
+ {
249
+ "content": chunk_content,
250
+ "metadata": {"definition": current_def or "module"},
251
+ }
252
+ )
253
+
254
+ return chunks
255
+
256
+ def _chunk_text(self, content: str) -> List[Dict[str, Any]]:
257
+ """
258
+ Character-based chunking with intelligent boundary detection.
259
+
260
+ Strategy:
261
+ - Target chunk_size characters
262
+ - Prefer breaking at paragraph, sentence, or word boundaries
263
+ - Maintain overlap for context continuity
264
+ """
265
+ chunks = []
266
+ chunk_size = self._config.chunk_size
267
+ overlap = self._config.chunk_overlap
268
+
269
+ start = 0
270
+ chunk_index = 0
271
+
272
+ while start < len(content):
273
+ end = start + chunk_size
274
+
275
+ # Don't exceed content length
276
+ if end >= len(content):
277
+ chunk_content = content[start:].strip()
278
+ if chunk_content:
279
+ chunks.append(
280
+ {
281
+ "content": chunk_content,
282
+ "metadata": {"chunk_index": chunk_index},
283
+ }
284
+ )
285
+ break
286
+
287
+ # Try to find a good break point
288
+ break_point = self._find_break_point(content, start, end)
289
+ end = break_point
290
+
291
+ chunk_content = content[start:end].strip()
292
+ if chunk_content:
293
+ chunks.append(
294
+ {"content": chunk_content, "metadata": {"chunk_index": chunk_index}}
295
+ )
296
+
297
+ # Move start with overlap
298
+ start = max(end - overlap, start + 1)
299
+ chunk_index += 1
300
+
301
+ # Safety check to prevent infinite loop
302
+ if chunk_index > 10000:
303
+ logger.warning("Chunk limit reached, truncating document")
304
+ break
305
+
306
+ return chunks
307
+
308
+ def _find_break_point(self, content: str, start: int, end: int) -> int:
309
+ """
310
+ Find the best break point near the target end position.
311
+
312
+ Priority: paragraph > sentence > word > hard cut
313
+ """
314
+ search_start = start + (end - start) // 2 # Search in latter half
315
+
316
+ # Try paragraph break (double newline)
317
+ para_break = content.rfind("\n\n", search_start, end)
318
+ if para_break > search_start:
319
+ return para_break + 2
320
+
321
+ # Find last sentence break
322
+ for i in range(end - 1, search_start, -1):
323
+ if i + 1 < len(content) and content[i] in ".!?" and content[i + 1] in " \n":
324
+ return i + 1
325
+
326
+ # Try word break (space or newline)
327
+ space_break = content.rfind(" ", search_start, end)
328
+ if space_break > search_start:
329
+ return space_break + 1
330
+
331
+ newline_break = content.rfind("\n", search_start, end)
332
+ if newline_break > search_start:
333
+ return newline_break + 1
334
+
335
+ # Hard cut at end
336
+ return end
337
+
338
+ def _get_overlap_lines(self, lines: List[str]) -> List[str]:
339
+ """Get lines for overlap context."""
340
+ total_chars = 0
341
+ overlap_lines = []
342
+
343
+ for line in reversed(lines):
344
+ total_chars += len(line) + 1 # +1 for newline
345
+ overlap_lines.insert(0, line)
346
+ if total_chars >= self._config.chunk_overlap:
347
+ break
348
+
349
+ return overlap_lines
350
+
351
+
352
+ def chunk_file(
353
+ file_path: Path,
354
+ config: Optional["ChunkingConfig"] = None,
355
+ base_metadata: Optional[Dict[str, Any]] = None,
356
+ ) -> List[Dict[str, Any]]:
357
+ """
358
+ Convenience function to chunk a file directly.
359
+
360
+ Args:
361
+ file_path: Path to the file to chunk
362
+ config: Optional ChunkingConfig
363
+ base_metadata: Base metadata to include in all chunks
364
+
365
+ Returns:
366
+ List of chunks with content and metadata
367
+ """
368
+ content = file_path.read_text(encoding="utf-8")
369
+
370
+ metadata = base_metadata or {}
371
+ metadata["source"] = file_path.name
372
+ metadata["file_path"] = str(file_path)
373
+
374
+ chunker = DocumentChunker(config)
375
+ return chunker.chunk_document(content, metadata=metadata)