PraisonAI 3.0.0__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 (393) hide show
  1. praisonai/__init__.py +54 -0
  2. praisonai/__main__.py +15 -0
  3. praisonai/acp/__init__.py +54 -0
  4. praisonai/acp/config.py +159 -0
  5. praisonai/acp/server.py +587 -0
  6. praisonai/acp/session.py +219 -0
  7. praisonai/adapters/__init__.py +50 -0
  8. praisonai/adapters/readers.py +395 -0
  9. praisonai/adapters/rerankers.py +315 -0
  10. praisonai/adapters/retrievers.py +394 -0
  11. praisonai/adapters/vector_stores.py +409 -0
  12. praisonai/agent_scheduler.py +337 -0
  13. praisonai/agents_generator.py +903 -0
  14. praisonai/api/call.py +292 -0
  15. praisonai/auto.py +1197 -0
  16. praisonai/capabilities/__init__.py +275 -0
  17. praisonai/capabilities/a2a.py +140 -0
  18. praisonai/capabilities/assistants.py +283 -0
  19. praisonai/capabilities/audio.py +320 -0
  20. praisonai/capabilities/batches.py +469 -0
  21. praisonai/capabilities/completions.py +336 -0
  22. praisonai/capabilities/container_files.py +155 -0
  23. praisonai/capabilities/containers.py +93 -0
  24. praisonai/capabilities/embeddings.py +158 -0
  25. praisonai/capabilities/files.py +467 -0
  26. praisonai/capabilities/fine_tuning.py +293 -0
  27. praisonai/capabilities/guardrails.py +182 -0
  28. praisonai/capabilities/images.py +330 -0
  29. praisonai/capabilities/mcp.py +190 -0
  30. praisonai/capabilities/messages.py +270 -0
  31. praisonai/capabilities/moderations.py +154 -0
  32. praisonai/capabilities/ocr.py +217 -0
  33. praisonai/capabilities/passthrough.py +204 -0
  34. praisonai/capabilities/rag.py +207 -0
  35. praisonai/capabilities/realtime.py +160 -0
  36. praisonai/capabilities/rerank.py +165 -0
  37. praisonai/capabilities/responses.py +266 -0
  38. praisonai/capabilities/search.py +109 -0
  39. praisonai/capabilities/skills.py +133 -0
  40. praisonai/capabilities/vector_store_files.py +334 -0
  41. praisonai/capabilities/vector_stores.py +304 -0
  42. praisonai/capabilities/videos.py +141 -0
  43. praisonai/chainlit_ui.py +304 -0
  44. praisonai/chat/__init__.py +106 -0
  45. praisonai/chat/app.py +125 -0
  46. praisonai/cli/__init__.py +26 -0
  47. praisonai/cli/app.py +213 -0
  48. praisonai/cli/commands/__init__.py +75 -0
  49. praisonai/cli/commands/acp.py +70 -0
  50. praisonai/cli/commands/completion.py +333 -0
  51. praisonai/cli/commands/config.py +166 -0
  52. praisonai/cli/commands/debug.py +142 -0
  53. praisonai/cli/commands/diag.py +55 -0
  54. praisonai/cli/commands/doctor.py +166 -0
  55. praisonai/cli/commands/environment.py +179 -0
  56. praisonai/cli/commands/lsp.py +112 -0
  57. praisonai/cli/commands/mcp.py +210 -0
  58. praisonai/cli/commands/profile.py +457 -0
  59. praisonai/cli/commands/run.py +228 -0
  60. praisonai/cli/commands/schedule.py +150 -0
  61. praisonai/cli/commands/serve.py +97 -0
  62. praisonai/cli/commands/session.py +212 -0
  63. praisonai/cli/commands/traces.py +145 -0
  64. praisonai/cli/commands/version.py +101 -0
  65. praisonai/cli/configuration/__init__.py +18 -0
  66. praisonai/cli/configuration/loader.py +353 -0
  67. praisonai/cli/configuration/paths.py +114 -0
  68. praisonai/cli/configuration/schema.py +164 -0
  69. praisonai/cli/features/__init__.py +268 -0
  70. praisonai/cli/features/acp.py +236 -0
  71. praisonai/cli/features/action_orchestrator.py +546 -0
  72. praisonai/cli/features/agent_scheduler.py +773 -0
  73. praisonai/cli/features/agent_tools.py +474 -0
  74. praisonai/cli/features/agents.py +375 -0
  75. praisonai/cli/features/at_mentions.py +471 -0
  76. praisonai/cli/features/auto_memory.py +182 -0
  77. praisonai/cli/features/autonomy_mode.py +490 -0
  78. praisonai/cli/features/background.py +356 -0
  79. praisonai/cli/features/base.py +168 -0
  80. praisonai/cli/features/capabilities.py +1326 -0
  81. praisonai/cli/features/checkpoints.py +338 -0
  82. praisonai/cli/features/code_intelligence.py +652 -0
  83. praisonai/cli/features/compaction.py +294 -0
  84. praisonai/cli/features/compare.py +534 -0
  85. praisonai/cli/features/cost_tracker.py +514 -0
  86. praisonai/cli/features/debug.py +810 -0
  87. praisonai/cli/features/deploy.py +517 -0
  88. praisonai/cli/features/diag.py +289 -0
  89. praisonai/cli/features/doctor/__init__.py +63 -0
  90. praisonai/cli/features/doctor/checks/__init__.py +24 -0
  91. praisonai/cli/features/doctor/checks/acp_checks.py +240 -0
  92. praisonai/cli/features/doctor/checks/config_checks.py +366 -0
  93. praisonai/cli/features/doctor/checks/db_checks.py +366 -0
  94. praisonai/cli/features/doctor/checks/env_checks.py +543 -0
  95. praisonai/cli/features/doctor/checks/lsp_checks.py +199 -0
  96. praisonai/cli/features/doctor/checks/mcp_checks.py +349 -0
  97. praisonai/cli/features/doctor/checks/memory_checks.py +268 -0
  98. praisonai/cli/features/doctor/checks/network_checks.py +251 -0
  99. praisonai/cli/features/doctor/checks/obs_checks.py +328 -0
  100. praisonai/cli/features/doctor/checks/performance_checks.py +235 -0
  101. praisonai/cli/features/doctor/checks/permissions_checks.py +259 -0
  102. praisonai/cli/features/doctor/checks/selftest_checks.py +322 -0
  103. praisonai/cli/features/doctor/checks/serve_checks.py +426 -0
  104. praisonai/cli/features/doctor/checks/skills_checks.py +231 -0
  105. praisonai/cli/features/doctor/checks/tools_checks.py +371 -0
  106. praisonai/cli/features/doctor/engine.py +266 -0
  107. praisonai/cli/features/doctor/formatters.py +310 -0
  108. praisonai/cli/features/doctor/handler.py +397 -0
  109. praisonai/cli/features/doctor/models.py +264 -0
  110. praisonai/cli/features/doctor/registry.py +239 -0
  111. praisonai/cli/features/endpoints.py +1019 -0
  112. praisonai/cli/features/eval.py +560 -0
  113. praisonai/cli/features/external_agents.py +231 -0
  114. praisonai/cli/features/fast_context.py +410 -0
  115. praisonai/cli/features/flow_display.py +566 -0
  116. praisonai/cli/features/git_integration.py +651 -0
  117. praisonai/cli/features/guardrail.py +171 -0
  118. praisonai/cli/features/handoff.py +185 -0
  119. praisonai/cli/features/hooks.py +583 -0
  120. praisonai/cli/features/image.py +384 -0
  121. praisonai/cli/features/interactive_runtime.py +585 -0
  122. praisonai/cli/features/interactive_tools.py +380 -0
  123. praisonai/cli/features/interactive_tui.py +603 -0
  124. praisonai/cli/features/jobs.py +632 -0
  125. praisonai/cli/features/knowledge.py +531 -0
  126. praisonai/cli/features/lite.py +244 -0
  127. praisonai/cli/features/lsp_cli.py +225 -0
  128. praisonai/cli/features/mcp.py +169 -0
  129. praisonai/cli/features/message_queue.py +587 -0
  130. praisonai/cli/features/metrics.py +211 -0
  131. praisonai/cli/features/n8n.py +673 -0
  132. praisonai/cli/features/observability.py +293 -0
  133. praisonai/cli/features/ollama.py +361 -0
  134. praisonai/cli/features/output_style.py +273 -0
  135. praisonai/cli/features/package.py +631 -0
  136. praisonai/cli/features/performance.py +308 -0
  137. praisonai/cli/features/persistence.py +636 -0
  138. praisonai/cli/features/profile.py +226 -0
  139. praisonai/cli/features/profiler/__init__.py +81 -0
  140. praisonai/cli/features/profiler/core.py +558 -0
  141. praisonai/cli/features/profiler/optimizations.py +652 -0
  142. praisonai/cli/features/profiler/suite.py +386 -0
  143. praisonai/cli/features/profiling.py +350 -0
  144. praisonai/cli/features/queue/__init__.py +73 -0
  145. praisonai/cli/features/queue/manager.py +395 -0
  146. praisonai/cli/features/queue/models.py +286 -0
  147. praisonai/cli/features/queue/persistence.py +564 -0
  148. praisonai/cli/features/queue/scheduler.py +484 -0
  149. praisonai/cli/features/queue/worker.py +372 -0
  150. praisonai/cli/features/recipe.py +1723 -0
  151. praisonai/cli/features/recipes.py +449 -0
  152. praisonai/cli/features/registry.py +229 -0
  153. praisonai/cli/features/repo_map.py +860 -0
  154. praisonai/cli/features/router.py +466 -0
  155. praisonai/cli/features/sandbox_executor.py +515 -0
  156. praisonai/cli/features/serve.py +829 -0
  157. praisonai/cli/features/session.py +222 -0
  158. praisonai/cli/features/skills.py +856 -0
  159. praisonai/cli/features/slash_commands.py +650 -0
  160. praisonai/cli/features/telemetry.py +179 -0
  161. praisonai/cli/features/templates.py +1384 -0
  162. praisonai/cli/features/thinking.py +305 -0
  163. praisonai/cli/features/todo.py +334 -0
  164. praisonai/cli/features/tools.py +680 -0
  165. praisonai/cli/features/tui/__init__.py +83 -0
  166. praisonai/cli/features/tui/app.py +580 -0
  167. praisonai/cli/features/tui/cli.py +566 -0
  168. praisonai/cli/features/tui/debug.py +511 -0
  169. praisonai/cli/features/tui/events.py +99 -0
  170. praisonai/cli/features/tui/mock_provider.py +328 -0
  171. praisonai/cli/features/tui/orchestrator.py +652 -0
  172. praisonai/cli/features/tui/screens/__init__.py +50 -0
  173. praisonai/cli/features/tui/screens/main.py +245 -0
  174. praisonai/cli/features/tui/screens/queue.py +174 -0
  175. praisonai/cli/features/tui/screens/session.py +124 -0
  176. praisonai/cli/features/tui/screens/settings.py +148 -0
  177. praisonai/cli/features/tui/widgets/__init__.py +56 -0
  178. praisonai/cli/features/tui/widgets/chat.py +261 -0
  179. praisonai/cli/features/tui/widgets/composer.py +224 -0
  180. praisonai/cli/features/tui/widgets/queue_panel.py +200 -0
  181. praisonai/cli/features/tui/widgets/status.py +167 -0
  182. praisonai/cli/features/tui/widgets/tool_panel.py +248 -0
  183. praisonai/cli/features/workflow.py +720 -0
  184. praisonai/cli/legacy.py +236 -0
  185. praisonai/cli/main.py +5559 -0
  186. praisonai/cli/schedule_cli.py +54 -0
  187. praisonai/cli/state/__init__.py +31 -0
  188. praisonai/cli/state/identifiers.py +161 -0
  189. praisonai/cli/state/sessions.py +313 -0
  190. praisonai/code/__init__.py +93 -0
  191. praisonai/code/agent_tools.py +344 -0
  192. praisonai/code/diff/__init__.py +21 -0
  193. praisonai/code/diff/diff_strategy.py +432 -0
  194. praisonai/code/tools/__init__.py +27 -0
  195. praisonai/code/tools/apply_diff.py +221 -0
  196. praisonai/code/tools/execute_command.py +275 -0
  197. praisonai/code/tools/list_files.py +274 -0
  198. praisonai/code/tools/read_file.py +206 -0
  199. praisonai/code/tools/search_replace.py +248 -0
  200. praisonai/code/tools/write_file.py +217 -0
  201. praisonai/code/utils/__init__.py +46 -0
  202. praisonai/code/utils/file_utils.py +307 -0
  203. praisonai/code/utils/ignore_utils.py +308 -0
  204. praisonai/code/utils/text_utils.py +276 -0
  205. praisonai/db/__init__.py +64 -0
  206. praisonai/db/adapter.py +531 -0
  207. praisonai/deploy/__init__.py +62 -0
  208. praisonai/deploy/api.py +231 -0
  209. praisonai/deploy/docker.py +454 -0
  210. praisonai/deploy/doctor.py +367 -0
  211. praisonai/deploy/main.py +327 -0
  212. praisonai/deploy/models.py +179 -0
  213. praisonai/deploy/providers/__init__.py +33 -0
  214. praisonai/deploy/providers/aws.py +331 -0
  215. praisonai/deploy/providers/azure.py +358 -0
  216. praisonai/deploy/providers/base.py +101 -0
  217. praisonai/deploy/providers/gcp.py +314 -0
  218. praisonai/deploy/schema.py +208 -0
  219. praisonai/deploy.py +185 -0
  220. praisonai/endpoints/__init__.py +53 -0
  221. praisonai/endpoints/a2u_server.py +410 -0
  222. praisonai/endpoints/discovery.py +165 -0
  223. praisonai/endpoints/providers/__init__.py +28 -0
  224. praisonai/endpoints/providers/a2a.py +253 -0
  225. praisonai/endpoints/providers/a2u.py +208 -0
  226. praisonai/endpoints/providers/agents_api.py +171 -0
  227. praisonai/endpoints/providers/base.py +231 -0
  228. praisonai/endpoints/providers/mcp.py +263 -0
  229. praisonai/endpoints/providers/recipe.py +206 -0
  230. praisonai/endpoints/providers/tools_mcp.py +150 -0
  231. praisonai/endpoints/registry.py +131 -0
  232. praisonai/endpoints/server.py +161 -0
  233. praisonai/inbuilt_tools/__init__.py +24 -0
  234. praisonai/inbuilt_tools/autogen_tools.py +117 -0
  235. praisonai/inc/__init__.py +2 -0
  236. praisonai/inc/config.py +96 -0
  237. praisonai/inc/models.py +155 -0
  238. praisonai/integrations/__init__.py +56 -0
  239. praisonai/integrations/base.py +303 -0
  240. praisonai/integrations/claude_code.py +270 -0
  241. praisonai/integrations/codex_cli.py +255 -0
  242. praisonai/integrations/cursor_cli.py +195 -0
  243. praisonai/integrations/gemini_cli.py +222 -0
  244. praisonai/jobs/__init__.py +67 -0
  245. praisonai/jobs/executor.py +425 -0
  246. praisonai/jobs/models.py +230 -0
  247. praisonai/jobs/router.py +314 -0
  248. praisonai/jobs/server.py +186 -0
  249. praisonai/jobs/store.py +203 -0
  250. praisonai/llm/__init__.py +66 -0
  251. praisonai/llm/registry.py +382 -0
  252. praisonai/mcp_server/__init__.py +152 -0
  253. praisonai/mcp_server/adapters/__init__.py +74 -0
  254. praisonai/mcp_server/adapters/agents.py +128 -0
  255. praisonai/mcp_server/adapters/capabilities.py +168 -0
  256. praisonai/mcp_server/adapters/cli_tools.py +568 -0
  257. praisonai/mcp_server/adapters/extended_capabilities.py +462 -0
  258. praisonai/mcp_server/adapters/knowledge.py +93 -0
  259. praisonai/mcp_server/adapters/memory.py +104 -0
  260. praisonai/mcp_server/adapters/prompts.py +306 -0
  261. praisonai/mcp_server/adapters/resources.py +124 -0
  262. praisonai/mcp_server/adapters/tools_bridge.py +280 -0
  263. praisonai/mcp_server/auth/__init__.py +48 -0
  264. praisonai/mcp_server/auth/api_key.py +291 -0
  265. praisonai/mcp_server/auth/oauth.py +460 -0
  266. praisonai/mcp_server/auth/oidc.py +289 -0
  267. praisonai/mcp_server/auth/scopes.py +260 -0
  268. praisonai/mcp_server/cli.py +852 -0
  269. praisonai/mcp_server/elicitation.py +445 -0
  270. praisonai/mcp_server/icons.py +302 -0
  271. praisonai/mcp_server/recipe_adapter.py +573 -0
  272. praisonai/mcp_server/recipe_cli.py +824 -0
  273. praisonai/mcp_server/registry.py +703 -0
  274. praisonai/mcp_server/sampling.py +422 -0
  275. praisonai/mcp_server/server.py +490 -0
  276. praisonai/mcp_server/tasks.py +443 -0
  277. praisonai/mcp_server/transports/__init__.py +18 -0
  278. praisonai/mcp_server/transports/http_stream.py +376 -0
  279. praisonai/mcp_server/transports/stdio.py +132 -0
  280. praisonai/persistence/__init__.py +84 -0
  281. praisonai/persistence/config.py +238 -0
  282. praisonai/persistence/conversation/__init__.py +25 -0
  283. praisonai/persistence/conversation/async_mysql.py +427 -0
  284. praisonai/persistence/conversation/async_postgres.py +410 -0
  285. praisonai/persistence/conversation/async_sqlite.py +371 -0
  286. praisonai/persistence/conversation/base.py +151 -0
  287. praisonai/persistence/conversation/json_store.py +250 -0
  288. praisonai/persistence/conversation/mysql.py +387 -0
  289. praisonai/persistence/conversation/postgres.py +401 -0
  290. praisonai/persistence/conversation/singlestore.py +240 -0
  291. praisonai/persistence/conversation/sqlite.py +341 -0
  292. praisonai/persistence/conversation/supabase.py +203 -0
  293. praisonai/persistence/conversation/surrealdb.py +287 -0
  294. praisonai/persistence/factory.py +301 -0
  295. praisonai/persistence/hooks/__init__.py +18 -0
  296. praisonai/persistence/hooks/agent_hooks.py +297 -0
  297. praisonai/persistence/knowledge/__init__.py +26 -0
  298. praisonai/persistence/knowledge/base.py +144 -0
  299. praisonai/persistence/knowledge/cassandra.py +232 -0
  300. praisonai/persistence/knowledge/chroma.py +295 -0
  301. praisonai/persistence/knowledge/clickhouse.py +242 -0
  302. praisonai/persistence/knowledge/cosmosdb_vector.py +438 -0
  303. praisonai/persistence/knowledge/couchbase.py +286 -0
  304. praisonai/persistence/knowledge/lancedb.py +216 -0
  305. praisonai/persistence/knowledge/langchain_adapter.py +291 -0
  306. praisonai/persistence/knowledge/lightrag_adapter.py +212 -0
  307. praisonai/persistence/knowledge/llamaindex_adapter.py +256 -0
  308. praisonai/persistence/knowledge/milvus.py +277 -0
  309. praisonai/persistence/knowledge/mongodb_vector.py +306 -0
  310. praisonai/persistence/knowledge/pgvector.py +335 -0
  311. praisonai/persistence/knowledge/pinecone.py +253 -0
  312. praisonai/persistence/knowledge/qdrant.py +301 -0
  313. praisonai/persistence/knowledge/redis_vector.py +291 -0
  314. praisonai/persistence/knowledge/singlestore_vector.py +299 -0
  315. praisonai/persistence/knowledge/surrealdb_vector.py +309 -0
  316. praisonai/persistence/knowledge/upstash_vector.py +266 -0
  317. praisonai/persistence/knowledge/weaviate.py +223 -0
  318. praisonai/persistence/migrations/__init__.py +10 -0
  319. praisonai/persistence/migrations/manager.py +251 -0
  320. praisonai/persistence/orchestrator.py +406 -0
  321. praisonai/persistence/state/__init__.py +21 -0
  322. praisonai/persistence/state/async_mongodb.py +200 -0
  323. praisonai/persistence/state/base.py +107 -0
  324. praisonai/persistence/state/dynamodb.py +226 -0
  325. praisonai/persistence/state/firestore.py +175 -0
  326. praisonai/persistence/state/gcs.py +155 -0
  327. praisonai/persistence/state/memory.py +245 -0
  328. praisonai/persistence/state/mongodb.py +158 -0
  329. praisonai/persistence/state/redis.py +190 -0
  330. praisonai/persistence/state/upstash.py +144 -0
  331. praisonai/persistence/tests/__init__.py +3 -0
  332. praisonai/persistence/tests/test_all_backends.py +633 -0
  333. praisonai/profiler.py +1214 -0
  334. praisonai/recipe/__init__.py +134 -0
  335. praisonai/recipe/bridge.py +278 -0
  336. praisonai/recipe/core.py +893 -0
  337. praisonai/recipe/exceptions.py +54 -0
  338. praisonai/recipe/history.py +402 -0
  339. praisonai/recipe/models.py +266 -0
  340. praisonai/recipe/operations.py +440 -0
  341. praisonai/recipe/policy.py +422 -0
  342. praisonai/recipe/registry.py +849 -0
  343. praisonai/recipe/runtime.py +214 -0
  344. praisonai/recipe/security.py +711 -0
  345. praisonai/recipe/serve.py +859 -0
  346. praisonai/recipe/server.py +613 -0
  347. praisonai/scheduler/__init__.py +45 -0
  348. praisonai/scheduler/agent_scheduler.py +552 -0
  349. praisonai/scheduler/base.py +124 -0
  350. praisonai/scheduler/daemon_manager.py +225 -0
  351. praisonai/scheduler/state_manager.py +155 -0
  352. praisonai/scheduler/yaml_loader.py +193 -0
  353. praisonai/scheduler.py +194 -0
  354. praisonai/setup/__init__.py +1 -0
  355. praisonai/setup/build.py +21 -0
  356. praisonai/setup/post_install.py +23 -0
  357. praisonai/setup/setup_conda_env.py +25 -0
  358. praisonai/setup.py +16 -0
  359. praisonai/templates/__init__.py +116 -0
  360. praisonai/templates/cache.py +364 -0
  361. praisonai/templates/dependency_checker.py +358 -0
  362. praisonai/templates/discovery.py +391 -0
  363. praisonai/templates/loader.py +564 -0
  364. praisonai/templates/registry.py +511 -0
  365. praisonai/templates/resolver.py +206 -0
  366. praisonai/templates/security.py +327 -0
  367. praisonai/templates/tool_override.py +498 -0
  368. praisonai/templates/tools_doctor.py +256 -0
  369. praisonai/test.py +105 -0
  370. praisonai/train.py +562 -0
  371. praisonai/train_vision.py +306 -0
  372. praisonai/ui/agents.py +824 -0
  373. praisonai/ui/callbacks.py +57 -0
  374. praisonai/ui/chainlit_compat.py +246 -0
  375. praisonai/ui/chat.py +532 -0
  376. praisonai/ui/code.py +717 -0
  377. praisonai/ui/colab.py +474 -0
  378. praisonai/ui/colab_chainlit.py +81 -0
  379. praisonai/ui/components/aicoder.py +284 -0
  380. praisonai/ui/context.py +283 -0
  381. praisonai/ui/database_config.py +56 -0
  382. praisonai/ui/db.py +294 -0
  383. praisonai/ui/realtime.py +488 -0
  384. praisonai/ui/realtimeclient/__init__.py +756 -0
  385. praisonai/ui/realtimeclient/tools.py +242 -0
  386. praisonai/ui/sql_alchemy.py +710 -0
  387. praisonai/upload_vision.py +140 -0
  388. praisonai/version.py +1 -0
  389. praisonai-3.0.0.dist-info/METADATA +3493 -0
  390. praisonai-3.0.0.dist-info/RECORD +393 -0
  391. praisonai-3.0.0.dist-info/WHEEL +5 -0
  392. praisonai-3.0.0.dist-info/entry_points.txt +4 -0
  393. praisonai-3.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,860 @@
