attocode-code-intel 0.1.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.
- attocode_code_intel-0.1.0.dist-info/METADATA +221 -0
- attocode_code_intel-0.1.0.dist-info/RECORD +345 -0
- attocode_code_intel-0.1.0.dist-info/WHEEL +4 -0
- attocode_code_intel-0.1.0.dist-info/entry_points.txt +2 -0
- attocode_code_intel-0.1.0.dist-info/licenses/LICENSE +21 -0
- attocode_intel/GUIDELINES.md +432 -0
- attocode_intel/__init__.py +52 -0
- attocode_intel/_internal/__init__.py +1 -0
- attocode_intel/_internal/errors/__init__.py +126 -0
- attocode_intel/_internal/integrations/__init__.py +1 -0
- attocode_intel/_internal/integrations/context/__init__.py +1 -0
- attocode_intel/_internal/integrations/context/architecture_drift.py +354 -0
- attocode_intel/_internal/integrations/context/ast_chunker.py +202 -0
- attocode_intel/_internal/integrations/context/ast_service.py +1483 -0
- attocode_intel/_internal/integrations/context/blob_oid.py +423 -0
- attocode_intel/_internal/integrations/context/cache_manifest.py +221 -0
- attocode_intel/_internal/integrations/context/cas.py +466 -0
- attocode_intel/_internal/integrations/context/code_analyzer.py +528 -0
- attocode_intel/_internal/integrations/context/codebase_ast.py +1461 -0
- attocode_intel/_internal/integrations/context/codebase_context.py +1736 -0
- attocode_intel/_internal/integrations/context/cross_mode.py +263 -0
- attocode_intel/_internal/integrations/context/cross_references.py +616 -0
- attocode_intel/_internal/integrations/context/embedding_rotation.py +597 -0
- attocode_intel/_internal/integrations/context/embeddings.py +282 -0
- attocode_intel/_internal/integrations/context/frecency.py +427 -0
- attocode_intel/_internal/integrations/context/fuzzy.py +320 -0
- attocode_intel/_internal/integrations/context/hierarchical_explorer.py +331 -0
- attocode_intel/_internal/integrations/context/hydration.py +90 -0
- attocode_intel/_internal/integrations/context/index_repair.py +87 -0
- attocode_intel/_internal/integrations/context/index_store.py +586 -0
- attocode_intel/_internal/integrations/context/memory_store.py +602 -0
- attocode_intel/_internal/integrations/context/module_resolution.py +52 -0
- attocode_intel/_internal/integrations/context/query_constraints.py +361 -0
- attocode_intel/_internal/integrations/context/query_history.py +417 -0
- attocode_intel/_internal/integrations/context/reranker.py +351 -0
- attocode_intel/_internal/integrations/context/semantic_search.py +2931 -0
- attocode_intel/_internal/integrations/context/syntax_evidence.py +94 -0
- attocode_intel/_internal/integrations/context/systemone_ranker.py +245 -0
- attocode_intel/_internal/integrations/context/temporal_coupling.py +411 -0
- attocode_intel/_internal/integrations/context/trigram_index.py +820 -0
- attocode_intel/_internal/integrations/context/trigram_regex.py +171 -0
- attocode_intel/_internal/integrations/context/ts_parser.py +2199 -0
- attocode_intel/_internal/integrations/context/vector_store.py +795 -0
- attocode_intel/_internal/integrations/feature_flags/__init__.py +357 -0
- attocode_intel/_internal/integrations/lsp/__init__.py +1 -0
- attocode_intel/_internal/integrations/lsp/client.py +1140 -0
- attocode_intel/_internal/integrations/mcp/__init__.py +1 -0
- attocode_intel/_internal/integrations/mcp/client.py +253 -0
- attocode_intel/_internal/integrations/security/__init__.py +1 -0
- attocode_intel/_internal/integrations/security/custom_rules.py +176 -0
- attocode_intel/_internal/integrations/security/dataflow.py +522 -0
- attocode_intel/_internal/integrations/security/dependency_audit.py +303 -0
- attocode_intel/_internal/integrations/security/matcher.py +44 -0
- attocode_intel/_internal/integrations/security/patterns.py +724 -0
- attocode_intel/_internal/integrations/security/scanner.py +351 -0
- attocode_intel/_internal/integrations/utilities/__init__.py +1 -0
- attocode_intel/_internal/integrations/utilities/ignore.py +64 -0
- attocode_intel/_internal/integrations/utilities/token_estimate.py +51 -0
- attocode_intel/_internal/tricks/__init__.py +1 -0
- attocode_intel/_internal/tricks/recursive_context.py +357 -0
- attocode_intel/_internal/types/__init__.py +1 -0
- attocode_intel/_internal/types/events.py +289 -0
- attocode_intel/_internal/types/severity.py +15 -0
- attocode_intel/_shared.py +341 -0
- attocode_intel/analysis_coverage.py +56 -0
- attocode_intel/api/__init__.py +1 -0
- attocode_intel/api/access.py +32 -0
- attocode_intel/api/app.py +247 -0
- attocode_intel/api/auth/__init__.py +128 -0
- attocode_intel/api/auth/api_keys.py +61 -0
- attocode_intel/api/auth/context.py +34 -0
- attocode_intel/api/auth/jwt.py +66 -0
- attocode_intel/api/auth/oauth.py +146 -0
- attocode_intel/api/auth/passwords.py +15 -0
- attocode_intel/api/deps.py +351 -0
- attocode_intel/api/knowledge_bridge.py +13 -0
- attocode_intel/api/mcp_transport.py +51 -0
- attocode_intel/api/middleware.py +411 -0
- attocode_intel/api/models.py +698 -0
- attocode_intel/api/providers/__init__.py +1 -0
- attocode_intel/api/providers/db_provider.py +1106 -0
- attocode_intel/api/providers/local_provider.py +476 -0
- attocode_intel/api/providers/remote_provider.py +740 -0
- attocode_intel/api/repository_source.py +21 -0
- attocode_intel/api/routes/__init__.py +1 -0
- attocode_intel/api/routes/_pin_helper.py +158 -0
- attocode_intel/api/routes/activity.py +148 -0
- attocode_intel/api/routes/adr.py +102 -0
- attocode_intel/api/routes/analysis.py +515 -0
- attocode_intel/api/routes/api_keys.py +170 -0
- attocode_intel/api/routes/auth.py +428 -0
- attocode_intel/api/routes/branches.py +262 -0
- attocode_intel/api/routes/cross_repo_search.py +198 -0
- attocode_intel/api/routes/embeddings.py +386 -0
- attocode_intel/api/routes/files.py +132 -0
- attocode_intel/api/routes/files_v2.py +388 -0
- attocode_intel/api/routes/gc.py +322 -0
- attocode_intel/api/routes/git_v2.py +433 -0
- attocode_intel/api/routes/graph.py +174 -0
- attocode_intel/api/routes/graph_viz.py +160 -0
- attocode_intel/api/routes/health.py +62 -0
- attocode_intel/api/routes/history.py +175 -0
- attocode_intel/api/routes/jobs.py +84 -0
- attocode_intel/api/routes/learning.py +205 -0
- attocode_intel/api/routes/lsp.py +272 -0
- attocode_intel/api/routes/notify.py +623 -0
- attocode_intel/api/routes/operations.py +27 -0
- attocode_intel/api/routes/orgs.py +924 -0
- attocode_intel/api/routes/preferences.py +100 -0
- attocode_intel/api/routes/presence.py +42 -0
- attocode_intel/api/routes/projects.py +116 -0
- attocode_intel/api/routes/repos.py +47 -0
- attocode_intel/api/routes/search.py +180 -0
- attocode_intel/api/routes/snapshots.py +614 -0
- attocode_intel/api/routes/webhooks.py +313 -0
- attocode_intel/api/routes/websocket.py +116 -0
- attocode_intel/api/static/graph.html +239 -0
- attocode_intel/api/utils.py +96 -0
- attocode_intel/artifacts/__init__.py +45 -0
- attocode_intel/artifacts/hashing.py +230 -0
- attocode_intel/artifacts/provenance.py +190 -0
- attocode_intel/artifacts/retrieval_pin.py +159 -0
- attocode_intel/audit.py +59 -0
- attocode_intel/bug_finder.py +206 -0
- attocode_intel/bundle.py +80 -0
- attocode_intel/catalog.py +242 -0
- attocode_intel/change_review.py +75 -0
- attocode_intel/cli.py +3062 -0
- attocode_intel/community.py +63 -0
- attocode_intel/confidence/__init__.py +144 -0
- attocode_intel/confidence/jev.py +234 -0
- attocode_intel/confidence/llm.py +330 -0
- attocode_intel/confidence/redact.py +44 -0
- attocode_intel/confidence/settings.py +74 -0
- attocode_intel/config.py +256 -0
- attocode_intel/crypto.py +31 -0
- attocode_intel/db/__init__.py +7 -0
- attocode_intel/db/base.py +33 -0
- attocode_intel/db/engine.py +61 -0
- attocode_intel/db/models.py +529 -0
- attocode_intel/entrypoint.py +185 -0
- attocode_intel/focused_evidence.py +130 -0
- attocode_intel/freshness.py +154 -0
- attocode_intel/gateway.py +734 -0
- attocode_intel/git/__init__.py +7 -0
- attocode_intel/git/credentials.py +75 -0
- attocode_intel/git/manager.py +493 -0
- attocode_intel/git/models.py +96 -0
- attocode_intel/git/storage.py +98 -0
- attocode_intel/graph_query_parser.py +761 -0
- attocode_intel/helpers.py +712 -0
- attocode_intel/indexing/__init__.py +29 -0
- attocode_intel/indexing/debouncer.py +136 -0
- attocode_intel/indexing/delta_indexer.py +151 -0
- attocode_intel/indexing/full_indexer.py +323 -0
- attocode_intel/indexing/incremental.py +240 -0
- attocode_intel/indexing/parser.py +403 -0
- attocode_intel/indexing/summarizer.py +264 -0
- attocode_intel/installer.py +1896 -0
- attocode_intel/knowledge.py +269 -0
- attocode_intel/local_knowledge.py +77 -0
- attocode_intel/migrations/alembic.ini +36 -0
- attocode_intel/migrations/env.py +71 -0
- attocode_intel/migrations/script.py.mako +27 -0
- attocode_intel/migrations/versions/001_initial_schema.py +219 -0
- attocode_intel/migrations/versions/002_branch_version_and_embeddings.py +61 -0
- attocode_intel/migrations/versions/003_embedding_uniqueness_and_cascade.py +51 -0
- attocode_intel/migrations/versions/004_audit_events.py +38 -0
- attocode_intel/migrations/versions/005_user_preferences.py +30 -0
- attocode_intel/migrations/versions/006_google_oauth.py +27 -0
- attocode_intel/migrations/versions/007_pgvector_embeddings.py +28 -0
- attocode_intel/migrations/versions/008_symbol_references.py +37 -0
- attocode_intel/migrations/versions/009_webhook_secret_encryption.py +25 -0
- attocode_intel/migrations/versions/010_add_commits_table.py +40 -0
- attocode_intel/migrations/versions/011_commit_changed_files.py +25 -0
- attocode_intel/migrations/versions/012_branch_merged_at.py +25 -0
- attocode_intel/migrations/versions/013_revoked_tokens.py +32 -0
- attocode_intel/migrations/versions/014_blame_hunks.py +40 -0
- attocode_intel/migrations/versions/015_commit_file_stats.py +40 -0
- attocode_intel/migrations/versions/016_embedding_provenance_columns.py +179 -0
- attocode_intel/migrations/versions/017_provenance_table.py +102 -0
- attocode_intel/migrations/versions/018_repo_snapshots.py +178 -0
- attocode_intel/migrations/versions/019_symref_caller.py +42 -0
- attocode_intel/migrations/versions/020_shared_knowledge.py +29 -0
- attocode_intel/onboarding.py +258 -0
- attocode_intel/output.py +178 -0
- attocode_intel/precision.py +133 -0
- attocode_intel/presence.py +160 -0
- attocode_intel/probe.py +110 -0
- attocode_intel/project_dir.py +97 -0
- attocode_intel/pubsub.py +113 -0
- attocode_intel/query_ranking.py +242 -0
- attocode_intel/readiness.py +972 -0
- attocode_intel/redis.py +51 -0
- attocode_intel/reference_pages.py +60 -0
- attocode_intel/remote.py +131 -0
- attocode_intel/repo_ranker.py +246 -0
- attocode_intel/request_context.py +82 -0
- attocode_intel/rules/__init__.py +28 -0
- attocode_intel/rules/ci.py +320 -0
- attocode_intel/rules/combinators.py +293 -0
- attocode_intel/rules/enricher.py +127 -0
- attocode_intel/rules/evolution.py +452 -0
- attocode_intel/rules/executor.py +531 -0
- attocode_intel/rules/filters/__init__.py +5 -0
- attocode_intel/rules/filters/pipeline.py +151 -0
- attocode_intel/rules/formatter.py +198 -0
- attocode_intel/rules/hygiene.py +242 -0
- attocode_intel/rules/importers/__init__.py +1 -0
- attocode_intel/rules/importers/semgrep.py +382 -0
- attocode_intel/rules/loader.py +292 -0
- attocode_intel/rules/marketplace.py +375 -0
- attocode_intel/rules/metavar.py +181 -0
- attocode_intel/rules/model.py +317 -0
- attocode_intel/rules/packs/__init__.py +1 -0
- attocode_intel/rules/packs/community/_license_templates/Apache-2.0.txt +176 -0
- attocode_intel/rules/packs/community/_license_templates/MIT.txt +21 -0
- attocode_intel/rules/packs/community/bandit-python/LICENSE +176 -0
- attocode_intel/rules/packs/community/bandit-python/NOTICE +13 -0
- attocode_intel/rules/packs/community/bandit-python/PORTING.md +23 -0
- attocode_intel/rules/packs/community/bandit-python/manifest.yaml +14 -0
- attocode_intel/rules/packs/community/bandit-python/rules/B105-hardcoded-password.yaml +23 -0
- attocode_intel/rules/packs/community/bandit-python/rules/B303-weak-md5.yaml +21 -0
- attocode_intel/rules/packs/community/bandit-python/rules/B304-weak-des.yaml +20 -0
- attocode_intel/rules/packs/community/bandit-python/rules/B307-use-of-eval.yaml +22 -0
- attocode_intel/rules/packs/community/bandit-python/rules/B324-weak-sha1.yaml +21 -0
- attocode_intel/rules/packs/community/bandit-python/rules/B501-no-cert-validation.yaml +23 -0
- attocode_intel/rules/packs/community/bandit-python/rules/B602-shell-true.yaml +22 -0
- attocode_intel/rules/packs/community/bandit-python/rules/B608-sqli.yaml +22 -0
- attocode_intel/rules/packs/community/eslint-typescript/LICENSE +21 -0
- attocode_intel/rules/packs/community/eslint-typescript/NOTICE +13 -0
- attocode_intel/rules/packs/community/eslint-typescript/PORTING.md +23 -0
- attocode_intel/rules/packs/community/eslint-typescript/manifest.yaml +14 -0
- attocode_intel/rules/packs/community/eslint-typescript/rules/eqeqeq.yaml +20 -0
- attocode_intel/rules/packs/community/eslint-typescript/rules/no-eval.yaml +22 -0
- attocode_intel/rules/packs/community/eslint-typescript/rules/no-implied-eval.yaml +20 -0
- attocode_intel/rules/packs/community/eslint-typescript/rules/no-new-func.yaml +21 -0
- attocode_intel/rules/packs/community/eslint-typescript/rules/no-throw-literal.yaml +19 -0
- attocode_intel/rules/packs/community/eslint-typescript/rules/no-var.yaml +20 -0
- attocode_intel/rules/packs/community/eslint-typescript/rules/no-with.yaml +18 -0
- attocode_intel/rules/packs/community/gosec-go/LICENSE +176 -0
- attocode_intel/rules/packs/community/gosec-go/NOTICE +13 -0
- attocode_intel/rules/packs/community/gosec-go/PORTING.md +23 -0
- attocode_intel/rules/packs/community/gosec-go/manifest.yaml +14 -0
- attocode_intel/rules/packs/community/gosec-go/rules/G101-hardcoded-credentials.yaml +22 -0
- attocode_intel/rules/packs/community/gosec-go/rules/G102-bind-all-interfaces.yaml +21 -0
- attocode_intel/rules/packs/community/gosec-go/rules/G103-unsafe-block.yaml +21 -0
- attocode_intel/rules/packs/community/gosec-go/rules/G201-sql-format.yaml +21 -0
- attocode_intel/rules/packs/community/gosec-go/rules/G304-file-inclusion.yaml +23 -0
- attocode_intel/rules/packs/community/gosec-go/rules/G401-weak-cipher.yaml +20 -0
- attocode_intel/rules/packs/community/gosec-go/rules/G402-tls-min-version.yaml +20 -0
- attocode_intel/rules/packs/community/gosec-go/rules/G501-md5-import.yaml +21 -0
- attocode_intel/rules/packs/examples/cpp/manifest.yaml +4 -0
- attocode_intel/rules/packs/examples/cpp/rules/security.yaml +90 -0
- attocode_intel/rules/packs/examples/cpp/rules/structural.yaml +42 -0
- attocode_intel/rules/packs/examples/csharp/manifest.yaml +4 -0
- attocode_intel/rules/packs/examples/csharp/rules/security.yaml +69 -0
- attocode_intel/rules/packs/examples/csharp/rules/structural.yaml +42 -0
- attocode_intel/rules/packs/examples/go/manifest.yaml +4 -0
- attocode_intel/rules/packs/examples/go/rules/performance.yaml +136 -0
- attocode_intel/rules/packs/examples/go/rules/security.yaml +48 -0
- attocode_intel/rules/packs/examples/go/rules/structural.yaml +58 -0
- attocode_intel/rules/packs/examples/go/taint/sanitizers.yaml +24 -0
- attocode_intel/rules/packs/examples/go/taint/sinks.yaml +51 -0
- attocode_intel/rules/packs/examples/go/taint/sources.yaml +26 -0
- attocode_intel/rules/packs/examples/java/manifest.yaml +4 -0
- attocode_intel/rules/packs/examples/java/rules/correctness.yaml +55 -0
- attocode_intel/rules/packs/examples/java/rules/structural.yaml +48 -0
- attocode_intel/rules/packs/examples/java/taint/sinks.yaml +37 -0
- attocode_intel/rules/packs/examples/java/taint/sources.yaml +21 -0
- attocode_intel/rules/packs/examples/kotlin/manifest.yaml +4 -0
- attocode_intel/rules/packs/examples/kotlin/rules/correctness.yaml +50 -0
- attocode_intel/rules/packs/examples/kotlin/rules/structural.yaml +38 -0
- attocode_intel/rules/packs/examples/php/manifest.yaml +4 -0
- attocode_intel/rules/packs/examples/php/rules/security.yaml +66 -0
- attocode_intel/rules/packs/examples/php/rules/structural.yaml +41 -0
- attocode_intel/rules/packs/examples/python/manifest.yaml +4 -0
- attocode_intel/rules/packs/examples/python/rules/performance.yaml +90 -0
- attocode_intel/rules/packs/examples/python/rules/structural.yaml +55 -0
- attocode_intel/rules/packs/examples/ruby/manifest.yaml +4 -0
- attocode_intel/rules/packs/examples/ruby/rules/security.yaml +73 -0
- attocode_intel/rules/packs/examples/ruby/rules/structural.yaml +41 -0
- attocode_intel/rules/packs/examples/rust/manifest.yaml +4 -0
- attocode_intel/rules/packs/examples/rust/rules/correctness.yaml +51 -0
- attocode_intel/rules/packs/examples/rust/rules/structural.yaml +49 -0
- attocode_intel/rules/packs/examples/typescript/manifest.yaml +4 -0
- attocode_intel/rules/packs/examples/typescript/rules/performance.yaml +70 -0
- attocode_intel/rules/packs/examples/typescript/rules/structural.yaml +48 -0
- attocode_intel/rules/packs/pack_loader.py +244 -0
- attocode_intel/rules/plugins/__init__.py +1 -0
- attocode_intel/rules/plugins/plugin_loader.py +132 -0
- attocode_intel/rules/profiling.py +332 -0
- attocode_intel/rules/registry.py +213 -0
- attocode_intel/rules/sarif.py +187 -0
- attocode_intel/rules/synthesis.py +420 -0
- attocode_intel/rules/testing.py +326 -0
- attocode_intel/server.py +734 -0
- attocode_intel/service.py +3416 -0
- attocode_intel/sharing.py +57 -0
- attocode_intel/storage/__init__.py +6 -0
- attocode_intel/storage/branch_overlay.py +352 -0
- attocode_intel/storage/content_store.py +220 -0
- attocode_intel/storage/dependency_store.py +143 -0
- attocode_intel/storage/diff_engine.py +187 -0
- attocode_intel/storage/embedding_store.py +594 -0
- attocode_intel/storage/provenance_store.py +137 -0
- attocode_intel/storage/security_scanner_db.py +164 -0
- attocode_intel/storage/symbol_store.py +160 -0
- attocode_intel/structural_evidence.py +144 -0
- attocode_intel/symbol_inspection.py +141 -0
- attocode_intel/symbol_links.py +167 -0
- attocode_intel/telemetry.py +42 -0
- attocode_intel/test_ranking.py +73 -0
- attocode_intel/testing/__init__.py +43 -0
- attocode_intel/testing/fixtures.py +122 -0
- attocode_intel/testing/helpers.py +75 -0
- attocode_intel/testing/mocks.py +99 -0
- attocode_intel/tools/__init__.py +2 -0
- attocode_intel/tools/adr_tools.py +628 -0
- attocode_intel/tools/analysis_tools.py +382 -0
- attocode_intel/tools/composite_tools.py +695 -0
- attocode_intel/tools/cross_mode_tools.py +173 -0
- attocode_intel/tools/dead_code_tools.py +582 -0
- attocode_intel/tools/distill_tools.py +353 -0
- attocode_intel/tools/frecency_tools.py +220 -0
- attocode_intel/tools/fuzzy_tools.py +255 -0
- attocode_intel/tools/history_tools.py +486 -0
- attocode_intel/tools/learning_tools.py +222 -0
- attocode_intel/tools/lsp_tools.py +218 -0
- attocode_intel/tools/maintenance_tools.py +1662 -0
- attocode_intel/tools/navigation_tools.py +220 -0
- attocode_intel/tools/overlay_tools.py +540 -0
- attocode_intel/tools/pin_store.py +432 -0
- attocode_intel/tools/pin_tools.py +266 -0
- attocode_intel/tools/query_constraints_tools.py +262 -0
- attocode_intel/tools/query_history_tools.py +158 -0
- attocode_intel/tools/readiness_tools.py +44 -0
- attocode_intel/tools/rule_tools.py +992 -0
- attocode_intel/tools/search_tools.py +770 -0
- attocode_intel/tools/snapshot_tools.py +624 -0
- attocode_intel/workers/__init__.py +1 -0
- attocode_intel/workers/job_utils.py +111 -0
- attocode_intel/workers/jobs.py +573 -0
- attocode_intel/workers/run.py +30 -0
- attocode_intel/workers/settings.py +61 -0
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
"""Dependency injection for the HTTP API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import uuid
|
|
7
|
+
from typing import TYPE_CHECKING, Annotated
|
|
8
|
+
|
|
9
|
+
from fastapi import HTTPException, Query
|
|
10
|
+
|
|
11
|
+
from attocode_intel.config import CodeIntelConfig
|
|
12
|
+
from attocode_intel.service import CodeIntelService
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from collections.abc import AsyncGenerator
|
|
16
|
+
|
|
17
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
18
|
+
|
|
19
|
+
from attocode_intel.api.auth.context import AuthContext
|
|
20
|
+
from attocode_intel.git.manager import GitRepoManager
|
|
21
|
+
|
|
22
|
+
# N2: Shared BranchParam type alias — used by all route modules
|
|
23
|
+
BranchParam = Annotated[str, Query(alias="branch", description="Branch name (empty = default/working dir)")]
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
# Module-level singletons set during app startup
|
|
28
|
+
_config: CodeIntelConfig | None = None
|
|
29
|
+
_services: dict[str, CodeIntelService] = {}
|
|
30
|
+
_default_project_id: str = ""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def configure(config: CodeIntelConfig) -> None:
|
|
34
|
+
"""Initialize the DI container with a config. Called once at app startup."""
|
|
35
|
+
global _config, _default_project_id
|
|
36
|
+
if _config is not None:
|
|
37
|
+
logger.warning("configure() called more than once; overwriting previous config")
|
|
38
|
+
_config = config
|
|
39
|
+
if config.project_dir:
|
|
40
|
+
svc = CodeIntelService.get_instance(config.project_dir, config)
|
|
41
|
+
_default_project_id = "default"
|
|
42
|
+
_services["default"] = svc
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def reset() -> None:
|
|
46
|
+
"""Reset all state. For test isolation only."""
|
|
47
|
+
global _config, _default_project_id
|
|
48
|
+
_config = None
|
|
49
|
+
_services.clear()
|
|
50
|
+
_default_project_id = ""
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def get_config() -> CodeIntelConfig:
|
|
54
|
+
"""Return the active config."""
|
|
55
|
+
if _config is None:
|
|
56
|
+
return CodeIntelConfig.from_env()
|
|
57
|
+
return _config
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def ensure_branch_supported(branch: str, *, allow_service_mode: bool = False) -> None:
|
|
61
|
+
"""Reject unsupported branch scoping.
|
|
62
|
+
|
|
63
|
+
Local/CLI mode uses the working tree directly, so non-empty branch values
|
|
64
|
+
are not supported. Text v1 routes also reject branch overlays unless the
|
|
65
|
+
caller explicitly opts into service-mode handling.
|
|
66
|
+
"""
|
|
67
|
+
if not branch:
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
config = get_config()
|
|
71
|
+
if config.is_service_mode and allow_service_mode:
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
detail = (
|
|
75
|
+
"Branch scoping is only supported on branch-aware service-mode endpoints; "
|
|
76
|
+
"this endpoint uses the working tree and does not accept a non-empty branch."
|
|
77
|
+
)
|
|
78
|
+
raise HTTPException(status_code=422, detail=detail)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def get_service(project_id: str = "") -> CodeIntelService:
|
|
82
|
+
"""Return the CodeIntelService for a project.
|
|
83
|
+
|
|
84
|
+
For Phase 1, only a single project is supported (the default).
|
|
85
|
+
Multi-project support comes in Phase 2.
|
|
86
|
+
"""
|
|
87
|
+
pid = project_id or _default_project_id
|
|
88
|
+
if pid not in _services:
|
|
89
|
+
raise ValueError(f"Project '{pid}' not found. Register it first via POST /api/v1/projects")
|
|
90
|
+
return _services[pid]
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
async def get_service_or_404(project_id: str) -> CodeIntelService:
|
|
94
|
+
"""Return the service for a project or raise HTTP 404.
|
|
95
|
+
|
|
96
|
+
Supports both CLI-mode string IDs ("default") and service-mode UUIDs.
|
|
97
|
+
For UUIDs in service mode, looks up the repo path from the DB.
|
|
98
|
+
"""
|
|
99
|
+
# Fast path: already registered
|
|
100
|
+
if project_id in _services:
|
|
101
|
+
return _services[project_id]
|
|
102
|
+
pid = project_id or _default_project_id
|
|
103
|
+
if pid in _services:
|
|
104
|
+
return _services[pid]
|
|
105
|
+
|
|
106
|
+
# Service mode: try UUID lookup from DB
|
|
107
|
+
config = get_config()
|
|
108
|
+
if config.is_service_mode:
|
|
109
|
+
try:
|
|
110
|
+
uuid.UUID(project_id)
|
|
111
|
+
except ValueError:
|
|
112
|
+
pass
|
|
113
|
+
else:
|
|
114
|
+
from attocode_intel.db.engine import get_engine
|
|
115
|
+
|
|
116
|
+
engine = get_engine()
|
|
117
|
+
if engine is not None:
|
|
118
|
+
from sqlalchemy import text
|
|
119
|
+
|
|
120
|
+
async with engine.connect() as conn:
|
|
121
|
+
result = await conn.execute(
|
|
122
|
+
text("SELECT clone_path, local_path FROM repositories WHERE id = :rid"),
|
|
123
|
+
{"rid": project_id},
|
|
124
|
+
)
|
|
125
|
+
row = result.first()
|
|
126
|
+
|
|
127
|
+
if row is not None:
|
|
128
|
+
path = row[0] or row[1]
|
|
129
|
+
if not path:
|
|
130
|
+
raise HTTPException(
|
|
131
|
+
status_code=422,
|
|
132
|
+
detail=f"Repository '{project_id}' has no indexed content yet",
|
|
133
|
+
)
|
|
134
|
+
return register_project(project_id, path)
|
|
135
|
+
|
|
136
|
+
raise HTTPException(status_code=404, detail=f"Project '{project_id}' not found")
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def register_project(project_id: str, path: str, name: str = "") -> CodeIntelService:
|
|
140
|
+
"""Register a new project and return its service instance."""
|
|
141
|
+
svc = CodeIntelService.get_instance(path, _config)
|
|
142
|
+
_services[project_id] = svc
|
|
143
|
+
return svc
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def list_projects() -> dict[str, CodeIntelService]:
|
|
147
|
+
"""Return all registered projects."""
|
|
148
|
+
return dict(_services)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def get_default_project_id() -> str:
|
|
152
|
+
"""Return the default project ID."""
|
|
153
|
+
return _default_project_id
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
# --- Provider factories ---
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
async def get_analysis_provider(project_id: str):
|
|
160
|
+
"""Return the appropriate analysis provider based on mode."""
|
|
161
|
+
config = get_config()
|
|
162
|
+
if config.is_service_mode:
|
|
163
|
+
from attocode_intel.api.providers.db_provider import DbAnalysisProvider
|
|
164
|
+
|
|
165
|
+
return DbAnalysisProvider(project_id)
|
|
166
|
+
|
|
167
|
+
from attocode_intel.api.providers.local_provider import LocalAnalysisProvider
|
|
168
|
+
|
|
169
|
+
svc = await get_service_or_404(project_id)
|
|
170
|
+
return LocalAnalysisProvider(svc)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
async def get_search_provider(project_id: str):
|
|
174
|
+
"""Return the appropriate search provider based on mode."""
|
|
175
|
+
config = get_config()
|
|
176
|
+
if config.is_service_mode:
|
|
177
|
+
from attocode_intel.api.providers.db_provider import DbSearchProvider
|
|
178
|
+
|
|
179
|
+
return DbSearchProvider(project_id)
|
|
180
|
+
|
|
181
|
+
from attocode_intel.api.providers.local_provider import LocalSearchProvider
|
|
182
|
+
|
|
183
|
+
svc = await get_service_or_404(project_id)
|
|
184
|
+
return LocalSearchProvider(svc)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
async def get_graph_provider(project_id: str):
|
|
188
|
+
"""Return the appropriate graph provider based on mode."""
|
|
189
|
+
config = get_config()
|
|
190
|
+
if config.is_service_mode:
|
|
191
|
+
from attocode_intel.api.providers.db_provider import DbGraphProvider
|
|
192
|
+
|
|
193
|
+
return DbGraphProvider(project_id)
|
|
194
|
+
|
|
195
|
+
from attocode_intel.api.providers.local_provider import LocalGraphProvider
|
|
196
|
+
|
|
197
|
+
svc = await get_service_or_404(project_id)
|
|
198
|
+
return LocalGraphProvider(svc)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
async def get_lsp_provider(project_id: str):
|
|
202
|
+
"""Return the LSP provider. Uses DbLSPProvider in service mode for remote repos."""
|
|
203
|
+
config = get_config()
|
|
204
|
+
if config.is_service_mode:
|
|
205
|
+
from attocode_intel.api.providers.db_provider import DbLSPProvider
|
|
206
|
+
|
|
207
|
+
return DbLSPProvider(project_id)
|
|
208
|
+
|
|
209
|
+
from attocode_intel.api.providers.local_provider import LocalLSPProvider
|
|
210
|
+
|
|
211
|
+
svc = await get_service_or_404(project_id)
|
|
212
|
+
return LocalLSPProvider(svc)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
# --- Branch context ---
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class BranchContext:
|
|
219
|
+
"""Resolved branch context for service mode queries.
|
|
220
|
+
|
|
221
|
+
Contains the branch ID and resolved manifest (path → content_sha).
|
|
222
|
+
Passed to service methods so all queries are branch-aware.
|
|
223
|
+
"""
|
|
224
|
+
|
|
225
|
+
def __init__(
|
|
226
|
+
self,
|
|
227
|
+
branch_id: uuid.UUID,
|
|
228
|
+
branch_name: str,
|
|
229
|
+
manifest: dict[str, str],
|
|
230
|
+
version: int = 0,
|
|
231
|
+
) -> None:
|
|
232
|
+
self.branch_id = branch_id
|
|
233
|
+
self.branch_name = branch_name
|
|
234
|
+
self.manifest = manifest
|
|
235
|
+
self.version = version
|
|
236
|
+
self._sha_to_path: dict[str, str] | None = None
|
|
237
|
+
|
|
238
|
+
@property
|
|
239
|
+
def content_shas(self) -> set[str]:
|
|
240
|
+
return set(self.manifest.values())
|
|
241
|
+
|
|
242
|
+
@property
|
|
243
|
+
def sha_to_path(self) -> dict[str, str]:
|
|
244
|
+
if self._sha_to_path is None:
|
|
245
|
+
self._sha_to_path = {sha: path for path, sha in self.manifest.items()}
|
|
246
|
+
return self._sha_to_path
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
async def get_branch_context(
|
|
250
|
+
repo_id: uuid.UUID,
|
|
251
|
+
branch_name: str,
|
|
252
|
+
session: AsyncSession,
|
|
253
|
+
) -> BranchContext:
|
|
254
|
+
"""Resolve branch context for a query. Returns BranchContext with manifest.
|
|
255
|
+
|
|
256
|
+
If branch_name is empty, uses the repository's default branch.
|
|
257
|
+
"""
|
|
258
|
+
from sqlalchemy import select
|
|
259
|
+
|
|
260
|
+
from attocode_intel.db.models import Branch, Repository
|
|
261
|
+
from attocode_intel.storage.branch_overlay import BranchOverlay
|
|
262
|
+
|
|
263
|
+
if not branch_name:
|
|
264
|
+
result = await session.execute(
|
|
265
|
+
select(Repository.default_branch).where(Repository.id == repo_id)
|
|
266
|
+
)
|
|
267
|
+
branch_name = result.scalar_one_or_none() or "main"
|
|
268
|
+
|
|
269
|
+
result = await session.execute(
|
|
270
|
+
select(Branch).where(
|
|
271
|
+
Branch.repo_id == repo_id,
|
|
272
|
+
Branch.name == branch_name,
|
|
273
|
+
)
|
|
274
|
+
)
|
|
275
|
+
branch = result.scalar_one_or_none()
|
|
276
|
+
if branch is None:
|
|
277
|
+
raise HTTPException(status_code=404, detail=f"Branch '{branch_name}' not found")
|
|
278
|
+
|
|
279
|
+
overlay = BranchOverlay(session)
|
|
280
|
+
manifest = await overlay.resolve_manifest(branch.id)
|
|
281
|
+
version = await overlay.get_version(branch.id)
|
|
282
|
+
|
|
283
|
+
return BranchContext(
|
|
284
|
+
branch_id=branch.id,
|
|
285
|
+
branch_name=branch_name,
|
|
286
|
+
manifest=manifest,
|
|
287
|
+
version=version,
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
# --- Git manager ---
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def get_git_manager() -> GitRepoManager:
|
|
295
|
+
"""Return a GitRepoManager configured from the active config."""
|
|
296
|
+
from attocode_intel.git.manager import GitRepoManager
|
|
297
|
+
|
|
298
|
+
config = get_config()
|
|
299
|
+
return GitRepoManager(config.git_clone_dir, config.git_ssh_key_path)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
# --- Service mode dependencies ---
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
async def get_db_session() -> AsyncGenerator[AsyncSession, None]:
|
|
306
|
+
"""Yield an async DB session. Raises 503 if not in service mode."""
|
|
307
|
+
config = get_config()
|
|
308
|
+
if not config.is_service_mode:
|
|
309
|
+
raise HTTPException(status_code=503, detail="Service mode not enabled (DATABASE_URL not set)")
|
|
310
|
+
from attocode_intel.db.engine import get_session
|
|
311
|
+
|
|
312
|
+
async for session in get_session():
|
|
313
|
+
yield session
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
async def get_org_scoped_session(
|
|
317
|
+
auth: AuthContext,
|
|
318
|
+
session: AsyncSession,
|
|
319
|
+
) -> AsyncSession:
|
|
320
|
+
"""Set RLS context for org isolation, then return the session.
|
|
321
|
+
|
|
322
|
+
Uses parameterized SET to prevent SQL injection.
|
|
323
|
+
"""
|
|
324
|
+
if auth.org_id:
|
|
325
|
+
from sqlalchemy import text
|
|
326
|
+
await session.execute(
|
|
327
|
+
text("SET LOCAL app.current_org_id = :org_id").bindparams(org_id=str(auth.org_id))
|
|
328
|
+
)
|
|
329
|
+
return session
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
async def get_repo_service(
|
|
333
|
+
org_id: uuid.UUID,
|
|
334
|
+
repo_id: uuid.UUID,
|
|
335
|
+
session: AsyncSession,
|
|
336
|
+
) -> CodeIntelService:
|
|
337
|
+
"""Look up a Repository from DB and return a CodeIntelService for its local_path."""
|
|
338
|
+
from sqlalchemy import select
|
|
339
|
+
|
|
340
|
+
from attocode_intel.db.models import Repository
|
|
341
|
+
|
|
342
|
+
result = await session.execute(
|
|
343
|
+
select(Repository).where(Repository.id == repo_id, Repository.org_id == org_id)
|
|
344
|
+
)
|
|
345
|
+
repo = result.scalar_one_or_none()
|
|
346
|
+
if repo is None:
|
|
347
|
+
raise HTTPException(status_code=404, detail="Repository not found")
|
|
348
|
+
path = repo.clone_path or repo.local_path
|
|
349
|
+
if not path:
|
|
350
|
+
raise HTTPException(status_code=422, detail="Repository has no local path or clone")
|
|
351
|
+
return CodeIntelService.get_instance(path, _config)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Keep historical HTTP knowledge endpoints on the team's canonical store."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from attocode_intel.api.auth import resolve_auth
|
|
6
|
+
from attocode_intel.api.routes.operations import execute_operation
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
async def shared_operation(request, workspace, name, arguments):
|
|
10
|
+
if not request.app.state.config.is_service_mode:
|
|
11
|
+
return None
|
|
12
|
+
auth = await resolve_auth(request.headers.get("authorization"))
|
|
13
|
+
return await execute_operation(name, {"workspace": workspace, **arguments}, request, auth)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Native Streamable HTTP MCP, authenticated before protocol dispatch."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
|
|
8
|
+
from starlette.responses import JSONResponse
|
|
9
|
+
|
|
10
|
+
from attocode_intel.gateway import OperationGateway, create_mcp_server
|
|
11
|
+
from attocode_intel.remote import RemoteResolver, remote_identity, remote_profile, remote_workspace
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MCPTransport:
|
|
15
|
+
def __init__(self, config):
|
|
16
|
+
self.config = config
|
|
17
|
+
self.gateway = OperationGateway(
|
|
18
|
+
config.project_dir,
|
|
19
|
+
os.environ.get("ATTOCODE_MCP_PROFILE", "full"),
|
|
20
|
+
RemoteResolver(config) if config.is_service_mode else None,
|
|
21
|
+
watch=os.environ.get("ATTOCODE_INTEL_WATCH", "1") != "0",
|
|
22
|
+
watch_debounce=int(os.environ.get("ATTOCODE_INTEL_WATCH_DEBOUNCE", "500")),
|
|
23
|
+
)
|
|
24
|
+
self.manager = StreamableHTTPSessionManager(
|
|
25
|
+
create_mcp_server(self.gateway), json_response=True, stateless=True
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
async def __call__(self, scope, receive, send):
|
|
29
|
+
from fastapi import HTTPException
|
|
30
|
+
|
|
31
|
+
from attocode_intel.api.auth import resolve_auth
|
|
32
|
+
|
|
33
|
+
if scope["type"] != "http":
|
|
34
|
+
return
|
|
35
|
+
headers = {k.decode().lower(): v.decode() for k, v in scope.get("headers", [])}
|
|
36
|
+
try:
|
|
37
|
+
auth = await resolve_auth(headers.get("authorization"))
|
|
38
|
+
except HTTPException as exc:
|
|
39
|
+
await JSONResponse({"detail": exc.detail}, status_code=exc.status_code)(
|
|
40
|
+
scope, receive, send
|
|
41
|
+
)
|
|
42
|
+
return
|
|
43
|
+
token = remote_identity.set(auth)
|
|
44
|
+
workspace_token = remote_workspace.set(headers.get("x-attocode-workspace", ""))
|
|
45
|
+
profile_token = remote_profile.set(headers.get("x-attocode-profile", self.gateway.profile))
|
|
46
|
+
try:
|
|
47
|
+
await self.manager.handle_request(scope, receive, send)
|
|
48
|
+
finally:
|
|
49
|
+
remote_identity.reset(token)
|
|
50
|
+
remote_workspace.reset(workspace_token)
|
|
51
|
+
remote_profile.reset(profile_token)
|