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,244 @@
|
|
|
1
|
+
"""Language pack loader — discovers and loads user-activated analysis packs.
|
|
2
|
+
|
|
3
|
+
Packs provide language-specific rules and few-shot examples. Example packs ship with attocode under ``packs/examples/``
|
|
4
|
+
but are NOT auto-loaded. Users activate packs by copying them to
|
|
5
|
+
``.attocode/packs/<name>/`` in their project.
|
|
6
|
+
|
|
7
|
+
Each pack directory contains:
|
|
8
|
+
- ``manifest.yaml`` — metadata (name, version, languages, description)
|
|
9
|
+
- ``rules/*.yaml`` — YAML rule definitions
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
import os
|
|
16
|
+
import shutil
|
|
17
|
+
from dataclasses import dataclass, field
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
from attocode_intel.rules.loader import load_yaml_rules
|
|
21
|
+
from attocode_intel.rules.model import RuleSource, UnifiedRule
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
# Example packs ship here — NOT auto-loaded
|
|
26
|
+
_EXAMPLES_DIR = Path(__file__).parent / "examples"
|
|
27
|
+
|
|
28
|
+
# Community packs (imported from upstream sources like semgrep-rules, bandit,
|
|
29
|
+
# gosec) ship under ``community/`` with their own LICENSE + NOTICE files.
|
|
30
|
+
# They are NOT auto-loaded — the rule-bench harness loads them explicitly.
|
|
31
|
+
_COMMUNITY_DIR = Path(__file__).parent / "community"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(slots=True)
|
|
35
|
+
class PackManifest:
|
|
36
|
+
"""Parsed pack manifest."""
|
|
37
|
+
|
|
38
|
+
name: str
|
|
39
|
+
version: str = "1.0.0"
|
|
40
|
+
languages: list[str] = field(default_factory=list)
|
|
41
|
+
description: str = ""
|
|
42
|
+
pack_dir: str = ""
|
|
43
|
+
|
|
44
|
+
# Provenance fields — populated by the community pack importer.
|
|
45
|
+
# Kept additive so older manifests (without these keys) continue to load.
|
|
46
|
+
source: str = "" # "community" | "example" | "user"
|
|
47
|
+
source_url: str = ""
|
|
48
|
+
source_commit: str = ""
|
|
49
|
+
source_license: str = "" # SPDX identifier
|
|
50
|
+
attribution: str = "" # human-readable attribution string
|
|
51
|
+
imported_at: str = "" # ISO-8601 timestamp
|
|
52
|
+
upstream_rule_count: int = 0
|
|
53
|
+
imported_rule_count: int = 0
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _load_manifest(pack_dir: Path) -> PackManifest | None:
|
|
57
|
+
"""Load manifest.yaml from a pack directory."""
|
|
58
|
+
manifest_path = pack_dir / "manifest.yaml"
|
|
59
|
+
if not manifest_path.is_file():
|
|
60
|
+
return PackManifest(
|
|
61
|
+
name=pack_dir.name,
|
|
62
|
+
languages=[pack_dir.name],
|
|
63
|
+
pack_dir=str(pack_dir),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
import yaml # type: ignore[import-untyped]
|
|
68
|
+
except ImportError:
|
|
69
|
+
return PackManifest(
|
|
70
|
+
name=pack_dir.name,
|
|
71
|
+
languages=[pack_dir.name],
|
|
72
|
+
pack_dir=str(pack_dir),
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
data = yaml.safe_load(manifest_path.read_text(encoding="utf-8")) or {}
|
|
77
|
+
except Exception as exc:
|
|
78
|
+
logger.warning("Failed to parse manifest %s: %s", manifest_path, exc)
|
|
79
|
+
return None
|
|
80
|
+
|
|
81
|
+
return PackManifest(
|
|
82
|
+
name=str(data.get("name", pack_dir.name)),
|
|
83
|
+
version=str(data.get("version", "1.0.0")),
|
|
84
|
+
languages=data.get("languages", [pack_dir.name]),
|
|
85
|
+
description=str(data.get("description", "")),
|
|
86
|
+
pack_dir=str(pack_dir),
|
|
87
|
+
source=str(data.get("source", "")),
|
|
88
|
+
source_url=str(data.get("source_url", "")),
|
|
89
|
+
source_commit=str(data.get("source_commit", "")),
|
|
90
|
+
source_license=str(data.get("source_license", "")),
|
|
91
|
+
attribution=str(data.get("attribution", "")),
|
|
92
|
+
imported_at=str(data.get("imported_at", "")),
|
|
93
|
+
upstream_rule_count=int(data.get("upstream_rule_count", 0) or 0),
|
|
94
|
+
imported_rule_count=int(data.get("imported_rule_count", 0) or 0),
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _load_pack_rules(pack_dir: Path, manifest: PackManifest) -> list[UnifiedRule]:
|
|
99
|
+
"""Load all rules from a pack's rules/ directory."""
|
|
100
|
+
rules_dir = pack_dir / "rules"
|
|
101
|
+
if not rules_dir.is_dir():
|
|
102
|
+
rules_dir = pack_dir
|
|
103
|
+
|
|
104
|
+
return load_yaml_rules(
|
|
105
|
+
rules_dir,
|
|
106
|
+
source=RuleSource.PACK,
|
|
107
|
+
pack=manifest.name,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def discover_packs(project_dir: str = "") -> list[PackManifest]:
|
|
112
|
+
"""Discover user-activated packs from ``.attocode/packs/``.
|
|
113
|
+
|
|
114
|
+
Only loads packs the user has explicitly placed in their project.
|
|
115
|
+
Example packs under ``packs/examples/`` are NOT auto-loaded.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
List of pack manifests sorted by name.
|
|
119
|
+
"""
|
|
120
|
+
manifests: list[PackManifest] = []
|
|
121
|
+
|
|
122
|
+
if project_dir:
|
|
123
|
+
user_packs = Path(project_dir) / ".attocode" / "packs"
|
|
124
|
+
if user_packs.is_dir():
|
|
125
|
+
for entry in sorted(user_packs.iterdir()):
|
|
126
|
+
if entry.is_dir() and not entry.name.startswith("_"):
|
|
127
|
+
m = _load_manifest(entry)
|
|
128
|
+
if m:
|
|
129
|
+
manifests.append(m)
|
|
130
|
+
|
|
131
|
+
return manifests
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def list_example_packs() -> list[PackManifest]:
|
|
135
|
+
"""List available example packs (shipped but not auto-loaded).
|
|
136
|
+
|
|
137
|
+
These can be installed into a project via ``install_pack()``.
|
|
138
|
+
"""
|
|
139
|
+
manifests: list[PackManifest] = []
|
|
140
|
+
if _EXAMPLES_DIR.is_dir():
|
|
141
|
+
for entry in sorted(_EXAMPLES_DIR.iterdir()):
|
|
142
|
+
if entry.is_dir() and not entry.name.startswith("_"):
|
|
143
|
+
m = _load_manifest(entry)
|
|
144
|
+
if m:
|
|
145
|
+
manifests.append(m)
|
|
146
|
+
return manifests
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def list_community_packs() -> list[PackManifest]:
|
|
150
|
+
"""List community packs imported from upstream sources.
|
|
151
|
+
|
|
152
|
+
Community packs live under ``packs/community/<name>/`` with their own
|
|
153
|
+
``LICENSE`` and ``NOTICE`` files. They are NOT auto-loaded — the
|
|
154
|
+
rule-bench harness loads them explicitly when running evaluations.
|
|
155
|
+
"""
|
|
156
|
+
manifests: list[PackManifest] = []
|
|
157
|
+
if _COMMUNITY_DIR.is_dir():
|
|
158
|
+
for entry in sorted(_COMMUNITY_DIR.iterdir()):
|
|
159
|
+
if entry.is_dir() and not entry.name.startswith("_"):
|
|
160
|
+
m = _load_manifest(entry)
|
|
161
|
+
if m:
|
|
162
|
+
manifests.append(m)
|
|
163
|
+
return manifests
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def get_community_pack_dir() -> Path:
|
|
167
|
+
"""Return the directory where community packs are stored."""
|
|
168
|
+
return _COMMUNITY_DIR
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def install_pack(pack_name: str, project_dir: str) -> str:
|
|
172
|
+
"""Copy an example pack into ``.attocode/packs/<name>/``.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
pack_name: Name of the example pack (go, python, typescript, rust, java).
|
|
176
|
+
project_dir: Project root directory.
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
Status message.
|
|
180
|
+
"""
|
|
181
|
+
source = _EXAMPLES_DIR / pack_name
|
|
182
|
+
if not source.is_dir():
|
|
183
|
+
available = [d.name for d in _EXAMPLES_DIR.iterdir() if d.is_dir() and not d.name.startswith("_")]
|
|
184
|
+
return f"Pack '{pack_name}' not found. Available: {', '.join(available)}"
|
|
185
|
+
|
|
186
|
+
dest = Path(project_dir) / ".attocode" / "packs" / pack_name
|
|
187
|
+
if dest.is_dir():
|
|
188
|
+
return f"Pack '{pack_name}' already installed at {dest}. Delete it first to reinstall."
|
|
189
|
+
|
|
190
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
191
|
+
shutil.copytree(str(source), str(dest))
|
|
192
|
+
|
|
193
|
+
manifest = _load_manifest(dest)
|
|
194
|
+
rule_count = len(_load_pack_rules(dest, manifest)) if manifest else 0
|
|
195
|
+
|
|
196
|
+
return (
|
|
197
|
+
f"Installed pack '{pack_name}' to {dest}\n"
|
|
198
|
+
f" Rules: {rule_count}\n"
|
|
199
|
+
f" Languages: {', '.join(manifest.languages) if manifest else pack_name}\n"
|
|
200
|
+
f" Customize rules in {dest}/rules/*.yaml"
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def load_pack(manifest: PackManifest) -> list[UnifiedRule]:
|
|
205
|
+
"""Load all rules from a single pack."""
|
|
206
|
+
pack_dir = Path(manifest.pack_dir)
|
|
207
|
+
rules = _load_pack_rules(pack_dir, manifest)
|
|
208
|
+
if rules:
|
|
209
|
+
logger.info(
|
|
210
|
+
"Pack '%s': loaded %d rules for %s",
|
|
211
|
+
manifest.name, len(rules), ", ".join(manifest.languages),
|
|
212
|
+
)
|
|
213
|
+
return rules
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def load_all_packs(
|
|
217
|
+
project_dir: str = "", *, include_shipped: bool | None = None,
|
|
218
|
+
) -> tuple[list[PackManifest], list[UnifiedRule]]:
|
|
219
|
+
"""Discover and load packs.
|
|
220
|
+
|
|
221
|
+
Loads user-activated packs from ``.attocode/packs/`` and, by default, the
|
|
222
|
+
shipped example packs (the language packs that carry the ast-grep
|
|
223
|
+
structural rules) — otherwise the engine would ship with only the builtin
|
|
224
|
+
regex rules. A user-installed pack shadows the shipped pack of the same
|
|
225
|
+
name. Set ``ATTOCODE_NO_SHIPPED_PACKS=1`` (or ``include_shipped=False``)
|
|
226
|
+
to load only user packs.
|
|
227
|
+
"""
|
|
228
|
+
if include_shipped is None:
|
|
229
|
+
include_shipped = os.environ.get("ATTOCODE_NO_SHIPPED_PACKS", "") not in (
|
|
230
|
+
"1", "true", "True",
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
manifests = discover_packs(project_dir)
|
|
234
|
+
if include_shipped:
|
|
235
|
+
seen = {m.name for m in manifests}
|
|
236
|
+
for m in list_example_packs():
|
|
237
|
+
if m.name not in seen:
|
|
238
|
+
manifests.append(m)
|
|
239
|
+
seen.add(m.name)
|
|
240
|
+
|
|
241
|
+
all_rules: list[UnifiedRule] = []
|
|
242
|
+
for m in manifests:
|
|
243
|
+
all_rules.extend(load_pack(m))
|
|
244
|
+
return manifests, all_rules
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""User plugin system for custom analysis rules."""
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""Plugin loader — discovers and loads user analysis plugins.
|
|
2
|
+
|
|
3
|
+
Plugins live in ``.attocode/plugins/<name>/`` and contain:
|
|
4
|
+
- ``plugin.yaml`` — manifest with name, version, description, rule paths
|
|
5
|
+
- ``rules/*.yaml`` — YAML rule definitions (same format as pack rules)
|
|
6
|
+
|
|
7
|
+
Example plugin structure::
|
|
8
|
+
|
|
9
|
+
.attocode/plugins/my-company-rules/
|
|
10
|
+
├── plugin.yaml
|
|
11
|
+
└── rules/
|
|
12
|
+
├── no-direct-db.yaml
|
|
13
|
+
└── require-logging.yaml
|
|
14
|
+
|
|
15
|
+
Example plugin.yaml::
|
|
16
|
+
|
|
17
|
+
name: my-company-rules
|
|
18
|
+
version: 1.0.0
|
|
19
|
+
description: "Internal coding standards"
|
|
20
|
+
rules:
|
|
21
|
+
- rules/*.yaml
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import logging
|
|
27
|
+
from dataclasses import dataclass, field
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
|
|
30
|
+
from attocode_intel.rules.loader import load_yaml_rules
|
|
31
|
+
from attocode_intel.rules.model import RuleSource, UnifiedRule
|
|
32
|
+
|
|
33
|
+
logger = logging.getLogger(__name__)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass(slots=True)
|
|
37
|
+
class PluginManifest:
|
|
38
|
+
"""Parsed plugin manifest."""
|
|
39
|
+
|
|
40
|
+
name: str
|
|
41
|
+
version: str = "1.0.0"
|
|
42
|
+
description: str = ""
|
|
43
|
+
plugin_dir: str = ""
|
|
44
|
+
rule_globs: list[str] = field(default_factory=list)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _load_plugin_manifest(plugin_dir: Path) -> PluginManifest | None:
|
|
48
|
+
"""Load plugin.yaml from a plugin directory."""
|
|
49
|
+
manifest_path = plugin_dir / "plugin.yaml"
|
|
50
|
+
if not manifest_path.is_file():
|
|
51
|
+
# No manifest — try to load rules directly
|
|
52
|
+
return PluginManifest(
|
|
53
|
+
name=plugin_dir.name,
|
|
54
|
+
plugin_dir=str(plugin_dir),
|
|
55
|
+
rule_globs=["rules/*.yaml"],
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
import yaml # type: ignore[import-untyped]
|
|
60
|
+
except ImportError:
|
|
61
|
+
return PluginManifest(
|
|
62
|
+
name=plugin_dir.name,
|
|
63
|
+
plugin_dir=str(plugin_dir),
|
|
64
|
+
rule_globs=["rules/*.yaml"],
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
data = yaml.safe_load(manifest_path.read_text(encoding="utf-8")) or {}
|
|
69
|
+
except Exception as exc:
|
|
70
|
+
logger.warning("Failed to parse plugin manifest %s: %s", manifest_path, exc)
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
return PluginManifest(
|
|
74
|
+
name=str(data.get("name", plugin_dir.name)),
|
|
75
|
+
version=str(data.get("version", "1.0.0")),
|
|
76
|
+
description=str(data.get("description", "")),
|
|
77
|
+
plugin_dir=str(plugin_dir),
|
|
78
|
+
rule_globs=data.get("rules", ["rules/*.yaml"]),
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def discover_plugins(project_dir: str) -> list[PluginManifest]:
|
|
83
|
+
"""Discover all plugins in .attocode/plugins/."""
|
|
84
|
+
plugins_dir = Path(project_dir) / ".attocode" / "plugins"
|
|
85
|
+
if not plugins_dir.is_dir():
|
|
86
|
+
return []
|
|
87
|
+
|
|
88
|
+
manifests: list[PluginManifest] = []
|
|
89
|
+
for entry in sorted(plugins_dir.iterdir()):
|
|
90
|
+
if entry.is_dir() and not entry.name.startswith("_"):
|
|
91
|
+
m = _load_plugin_manifest(entry)
|
|
92
|
+
if m:
|
|
93
|
+
manifests.append(m)
|
|
94
|
+
|
|
95
|
+
return manifests
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def load_plugin(manifest: PluginManifest) -> list[UnifiedRule]:
|
|
99
|
+
"""Load all rules from a plugin."""
|
|
100
|
+
plugin_dir = Path(manifest.plugin_dir)
|
|
101
|
+
rules: list[UnifiedRule] = []
|
|
102
|
+
|
|
103
|
+
# Load rules from each glob pattern
|
|
104
|
+
for glob_pattern in manifest.rule_globs:
|
|
105
|
+
# Resolve the glob relative to plugin dir
|
|
106
|
+
parts = glob_pattern.rsplit("/", 1)
|
|
107
|
+
if len(parts) == 2:
|
|
108
|
+
sub_dir = plugin_dir / parts[0]
|
|
109
|
+
else:
|
|
110
|
+
sub_dir = plugin_dir
|
|
111
|
+
|
|
112
|
+
if sub_dir.is_dir():
|
|
113
|
+
loaded = load_yaml_rules(
|
|
114
|
+
sub_dir,
|
|
115
|
+
source=RuleSource.USER,
|
|
116
|
+
pack=f"plugin:{manifest.name}",
|
|
117
|
+
)
|
|
118
|
+
rules.extend(loaded)
|
|
119
|
+
|
|
120
|
+
if rules:
|
|
121
|
+
logger.info("Plugin '%s': loaded %d rules", manifest.name, len(rules))
|
|
122
|
+
|
|
123
|
+
return rules
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def load_all_plugins(project_dir: str) -> tuple[list[PluginManifest], list[UnifiedRule]]:
|
|
127
|
+
"""Discover and load all plugins. Returns (manifests, all_rules)."""
|
|
128
|
+
manifests = discover_plugins(project_dir)
|
|
129
|
+
all_rules: list[UnifiedRule] = []
|
|
130
|
+
for m in manifests:
|
|
131
|
+
all_rules.extend(load_plugin(m))
|
|
132
|
+
return manifests, all_rules
|