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
attocode_intel/config.py
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"""Configuration for the code intelligence service."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import ipaddress
|
|
6
|
+
import os
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def is_loopback(host: str) -> bool:
|
|
12
|
+
"""True for a host that only this computer can reach."""
|
|
13
|
+
if host == "localhost":
|
|
14
|
+
return True
|
|
15
|
+
try:
|
|
16
|
+
return ipaddress.ip_address(host).is_loopback
|
|
17
|
+
except ValueError:
|
|
18
|
+
return False
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(slots=True)
|
|
22
|
+
class CodeIntelConfig:
|
|
23
|
+
"""Configuration for the CodeIntelService."""
|
|
24
|
+
|
|
25
|
+
# Core settings (Phase 1)
|
|
26
|
+
project_dir: str = ""
|
|
27
|
+
host: str = "127.0.0.1"
|
|
28
|
+
port: int = 8080
|
|
29
|
+
api_key: str = ""
|
|
30
|
+
# No other web origin can call the API unless ATTOCODE_CORS_ORIGINS names it.
|
|
31
|
+
cors_origins: list[str] = field(default_factory=list)
|
|
32
|
+
log_level: str = "info"
|
|
33
|
+
|
|
34
|
+
# Indexing settings
|
|
35
|
+
file_cap: int = 5000 # Max files to index; higher = better coverage, slower bootstrap
|
|
36
|
+
|
|
37
|
+
# Service mode (multi-user)
|
|
38
|
+
database_url: str = ""
|
|
39
|
+
secret_key: str = ""
|
|
40
|
+
redis_url: str = ""
|
|
41
|
+
|
|
42
|
+
# Git clone settings
|
|
43
|
+
git_clone_dir: str = "/var/lib/code-intel/repos"
|
|
44
|
+
git_clone_max_gb: float = 50.0
|
|
45
|
+
git_ssh_key_path: str = ""
|
|
46
|
+
|
|
47
|
+
# Embedding settings
|
|
48
|
+
embedding_model: str = "" # auto-detect or explicit: all-MiniLM-L6-v2 | nomic-embed-text | openai
|
|
49
|
+
embedding_dimension: int = 0 # 0 = auto from model
|
|
50
|
+
embedding_nl_mode: str = "none" # "none" = embed raw code, "heuristic" = code-to-NL before embedding
|
|
51
|
+
|
|
52
|
+
# Optional local-only reranker. Both values must be supplied; a model is
|
|
53
|
+
# never downloaded or loaded synchronously by a search request.
|
|
54
|
+
local_reranker_path: str = ""
|
|
55
|
+
local_reranker_sha256: str = ""
|
|
56
|
+
|
|
57
|
+
# Optional shortlist ranking. Empty provider keeps the legacy local
|
|
58
|
+
# cross-encoder opt-in; "off" disables every model-based ranker.
|
|
59
|
+
ranking_provider: str = ""
|
|
60
|
+
ranking_endpoint: str = ""
|
|
61
|
+
ranking_model: str = ""
|
|
62
|
+
ranking_auth_env: str = ""
|
|
63
|
+
ranking_allow_remote: bool = False
|
|
64
|
+
ranking_remote_workspace: str = ""
|
|
65
|
+
ranking_timeout_ms: int = 0 # auto: 5s loopback, 800ms remote
|
|
66
|
+
ranking_max_candidates: int = 24
|
|
67
|
+
|
|
68
|
+
# Remote connection (CLI → server bridge)
|
|
69
|
+
remote_url: str = "" # e.g. "https://code.example.com"
|
|
70
|
+
remote_token: str = "" # JWT or API key for remote server
|
|
71
|
+
remote_repo_id: str = "" # UUID of the repo on the remote server
|
|
72
|
+
|
|
73
|
+
# GC settings
|
|
74
|
+
gc_merged_branch_retention_days: int = 7
|
|
75
|
+
gc_inactive_branch_retention_days: int = 30
|
|
76
|
+
gc_content_min_age_minutes: int = 60
|
|
77
|
+
|
|
78
|
+
# Auth settings
|
|
79
|
+
jwt_expiry_minutes: int = 60
|
|
80
|
+
refresh_expiry_days: int = 30
|
|
81
|
+
github_client_id: str = ""
|
|
82
|
+
github_client_secret: str = ""
|
|
83
|
+
google_client_id: str = ""
|
|
84
|
+
google_client_secret: str = ""
|
|
85
|
+
base_url: str = "" # e.g. "https://code.example.com" — for OAuth redirect_uri
|
|
86
|
+
registration_key: str = "" # When set, required in register request to prevent open signup
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def is_service_mode(self) -> bool:
|
|
90
|
+
"""True when DATABASE_URL is set — enables multi-user service features."""
|
|
91
|
+
return bool(self.database_url)
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def effective_base_url(self) -> str:
|
|
95
|
+
"""Base URL for OAuth redirect URIs. Falls back to http://host:port."""
|
|
96
|
+
if self.base_url:
|
|
97
|
+
return self.base_url.rstrip("/")
|
|
98
|
+
return f"http://{self.host}:{self.port}"
|
|
99
|
+
|
|
100
|
+
@classmethod
|
|
101
|
+
def from_env(cls) -> CodeIntelConfig:
|
|
102
|
+
"""Build config from environment variables."""
|
|
103
|
+
return cls(
|
|
104
|
+
project_dir=os.environ.get("ATTOCODE_PROJECT_DIR", ""),
|
|
105
|
+
host=os.environ.get("ATTOCODE_HOST", "127.0.0.1"),
|
|
106
|
+
port=int(os.environ.get("ATTOCODE_PORT", "8080")),
|
|
107
|
+
api_key=os.environ.get("ATTOCODE_API_KEY", ""),
|
|
108
|
+
cors_origins=[o for o in os.environ.get("ATTOCODE_CORS_ORIGINS", "").split(",") if o],
|
|
109
|
+
log_level=os.environ.get("ATTOCODE_LOG_LEVEL", "info"),
|
|
110
|
+
file_cap=int(os.environ.get("ATTOCODE_FILE_CAP", "5000")),
|
|
111
|
+
database_url=os.environ.get("DATABASE_URL", ""),
|
|
112
|
+
secret_key=os.environ.get("SECRET_KEY", ""),
|
|
113
|
+
redis_url=os.environ.get("REDIS_URL", ""),
|
|
114
|
+
git_clone_dir=os.environ.get("GIT_CLONE_DIR", "/var/lib/code-intel/repos"),
|
|
115
|
+
git_clone_max_gb=float(os.environ.get("GIT_CLONE_MAX_GB", "50")),
|
|
116
|
+
git_ssh_key_path=os.environ.get("GIT_SSH_KEY_PATH", ""),
|
|
117
|
+
embedding_model=os.environ.get("ATTOCODE_EMBEDDING_MODEL", ""),
|
|
118
|
+
embedding_dimension=int(os.environ.get("ATTOCODE_EMBEDDING_DIMENSION", "0")),
|
|
119
|
+
embedding_nl_mode=os.environ.get("ATTOCODE_NL_EMBEDDING_MODE", "none"),
|
|
120
|
+
local_reranker_path=os.environ.get("ATTOCODE_INTEL_RERANKER_PATH", ""),
|
|
121
|
+
local_reranker_sha256=os.environ.get("ATTOCODE_INTEL_RERANKER_SHA256", ""),
|
|
122
|
+
ranking_provider=os.environ.get("ATTOCODE_INTEL_RANKING_PROVIDER", ""),
|
|
123
|
+
ranking_endpoint=os.environ.get("ATTOCODE_INTEL_RANKING_ENDPOINT", ""),
|
|
124
|
+
ranking_model=os.environ.get("ATTOCODE_INTEL_RANKING_MODEL", ""),
|
|
125
|
+
ranking_auth_env=os.environ.get("ATTOCODE_INTEL_RANKING_AUTH_ENV", ""),
|
|
126
|
+
ranking_allow_remote=os.environ.get("ATTOCODE_INTEL_RANKING_ALLOW_REMOTE", "").lower()
|
|
127
|
+
in {"1", "true", "yes", "on"},
|
|
128
|
+
ranking_remote_workspace=os.environ.get("ATTOCODE_INTEL_RANKING_REMOTE_WORKSPACE", ""),
|
|
129
|
+
ranking_timeout_ms=int(os.environ.get("ATTOCODE_INTEL_RANKING_TIMEOUT_MS", "0")),
|
|
130
|
+
ranking_max_candidates=int(os.environ.get("ATTOCODE_INTEL_RANKING_MAX_CANDIDATES", "24")),
|
|
131
|
+
gc_merged_branch_retention_days=int(os.environ.get("GC_MERGED_BRANCH_RETENTION_DAYS", "7")),
|
|
132
|
+
gc_inactive_branch_retention_days=int(os.environ.get("GC_INACTIVE_BRANCH_RETENTION_DAYS", "30")),
|
|
133
|
+
gc_content_min_age_minutes=int(os.environ.get("GC_CONTENT_MIN_AGE_MINUTES", "60")),
|
|
134
|
+
jwt_expiry_minutes=int(os.environ.get("JWT_EXPIRY_MINUTES", "60")),
|
|
135
|
+
refresh_expiry_days=int(os.environ.get("REFRESH_EXPIRY_DAYS", "30")),
|
|
136
|
+
github_client_id=os.environ.get("GITHUB_CLIENT_ID", ""),
|
|
137
|
+
github_client_secret=os.environ.get("GITHUB_CLIENT_SECRET", ""),
|
|
138
|
+
google_client_id=os.environ.get("GOOGLE_CLIENT_ID", ""),
|
|
139
|
+
google_client_secret=os.environ.get("GOOGLE_CLIENT_SECRET", ""),
|
|
140
|
+
base_url=os.environ.get("ATTOCODE_BASE_URL", ""),
|
|
141
|
+
registration_key=os.environ.get("REGISTRATION_KEY", ""),
|
|
142
|
+
remote_url=os.environ.get("ATTOCODE_REMOTE_URL", ""),
|
|
143
|
+
remote_token=os.environ.get("ATTOCODE_REMOTE_TOKEN", ""),
|
|
144
|
+
remote_repo_id=os.environ.get("ATTOCODE_REMOTE_REPO_ID", ""),
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@dataclass(slots=True)
|
|
149
|
+
class RemoteConfig:
|
|
150
|
+
"""Remote server connection configuration, persisted to .attocode/config.toml."""
|
|
151
|
+
|
|
152
|
+
server: str = ""
|
|
153
|
+
token: str = ""
|
|
154
|
+
repo_id: str = ""
|
|
155
|
+
branch_auto_detect: bool = True
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def is_configured(self) -> bool:
|
|
159
|
+
return bool(self.server and self.token)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def token_is_expired(token: str, *, leeway_seconds: int = 30) -> bool:
|
|
163
|
+
"""Best-effort check whether a JWT's ``exp`` claim is in the past.
|
|
164
|
+
|
|
165
|
+
Decodes the token payload WITHOUT verifying the signature — we don't hold
|
|
166
|
+
the remote server's signing key, we only need the expiry. Returns ``False``
|
|
167
|
+
when the token has no ``exp`` claim or can't be parsed (we can't prove it's
|
|
168
|
+
expired, so let the reachability probe decide). ``leeway_seconds`` treats a
|
|
169
|
+
token as expired slightly early so we don't start a session on a token
|
|
170
|
+
about to lapse.
|
|
171
|
+
"""
|
|
172
|
+
import base64
|
|
173
|
+
import json
|
|
174
|
+
import time
|
|
175
|
+
|
|
176
|
+
parts = token.split(".")
|
|
177
|
+
if len(parts) != 3:
|
|
178
|
+
return False
|
|
179
|
+
try:
|
|
180
|
+
payload_b64 = parts[1] + "=" * (-len(parts[1]) % 4)
|
|
181
|
+
payload = json.loads(base64.urlsafe_b64decode(payload_b64))
|
|
182
|
+
except Exception:
|
|
183
|
+
return False
|
|
184
|
+
exp = payload.get("exp")
|
|
185
|
+
if not isinstance(exp, (int, float)):
|
|
186
|
+
return False
|
|
187
|
+
return time.time() >= (float(exp) - leeway_seconds)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def load_remote_config(project_dir: str) -> RemoteConfig:
|
|
191
|
+
"""Load remote config from .attocode/config.toml, falling back to env vars."""
|
|
192
|
+
config_path = Path(project_dir) / ".attocode" / "config.toml"
|
|
193
|
+
|
|
194
|
+
rc = RemoteConfig()
|
|
195
|
+
|
|
196
|
+
if config_path.exists():
|
|
197
|
+
try:
|
|
198
|
+
import tomllib
|
|
199
|
+
data = tomllib.loads(config_path.read_text(encoding="utf-8"))
|
|
200
|
+
remote = data.get("remote", {})
|
|
201
|
+
rc.server = remote.get("server", "")
|
|
202
|
+
rc.token = remote.get("token", "")
|
|
203
|
+
rc.repo_id = remote.get("repo_id", "")
|
|
204
|
+
rc.branch_auto_detect = remote.get("branch_auto_detect", True)
|
|
205
|
+
except Exception:
|
|
206
|
+
pass
|
|
207
|
+
|
|
208
|
+
# Env vars override file config
|
|
209
|
+
if os.environ.get("ATTOCODE_REMOTE_URL"):
|
|
210
|
+
rc.server = os.environ["ATTOCODE_REMOTE_URL"]
|
|
211
|
+
if os.environ.get("ATTOCODE_REMOTE_TOKEN"):
|
|
212
|
+
rc.token = os.environ["ATTOCODE_REMOTE_TOKEN"]
|
|
213
|
+
if os.environ.get("ATTOCODE_REMOTE_REPO_ID"):
|
|
214
|
+
rc.repo_id = os.environ["ATTOCODE_REMOTE_REPO_ID"]
|
|
215
|
+
|
|
216
|
+
return rc
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def save_remote_config(project_dir: str, rc: RemoteConfig) -> Path:
|
|
220
|
+
"""Save remote config to .attocode/config.toml. Returns the config path."""
|
|
221
|
+
config_path = Path(project_dir) / ".attocode" / "config.toml"
|
|
222
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
223
|
+
|
|
224
|
+
# Preserve existing config sections
|
|
225
|
+
existing: dict = {}
|
|
226
|
+
if config_path.exists():
|
|
227
|
+
try:
|
|
228
|
+
import tomllib
|
|
229
|
+
existing = tomllib.loads(config_path.read_text(encoding="utf-8"))
|
|
230
|
+
except Exception:
|
|
231
|
+
pass
|
|
232
|
+
|
|
233
|
+
existing["remote"] = {
|
|
234
|
+
"server": rc.server,
|
|
235
|
+
"token": rc.token,
|
|
236
|
+
"repo_id": rc.repo_id,
|
|
237
|
+
"branch_auto_detect": rc.branch_auto_detect,
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
# Write TOML manually (tomllib is read-only, avoid tomli_w dependency)
|
|
241
|
+
lines: list[str] = []
|
|
242
|
+
for section, values in existing.items():
|
|
243
|
+
if isinstance(values, dict):
|
|
244
|
+
lines.append(f"[{section}]")
|
|
245
|
+
for k, v in values.items():
|
|
246
|
+
if isinstance(v, bool):
|
|
247
|
+
lines.append(f"{k} = {'true' if v else 'false'}")
|
|
248
|
+
elif isinstance(v, str):
|
|
249
|
+
esc = v.replace("\\", "\\\\").replace('"', '\\"')
|
|
250
|
+
lines.append(f'{k} = "{esc}"')
|
|
251
|
+
else:
|
|
252
|
+
lines.append(f"{k} = {v}")
|
|
253
|
+
lines.append("")
|
|
254
|
+
|
|
255
|
+
config_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
256
|
+
return config_path
|
attocode_intel/crypto.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Encryption utilities for credential storage using Fernet."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import base64
|
|
6
|
+
import hashlib
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from cryptography.fernet import Fernet
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _get_fernet() -> Fernet:
|
|
14
|
+
"""Get Fernet instance using SECRET_KEY from config."""
|
|
15
|
+
from cryptography.fernet import Fernet
|
|
16
|
+
|
|
17
|
+
from attocode_intel.api.deps import get_config
|
|
18
|
+
|
|
19
|
+
config = get_config()
|
|
20
|
+
key = hashlib.sha256(config.secret_key.encode()).digest()
|
|
21
|
+
return Fernet(base64.urlsafe_b64encode(key))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def encrypt_credential(plaintext: str) -> bytes:
|
|
25
|
+
"""Encrypt a credential value for storage."""
|
|
26
|
+
return _get_fernet().encrypt(plaintext.encode())
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def decrypt_credential(encrypted: bytes) -> str:
|
|
30
|
+
"""Decrypt a stored credential value."""
|
|
31
|
+
return _get_fernet().decrypt(encrypted).decode()
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""Database layer for service mode (PostgreSQL + SQLAlchemy async).
|
|
2
|
+
|
|
3
|
+
These modules require SQLAlchemy/asyncpg (service mode dependencies).
|
|
4
|
+
Import them directly when needed:
|
|
5
|
+
from attocode_intel.db.engine import init_engine, dispose_engine, get_session
|
|
6
|
+
from attocode_intel.db.models import Organization, User, ...
|
|
7
|
+
"""
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""SQLAlchemy declarative base and common mixins."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import uuid
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
|
|
8
|
+
from sqlalchemy import DateTime, func
|
|
9
|
+
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Base(DeclarativeBase):
|
|
13
|
+
"""Base class for all ORM models."""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TimestampMixin:
|
|
17
|
+
"""Adds created_at and updated_at columns."""
|
|
18
|
+
|
|
19
|
+
created_at: Mapped[datetime] = mapped_column(
|
|
20
|
+
DateTime(timezone=True),
|
|
21
|
+
server_default=func.now(),
|
|
22
|
+
nullable=False,
|
|
23
|
+
)
|
|
24
|
+
updated_at: Mapped[datetime] = mapped_column(
|
|
25
|
+
DateTime(timezone=True),
|
|
26
|
+
server_default=func.now(),
|
|
27
|
+
onupdate=func.now(),
|
|
28
|
+
nullable=False,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def generate_uuid() -> uuid.UUID:
|
|
33
|
+
return uuid.uuid4()
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Async SQLAlchemy engine and session management."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import TYPE_CHECKING, AsyncGenerator
|
|
7
|
+
|
|
8
|
+
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from sqlalchemy.ext.asyncio import AsyncEngine
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
_engine: AsyncEngine | None = None
|
|
16
|
+
_session_factory: async_sessionmaker[AsyncSession] | None = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def init_engine(database_url: str) -> None:
|
|
20
|
+
"""Create the async engine and session factory.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
database_url: PostgreSQL connection string (postgresql+asyncpg://...).
|
|
24
|
+
"""
|
|
25
|
+
global _engine, _session_factory
|
|
26
|
+
|
|
27
|
+
if _engine is not None:
|
|
28
|
+
logger.warning("init_engine() called more than once; disposing previous engine")
|
|
29
|
+
|
|
30
|
+
_engine = create_async_engine(
|
|
31
|
+
database_url,
|
|
32
|
+
pool_size=10,
|
|
33
|
+
max_overflow=20,
|
|
34
|
+
pool_pre_ping=True,
|
|
35
|
+
echo=False,
|
|
36
|
+
)
|
|
37
|
+
_session_factory = async_sessionmaker(_engine, expire_on_commit=False)
|
|
38
|
+
logger.info("Database engine initialized")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
async def dispose_engine() -> None:
|
|
42
|
+
"""Dispose the async engine, closing all connections."""
|
|
43
|
+
global _engine, _session_factory
|
|
44
|
+
if _engine is not None:
|
|
45
|
+
await _engine.dispose()
|
|
46
|
+
_engine = None
|
|
47
|
+
_session_factory = None
|
|
48
|
+
logger.info("Database engine disposed")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
async def get_session() -> AsyncGenerator[AsyncSession, None]:
|
|
52
|
+
"""Yield an async session. For use as a FastAPI dependency."""
|
|
53
|
+
if _session_factory is None:
|
|
54
|
+
raise RuntimeError("Database not initialized. Call init_engine() first.")
|
|
55
|
+
async with _session_factory() as session:
|
|
56
|
+
yield session
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def get_engine() -> AsyncEngine | None:
|
|
60
|
+
"""Return the current engine (or None if not initialized)."""
|
|
61
|
+
return _engine
|