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,100 @@
|
|
|
1
|
+
"""User preferences endpoints (service mode only)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from fastapi import APIRouter, Depends, HTTPException
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
from sqlalchemy import select
|
|
8
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
9
|
+
|
|
10
|
+
from attocode_intel.api.auth import resolve_auth
|
|
11
|
+
from attocode_intel.api.auth.context import AuthContext
|
|
12
|
+
from attocode_intel.api.deps import get_db_session
|
|
13
|
+
|
|
14
|
+
router = APIRouter(prefix="/api/v2/me", tags=["preferences"])
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PreferencesResponse(BaseModel):
|
|
18
|
+
preferences: dict
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PreferencesUpdateRequest(BaseModel):
|
|
22
|
+
preferences: dict
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# --- Endpoints ---
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@router.get("/preferences", response_model=PreferencesResponse)
|
|
29
|
+
async def get_preferences(
|
|
30
|
+
auth: AuthContext = Depends(resolve_auth),
|
|
31
|
+
session: AsyncSession = Depends(get_db_session),
|
|
32
|
+
) -> PreferencesResponse:
|
|
33
|
+
"""Get current user's preferences."""
|
|
34
|
+
if not auth.user_id:
|
|
35
|
+
raise HTTPException(status_code=401, detail="Authentication required")
|
|
36
|
+
|
|
37
|
+
from attocode_intel.db.models import UserPreference
|
|
38
|
+
|
|
39
|
+
result = await session.execute(
|
|
40
|
+
select(UserPreference).where(UserPreference.user_id == auth.user_id)
|
|
41
|
+
)
|
|
42
|
+
pref = result.scalar_one_or_none()
|
|
43
|
+
return PreferencesResponse(preferences=pref.preferences if pref else {})
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@router.put("/preferences", response_model=PreferencesResponse)
|
|
47
|
+
async def replace_preferences(
|
|
48
|
+
req: PreferencesUpdateRequest,
|
|
49
|
+
auth: AuthContext = Depends(resolve_auth),
|
|
50
|
+
session: AsyncSession = Depends(get_db_session),
|
|
51
|
+
) -> PreferencesResponse:
|
|
52
|
+
"""Full replace of user preferences."""
|
|
53
|
+
if not auth.user_id:
|
|
54
|
+
raise HTTPException(status_code=401, detail="Authentication required")
|
|
55
|
+
|
|
56
|
+
from attocode_intel.db.models import UserPreference
|
|
57
|
+
|
|
58
|
+
result = await session.execute(
|
|
59
|
+
select(UserPreference).where(UserPreference.user_id == auth.user_id)
|
|
60
|
+
)
|
|
61
|
+
pref = result.scalar_one_or_none()
|
|
62
|
+
|
|
63
|
+
if pref:
|
|
64
|
+
pref.preferences = req.preferences
|
|
65
|
+
else:
|
|
66
|
+
pref = UserPreference(user_id=auth.user_id, preferences=req.preferences)
|
|
67
|
+
session.add(pref)
|
|
68
|
+
|
|
69
|
+
await session.commit()
|
|
70
|
+
await session.refresh(pref)
|
|
71
|
+
return PreferencesResponse(preferences=pref.preferences)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@router.patch("/preferences", response_model=PreferencesResponse)
|
|
75
|
+
async def merge_preferences(
|
|
76
|
+
req: PreferencesUpdateRequest,
|
|
77
|
+
auth: AuthContext = Depends(resolve_auth),
|
|
78
|
+
session: AsyncSession = Depends(get_db_session),
|
|
79
|
+
) -> PreferencesResponse:
|
|
80
|
+
"""Merge update of user preferences (shallow merge)."""
|
|
81
|
+
if not auth.user_id:
|
|
82
|
+
raise HTTPException(status_code=401, detail="Authentication required")
|
|
83
|
+
|
|
84
|
+
from attocode_intel.db.models import UserPreference
|
|
85
|
+
|
|
86
|
+
result = await session.execute(
|
|
87
|
+
select(UserPreference).where(UserPreference.user_id == auth.user_id)
|
|
88
|
+
)
|
|
89
|
+
pref = result.scalar_one_or_none()
|
|
90
|
+
|
|
91
|
+
if pref:
|
|
92
|
+
merged = {**pref.preferences, **req.preferences}
|
|
93
|
+
pref.preferences = merged
|
|
94
|
+
else:
|
|
95
|
+
pref = UserPreference(user_id=auth.user_id, preferences=req.preferences)
|
|
96
|
+
session.add(pref)
|
|
97
|
+
|
|
98
|
+
await session.commit()
|
|
99
|
+
await session.refresh(pref)
|
|
100
|
+
return PreferencesResponse(preferences=pref.preferences)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Presence tracking REST endpoints (service mode only)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import uuid
|
|
6
|
+
|
|
7
|
+
from fastapi import APIRouter, Depends
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
|
|
10
|
+
from attocode_intel.api.auth import resolve_auth
|
|
11
|
+
from attocode_intel.api.auth.context import AuthContext
|
|
12
|
+
|
|
13
|
+
router = APIRouter(prefix="/api/v2/repos/{repo_id}", tags=["presence"])
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PresenceEntryResponse(BaseModel):
|
|
17
|
+
user_id: str
|
|
18
|
+
user_name: str
|
|
19
|
+
file: str
|
|
20
|
+
session_id: str
|
|
21
|
+
timestamp: float
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@router.get("/presence", response_model=list[PresenceEntryResponse])
|
|
25
|
+
async def get_repo_presence(
|
|
26
|
+
repo_id: uuid.UUID,
|
|
27
|
+
auth: AuthContext = Depends(resolve_auth),
|
|
28
|
+
) -> list[PresenceEntryResponse]:
|
|
29
|
+
"""Get current viewers of a repository (REST fallback for non-WebSocket clients)."""
|
|
30
|
+
from attocode_intel.presence import get_presence
|
|
31
|
+
|
|
32
|
+
entries = await get_presence(str(repo_id))
|
|
33
|
+
return [
|
|
34
|
+
PresenceEntryResponse(
|
|
35
|
+
user_id=e.user_id,
|
|
36
|
+
user_name=e.user_name,
|
|
37
|
+
file=e.file,
|
|
38
|
+
session_id=e.session_id,
|
|
39
|
+
timestamp=e.timestamp,
|
|
40
|
+
)
|
|
41
|
+
for e in entries
|
|
42
|
+
]
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Project management endpoints."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import os
|
|
7
|
+
import uuid
|
|
8
|
+
|
|
9
|
+
from fastapi import APIRouter, Depends, HTTPException
|
|
10
|
+
|
|
11
|
+
from attocode_intel.api.auth import verify_auth
|
|
12
|
+
from attocode_intel.api.deps import (
|
|
13
|
+
get_config,
|
|
14
|
+
get_service_or_404,
|
|
15
|
+
list_projects,
|
|
16
|
+
register_project,
|
|
17
|
+
)
|
|
18
|
+
from fastapi import Query as QueryParam
|
|
19
|
+
|
|
20
|
+
from attocode_intel.api.models import (
|
|
21
|
+
ProjectInfo,
|
|
22
|
+
ProjectListResponse,
|
|
23
|
+
ProjectRegister,
|
|
24
|
+
TextResult,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
router = APIRouter(prefix="/api/v1/projects", tags=["projects"], dependencies=[Depends(verify_auth)])
|
|
28
|
+
|
|
29
|
+
# Serializes reindex operations so concurrent requests don't corrupt state
|
|
30
|
+
_reindex_lock = asyncio.Lock()
|
|
31
|
+
|
|
32
|
+
# System directories that should never be registered as projects
|
|
33
|
+
_BLOCKED_PREFIXES = ("/etc", "/var", "/usr", "/sys", "/proc", "/dev")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@router.post("", response_model=ProjectInfo)
|
|
37
|
+
async def create_project(req: ProjectRegister) -> ProjectInfo:
|
|
38
|
+
"""Register a project (local path)."""
|
|
39
|
+
abs_path = os.path.abspath(req.path)
|
|
40
|
+
if not os.path.isdir(abs_path):
|
|
41
|
+
raise HTTPException(status_code=400, detail=f"Directory not found: {abs_path}")
|
|
42
|
+
|
|
43
|
+
# Block system directories when API key auth is active
|
|
44
|
+
config = get_config()
|
|
45
|
+
if config.api_key:
|
|
46
|
+
for prefix in _BLOCKED_PREFIXES:
|
|
47
|
+
if abs_path == prefix or abs_path.startswith(prefix + "/"):
|
|
48
|
+
raise HTTPException(status_code=400, detail=f"Cannot register system directory: {abs_path}")
|
|
49
|
+
|
|
50
|
+
# Idempotent: return existing project if same path already registered
|
|
51
|
+
for pid, svc in list_projects().items():
|
|
52
|
+
if svc.project_dir == abs_path:
|
|
53
|
+
return ProjectInfo(
|
|
54
|
+
id=pid,
|
|
55
|
+
name=req.name or os.path.basename(abs_path),
|
|
56
|
+
path=abs_path,
|
|
57
|
+
status="ready",
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
project_id = str(uuid.uuid4())[:8]
|
|
61
|
+
name = req.name or os.path.basename(abs_path)
|
|
62
|
+
register_project(project_id, abs_path, name)
|
|
63
|
+
|
|
64
|
+
return ProjectInfo(
|
|
65
|
+
id=project_id,
|
|
66
|
+
name=name,
|
|
67
|
+
path=abs_path,
|
|
68
|
+
status="ready",
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@router.get("", response_model=ProjectListResponse)
|
|
73
|
+
async def get_projects(
|
|
74
|
+
limit: int = QueryParam(20, ge=1, le=100),
|
|
75
|
+
offset: int = QueryParam(0, ge=0),
|
|
76
|
+
) -> ProjectListResponse:
|
|
77
|
+
"""List all registered projects."""
|
|
78
|
+
projects = list_projects()
|
|
79
|
+
all_items = []
|
|
80
|
+
for pid, svc in projects.items():
|
|
81
|
+
all_items.append(ProjectInfo(
|
|
82
|
+
id=pid,
|
|
83
|
+
name=os.path.basename(svc.project_dir),
|
|
84
|
+
path=svc.project_dir,
|
|
85
|
+
status="ready",
|
|
86
|
+
))
|
|
87
|
+
total = len(all_items)
|
|
88
|
+
page = all_items[offset:offset + limit]
|
|
89
|
+
return ProjectListResponse(
|
|
90
|
+
projects=page, total=total, limit=limit, offset=offset,
|
|
91
|
+
has_more=(offset + limit < total),
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@router.get("/{project_id}", response_model=ProjectInfo)
|
|
96
|
+
async def get_project(project_id: str) -> ProjectInfo:
|
|
97
|
+
"""Get project details."""
|
|
98
|
+
svc = await get_service_or_404(project_id)
|
|
99
|
+
return ProjectInfo(
|
|
100
|
+
id=project_id,
|
|
101
|
+
name=os.path.basename(svc.project_dir),
|
|
102
|
+
path=svc.project_dir,
|
|
103
|
+
status="ready",
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@router.post("/{project_id}/reindex", response_model=TextResult)
|
|
108
|
+
async def reindex_project(project_id: str) -> TextResult:
|
|
109
|
+
"""Trigger a full reindex of the project."""
|
|
110
|
+
svc = await get_service_or_404(project_id)
|
|
111
|
+
async with _reindex_lock:
|
|
112
|
+
svc._ast_service = None
|
|
113
|
+
svc._context_mgr = None
|
|
114
|
+
svc._get_ast_service()
|
|
115
|
+
svc._get_context_mgr()
|
|
116
|
+
return TextResult(result="Reindex complete")
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Direct repo lookup endpoint (by repo_id, without org context)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import uuid
|
|
6
|
+
|
|
7
|
+
from fastapi import APIRouter, Depends, HTTPException
|
|
8
|
+
from sqlalchemy import select
|
|
9
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
10
|
+
|
|
11
|
+
from attocode_intel.api.auth import resolve_auth
|
|
12
|
+
from attocode_intel.api.auth.context import AuthContext
|
|
13
|
+
from attocode_intel.api.deps import get_db_session
|
|
14
|
+
from attocode_intel.api.routes.orgs import RepoResponse
|
|
15
|
+
from attocode_intel.db.models import Repository
|
|
16
|
+
|
|
17
|
+
router = APIRouter(prefix="/api/v1/repos", tags=["repos"])
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@router.get("/{repo_id}", response_model=RepoResponse)
|
|
21
|
+
async def get_repo_by_id(
|
|
22
|
+
repo_id: uuid.UUID,
|
|
23
|
+
auth: AuthContext = Depends(resolve_auth),
|
|
24
|
+
session: AsyncSession = Depends(get_db_session),
|
|
25
|
+
) -> RepoResponse:
|
|
26
|
+
"""Look up a repository directly by ID (without requiring org context)."""
|
|
27
|
+
result = await session.execute(
|
|
28
|
+
select(Repository).where(Repository.id == repo_id)
|
|
29
|
+
)
|
|
30
|
+
repo = result.scalar_one_or_none()
|
|
31
|
+
if repo is None:
|
|
32
|
+
raise HTTPException(status_code=404, detail="Repository not found")
|
|
33
|
+
|
|
34
|
+
# Org isolation: verify the repo belongs to the authenticated user's org
|
|
35
|
+
if auth.org_id and repo.org_id != auth.org_id:
|
|
36
|
+
raise HTTPException(status_code=404, detail="Repository not found")
|
|
37
|
+
|
|
38
|
+
return RepoResponse(
|
|
39
|
+
id=str(repo.id),
|
|
40
|
+
name=repo.name,
|
|
41
|
+
clone_url=repo.clone_url,
|
|
42
|
+
default_branch=repo.default_branch,
|
|
43
|
+
language=repo.language,
|
|
44
|
+
index_status=repo.index_status,
|
|
45
|
+
last_indexed_at=repo.last_indexed_at.isoformat() if repo.last_indexed_at else None,
|
|
46
|
+
created_at=repo.created_at.isoformat(),
|
|
47
|
+
)
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""Search endpoints — unified v1 (text) + v2 (structured JSON)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
from fastapi import APIRouter, Depends
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
|
|
10
|
+
from attocode_intel.api.auth import verify_auth
|
|
11
|
+
from attocode_intel.api.deps import (
|
|
12
|
+
BranchParam,
|
|
13
|
+
ensure_branch_supported,
|
|
14
|
+
get_search_provider,
|
|
15
|
+
get_service_or_404,
|
|
16
|
+
)
|
|
17
|
+
from attocode_intel.api.models import (
|
|
18
|
+
FastSearchRequest,
|
|
19
|
+
SearchResultsResponse,
|
|
20
|
+
SecurityScanRequest,
|
|
21
|
+
SecurityScanResponse,
|
|
22
|
+
SemanticSearchRequest,
|
|
23
|
+
TextResult,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
# --- v1 router: text responses ---
|
|
29
|
+
|
|
30
|
+
router_v1 = APIRouter(
|
|
31
|
+
prefix="/api/v1/projects/{project_id}",
|
|
32
|
+
tags=["search"],
|
|
33
|
+
dependencies=[Depends(verify_auth)],
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# --- v2 router: structured JSON ---
|
|
37
|
+
|
|
38
|
+
router_v2 = APIRouter(
|
|
39
|
+
prefix="/api/v2/projects/{project_id}",
|
|
40
|
+
tags=["search-v2"],
|
|
41
|
+
dependencies=[Depends(verify_auth)],
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class IndexStatusResponse(BaseModel):
|
|
46
|
+
provider: str = ""
|
|
47
|
+
available: bool = False
|
|
48
|
+
status: str = "idle"
|
|
49
|
+
total_files: int = 0
|
|
50
|
+
indexed_files: int = 0
|
|
51
|
+
failed_files: int = 0
|
|
52
|
+
coverage: float = 0.0
|
|
53
|
+
elapsed_seconds: float = 0.0
|
|
54
|
+
vector_search_active: bool = False
|
|
55
|
+
health_status: str = "unknown"
|
|
56
|
+
degraded_reason: str = ""
|
|
57
|
+
last_error: str = ""
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# ===================================================================
|
|
61
|
+
# v1 endpoints
|
|
62
|
+
# ===================================================================
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@router_v1.post("/search", response_model=TextResult)
|
|
66
|
+
async def semantic_search_v1(
|
|
67
|
+
project_id: str,
|
|
68
|
+
req: SemanticSearchRequest,
|
|
69
|
+
branch: BranchParam = "",
|
|
70
|
+
) -> TextResult:
|
|
71
|
+
"""Semantic search (vector + keyword RRF)."""
|
|
72
|
+
ensure_branch_supported(branch)
|
|
73
|
+
svc = await get_service_or_404(project_id)
|
|
74
|
+
return TextResult(result=svc.semantic_search(
|
|
75
|
+
query=req.query, top_k=req.top_k, file_filter=req.file_filter,
|
|
76
|
+
))
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@router_v1.post("/index", response_model=IndexStatusResponse)
|
|
80
|
+
async def start_indexing(
|
|
81
|
+
project_id: str,
|
|
82
|
+
branch: BranchParam = "",
|
|
83
|
+
) -> IndexStatusResponse:
|
|
84
|
+
"""Start background embedding indexing."""
|
|
85
|
+
ensure_branch_supported(branch)
|
|
86
|
+
svc = await get_service_or_404(project_id)
|
|
87
|
+
result = svc.start_indexing()
|
|
88
|
+
return IndexStatusResponse(**result)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@router_v1.get("/index/status", response_model=IndexStatusResponse)
|
|
92
|
+
async def index_status(
|
|
93
|
+
project_id: str,
|
|
94
|
+
branch: BranchParam = "",
|
|
95
|
+
) -> IndexStatusResponse:
|
|
96
|
+
"""Get current embedding index status."""
|
|
97
|
+
ensure_branch_supported(branch)
|
|
98
|
+
svc = await get_service_or_404(project_id)
|
|
99
|
+
result = svc.indexing_status()
|
|
100
|
+
return IndexStatusResponse(**result)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@router_v1.get("/semantic-search-status", response_model=TextResult)
|
|
104
|
+
async def semantic_search_status_v1(
|
|
105
|
+
project_id: str,
|
|
106
|
+
branch: BranchParam = "",
|
|
107
|
+
) -> TextResult:
|
|
108
|
+
"""Human-readable semantic-search health."""
|
|
109
|
+
ensure_branch_supported(branch)
|
|
110
|
+
svc = await get_service_or_404(project_id)
|
|
111
|
+
return TextResult(result=svc.semantic_search_status())
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@router_v1.post("/fast-search", response_model=TextResult)
|
|
115
|
+
async def fast_search_v1(
|
|
116
|
+
project_id: str,
|
|
117
|
+
req: FastSearchRequest,
|
|
118
|
+
branch: BranchParam = "",
|
|
119
|
+
) -> TextResult:
|
|
120
|
+
"""Regex search with trigram acceleration when available."""
|
|
121
|
+
ensure_branch_supported(branch)
|
|
122
|
+
svc = await get_service_or_404(project_id)
|
|
123
|
+
return TextResult(result=svc.fast_search(
|
|
124
|
+
pattern=req.pattern,
|
|
125
|
+
path=req.path,
|
|
126
|
+
max_results=req.max_results,
|
|
127
|
+
case_insensitive=req.case_insensitive,
|
|
128
|
+
selectivity_threshold=req.selectivity_threshold,
|
|
129
|
+
explain=req.explain,
|
|
130
|
+
))
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# ===================================================================
|
|
134
|
+
# v2 endpoints
|
|
135
|
+
# ===================================================================
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@router_v2.post("/search", response_model=SearchResultsResponse)
|
|
139
|
+
async def semantic_search_v2(
|
|
140
|
+
project_id: str,
|
|
141
|
+
req: SemanticSearchRequest,
|
|
142
|
+
branch: BranchParam = "",
|
|
143
|
+
) -> SearchResultsResponse:
|
|
144
|
+
"""Semantic search (structured).
|
|
145
|
+
|
|
146
|
+
In service mode with pgvector, embeds the query and runs cosine similarity search.
|
|
147
|
+
Falls back to the local service's text-based search otherwise.
|
|
148
|
+
"""
|
|
149
|
+
from fastapi import HTTPException
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
provider = await get_search_provider(project_id)
|
|
153
|
+
return await provider.semantic_search(
|
|
154
|
+
query=req.query, top_k=req.top_k, file_filter=req.file_filter, branch=branch,
|
|
155
|
+
)
|
|
156
|
+
except (RuntimeError, ImportError) as exc:
|
|
157
|
+
logger.warning("Search unavailable for project %s: %s", project_id, exc)
|
|
158
|
+
raise HTTPException(
|
|
159
|
+
status_code=503,
|
|
160
|
+
detail=f"Search is not available: {exc}",
|
|
161
|
+
) from exc
|
|
162
|
+
except HTTPException:
|
|
163
|
+
raise
|
|
164
|
+
except Exception as exc:
|
|
165
|
+
logger.exception("Search failed for project %s", project_id)
|
|
166
|
+
raise HTTPException(
|
|
167
|
+
status_code=500,
|
|
168
|
+
detail=f"Search failed: {exc}",
|
|
169
|
+
) from exc
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@router_v2.post("/security-scan", response_model=SecurityScanResponse)
|
|
173
|
+
async def security_scan_v2(
|
|
174
|
+
project_id: str,
|
|
175
|
+
req: SecurityScanRequest,
|
|
176
|
+
branch: BranchParam = "",
|
|
177
|
+
) -> SecurityScanResponse:
|
|
178
|
+
"""Security analysis (structured)."""
|
|
179
|
+
provider = await get_search_provider(project_id)
|
|
180
|
+
return await provider.security_scan(mode=req.mode, path=req.path, branch=branch)
|