1
+ """
2
+ Repository Map System for PraisonAI CLI.
3
+
4
+ Inspired by Aider's RepoMap using tree-sitter for code parsing.
5
+ Provides intelligent codebase context for LLM interactions.
6
+
7
+ Architecture:
8
+ - RepoMap: Main class for building repository maps
9
+ - SymbolExtractor: Extracts symbols using tree-sitter or regex fallback
10
+ - SymbolRanker: Ranks symbols by importance using graph analysis
11
+ """
12
+
13
+ from dataclasses import dataclass, field
14
+ from typing import Any, Dict, List, Optional, Set
15
+ from pathlib import Path
16
+ from collections import defaultdict
17
+ import logging
18
+ import re
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ # ============================================================================
24
+ # Data Classes
25
+ # ============================================================================
26
+
27
+ @dataclass
28
+ class Symbol:
29
+ """
30
+ Represents a code symbol (class, function, method, variable).
31
+ """
32
+ name: str
33
+ kind: str # class, function, method, variable, import
34
+ file_path: str
35
+ line_number: int
36
+ signature: str = "" # Full signature for functions/methods
37
+ parent: Optional[str] = None # Parent class for methods
38
+ references: int = 0 # Number of references in codebase
39
+
40
+ @property
41
+ def qualified_name(self) -> str:
42
+ """Get fully qualified name."""
43
+ if self.parent:
44
+ return f"{self.parent}.{self.name}"
45
+ return self.name
46
+
47
+ def __hash__(self):
48
+ return hash((self.name, self.kind, self.file_path, self.line_number))
49
+
50
+
51
+ @dataclass
52
+ class FileMap:
53
+ """
54
+ Map of symbols in a single file.
55
+ """
56
+ file_path: str
57
+ symbols: List[Symbol] = field(default_factory=list)
58
+ imports: List[str] = field(default_factory=list)
59
+
60
+ def add_symbol(self, symbol: Symbol) -> None:
61
+ """Add a symbol to the file map."""
62
+ self.symbols.append(symbol)
63
+
64
+ def get_summary(self, max_symbols: int = 10) -> str:
65
+ """Get a summary of the file's symbols."""
66
+ lines = [f"{self.file_path}:"]
67
+
68
+ # Sort by importance (references) and take top symbols
69
+ sorted_symbols = sorted(
70
+ self.symbols,
71
+ key=lambda s: (s.references, s.kind == "class"),
72
+ reverse=True
73
+ )[:max_symbols]
74
+
75
+ for symbol in sorted_symbols:
76
+ if symbol.signature:
77
+ lines.append(f" {symbol.signature}")
78
+ else:
79
+ lines.append(f" {symbol.kind} {symbol.name}")
80
+
81
+ return "\n".join(lines)
82
+
83
+
84
+ @dataclass
85
+ class RepoMapConfig:
86
+ """Configuration for repository mapping."""
87
+ max_tokens: int = 1024
88
+ max_files: int = 50
89
+ max_symbols_per_file: int = 20
90
+ include_imports: bool = True
91
+ include_docstrings: bool = False
92
+ file_extensions: Set[str] = field(default_factory=lambda: {
93
+ ".py", ".js", ".ts", ".jsx", ".tsx", ".java", ".go", ".rs",
94
+ ".cpp", ".c", ".h", ".hpp", ".rb", ".php", ".swift", ".kt"
95
+ })
96
+ exclude_patterns: Set[str] = field(default_factory=lambda: {
97
+ "__pycache__", "node_modules", ".git", ".venv", "venv",
98
+ "build", "dist", ".egg-info", "__init__.py"
99
+ })
100
+
101
+
102
+ # ============================================================================
103
+ # Symbol Extraction
104
+ # ============================================================================
105
+
106
+ class SymbolExtractor:
107
+ """
108
+ Extracts symbols from source code.
109
+
110
+ Uses tree-sitter when available, falls back to regex patterns.
111
+ """
112
+
113
+ def __init__(self, use_tree_sitter: bool = True):
114
+ self.use_tree_sitter = use_tree_sitter
115
+ self._tree_sitter_available = False
116
+ self._parsers: Dict[str, Any] = {}
117
+
118
+ if use_tree_sitter:
119
+ self._init_tree_sitter()
120
+
121
+ def _init_tree_sitter(self) -> None:
122
+ """Initialize tree-sitter if available."""
123
+ try:
124
+ # Try to import tree-sitter-languages (pip installable)
125
+ import tree_sitter_languages
126
+ self._tree_sitter_available = True
127
+ self._ts_languages = tree_sitter_languages
128
+ logger.debug("tree-sitter-languages available")
129
+ except ImportError:
130
+ try:
131
+ # Fallback to tree-sitter with manual language setup
132
+ import tree_sitter
133
+ self._tree_sitter_available = True
134
+ self._tree_sitter = tree_sitter
135
+ logger.debug("tree-sitter available (manual setup required)")
136
+ except ImportError:
137
+ logger.debug("tree-sitter not available, using regex fallback")
138
+ self._tree_sitter_available = False
139
+
140
+ def extract_symbols(self, file_path: str, content: str) -> List[Symbol]:
141
+ """
142
+ Extract symbols from file content.
143
+
144
+ Args:
145
+ file_path: Path to the file
146
+ content: File content
147
+
148
+ Returns:
149
+ List of extracted symbols
150
+ """
151
+ ext = Path(file_path).suffix.lower()
152
+
153
+ if self._tree_sitter_available:
154
+ try:
155
+ return self._extract_with_tree_sitter(file_path, content, ext)
156
+ except Exception as e:
157
+ logger.debug(f"tree-sitter extraction failed: {e}")
158
+
159
+ # Fallback to regex
160
+ return self._extract_with_regex(file_path, content, ext)
161
+
162
+ def _extract_with_tree_sitter(
163
+ self, file_path: str, content: str, ext: str
164
+ ) -> List[Symbol]:
165
+ """Extract symbols using tree-sitter."""
166
+ symbols = []
167
+
168
+ # Map extension to language
169
+ lang_map = {
170
+ ".py": "python",
171
+ ".js": "javascript",
172
+ ".ts": "typescript",
173
+ ".jsx": "javascript",
174
+ ".tsx": "typescript",
175
+ ".java": "java",
176
+ ".go": "go",
177
+ ".rs": "rust",
178
+ ".rb": "ruby",
179
+ ".cpp": "cpp",
180
+ ".c": "c",
181
+ ".h": "c",
182
+ }
183
+
184
+ lang = lang_map.get(ext)
185
+ if not lang:
186
+ return self._extract_with_regex(file_path, content, ext)
187
+
188
+ try:
189
+ parser = self._ts_languages.get_parser(lang)
190
+ tree = parser.parse(content.encode())
191
+
192
+ # Extract based on language
193
+ if lang == "python":
194
+ symbols = self._extract_python_symbols(file_path, tree, content)
195
+ else:
196
+ # Generic extraction for other languages
197
+ symbols = self._extract_generic_symbols(file_path, tree, content, lang)
198
+ except Exception as e:
199
+ logger.debug(f"tree-sitter parsing failed for {lang}: {e}")
200
+ return self._extract_with_regex(file_path, content, ext)
201
+
202
+ return symbols
203
+
204
+ def _extract_python_symbols(
205
+ self, file_path: str, tree: Any, content: str
206
+ ) -> List[Symbol]:
207
+ """Extract Python symbols from tree-sitter AST."""
208
+ symbols = []
209
+ lines = content.split("\n")
210
+
211
+ def visit_node(node, parent_class=None):
212
+ if node.type == "class_definition":
213
+ name_node = node.child_by_field_name("name")
214
+ if name_node:
215
+ class_name = content[name_node.start_byte:name_node.end_byte]
216
+ line_num = node.start_point[0] + 1
217
+
218
+ # Get class signature
219
+ sig_end = node.children[0].end_byte if node.children else node.start_byte + 50
220
+ signature = content[node.start_byte:sig_end].split("\n")[0]
221
+
222
+ symbols.append(Symbol(
223
+ name=class_name,
224
+ kind="class",
225
+ file_path=file_path,
226
+ line_number=line_num,
227
+ signature=signature.strip()
228
+ ))
229
+
230
+ # Visit children with class context
231
+ for child in node.children:
232
+ visit_node(child, class_name)
233
+ return
234
+
235
+ elif node.type == "function_definition":
236
+ name_node = node.child_by_field_name("name")
237
+ if name_node:
238
+ func_name = content[name_node.start_byte:name_node.end_byte]
239
+ line_num = node.start_point[0] + 1
240
+
241
+ # Get function signature
242
+ sig_line = lines[line_num - 1] if line_num <= len(lines) else ""
243
+
244
+ kind = "method" if parent_class else "function"
245
+ symbols.append(Symbol(
246
+ name=func_name,
247
+ kind=kind,
248
+ file_path=file_path,
249
+ line_number=line_num,
250
+ signature=sig_line.strip(),
251
+ parent=parent_class
252
+ ))
253
+
254
+ # Visit children
255
+ for child in node.children:
256
+ visit_node(child, parent_class)
257
+
258
+ visit_node(tree.root_node)
259
+ return symbols
260
+
261
+ def _extract_generic_symbols(
262
+ self, file_path: str, tree: Any, content: str, lang: str
263
+ ) -> List[Symbol]:
264
+ """Generic symbol extraction for other languages."""
265
+ symbols = []
266
+
267
+ # Node types for different languages
268
+ class_types = {"class_definition", "class_declaration", "struct_definition"}
269
+ func_types = {"function_definition", "function_declaration", "method_definition"}
270
+
271
+ def visit_node(node, parent=None):
272
+ if node.type in class_types:
273
+ name = self._get_node_name(node, content)
274
+ if name:
275
+ symbols.append(Symbol(
276
+ name=name,
277
+ kind="class",
278
+ file_path=file_path,
279
+ line_number=node.start_point[0] + 1
280
+ ))
281
+ for child in node.children:
282
+ visit_node(child, name)
283
+ return
284
+
285
+ elif node.type in func_types:
286
+ name = self._get_node_name(node, content)
287
+ if name:
288
+ symbols.append(Symbol(
289
+ name=name,
290
+ kind="method" if parent else "function",
291
+ file_path=file_path,
292
+ line_number=node.start_point[0] + 1,
293
+ parent=parent
294
+ ))
295
+
296
+ for child in node.children:
297
+ visit_node(child, parent)
298
+
299
+ visit_node(tree.root_node)
300
+ return symbols
301
+
302
+ def _get_node_name(self, node: Any, content: str) -> Optional[str]:
303
+ """Get name from a tree-sitter node."""
304
+ name_node = node.child_by_field_name("name")
305
+ if name_node:
306
+ return content[name_node.start_byte:name_node.end_byte]
307
+ return None
308
+
309
+ def _extract_with_regex(
310
+ self, file_path: str, content: str, ext: str
311
+ ) -> List[Symbol]:
312
+ """Extract symbols using regex patterns (fallback)."""
313
+ symbols = []
314
+ lines = content.split("\n")
315
+
316
+ if ext == ".py":
317
+ symbols = self._extract_python_regex(file_path, lines)
318
+ elif ext in {".js", ".ts", ".jsx", ".tsx"}:
319
+ symbols = self._extract_js_regex(file_path, lines)
320
+ elif ext in {".java", ".kt"}:
321
+ symbols = self._extract_java_regex(file_path, lines)
322
+ elif ext == ".go":
323
+ symbols = self._extract_go_regex(file_path, lines)
324
+ elif ext == ".rs":
325
+ symbols = self._extract_rust_regex(file_path, lines)
326
+ else:
327
+ # Generic extraction
328
+ symbols = self._extract_generic_regex(file_path, lines)
329
+
330
+ return symbols
331
+
332
+ def _extract_python_regex(self, file_path: str, lines: List[str]) -> List[Symbol]:
333
+ """Extract Python symbols using regex."""
334
+ symbols = []
335
+ current_class = None
336
+
337
+ class_pattern = re.compile(r'^class\s+(\w+)')
338
+ func_pattern = re.compile(r'^(\s*)def\s+(\w+)\s*\(([^)]*)\)')
339
+
340
+ for i, line in enumerate(lines):
341
+ line_num = i + 1
342
+
343
+ # Check for class
344
+ class_match = class_pattern.match(line)
345
+ if class_match:
346
+ current_class = class_match.group(1)
347
+ symbols.append(Symbol(
348
+ name=current_class,
349
+ kind="class",
350
+ file_path=file_path,
351
+ line_number=line_num,
352
+ signature=line.strip()
353
+ ))
354
+ continue
355
+
356
+ # Check for function/method
357
+ func_match = func_pattern.match(line)
358
+ if func_match:
359
+ indent = len(func_match.group(1))
360
+ func_name = func_match.group(2)
361
+
362
+ # If indented, it's a method
363
+ if indent > 0 and current_class:
364
+ symbols.append(Symbol(
365
+ name=func_name,
366
+ kind="method",
367
+ file_path=file_path,
368
+ line_number=line_num,
369
+ signature=line.strip(),
370
+ parent=current_class
371
+ ))
372
+ else:
373
+ current_class = None # Reset class context
374
+ symbols.append(Symbol(
375
+ name=func_name,
376
+ kind="function",
377
+ file_path=file_path,
378
+ line_number=line_num,
379
+ signature=line.strip()
380
+ ))
381
+
382
+ return symbols
383
+
384
+ def _extract_js_regex(self, file_path: str, lines: List[str]) -> List[Symbol]:
385
+ """Extract JavaScript/TypeScript symbols using regex."""
386
+ symbols = []
387
+
388
+ class_pattern = re.compile(r'(?:export\s+)?class\s+(\w+)')
389
+ func_pattern = re.compile(
390
+ r'(?:export\s+)?(?:async\s+)?function\s+(\w+)|'
391
+ r'(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>'
392
+ )
393
+
394
+ for i, line in enumerate(lines):
395
+ line_num = i + 1
396
+
397
+ class_match = class_pattern.search(line)
398
+ if class_match:
399
+ symbols.append(Symbol(
400
+ name=class_match.group(1),
401
+ kind="class",
402
+ file_path=file_path,
403
+ line_number=line_num,
404
+ signature=line.strip()[:80]
405
+ ))
406
+
407
+ func_match = func_pattern.search(line)
408
+ if func_match:
409
+ name = func_match.group(1) or func_match.group(2)
410
+ if name:
411
+ symbols.append(Symbol(
412
+ name=name,
413
+ kind="function",
414
+ file_path=file_path,
415
+ line_number=line_num,
416
+ signature=line.strip()[:80]
417
+ ))
418
+
419
+ return symbols
420
+
421
+ def _extract_java_regex(self, file_path: str, lines: List[str]) -> List[Symbol]:
422
+ """Extract Java/Kotlin symbols using regex."""
423
+ symbols = []
424
+
425
+ class_pattern = re.compile(
426
+ r'(?:public|private|protected)?\s*(?:static\s+)?'
427
+ r'(?:class|interface|enum)\s+(\w+)'
428
+ )
429
+ method_pattern = re.compile(
430
+ r'(?:public|private|protected)?\s*(?:static\s+)?'
431
+ r'(?:\w+(?:<[^>]+>)?)\s+(\w+)\s*\('
432
+ )
433
+
434
+ for i, line in enumerate(lines):
435
+ line_num = i + 1
436
+
437
+ class_match = class_pattern.search(line)
438
+ if class_match:
439
+ symbols.append(Symbol(
440
+ name=class_match.group(1),
441
+ kind="class",
442
+ file_path=file_path,
443
+ line_number=line_num
444
+ ))
445
+
446
+ method_match = method_pattern.search(line)
447
+ if method_match and not class_match:
448
+ symbols.append(Symbol(
449
+ name=method_match.group(1),
450
+ kind="method",
451
+ file_path=file_path,
452
+ line_number=line_num
453
+ ))
454
+
455
+ return symbols
456
+
457
+ def _extract_go_regex(self, file_path: str, lines: List[str]) -> List[Symbol]:
458
+ """Extract Go symbols using regex."""
459
+ symbols = []
460
+
461
+ func_pattern = re.compile(r'^func\s+(?:\([^)]+\)\s+)?(\w+)')
462
+ type_pattern = re.compile(r'^type\s+(\w+)\s+(?:struct|interface)')
463
+
464
+ for i, line in enumerate(lines):
465
+ line_num = i + 1
466
+
467
+ type_match = type_pattern.match(line)
468
+ if type_match:
469
+ symbols.append(Symbol(
470
+ name=type_match.group(1),
471
+ kind="class",
472
+ file_path=file_path,
473
+ line_number=line_num
474
+ ))
475
+
476
+ func_match = func_pattern.match(line)
477
+ if func_match:
478
+ symbols.append(Symbol(
479
+ name=func_match.group(1),
480
+ kind="function",
481
+ file_path=file_path,
482
+ line_number=line_num
483
+ ))
484
+
485
+ return symbols
486
+
487
+ def _extract_rust_regex(self, file_path: str, lines: List[str]) -> List[Symbol]:
488
+ """Extract Rust symbols using regex."""
489
+ symbols = []
490
+
491
+ fn_pattern = re.compile(r'(?:pub\s+)?(?:async\s+)?fn\s+(\w+)')
492
+ struct_pattern = re.compile(r'(?:pub\s+)?struct\s+(\w+)')
493
+
494
+ for i, line in enumerate(lines):
495
+ line_num = i + 1
496
+
497
+ struct_match = struct_pattern.match(line)
498
+ if struct_match:
499
+ symbols.append(Symbol(
500
+ name=struct_match.group(1),
501
+ kind="class",
502
+ file_path=file_path,
503
+ line_number=line_num
504
+ ))
505
+
506
+ fn_match = fn_pattern.search(line)
507
+ if fn_match:
508
+ symbols.append(Symbol(
509
+ name=fn_match.group(1),
510
+ kind="function",
511
+ file_path=file_path,
512
+ line_number=line_num
513
+ ))
514
+
515
+ return symbols
516
+
517
+ def _extract_generic_regex(self, file_path: str, lines: List[str]) -> List[Symbol]:
518
+ """Generic symbol extraction for unknown languages."""
519
+ symbols = []
520
+
521
+ # Very basic patterns
522
+ class_pattern = re.compile(r'(?:class|struct|interface)\s+(\w+)')
523
+ func_pattern = re.compile(r'(?:function|def|fn|func)\s+(\w+)')
524
+
525
+ for i, line in enumerate(lines):
526
+ line_num = i + 1
527
+
528
+ class_match = class_pattern.search(line)
529
+ if class_match:
530
+ symbols.append(Symbol(
531
+ name=class_match.group(1),
532
+ kind="class",
533
+ file_path=file_path,
534
+ line_number=line_num
535
+ ))
536
+
537
+ func_match = func_pattern.search(line)
538
+ if func_match:
539
+ symbols.append(Symbol(
540
+ name=func_match.group(1),
541
+ kind="function",
542
+ file_path=file_path,
543
+ line_number=line_num
544
+ ))
545
+
546
+ return symbols
547
+
548
+
549
+ # ============================================================================
550
+ # Symbol Ranking
551
+ # ============================================================================
552
+
553
+ class SymbolRanker:
554
+ """
555
+ Ranks symbols by importance using reference counting and graph analysis.
556
+ """
557
+
558
+ def __init__(self):
559
+ self.symbol_refs: Dict[str, int] = defaultdict(int)
560
+ self.file_refs: Dict[str, Set[str]] = defaultdict(set)
561
+
562
+ def analyze_references(
563
+ self, file_maps: Dict[str, FileMap], all_content: Dict[str, str]
564
+ ) -> None:
565
+ """
566
+ Analyze references between symbols across files.
567
+
568
+ Args:
569
+ file_maps: Map of file path to FileMap
570
+ all_content: Map of file path to content
571
+ """
572
+ # Build symbol name set
573
+ all_symbols = set()
574
+ for file_map in file_maps.values():
575
+ for symbol in file_map.symbols:
576
+ all_symbols.add(symbol.name)
577
+
578
+ # Count references
579
+ for file_path, content in all_content.items():
580
+ for symbol_name in all_symbols:
581
+ # Simple word boundary match
582
+ pattern = rf'\b{re.escape(symbol_name)}\b'
583
+ matches = len(re.findall(pattern, content))
584
+ if matches > 0:
585
+ self.symbol_refs[symbol_name] += matches
586
+ self.file_refs[file_path].add(symbol_name)
587
+
588
+ # Update symbol reference counts
589
+ for file_map in file_maps.values():
590
+ for symbol in file_map.symbols:
591
+ symbol.references = self.symbol_refs.get(symbol.name, 0)
592
+
593
+ def get_top_symbols(
594
+ self, file_maps: Dict[str, FileMap], max_symbols: int = 50
595
+ ) -> List[Symbol]:
596
+ """Get the most important symbols across the codebase."""
597
+ all_symbols = []
598
+ for file_map in file_maps.values():
599
+ all_symbols.extend(file_map.symbols)
600
+
601
+ # Sort by references and kind priority
602
+ kind_priority = {"class": 3, "function": 2, "method": 1, "variable": 0}
603
+
604
+ sorted_symbols = sorted(
605
+ all_symbols,
606
+ key=lambda s: (s.references, kind_priority.get(s.kind, 0)),
607
+ reverse=True
608
+ )
609
+
610
+ return sorted_symbols[:max_symbols]
611
+
612
+
613
+ # ============================================================================
614
+ # Repository Map
615
+ # ============================================================================
616
+
617
+ class RepoMap:
618
+ """
619
+ Main class for building repository maps.
620
+
621
+ Provides intelligent codebase context for LLM interactions.
622
+ """
623
+
624
+ def __init__(
625
+ self,
626
+ root: Optional[str] = None,
627
+ config: Optional[RepoMapConfig] = None,
628
+ verbose: bool = False
629
+ ):
630
+ self.root = Path(root) if root else Path.cwd()
631
+ self.config = config or RepoMapConfig()
632
+ self.verbose = verbose
633
+
634
+ self.extractor = SymbolExtractor()
635
+ self.ranker = SymbolRanker()
636
+
637
+ self._file_maps: Dict[str, FileMap] = {}
638
+ self._all_content: Dict[str, str] = {}
639
+ self._last_map: Optional[str] = None
640
+
641
+ def scan(self, paths: Optional[List[str]] = None) -> None:
642
+ """
643
+ Scan the repository for symbols.
644
+
645
+ Args:
646
+ paths: Optional list of specific paths to scan
647
+ """
648
+ if paths:
649
+ files_to_scan = [Path(p) for p in paths]
650
+ else:
651
+ files_to_scan = self._find_files()
652
+
653
+ for file_path in files_to_scan:
654
+ try:
655
+ self._scan_file(file_path)
656
+ except Exception as e:
657
+ logger.debug(f"Error scanning {file_path}: {e}")
658
+
659
+ # Analyze references
660
+ self.ranker.analyze_references(self._file_maps, self._all_content)
661
+
662
+ if self.verbose:
663
+ logger.info(f"Scanned {len(self._file_maps)} files")
664
+
665
+ def _find_files(self) -> List[Path]:
666
+ """Find all relevant source files in the repository."""
667
+ files = []
668
+
669
+ for ext in self.config.file_extensions:
670
+ for file_path in self.root.rglob(f"*{ext}"):
671
+ # Check exclusions
672
+ if any(excl in str(file_path) for excl in self.config.exclude_patterns):
673
+ continue
674
+ files.append(file_path)
675
+
676
+ return files[:self.config.max_files]
677
+
678
+ def _scan_file(self, file_path: Path) -> None:
679
+ """Scan a single file for symbols."""
680
+ try:
681
+ content = file_path.read_text(encoding="utf-8", errors="ignore")
682
+ except Exception as e:
683
+ logger.debug(f"Could not read {file_path}: {e}")
684
+ return
685
+
686
+ rel_path = str(file_path.relative_to(self.root))
687
+ self._all_content[rel_path] = content
688
+
689
+ symbols = self.extractor.extract_symbols(rel_path, content)
690
+
691
+ file_map = FileMap(file_path=rel_path)
692
+ for symbol in symbols[:self.config.max_symbols_per_file]:
693
+ file_map.add_symbol(symbol)
694
+
695
+ self._file_maps[rel_path] = file_map
696
+
697
+ def get_map(
698
+ self,
699
+ focus_files: Optional[List[str]] = None,
700
+ max_tokens: Optional[int] = None
701
+ ) -> str:
702
+ """
703
+ Get the repository map as a string.
704
+
705
+ Args:
706
+ focus_files: Files to prioritize in the map
707
+ max_tokens: Maximum tokens for the map
708
+
709
+ Returns:
710
+ Formatted repository map string
711
+ """
712
+ if not self._file_maps:
713
+ self.scan()
714
+
715
+ max_tokens = max_tokens or self.config.max_tokens
716
+
717
+ # Get top symbols
718
+ top_symbols = self.ranker.get_top_symbols(self._file_maps)
719
+
720
+ # Group by file
721
+ file_symbols: Dict[str, List[Symbol]] = defaultdict(list)
722
+ for symbol in top_symbols:
723
+ file_symbols[symbol.file_path].append(symbol)
724
+
725
+ # Prioritize focus files
726
+ if focus_files:
727
+ focus_set = set(focus_files)
728
+ sorted_files = sorted(
729
+ file_symbols.keys(),
730
+ key=lambda f: (f not in focus_set, f)
731
+ )
732
+ else:
733
+ sorted_files = sorted(file_symbols.keys())
734
+
735
+ # Build map
736
+ lines = []
737
+ estimated_tokens = 0
738
+
739
+ for file_path in sorted_files:
740
+ symbols = file_symbols[file_path]
741
+
742
+ file_lines = [f"{file_path}:"]
743
+ for symbol in symbols:
744
+ if symbol.signature:
745
+ file_lines.append(f" │{symbol.signature}")
746
+ else:
747
+ file_lines.append(f" │{symbol.kind} {symbol.name}")
748
+ file_lines.append(" ⋮...")
749
+
750
+ file_text = "\n".join(file_lines)
751
+ file_tokens = len(file_text) // 4 # Rough token estimate
752
+
753
+ if estimated_tokens + file_tokens > max_tokens:
754
+ break
755
+
756
+ lines.extend(file_lines)
757
+ estimated_tokens += file_tokens
758
+
759
+ self._last_map = "\n".join(lines)
760
+ return self._last_map
761
+
762
+ def get_file_symbols(self, file_path: str) -> List[Symbol]:
763
+ """Get symbols for a specific file."""
764
+ if file_path in self._file_maps:
765
+ return self._file_maps[file_path].symbols
766
+ return []
767
+
768
+ def get_symbol_context(self, symbol_name: str) -> Optional[str]:
769
+ """Get context around a specific symbol."""
770
+ for file_path, file_map in self._file_maps.items():
771
+ for symbol in file_map.symbols:
772
+ if symbol.name == symbol_name:
773
+ content = self._all_content.get(file_path, "")
774
+ lines = content.split("\n")
775
+
776
+ # Get lines around the symbol
777
+ start = max(0, symbol.line_number - 3)
778
+ end = min(len(lines), symbol.line_number + 10)
779
+
780
+ context_lines = lines[start:end]
781
+ return f"{file_path}:{symbol.line_number}\n" + "\n".join(context_lines)
782
+
783
+ return None
784
+
785
+ def refresh(self) -> None:
786
+ """Refresh the repository map."""
787
+ self._file_maps.clear()
788
+ self._all_content.clear()
789
+ self.ranker = SymbolRanker()
790
+ self.scan()
791
+
792
+
793
+ # ============================================================================
794
+ # CLI Integration Handler
795
+ # ============================================================================
796
+
797
+ class RepoMapHandler:
798
+ """
799
+ Handler for integrating RepoMap with PraisonAI CLI.
800
+ """
801
+
802
+ def __init__(self, verbose: bool = False):
803
+ self.verbose = verbose
804
+ self._repo_map: Optional[RepoMap] = None
805
+
806
+ @property
807
+ def feature_name(self) -> str:
808
+ return "repo_map"
809
+
810
+ def initialize(
811
+ self,
812
+ root: Optional[str] = None,
813
+ config: Optional[RepoMapConfig] = None
814
+ ) -> RepoMap:
815
+ """Initialize the repository map."""
816
+ self._repo_map = RepoMap(
817
+ root=root,
818
+ config=config,
819
+ verbose=self.verbose
820
+ )
821
+
822
+ if self.verbose:
823
+ from rich import print as rprint
824
+ rprint(f"[cyan]RepoMap initialized for: {self._repo_map.root}[/cyan]")
825
+
826
+ return self._repo_map
827
+
828
+ def get_map(self, focus_files: Optional[List[str]] = None) -> str:
829
+ """Get the repository map."""
830
+ if not self._repo_map:
831
+ self._repo_map = self.initialize()
832
+
833
+ return self._repo_map.get_map(focus_files=focus_files)
834
+
835
+ def get_context(self, symbol_name: str) -> Optional[str]:
836
+ """Get context for a symbol."""
837
+ if not self._repo_map:
838
+ self._repo_map = self.initialize()
839
+
840
+ return self._repo_map.get_symbol_context(symbol_name)
841
+
842
+ def refresh(self) -> None:
843
+ """Refresh the map."""
844
+ if self._repo_map:
845
+ self._repo_map.refresh()
846
+
847
+ def print_map(self) -> None:
848
+ """Print the repository map."""
849
+ from rich.console import Console
850
+ from rich.panel import Panel
851
+ from rich.syntax import Syntax
852
+
853
+ console = Console()
854
+ map_str = self.get_map()
855
+
856
+ console.print(Panel(
857
+ Syntax(map_str, "text", theme="monokai"),
858
+ title="📁 Repository Map",
859
+ border_style="blue"
860
+ ))