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,143 @@
|
|
|
1
|
+
"""Dependency storage — file-level dependency graph."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import uuid
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DependencyStore:
|
|
13
|
+
"""Content-addressed dependency storage.
|
|
14
|
+
|
|
15
|
+
Dependencies are between content SHAs, enabling deduplication.
|
|
16
|
+
Branch-aware queries resolve through BranchOverlay.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, session: AsyncSession) -> None:
|
|
20
|
+
self._session = session
|
|
21
|
+
|
|
22
|
+
async def has_dependencies(self, source_sha: str) -> bool:
|
|
23
|
+
"""Check if dependencies already exist for a source SHA."""
|
|
24
|
+
from sqlalchemy import func, select
|
|
25
|
+
|
|
26
|
+
from attocode_intel.db.models import Dependency
|
|
27
|
+
|
|
28
|
+
result = await self._session.execute(
|
|
29
|
+
select(func.count()).where(Dependency.source_sha == source_sha)
|
|
30
|
+
)
|
|
31
|
+
return (result.scalar() or 0) > 0
|
|
32
|
+
|
|
33
|
+
async def upsert_dependencies(
|
|
34
|
+
self,
|
|
35
|
+
source_sha: str,
|
|
36
|
+
dependencies: list[dict],
|
|
37
|
+
) -> int:
|
|
38
|
+
"""Store dependencies from a source file.
|
|
39
|
+
|
|
40
|
+
Each dependency dict: {target_sha, dep_type, weight?, metadata?}
|
|
41
|
+
"""
|
|
42
|
+
from sqlalchemy import delete
|
|
43
|
+
|
|
44
|
+
from attocode_intel.db.models import Dependency
|
|
45
|
+
await self._session.execute(
|
|
46
|
+
delete(Dependency).where(Dependency.source_sha == source_sha)
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Insert new
|
|
50
|
+
for d in dependencies:
|
|
51
|
+
dep = Dependency(
|
|
52
|
+
source_sha=source_sha,
|
|
53
|
+
target_sha=d["target_sha"],
|
|
54
|
+
dep_type=d.get("dep_type", "imports"),
|
|
55
|
+
weight=d.get("weight", 1.0),
|
|
56
|
+
metadata_=d.get("metadata", {}),
|
|
57
|
+
)
|
|
58
|
+
self._session.add(dep)
|
|
59
|
+
|
|
60
|
+
await self._session.flush()
|
|
61
|
+
return len(dependencies)
|
|
62
|
+
|
|
63
|
+
async def get_forward(
|
|
64
|
+
self,
|
|
65
|
+
content_sha: str,
|
|
66
|
+
) -> list[dict]:
|
|
67
|
+
"""Get dependencies FROM a file (what it imports)."""
|
|
68
|
+
from sqlalchemy import select
|
|
69
|
+
|
|
70
|
+
from attocode_intel.db.models import Dependency
|
|
71
|
+
|
|
72
|
+
result = await self._session.execute(
|
|
73
|
+
select(Dependency).where(Dependency.source_sha == content_sha)
|
|
74
|
+
)
|
|
75
|
+
return [
|
|
76
|
+
{
|
|
77
|
+
"target_sha": d.target_sha,
|
|
78
|
+
"dep_type": d.dep_type,
|
|
79
|
+
"weight": d.weight,
|
|
80
|
+
}
|
|
81
|
+
for d in result.scalars()
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
async def get_reverse(
|
|
85
|
+
self,
|
|
86
|
+
content_sha: str,
|
|
87
|
+
) -> list[dict]:
|
|
88
|
+
"""Get reverse dependencies TO a file (what imports it)."""
|
|
89
|
+
from sqlalchemy import select
|
|
90
|
+
|
|
91
|
+
from attocode_intel.db.models import Dependency
|
|
92
|
+
|
|
93
|
+
result = await self._session.execute(
|
|
94
|
+
select(Dependency).where(Dependency.target_sha == content_sha)
|
|
95
|
+
)
|
|
96
|
+
return [
|
|
97
|
+
{
|
|
98
|
+
"source_sha": d.source_sha,
|
|
99
|
+
"dep_type": d.dep_type,
|
|
100
|
+
"weight": d.weight,
|
|
101
|
+
}
|
|
102
|
+
for d in result.scalars()
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
async def get_graph_for_branch(
|
|
106
|
+
self,
|
|
107
|
+
branch_id: uuid.UUID,
|
|
108
|
+
) -> dict:
|
|
109
|
+
"""Build the full dependency graph for a branch.
|
|
110
|
+
|
|
111
|
+
Returns {nodes: [...], edges: [...]} resolved through overlay.
|
|
112
|
+
"""
|
|
113
|
+
from sqlalchemy import select
|
|
114
|
+
|
|
115
|
+
from attocode_intel.db.models import Dependency
|
|
116
|
+
from attocode_intel.storage.branch_overlay import BranchOverlay
|
|
117
|
+
|
|
118
|
+
overlay = BranchOverlay(self._session)
|
|
119
|
+
manifest = await overlay.resolve_manifest(branch_id)
|
|
120
|
+
content_shas = set(manifest.values())
|
|
121
|
+
sha_to_path = {sha: path for path, sha in manifest.items()}
|
|
122
|
+
|
|
123
|
+
if not content_shas:
|
|
124
|
+
return {"nodes": [], "edges": []}
|
|
125
|
+
|
|
126
|
+
result = await self._session.execute(
|
|
127
|
+
select(Dependency).where(
|
|
128
|
+
Dependency.source_sha.in_(content_shas),
|
|
129
|
+
Dependency.target_sha.in_(content_shas),
|
|
130
|
+
)
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
nodes = [{"path": path, "sha": sha} for path, sha in manifest.items()]
|
|
134
|
+
edges = []
|
|
135
|
+
for dep in result.scalars():
|
|
136
|
+
edges.append({
|
|
137
|
+
"source": sha_to_path.get(dep.source_sha, dep.source_sha),
|
|
138
|
+
"target": sha_to_path.get(dep.target_sha, dep.target_sha),
|
|
139
|
+
"type": dep.dep_type,
|
|
140
|
+
"weight": dep.weight,
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
return {"nodes": nodes, "edges": edges}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"""Unified diff computation from DB-stored content.
|
|
2
|
+
|
|
3
|
+
Computes line-level diffs between two branch manifests using
|
|
4
|
+
BranchOverlay.diff_branches() for file-level changes and
|
|
5
|
+
difflib.unified_diff() for line-level hunks.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import difflib
|
|
11
|
+
import logging
|
|
12
|
+
import uuid
|
|
13
|
+
from typing import TYPE_CHECKING
|
|
14
|
+
|
|
15
|
+
# Canonical diff models live alongside the git manager; re-exported here for
|
|
16
|
+
# backward compatibility with consumers that import from this module.
|
|
17
|
+
from attocode_intel.git.models import DiffHunk, DiffLine, PatchEntry
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"DiffHunk",
|
|
26
|
+
"DiffLine",
|
|
27
|
+
"PatchEntry",
|
|
28
|
+
"compute_branch_diff",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _compute_hunks(old_text: str, new_text: str, path: str) -> list[DiffHunk]:
|
|
33
|
+
"""Compute unified diff hunks between two text contents."""
|
|
34
|
+
old_lines = old_text.splitlines(keepends=True)
|
|
35
|
+
new_lines = new_text.splitlines(keepends=True)
|
|
36
|
+
|
|
37
|
+
diff_lines = list(difflib.unified_diff(
|
|
38
|
+
old_lines, new_lines,
|
|
39
|
+
fromfile=f"a/{path}", tofile=f"b/{path}",
|
|
40
|
+
lineterm="",
|
|
41
|
+
))
|
|
42
|
+
|
|
43
|
+
hunks: list[DiffHunk] = []
|
|
44
|
+
current_hunk: DiffHunk | None = None
|
|
45
|
+
|
|
46
|
+
for line in diff_lines:
|
|
47
|
+
if line.startswith("@@"):
|
|
48
|
+
# Parse hunk header: @@ -old_start,old_lines +new_start,new_lines @@
|
|
49
|
+
parts = line.split("@@")
|
|
50
|
+
header = line.strip()
|
|
51
|
+
ranges = parts[1].strip().split()
|
|
52
|
+
old_range = ranges[0][1:] # remove '-'
|
|
53
|
+
new_range = ranges[1][1:] # remove '+'
|
|
54
|
+
|
|
55
|
+
old_start = int(old_range.split(",")[0]) if "," in old_range else int(old_range)
|
|
56
|
+
old_count = int(old_range.split(",")[1]) if "," in old_range else 1
|
|
57
|
+
new_start = int(new_range.split(",")[0]) if "," in new_range else int(new_range)
|
|
58
|
+
new_count = int(new_range.split(",")[1]) if "," in new_range else 1
|
|
59
|
+
|
|
60
|
+
current_hunk = DiffHunk(
|
|
61
|
+
old_start=old_start, old_lines=old_count,
|
|
62
|
+
new_start=new_start, new_lines=new_count,
|
|
63
|
+
header=header,
|
|
64
|
+
)
|
|
65
|
+
hunks.append(current_hunk)
|
|
66
|
+
elif current_hunk is not None:
|
|
67
|
+
if line.startswith("---") or line.startswith("+++"):
|
|
68
|
+
continue
|
|
69
|
+
if line.startswith("+"):
|
|
70
|
+
current_hunk.lines.append(DiffLine(
|
|
71
|
+
origin="+", content=line[1:],
|
|
72
|
+
))
|
|
73
|
+
elif line.startswith("-"):
|
|
74
|
+
current_hunk.lines.append(DiffLine(
|
|
75
|
+
origin="-", content=line[1:],
|
|
76
|
+
))
|
|
77
|
+
else:
|
|
78
|
+
current_hunk.lines.append(DiffLine(
|
|
79
|
+
origin=" ", content=line[1:] if line.startswith(" ") else line,
|
|
80
|
+
))
|
|
81
|
+
|
|
82
|
+
# Assign line numbers
|
|
83
|
+
for hunk in hunks:
|
|
84
|
+
old_ln = hunk.old_start
|
|
85
|
+
new_ln = hunk.new_start
|
|
86
|
+
for dl in hunk.lines:
|
|
87
|
+
if dl.origin == "-":
|
|
88
|
+
dl.old_lineno = old_ln
|
|
89
|
+
dl.new_lineno = None
|
|
90
|
+
old_ln += 1
|
|
91
|
+
elif dl.origin == "+":
|
|
92
|
+
dl.old_lineno = None
|
|
93
|
+
dl.new_lineno = new_ln
|
|
94
|
+
new_ln += 1
|
|
95
|
+
else:
|
|
96
|
+
dl.old_lineno = old_ln
|
|
97
|
+
dl.new_lineno = new_ln
|
|
98
|
+
old_ln += 1
|
|
99
|
+
new_ln += 1
|
|
100
|
+
|
|
101
|
+
return hunks
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
async def compute_branch_diff(
|
|
105
|
+
session: AsyncSession,
|
|
106
|
+
branch_a_id: uuid.UUID,
|
|
107
|
+
branch_b_id: uuid.UUID,
|
|
108
|
+
path_filter: str = "",
|
|
109
|
+
max_files: int = 1000,
|
|
110
|
+
) -> list[PatchEntry]:
|
|
111
|
+
"""Compute line-level diff between two branches using DB-stored content.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
session: Async DB session.
|
|
115
|
+
branch_a_id: Base branch UUID.
|
|
116
|
+
branch_b_id: Target branch UUID.
|
|
117
|
+
path_filter: Optional path prefix to filter files.
|
|
118
|
+
max_files: Maximum number of files to diff (prevents OOM on huge repos).
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
List of PatchEntry with file-level status and line-level hunks.
|
|
122
|
+
"""
|
|
123
|
+
from attocode_intel.storage.branch_overlay import BranchOverlay
|
|
124
|
+
from attocode_intel.storage.content_store import ContentStore
|
|
125
|
+
|
|
126
|
+
overlay = BranchOverlay(session)
|
|
127
|
+
content_store = ContentStore(session)
|
|
128
|
+
|
|
129
|
+
# Get file-level diff
|
|
130
|
+
file_diff = await overlay.diff_branches(branch_a_id, branch_b_id)
|
|
131
|
+
|
|
132
|
+
# Apply path filter
|
|
133
|
+
if path_filter:
|
|
134
|
+
file_diff = {p: s for p, s in file_diff.items() if p.startswith(path_filter)}
|
|
135
|
+
|
|
136
|
+
# Limit to max_files
|
|
137
|
+
paths = sorted(file_diff.keys())[:max_files]
|
|
138
|
+
|
|
139
|
+
# Resolve both manifests for content lookup
|
|
140
|
+
manifest_a = await overlay.resolve_manifest(branch_a_id)
|
|
141
|
+
manifest_b = await overlay.resolve_manifest(branch_b_id)
|
|
142
|
+
|
|
143
|
+
patches: list[PatchEntry] = []
|
|
144
|
+
|
|
145
|
+
for path in paths:
|
|
146
|
+
status = file_diff[path]
|
|
147
|
+
sha_a = manifest_a.get(path)
|
|
148
|
+
sha_b = manifest_b.get(path)
|
|
149
|
+
|
|
150
|
+
old_text = ""
|
|
151
|
+
new_text = ""
|
|
152
|
+
|
|
153
|
+
if sha_a:
|
|
154
|
+
data = await content_store.get(sha_a)
|
|
155
|
+
if data:
|
|
156
|
+
try:
|
|
157
|
+
old_text = data.decode("utf-8", errors="replace")
|
|
158
|
+
except Exception:
|
|
159
|
+
old_text = ""
|
|
160
|
+
|
|
161
|
+
if sha_b:
|
|
162
|
+
data = await content_store.get(sha_b)
|
|
163
|
+
if data:
|
|
164
|
+
try:
|
|
165
|
+
new_text = data.decode("utf-8", errors="replace")
|
|
166
|
+
except Exception:
|
|
167
|
+
new_text = ""
|
|
168
|
+
|
|
169
|
+
# Compute hunks
|
|
170
|
+
hunks = _compute_hunks(old_text, new_text, path)
|
|
171
|
+
|
|
172
|
+
additions = sum(
|
|
173
|
+
1 for h in hunks for dl in h.lines if dl.origin == "+"
|
|
174
|
+
)
|
|
175
|
+
deletions = sum(
|
|
176
|
+
1 for h in hunks for dl in h.lines if dl.origin == "-"
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
patches.append(PatchEntry(
|
|
180
|
+
path=path,
|
|
181
|
+
status=status,
|
|
182
|
+
additions=additions,
|
|
183
|
+
deletions=deletions,
|
|
184
|
+
hunks=hunks,
|
|
185
|
+
))
|
|
186
|
+
|
|
187
|
+
return patches
|