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.
- praisonai/__init__.py +54 -0
- praisonai/__main__.py +15 -0
- praisonai/acp/__init__.py +54 -0
- praisonai/acp/config.py +159 -0
- praisonai/acp/server.py +587 -0
- praisonai/acp/session.py +219 -0
- praisonai/adapters/__init__.py +50 -0
- praisonai/adapters/readers.py +395 -0
- praisonai/adapters/rerankers.py +315 -0
- praisonai/adapters/retrievers.py +394 -0
- praisonai/adapters/vector_stores.py +409 -0
- praisonai/agent_scheduler.py +337 -0
- praisonai/agents_generator.py +903 -0
- praisonai/api/call.py +292 -0
- praisonai/auto.py +1197 -0
- praisonai/capabilities/__init__.py +275 -0
- praisonai/capabilities/a2a.py +140 -0
- praisonai/capabilities/assistants.py +283 -0
- praisonai/capabilities/audio.py +320 -0
- praisonai/capabilities/batches.py +469 -0
- praisonai/capabilities/completions.py +336 -0
- praisonai/capabilities/container_files.py +155 -0
- praisonai/capabilities/containers.py +93 -0
- praisonai/capabilities/embeddings.py +158 -0
- praisonai/capabilities/files.py +467 -0
- praisonai/capabilities/fine_tuning.py +293 -0
- praisonai/capabilities/guardrails.py +182 -0
- praisonai/capabilities/images.py +330 -0
- praisonai/capabilities/mcp.py +190 -0
- praisonai/capabilities/messages.py +270 -0
- praisonai/capabilities/moderations.py +154 -0
- praisonai/capabilities/ocr.py +217 -0
- praisonai/capabilities/passthrough.py +204 -0
- praisonai/capabilities/rag.py +207 -0
- praisonai/capabilities/realtime.py +160 -0
- praisonai/capabilities/rerank.py +165 -0
- praisonai/capabilities/responses.py +266 -0
- praisonai/capabilities/search.py +109 -0
- praisonai/capabilities/skills.py +133 -0
- praisonai/capabilities/vector_store_files.py +334 -0
- praisonai/capabilities/vector_stores.py +304 -0
- praisonai/capabilities/videos.py +141 -0
- praisonai/chainlit_ui.py +304 -0
- praisonai/chat/__init__.py +106 -0
- praisonai/chat/app.py +125 -0
- praisonai/cli/__init__.py +26 -0
- praisonai/cli/app.py +213 -0
- praisonai/cli/commands/__init__.py +75 -0
- praisonai/cli/commands/acp.py +70 -0
- praisonai/cli/commands/completion.py +333 -0
- praisonai/cli/commands/config.py +166 -0
- praisonai/cli/commands/debug.py +142 -0
- praisonai/cli/commands/diag.py +55 -0
- praisonai/cli/commands/doctor.py +166 -0
- praisonai/cli/commands/environment.py +179 -0
- praisonai/cli/commands/lsp.py +112 -0
- praisonai/cli/commands/mcp.py +210 -0
- praisonai/cli/commands/profile.py +457 -0
- praisonai/cli/commands/run.py +228 -0
- praisonai/cli/commands/schedule.py +150 -0
- praisonai/cli/commands/serve.py +97 -0
- praisonai/cli/commands/session.py +212 -0
- praisonai/cli/commands/traces.py +145 -0
- praisonai/cli/commands/version.py +101 -0
- praisonai/cli/configuration/__init__.py +18 -0
- praisonai/cli/configuration/loader.py +353 -0
- praisonai/cli/configuration/paths.py +114 -0
- praisonai/cli/configuration/schema.py +164 -0
- praisonai/cli/features/__init__.py +268 -0
- praisonai/cli/features/acp.py +236 -0
- praisonai/cli/features/action_orchestrator.py +546 -0
- praisonai/cli/features/agent_scheduler.py +773 -0
- praisonai/cli/features/agent_tools.py +474 -0
- praisonai/cli/features/agents.py +375 -0
- praisonai/cli/features/at_mentions.py +471 -0
- praisonai/cli/features/auto_memory.py +182 -0
- praisonai/cli/features/autonomy_mode.py +490 -0
- praisonai/cli/features/background.py +356 -0
- praisonai/cli/features/base.py +168 -0
- praisonai/cli/features/capabilities.py +1326 -0
- praisonai/cli/features/checkpoints.py +338 -0
- praisonai/cli/features/code_intelligence.py +652 -0
- praisonai/cli/features/compaction.py +294 -0
- praisonai/cli/features/compare.py +534 -0
- praisonai/cli/features/cost_tracker.py +514 -0
- praisonai/cli/features/debug.py +810 -0
- praisonai/cli/features/deploy.py +517 -0
- praisonai/cli/features/diag.py +289 -0
- praisonai/cli/features/doctor/__init__.py +63 -0
- praisonai/cli/features/doctor/checks/__init__.py +24 -0
- praisonai/cli/features/doctor/checks/acp_checks.py +240 -0
- praisonai/cli/features/doctor/checks/config_checks.py +366 -0
- praisonai/cli/features/doctor/checks/db_checks.py +366 -0
- praisonai/cli/features/doctor/checks/env_checks.py +543 -0
- praisonai/cli/features/doctor/checks/lsp_checks.py +199 -0
- praisonai/cli/features/doctor/checks/mcp_checks.py +349 -0
- praisonai/cli/features/doctor/checks/memory_checks.py +268 -0
- praisonai/cli/features/doctor/checks/network_checks.py +251 -0
- praisonai/cli/features/doctor/checks/obs_checks.py +328 -0
- praisonai/cli/features/doctor/checks/performance_checks.py +235 -0
- praisonai/cli/features/doctor/checks/permissions_checks.py +259 -0
- praisonai/cli/features/doctor/checks/selftest_checks.py +322 -0
- praisonai/cli/features/doctor/checks/serve_checks.py +426 -0
- praisonai/cli/features/doctor/checks/skills_checks.py +231 -0
- praisonai/cli/features/doctor/checks/tools_checks.py +371 -0
- praisonai/cli/features/doctor/engine.py +266 -0
- praisonai/cli/features/doctor/formatters.py +310 -0
- praisonai/cli/features/doctor/handler.py +397 -0
- praisonai/cli/features/doctor/models.py +264 -0
- praisonai/cli/features/doctor/registry.py +239 -0
- praisonai/cli/features/endpoints.py +1019 -0
- praisonai/cli/features/eval.py +560 -0
- praisonai/cli/features/external_agents.py +231 -0
- praisonai/cli/features/fast_context.py +410 -0
- praisonai/cli/features/flow_display.py +566 -0
- praisonai/cli/features/git_integration.py +651 -0
- praisonai/cli/features/guardrail.py +171 -0
- praisonai/cli/features/handoff.py +185 -0
- praisonai/cli/features/hooks.py +583 -0
- praisonai/cli/features/image.py +384 -0
- praisonai/cli/features/interactive_runtime.py +585 -0
- praisonai/cli/features/interactive_tools.py +380 -0
- praisonai/cli/features/interactive_tui.py +603 -0
- praisonai/cli/features/jobs.py +632 -0
- praisonai/cli/features/knowledge.py +531 -0
- praisonai/cli/features/lite.py +244 -0
- praisonai/cli/features/lsp_cli.py +225 -0
- praisonai/cli/features/mcp.py +169 -0
- praisonai/cli/features/message_queue.py +587 -0
- praisonai/cli/features/metrics.py +211 -0
- praisonai/cli/features/n8n.py +673 -0
- praisonai/cli/features/observability.py +293 -0
- praisonai/cli/features/ollama.py +361 -0
- praisonai/cli/features/output_style.py +273 -0
- praisonai/cli/features/package.py +631 -0
- praisonai/cli/features/performance.py +308 -0
- praisonai/cli/features/persistence.py +636 -0
- praisonai/cli/features/profile.py +226 -0
- praisonai/cli/features/profiler/__init__.py +81 -0
- praisonai/cli/features/profiler/core.py +558 -0
- praisonai/cli/features/profiler/optimizations.py +652 -0
- praisonai/cli/features/profiler/suite.py +386 -0
- praisonai/cli/features/profiling.py +350 -0
- praisonai/cli/features/queue/__init__.py +73 -0
- praisonai/cli/features/queue/manager.py +395 -0
- praisonai/cli/features/queue/models.py +286 -0
- praisonai/cli/features/queue/persistence.py +564 -0
- praisonai/cli/features/queue/scheduler.py +484 -0
- praisonai/cli/features/queue/worker.py +372 -0
- praisonai/cli/features/recipe.py +1723 -0
- praisonai/cli/features/recipes.py +449 -0
- praisonai/cli/features/registry.py +229 -0
- praisonai/cli/features/repo_map.py +860 -0
- praisonai/cli/features/router.py +466 -0
- praisonai/cli/features/sandbox_executor.py +515 -0
- praisonai/cli/features/serve.py +829 -0
- praisonai/cli/features/session.py +222 -0
- praisonai/cli/features/skills.py +856 -0
- praisonai/cli/features/slash_commands.py +650 -0
- praisonai/cli/features/telemetry.py +179 -0
- praisonai/cli/features/templates.py +1384 -0
- praisonai/cli/features/thinking.py +305 -0
- praisonai/cli/features/todo.py +334 -0
- praisonai/cli/features/tools.py +680 -0
- praisonai/cli/features/tui/__init__.py +83 -0
- praisonai/cli/features/tui/app.py +580 -0
- praisonai/cli/features/tui/cli.py +566 -0
- praisonai/cli/features/tui/debug.py +511 -0
- praisonai/cli/features/tui/events.py +99 -0
- praisonai/cli/features/tui/mock_provider.py +328 -0
- praisonai/cli/features/tui/orchestrator.py +652 -0
- praisonai/cli/features/tui/screens/__init__.py +50 -0
- praisonai/cli/features/tui/screens/main.py +245 -0
- praisonai/cli/features/tui/screens/queue.py +174 -0
- praisonai/cli/features/tui/screens/session.py +124 -0
- praisonai/cli/features/tui/screens/settings.py +148 -0
- praisonai/cli/features/tui/widgets/__init__.py +56 -0
- praisonai/cli/features/tui/widgets/chat.py +261 -0
- praisonai/cli/features/tui/widgets/composer.py +224 -0
- praisonai/cli/features/tui/widgets/queue_panel.py +200 -0
- praisonai/cli/features/tui/widgets/status.py +167 -0
- praisonai/cli/features/tui/widgets/tool_panel.py +248 -0
- praisonai/cli/features/workflow.py +720 -0
- praisonai/cli/legacy.py +236 -0
- praisonai/cli/main.py +5559 -0
- praisonai/cli/schedule_cli.py +54 -0
- praisonai/cli/state/__init__.py +31 -0
- praisonai/cli/state/identifiers.py +161 -0
- praisonai/cli/state/sessions.py +313 -0
- praisonai/code/__init__.py +93 -0
- praisonai/code/agent_tools.py +344 -0
- praisonai/code/diff/__init__.py +21 -0
- praisonai/code/diff/diff_strategy.py +432 -0
- praisonai/code/tools/__init__.py +27 -0
- praisonai/code/tools/apply_diff.py +221 -0
- praisonai/code/tools/execute_command.py +275 -0
- praisonai/code/tools/list_files.py +274 -0
- praisonai/code/tools/read_file.py +206 -0
- praisonai/code/tools/search_replace.py +248 -0
- praisonai/code/tools/write_file.py +217 -0
- praisonai/code/utils/__init__.py +46 -0
- praisonai/code/utils/file_utils.py +307 -0
- praisonai/code/utils/ignore_utils.py +308 -0
- praisonai/code/utils/text_utils.py +276 -0
- praisonai/db/__init__.py +64 -0
- praisonai/db/adapter.py +531 -0
- praisonai/deploy/__init__.py +62 -0
- praisonai/deploy/api.py +231 -0
- praisonai/deploy/docker.py +454 -0
- praisonai/deploy/doctor.py +367 -0
- praisonai/deploy/main.py +327 -0
- praisonai/deploy/models.py +179 -0
- praisonai/deploy/providers/__init__.py +33 -0
- praisonai/deploy/providers/aws.py +331 -0
- praisonai/deploy/providers/azure.py +358 -0
- praisonai/deploy/providers/base.py +101 -0
- praisonai/deploy/providers/gcp.py +314 -0
- praisonai/deploy/schema.py +208 -0
- praisonai/deploy.py +185 -0
- praisonai/endpoints/__init__.py +53 -0
- praisonai/endpoints/a2u_server.py +410 -0
- praisonai/endpoints/discovery.py +165 -0
- praisonai/endpoints/providers/__init__.py +28 -0
- praisonai/endpoints/providers/a2a.py +253 -0
- praisonai/endpoints/providers/a2u.py +208 -0
- praisonai/endpoints/providers/agents_api.py +171 -0
- praisonai/endpoints/providers/base.py +231 -0
- praisonai/endpoints/providers/mcp.py +263 -0
- praisonai/endpoints/providers/recipe.py +206 -0
- praisonai/endpoints/providers/tools_mcp.py +150 -0
- praisonai/endpoints/registry.py +131 -0
- praisonai/endpoints/server.py +161 -0
- praisonai/inbuilt_tools/__init__.py +24 -0
- praisonai/inbuilt_tools/autogen_tools.py +117 -0
- praisonai/inc/__init__.py +2 -0
- praisonai/inc/config.py +96 -0
- praisonai/inc/models.py +155 -0
- praisonai/integrations/__init__.py +56 -0
- praisonai/integrations/base.py +303 -0
- praisonai/integrations/claude_code.py +270 -0
- praisonai/integrations/codex_cli.py +255 -0
- praisonai/integrations/cursor_cli.py +195 -0
- praisonai/integrations/gemini_cli.py +222 -0
- praisonai/jobs/__init__.py +67 -0
- praisonai/jobs/executor.py +425 -0
- praisonai/jobs/models.py +230 -0
- praisonai/jobs/router.py +314 -0
- praisonai/jobs/server.py +186 -0
- praisonai/jobs/store.py +203 -0
- praisonai/llm/__init__.py +66 -0
- praisonai/llm/registry.py +382 -0
- praisonai/mcp_server/__init__.py +152 -0
- praisonai/mcp_server/adapters/__init__.py +74 -0
- praisonai/mcp_server/adapters/agents.py +128 -0
- praisonai/mcp_server/adapters/capabilities.py +168 -0
- praisonai/mcp_server/adapters/cli_tools.py +568 -0
- praisonai/mcp_server/adapters/extended_capabilities.py +462 -0
- praisonai/mcp_server/adapters/knowledge.py +93 -0
- praisonai/mcp_server/adapters/memory.py +104 -0
- praisonai/mcp_server/adapters/prompts.py +306 -0
- praisonai/mcp_server/adapters/resources.py +124 -0
- praisonai/mcp_server/adapters/tools_bridge.py +280 -0
- praisonai/mcp_server/auth/__init__.py +48 -0
- praisonai/mcp_server/auth/api_key.py +291 -0
- praisonai/mcp_server/auth/oauth.py +460 -0
- praisonai/mcp_server/auth/oidc.py +289 -0
- praisonai/mcp_server/auth/scopes.py +260 -0
- praisonai/mcp_server/cli.py +852 -0
- praisonai/mcp_server/elicitation.py +445 -0
- praisonai/mcp_server/icons.py +302 -0
- praisonai/mcp_server/recipe_adapter.py +573 -0
- praisonai/mcp_server/recipe_cli.py +824 -0
- praisonai/mcp_server/registry.py +703 -0
- praisonai/mcp_server/sampling.py +422 -0
- praisonai/mcp_server/server.py +490 -0
- praisonai/mcp_server/tasks.py +443 -0
- praisonai/mcp_server/transports/__init__.py +18 -0
- praisonai/mcp_server/transports/http_stream.py +376 -0
- praisonai/mcp_server/transports/stdio.py +132 -0
- praisonai/persistence/__init__.py +84 -0
- praisonai/persistence/config.py +238 -0
- praisonai/persistence/conversation/__init__.py +25 -0
- praisonai/persistence/conversation/async_mysql.py +427 -0
- praisonai/persistence/conversation/async_postgres.py +410 -0
- praisonai/persistence/conversation/async_sqlite.py +371 -0
- praisonai/persistence/conversation/base.py +151 -0
- praisonai/persistence/conversation/json_store.py +250 -0
- praisonai/persistence/conversation/mysql.py +387 -0
- praisonai/persistence/conversation/postgres.py +401 -0
- praisonai/persistence/conversation/singlestore.py +240 -0
- praisonai/persistence/conversation/sqlite.py +341 -0
- praisonai/persistence/conversation/supabase.py +203 -0
- praisonai/persistence/conversation/surrealdb.py +287 -0
- praisonai/persistence/factory.py +301 -0
- praisonai/persistence/hooks/__init__.py +18 -0
- praisonai/persistence/hooks/agent_hooks.py +297 -0
- praisonai/persistence/knowledge/__init__.py +26 -0
- praisonai/persistence/knowledge/base.py +144 -0
- praisonai/persistence/knowledge/cassandra.py +232 -0
- praisonai/persistence/knowledge/chroma.py +295 -0
- praisonai/persistence/knowledge/clickhouse.py +242 -0
- praisonai/persistence/knowledge/cosmosdb_vector.py +438 -0
- praisonai/persistence/knowledge/couchbase.py +286 -0
- praisonai/persistence/knowledge/lancedb.py +216 -0
- praisonai/persistence/knowledge/langchain_adapter.py +291 -0
- praisonai/persistence/knowledge/lightrag_adapter.py +212 -0
- praisonai/persistence/knowledge/llamaindex_adapter.py +256 -0
- praisonai/persistence/knowledge/milvus.py +277 -0
- praisonai/persistence/knowledge/mongodb_vector.py +306 -0
- praisonai/persistence/knowledge/pgvector.py +335 -0
- praisonai/persistence/knowledge/pinecone.py +253 -0
- praisonai/persistence/knowledge/qdrant.py +301 -0
- praisonai/persistence/knowledge/redis_vector.py +291 -0
- praisonai/persistence/knowledge/singlestore_vector.py +299 -0
- praisonai/persistence/knowledge/surrealdb_vector.py +309 -0
- praisonai/persistence/knowledge/upstash_vector.py +266 -0
- praisonai/persistence/knowledge/weaviate.py +223 -0
- praisonai/persistence/migrations/__init__.py +10 -0
- praisonai/persistence/migrations/manager.py +251 -0
- praisonai/persistence/orchestrator.py +406 -0
- praisonai/persistence/state/__init__.py +21 -0
- praisonai/persistence/state/async_mongodb.py +200 -0
- praisonai/persistence/state/base.py +107 -0
- praisonai/persistence/state/dynamodb.py +226 -0
- praisonai/persistence/state/firestore.py +175 -0
- praisonai/persistence/state/gcs.py +155 -0
- praisonai/persistence/state/memory.py +245 -0
- praisonai/persistence/state/mongodb.py +158 -0
- praisonai/persistence/state/redis.py +190 -0
- praisonai/persistence/state/upstash.py +144 -0
- praisonai/persistence/tests/__init__.py +3 -0
- praisonai/persistence/tests/test_all_backends.py +633 -0
- praisonai/profiler.py +1214 -0
- praisonai/recipe/__init__.py +134 -0
- praisonai/recipe/bridge.py +278 -0
- praisonai/recipe/core.py +893 -0
- praisonai/recipe/exceptions.py +54 -0
- praisonai/recipe/history.py +402 -0
- praisonai/recipe/models.py +266 -0
- praisonai/recipe/operations.py +440 -0
- praisonai/recipe/policy.py +422 -0
- praisonai/recipe/registry.py +849 -0
- praisonai/recipe/runtime.py +214 -0
- praisonai/recipe/security.py +711 -0
- praisonai/recipe/serve.py +859 -0
- praisonai/recipe/server.py +613 -0
- praisonai/scheduler/__init__.py +45 -0
- praisonai/scheduler/agent_scheduler.py +552 -0
- praisonai/scheduler/base.py +124 -0
- praisonai/scheduler/daemon_manager.py +225 -0
- praisonai/scheduler/state_manager.py +155 -0
- praisonai/scheduler/yaml_loader.py +193 -0
- praisonai/scheduler.py +194 -0
- praisonai/setup/__init__.py +1 -0
- praisonai/setup/build.py +21 -0
- praisonai/setup/post_install.py +23 -0
- praisonai/setup/setup_conda_env.py +25 -0
- praisonai/setup.py +16 -0
- praisonai/templates/__init__.py +116 -0
- praisonai/templates/cache.py +364 -0
- praisonai/templates/dependency_checker.py +358 -0
- praisonai/templates/discovery.py +391 -0
- praisonai/templates/loader.py +564 -0
- praisonai/templates/registry.py +511 -0
- praisonai/templates/resolver.py +206 -0
- praisonai/templates/security.py +327 -0
- praisonai/templates/tool_override.py +498 -0
- praisonai/templates/tools_doctor.py +256 -0
- praisonai/test.py +105 -0
- praisonai/train.py +562 -0
- praisonai/train_vision.py +306 -0
- praisonai/ui/agents.py +824 -0
- praisonai/ui/callbacks.py +57 -0
- praisonai/ui/chainlit_compat.py +246 -0
- praisonai/ui/chat.py +532 -0
- praisonai/ui/code.py +717 -0
- praisonai/ui/colab.py +474 -0
- praisonai/ui/colab_chainlit.py +81 -0
- praisonai/ui/components/aicoder.py +284 -0
- praisonai/ui/context.py +283 -0
- praisonai/ui/database_config.py +56 -0
- praisonai/ui/db.py +294 -0
- praisonai/ui/realtime.py +488 -0
- praisonai/ui/realtimeclient/__init__.py +756 -0
- praisonai/ui/realtimeclient/tools.py +242 -0
- praisonai/ui/sql_alchemy.py +710 -0
- praisonai/upload_vision.py +140 -0
- praisonai/version.py +1 -0
- praisonai-3.0.0.dist-info/METADATA +3493 -0
- praisonai-3.0.0.dist-info/RECORD +393 -0
- praisonai-3.0.0.dist-info/WHEEL +5 -0
- praisonai-3.0.0.dist-info/entry_points.txt +4 -0
- 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
|
+
))
|