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,94 @@
|
|
|
1
|
+
"""Syntax evidence shared by navigation and optional precise lookup.
|
|
2
|
+
|
|
3
|
+
These candidates describe source syntax, not type-resolved relationships.
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def rust_imports(node, source: bytes) -> list[dict]:
|
|
11
|
+
def text(n):
|
|
12
|
+
return source[n.start_byte:n.end_byte].decode()
|
|
13
|
+
|
|
14
|
+
line = node.start_point[0] + 1
|
|
15
|
+
if node.type == "mod_item":
|
|
16
|
+
name = node.child_by_field_name("name")
|
|
17
|
+
return ([{"module": "mod:" + text(name), "start_line": line}]
|
|
18
|
+
if name and not node.child_by_field_name("body") else [])
|
|
19
|
+
result = []
|
|
20
|
+
|
|
21
|
+
def visit(n, prefix=""):
|
|
22
|
+
if n.type == "scoped_use_list":
|
|
23
|
+
path = n.child_by_field_name("path")
|
|
24
|
+
visit(n.child_by_field_name("list"), prefix + (text(path) + "::" if path else ""))
|
|
25
|
+
elif n.type == "use_list":
|
|
26
|
+
for child in n.named_children:
|
|
27
|
+
visit(child, prefix)
|
|
28
|
+
else:
|
|
29
|
+
path = n.child_by_field_name("path") if n.type == "use_as_clause" else n
|
|
30
|
+
alias = n.child_by_field_name("alias") if n.type == "use_as_clause" else None
|
|
31
|
+
module = prefix + text(path)
|
|
32
|
+
module = module.removesuffix("::self")
|
|
33
|
+
name = module.rsplit("::", 1)[-1]
|
|
34
|
+
result.append({"module": module, "names": [] if name == "*" else [name],
|
|
35
|
+
"alias": text(alias) if alias else "", "start_line": line})
|
|
36
|
+
|
|
37
|
+
argument = node.child_by_field_name("argument")
|
|
38
|
+
if argument:
|
|
39
|
+
visit(argument)
|
|
40
|
+
return result
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def syntax_tree(path: str, content: str, language: str):
|
|
44
|
+
from .ts_parser import _get_parser
|
|
45
|
+
parser = _get_parser("tsx" if Path(path).suffix == ".tsx" else language)
|
|
46
|
+
return parser.parse(content.encode()) if parser else None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def reference_candidates(path: str, content: str, language: str):
|
|
50
|
+
"""Return call/type candidates, or None when syntax parsing is unavailable."""
|
|
51
|
+
tree = syntax_tree(path, content, language)
|
|
52
|
+
if tree is None:
|
|
53
|
+
return None
|
|
54
|
+
source = content.encode()
|
|
55
|
+
result = []
|
|
56
|
+
stack = [tree.root_node]
|
|
57
|
+
while stack:
|
|
58
|
+
node = stack.pop()
|
|
59
|
+
if node.type in {"comment", "line_comment", "block_comment", "string", "string_literal"}:
|
|
60
|
+
continue
|
|
61
|
+
if node.type in {"call", "call_expression", "method_invocation", "object_creation_expression", "new_expression"}:
|
|
62
|
+
target = (node.child_by_field_name("function") or node.child_by_field_name("name")
|
|
63
|
+
or node.child_by_field_name("constructor"))
|
|
64
|
+
if target:
|
|
65
|
+
# Generic Rust calls have a wrapper around their function path.
|
|
66
|
+
target = target.child_by_field_name("function") or target
|
|
67
|
+
name = source[target.start_byte:target.end_byte].decode().replace("::", ".")
|
|
68
|
+
if all(part.isidentifier() for part in name.split(".")):
|
|
69
|
+
result.append((name, "call", target.start_point[0] + 1))
|
|
70
|
+
if language == "rust" and "." in name:
|
|
71
|
+
result.append((name.rsplit(".", 1)[0], "attribute", target.start_point[0] + 1))
|
|
72
|
+
stack.extend(reversed(node.named_children))
|
|
73
|
+
return result
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def symbol_position(path: str, name: str, start_line: int, end_line: int, language: str):
|
|
77
|
+
"""Locate the identifier token, returning LSP's UTF-16 position."""
|
|
78
|
+
content = Path(path).read_text(encoding="utf-8")
|
|
79
|
+
tree = syntax_tree(path, content, language)
|
|
80
|
+
if tree is None:
|
|
81
|
+
return None
|
|
82
|
+
source = content.encode()
|
|
83
|
+
stack = [tree.root_node]
|
|
84
|
+
while stack:
|
|
85
|
+
node = stack.pop()
|
|
86
|
+
line = node.start_point[0] + 1
|
|
87
|
+
if node.end_point[0] + 1 < start_line or line > end_line:
|
|
88
|
+
continue
|
|
89
|
+
if (node.type in {"identifier", "type_identifier", "property_identifier", "field_identifier"}
|
|
90
|
+
and source[node.start_byte:node.end_byte].decode() == name):
|
|
91
|
+
prefix = source[:node.start_byte].rsplit(b"\n", 1)[-1].decode()
|
|
92
|
+
return line - 1, len(prefix.encode("utf-16-le")) // 2
|
|
93
|
+
stack.extend(reversed(node.named_children))
|
|
94
|
+
return None
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"""Bounded, opt-in SystemOne shortlist ranking over an explicit HTTP endpoint.
|
|
2
|
+
|
|
3
|
+
This is a wire-protocol adapter, not a model recommendation. In particular,
|
|
4
|
+
being compatible with ``POST /v1/systemone`` says nothing about code-search
|
|
5
|
+
quality. No request is made during construction or for a disabled provider.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import ipaddress
|
|
11
|
+
import json
|
|
12
|
+
import math
|
|
13
|
+
import os
|
|
14
|
+
import re
|
|
15
|
+
import threading
|
|
16
|
+
from urllib.parse import urlsplit
|
|
17
|
+
|
|
18
|
+
import httpx
|
|
19
|
+
|
|
20
|
+
from attocode_intel._internal.integrations.context.reranker import RankingOutcome
|
|
21
|
+
from attocode_intel.confidence.redact import redact
|
|
22
|
+
|
|
23
|
+
_MAX_RESPONSE_BYTES = 64 * 1024
|
|
24
|
+
_REMOTE_BLOCKED = {"1", "true", "yes", "on"}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SystemOneChoiceReranker:
|
|
28
|
+
"""One ``choice`` request for a bounded page of distinct files.
|
|
29
|
+
|
|
30
|
+
The timeout is a whole-call deadline. A timed-out worker retains the one
|
|
31
|
+
in-flight slot until it exits, so a stalled endpoint cannot create an
|
|
32
|
+
unbounded queue. Responses must contain a complete probability distribution.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
endpoint: str,
|
|
38
|
+
*,
|
|
39
|
+
model: str = "",
|
|
40
|
+
auth_env: str = "",
|
|
41
|
+
allow_remote: bool = False,
|
|
42
|
+
service_mode: bool = False,
|
|
43
|
+
max_candidates: int = 24,
|
|
44
|
+
timeout_seconds: float | None = None,
|
|
45
|
+
max_query_chars: int = 512,
|
|
46
|
+
max_excerpt_chars: int = 1350,
|
|
47
|
+
transport: httpx.BaseTransport | None = None,
|
|
48
|
+
) -> None:
|
|
49
|
+
parsed = urlsplit(endpoint)
|
|
50
|
+
try:
|
|
51
|
+
host = parsed.hostname
|
|
52
|
+
_ = parsed.port
|
|
53
|
+
except ValueError as exc:
|
|
54
|
+
raise ValueError("invalid ranking endpoint") from exc
|
|
55
|
+
if (
|
|
56
|
+
parsed.scheme not in {"http", "https"}
|
|
57
|
+
or not host
|
|
58
|
+
or not parsed.path
|
|
59
|
+
or parsed.username
|
|
60
|
+
or parsed.password
|
|
61
|
+
or parsed.query
|
|
62
|
+
or parsed.fragment
|
|
63
|
+
):
|
|
64
|
+
raise ValueError(
|
|
65
|
+
"ranking endpoint must be an absolute HTTP(S) URL without credentials or query"
|
|
66
|
+
)
|
|
67
|
+
try:
|
|
68
|
+
loopback = ipaddress.ip_address(host).is_loopback
|
|
69
|
+
except ValueError:
|
|
70
|
+
# A hostname can resolve differently between validation and use.
|
|
71
|
+
# Local source access requires an explicit loopback IP literal.
|
|
72
|
+
loopback = False
|
|
73
|
+
if not loopback and parsed.scheme != "https":
|
|
74
|
+
raise ValueError("non-loopback ranking endpoints require HTTPS")
|
|
75
|
+
if not loopback and (
|
|
76
|
+
not allow_remote
|
|
77
|
+
or service_mode
|
|
78
|
+
or os.environ.get("ATTOCODE_LOCAL_ONLY", "").lower() in _REMOTE_BLOCKED
|
|
79
|
+
):
|
|
80
|
+
raise ValueError("remote source ranking is not permitted")
|
|
81
|
+
if auth_env and re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]*", auth_env) is None:
|
|
82
|
+
raise ValueError("ranking credential must be an environment-variable name")
|
|
83
|
+
if timeout_seconds is None:
|
|
84
|
+
timeout_seconds = 5.0 if loopback else 0.8
|
|
85
|
+
if not 1 <= max_candidates <= 64 or not 0 < timeout_seconds <= 30:
|
|
86
|
+
raise ValueError("invalid ranking limits")
|
|
87
|
+
if min(max_query_chars, max_excerpt_chars) < 1:
|
|
88
|
+
raise ValueError("ranking text limits must be positive")
|
|
89
|
+
self.endpoint = endpoint
|
|
90
|
+
self.model = model
|
|
91
|
+
self.auth_env = auth_env
|
|
92
|
+
self.remote = not loopback
|
|
93
|
+
self.max_candidates = max_candidates
|
|
94
|
+
self.timeout_seconds = timeout_seconds
|
|
95
|
+
self.max_query_chars = max_query_chars
|
|
96
|
+
self.max_excerpt_chars = max_excerpt_chars
|
|
97
|
+
self._transport = transport
|
|
98
|
+
self._inflight = threading.Lock()
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def status(self) -> str:
|
|
102
|
+
return "configured"
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def is_available(self) -> bool:
|
|
106
|
+
return True
|
|
107
|
+
|
|
108
|
+
def _post(self, body: dict, headers: dict[str, str]) -> dict:
|
|
109
|
+
# Do not inherit proxy settings or follow a redirect to a different
|
|
110
|
+
# origin; both could silently send source to an unapproved host.
|
|
111
|
+
with (
|
|
112
|
+
httpx.Client(
|
|
113
|
+
timeout=self.timeout_seconds,
|
|
114
|
+
trust_env=False,
|
|
115
|
+
follow_redirects=False,
|
|
116
|
+
transport=self._transport,
|
|
117
|
+
) as client,
|
|
118
|
+
client.stream("POST", self.endpoint, json=body, headers=headers) as response,
|
|
119
|
+
):
|
|
120
|
+
response.raise_for_status()
|
|
121
|
+
chunks: list[bytes] = []
|
|
122
|
+
size = 0
|
|
123
|
+
for chunk in response.iter_bytes():
|
|
124
|
+
size += len(chunk)
|
|
125
|
+
if size > _MAX_RESPONSE_BYTES:
|
|
126
|
+
raise ValueError("ranking response too large")
|
|
127
|
+
chunks.append(chunk)
|
|
128
|
+
result = json.loads(b"".join(chunks))
|
|
129
|
+
if not isinstance(result, dict):
|
|
130
|
+
raise ValueError("invalid ranking response")
|
|
131
|
+
# Workers AI wraps every answer as {"result": {...}, "success": true, "errors": []}.
|
|
132
|
+
inner = result.get("result")
|
|
133
|
+
if "answers" not in result and isinstance(inner, dict):
|
|
134
|
+
if result.get("success") is not True or result.get("errors"):
|
|
135
|
+
raise ValueError("ranking provider reported a failure")
|
|
136
|
+
result = {"model": result["model"], **inner} if "model" in result else inner
|
|
137
|
+
return result
|
|
138
|
+
|
|
139
|
+
@staticmethod
|
|
140
|
+
def _scores(response: dict, count: int) -> list[float]:
|
|
141
|
+
answers = response.get("answers")
|
|
142
|
+
answer = answers.get("best") if isinstance(answers, dict) else None
|
|
143
|
+
probabilities = answer.get("probabilities") if isinstance(answer, dict) else None
|
|
144
|
+
choice = answer.get("choice") if isinstance(answer, dict) else None
|
|
145
|
+
expected = {str(index) for index in range(count)}
|
|
146
|
+
if (
|
|
147
|
+
not isinstance(probabilities, dict)
|
|
148
|
+
or set(probabilities) != expected
|
|
149
|
+
or not isinstance(choice, str)
|
|
150
|
+
or choice not in expected
|
|
151
|
+
):
|
|
152
|
+
raise ValueError("incomplete ranking probabilities")
|
|
153
|
+
scores = [probabilities[str(index)] for index in range(count)]
|
|
154
|
+
if any(
|
|
155
|
+
isinstance(score, bool)
|
|
156
|
+
or not isinstance(score, int | float)
|
|
157
|
+
or not math.isfinite(score)
|
|
158
|
+
or not 0 <= score <= 1
|
|
159
|
+
for score in scores
|
|
160
|
+
):
|
|
161
|
+
raise ValueError("invalid ranking probability")
|
|
162
|
+
numeric = [float(score) for score in scores]
|
|
163
|
+
if not 0.95 <= sum(numeric) <= 1.05:
|
|
164
|
+
raise ValueError("ranking probabilities do not sum to one")
|
|
165
|
+
if numeric[int(choice)] < max(numeric) - 1e-6:
|
|
166
|
+
raise ValueError("choice disagrees with ranking probabilities")
|
|
167
|
+
return numeric
|
|
168
|
+
|
|
169
|
+
def rerank_result(
|
|
170
|
+
self,
|
|
171
|
+
query: str,
|
|
172
|
+
candidates: list[tuple[str, str, float]],
|
|
173
|
+
top_k: int = 10,
|
|
174
|
+
) -> RankingOutcome:
|
|
175
|
+
fallback = candidates[: max(0, top_k)]
|
|
176
|
+
if top_k <= 0 or not candidates:
|
|
177
|
+
return RankingOutcome(fallback, False, "empty")
|
|
178
|
+
if not query.strip():
|
|
179
|
+
return RankingOutcome(fallback, False, "empty_query")
|
|
180
|
+
if self.remote and os.environ.get("ATTOCODE_LOCAL_ONLY", "").lower() in _REMOTE_BLOCKED:
|
|
181
|
+
return RankingOutcome(fallback, False, "remote_disabled")
|
|
182
|
+
token = os.environ.get(self.auth_env) if self.auth_env else None
|
|
183
|
+
if self.auth_env and not token:
|
|
184
|
+
return RankingOutcome(fallback, False, "missing_credential")
|
|
185
|
+
bounded = candidates[: self.max_candidates]
|
|
186
|
+
question = {
|
|
187
|
+
"best": {
|
|
188
|
+
"type": "choice",
|
|
189
|
+
"instructions": "Which source file most directly implements the behavior in the query?",
|
|
190
|
+
"criteria": {
|
|
191
|
+
str(index): excerpt[: self.max_excerpt_chars]
|
|
192
|
+
for index, (_id, excerpt, _score) in enumerate(bounded)
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
}
|
|
196
|
+
state = {
|
|
197
|
+
"query": query[: self.max_query_chars],
|
|
198
|
+
"task": "Find the primary implementation file, not an incidental match.",
|
|
199
|
+
}
|
|
200
|
+
if self.remote:
|
|
201
|
+
state["query"] = redact(state["query"])
|
|
202
|
+
question["best"]["criteria"] = {
|
|
203
|
+
key: redact(value) for key, value in question["best"]["criteria"].items()
|
|
204
|
+
}
|
|
205
|
+
body = {"state": state, "questions": question}
|
|
206
|
+
if self.model:
|
|
207
|
+
body["model"] = self.model
|
|
208
|
+
headers = {"Authorization": f"Bearer {token}"} if token else {}
|
|
209
|
+
if not self._inflight.acquire(blocking=False):
|
|
210
|
+
return RankingOutcome(fallback, False, "busy")
|
|
211
|
+
done = threading.Event()
|
|
212
|
+
output: list[tuple[str, list[float] | None]] = []
|
|
213
|
+
|
|
214
|
+
def predict() -> None:
|
|
215
|
+
try:
|
|
216
|
+
response = self._post(body, headers)
|
|
217
|
+
if self.model and response.get("model") not in {None, self.model}:
|
|
218
|
+
raise ValueError("unexpected ranking model")
|
|
219
|
+
output.append(("ok", self._scores(response, len(bounded))))
|
|
220
|
+
except ValueError:
|
|
221
|
+
output.append(("invalid_response", None))
|
|
222
|
+
except (httpx.HTTPError, json.JSONDecodeError, OSError):
|
|
223
|
+
output.append(("transport_error", None))
|
|
224
|
+
except Exception:
|
|
225
|
+
output.append(("provider_error", None))
|
|
226
|
+
finally:
|
|
227
|
+
self._inflight.release()
|
|
228
|
+
done.set()
|
|
229
|
+
|
|
230
|
+
try:
|
|
231
|
+
threading.Thread(target=predict, name="systemone-ranker", daemon=True).start()
|
|
232
|
+
except RuntimeError:
|
|
233
|
+
self._inflight.release()
|
|
234
|
+
return RankingOutcome(fallback, False, "provider_error")
|
|
235
|
+
if not done.wait(self.timeout_seconds):
|
|
236
|
+
return RankingOutcome(fallback, False, "timeout")
|
|
237
|
+
status, scores = output[0]
|
|
238
|
+
if scores is None:
|
|
239
|
+
return RankingOutcome(fallback, False, status)
|
|
240
|
+
scored = [
|
|
241
|
+
(candidate_id, excerpt, score)
|
|
242
|
+
for (candidate_id, excerpt, _original), score in zip(bounded, scores, strict=True)
|
|
243
|
+
]
|
|
244
|
+
scored.sort(key=lambda item: item[2], reverse=True)
|
|
245
|
+
return RankingOutcome((scored + candidates[self.max_candidates :])[:top_k], True)
|