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.
Files changed (345) hide show
  1. attocode_code_intel-0.1.0.dist-info/METADATA +221 -0
  2. attocode_code_intel-0.1.0.dist-info/RECORD +345 -0
  3. attocode_code_intel-0.1.0.dist-info/WHEEL +4 -0
  4. attocode_code_intel-0.1.0.dist-info/entry_points.txt +2 -0
  5. attocode_code_intel-0.1.0.dist-info/licenses/LICENSE +21 -0
  6. attocode_intel/GUIDELINES.md +432 -0
  7. attocode_intel/__init__.py +52 -0
  8. attocode_intel/_internal/__init__.py +1 -0
  9. attocode_intel/_internal/errors/__init__.py +126 -0
  10. attocode_intel/_internal/integrations/__init__.py +1 -0
  11. attocode_intel/_internal/integrations/context/__init__.py +1 -0
  12. attocode_intel/_internal/integrations/context/architecture_drift.py +354 -0
  13. attocode_intel/_internal/integrations/context/ast_chunker.py +202 -0
  14. attocode_intel/_internal/integrations/context/ast_service.py +1483 -0
  15. attocode_intel/_internal/integrations/context/blob_oid.py +423 -0
  16. attocode_intel/_internal/integrations/context/cache_manifest.py +221 -0
  17. attocode_intel/_internal/integrations/context/cas.py +466 -0
  18. attocode_intel/_internal/integrations/context/code_analyzer.py +528 -0
  19. attocode_intel/_internal/integrations/context/codebase_ast.py +1461 -0
  20. attocode_intel/_internal/integrations/context/codebase_context.py +1736 -0
  21. attocode_intel/_internal/integrations/context/cross_mode.py +263 -0
  22. attocode_intel/_internal/integrations/context/cross_references.py +616 -0
  23. attocode_intel/_internal/integrations/context/embedding_rotation.py +597 -0
  24. attocode_intel/_internal/integrations/context/embeddings.py +282 -0
  25. attocode_intel/_internal/integrations/context/frecency.py +427 -0
  26. attocode_intel/_internal/integrations/context/fuzzy.py +320 -0
  27. attocode_intel/_internal/integrations/context/hierarchical_explorer.py +331 -0
  28. attocode_intel/_internal/integrations/context/hydration.py +90 -0
  29. attocode_intel/_internal/integrations/context/index_repair.py +87 -0
  30. attocode_intel/_internal/integrations/context/index_store.py +586 -0
  31. attocode_intel/_internal/integrations/context/memory_store.py +602 -0
  32. attocode_intel/_internal/integrations/context/module_resolution.py +52 -0
  33. attocode_intel/_internal/integrations/context/query_constraints.py +361 -0
  34. attocode_intel/_internal/integrations/context/query_history.py +417 -0
  35. attocode_intel/_internal/integrations/context/reranker.py +351 -0
  36. attocode_intel/_internal/integrations/context/semantic_search.py +2931 -0
  37. attocode_intel/_internal/integrations/context/syntax_evidence.py +94 -0
  38. attocode_intel/_internal/integrations/context/systemone_ranker.py +245 -0
  39. attocode_intel/_internal/integrations/context/temporal_coupling.py +411 -0
  40. attocode_intel/_internal/integrations/context/trigram_index.py +820 -0
  41. attocode_intel/_internal/integrations/context/trigram_regex.py +171 -0
  42. attocode_intel/_internal/integrations/context/ts_parser.py +2199 -0
  43. attocode_intel/_internal/integrations/context/vector_store.py +795 -0
  44. attocode_intel/_internal/integrations/feature_flags/__init__.py +357 -0
  45. attocode_intel/_internal/integrations/lsp/__init__.py +1 -0
  46. attocode_intel/_internal/integrations/lsp/client.py +1140 -0
  47. attocode_intel/_internal/integrations/mcp/__init__.py +1 -0
  48. attocode_intel/_internal/integrations/mcp/client.py +253 -0
  49. attocode_intel/_internal/integrations/security/__init__.py +1 -0
  50. attocode_intel/_internal/integrations/security/custom_rules.py +176 -0
  51. attocode_intel/_internal/integrations/security/dataflow.py +522 -0
  52. attocode_intel/_internal/integrations/security/dependency_audit.py +303 -0
  53. attocode_intel/_internal/integrations/security/matcher.py +44 -0
  54. attocode_intel/_internal/integrations/security/patterns.py +724 -0
  55. attocode_intel/_internal/integrations/security/scanner.py +351 -0
  56. attocode_intel/_internal/integrations/utilities/__init__.py +1 -0
  57. attocode_intel/_internal/integrations/utilities/ignore.py +64 -0
  58. attocode_intel/_internal/integrations/utilities/token_estimate.py +51 -0
  59. attocode_intel/_internal/tricks/__init__.py +1 -0
  60. attocode_intel/_internal/tricks/recursive_context.py +357 -0
  61. attocode_intel/_internal/types/__init__.py +1 -0
  62. attocode_intel/_internal/types/events.py +289 -0
  63. attocode_intel/_internal/types/severity.py +15 -0
  64. attocode_intel/_shared.py +341 -0
  65. attocode_intel/analysis_coverage.py +56 -0
  66. attocode_intel/api/__init__.py +1 -0
  67. attocode_intel/api/access.py +32 -0
  68. attocode_intel/api/app.py +247 -0
  69. attocode_intel/api/auth/__init__.py +128 -0
  70. attocode_intel/api/auth/api_keys.py +61 -0
  71. attocode_intel/api/auth/context.py +34 -0
  72. attocode_intel/api/auth/jwt.py +66 -0
  73. attocode_intel/api/auth/oauth.py +146 -0
  74. attocode_intel/api/auth/passwords.py +15 -0
  75. attocode_intel/api/deps.py +351 -0
  76. attocode_intel/api/knowledge_bridge.py +13 -0
  77. attocode_intel/api/mcp_transport.py +51 -0
  78. attocode_intel/api/middleware.py +411 -0
  79. attocode_intel/api/models.py +698 -0
  80. attocode_intel/api/providers/__init__.py +1 -0
  81. attocode_intel/api/providers/db_provider.py +1106 -0
  82. attocode_intel/api/providers/local_provider.py +476 -0
  83. attocode_intel/api/providers/remote_provider.py +740 -0
  84. attocode_intel/api/repository_source.py +21 -0
  85. attocode_intel/api/routes/__init__.py +1 -0
  86. attocode_intel/api/routes/_pin_helper.py +158 -0
  87. attocode_intel/api/routes/activity.py +148 -0
  88. attocode_intel/api/routes/adr.py +102 -0
  89. attocode_intel/api/routes/analysis.py +515 -0
  90. attocode_intel/api/routes/api_keys.py +170 -0
  91. attocode_intel/api/routes/auth.py +428 -0
  92. attocode_intel/api/routes/branches.py +262 -0
  93. attocode_intel/api/routes/cross_repo_search.py +198 -0
  94. attocode_intel/api/routes/embeddings.py +386 -0
  95. attocode_intel/api/routes/files.py +132 -0
  96. attocode_intel/api/routes/files_v2.py +388 -0
  97. attocode_intel/api/routes/gc.py +322 -0
  98. attocode_intel/api/routes/git_v2.py +433 -0
  99. attocode_intel/api/routes/graph.py +174 -0
  100. attocode_intel/api/routes/graph_viz.py +160 -0
  101. attocode_intel/api/routes/health.py +62 -0
  102. attocode_intel/api/routes/history.py +175 -0
  103. attocode_intel/api/routes/jobs.py +84 -0
  104. attocode_intel/api/routes/learning.py +205 -0
  105. attocode_intel/api/routes/lsp.py +272 -0
  106. attocode_intel/api/routes/notify.py +623 -0
  107. attocode_intel/api/routes/operations.py +27 -0
  108. attocode_intel/api/routes/orgs.py +924 -0
  109. attocode_intel/api/routes/preferences.py +100 -0
  110. attocode_intel/api/routes/presence.py +42 -0
  111. attocode_intel/api/routes/projects.py +116 -0
  112. attocode_intel/api/routes/repos.py +47 -0
  113. attocode_intel/api/routes/search.py +180 -0
  114. attocode_intel/api/routes/snapshots.py +614 -0
  115. attocode_intel/api/routes/webhooks.py +313 -0
  116. attocode_intel/api/routes/websocket.py +116 -0
  117. attocode_intel/api/static/graph.html +239 -0
  118. attocode_intel/api/utils.py +96 -0
  119. attocode_intel/artifacts/__init__.py +45 -0
  120. attocode_intel/artifacts/hashing.py +230 -0
  121. attocode_intel/artifacts/provenance.py +190 -0
  122. attocode_intel/artifacts/retrieval_pin.py +159 -0
  123. attocode_intel/audit.py +59 -0
  124. attocode_intel/bug_finder.py +206 -0
  125. attocode_intel/bundle.py +80 -0
  126. attocode_intel/catalog.py +242 -0
  127. attocode_intel/change_review.py +75 -0
  128. attocode_intel/cli.py +3062 -0
  129. attocode_intel/community.py +63 -0
  130. attocode_intel/confidence/__init__.py +144 -0
  131. attocode_intel/confidence/jev.py +234 -0
  132. attocode_intel/confidence/llm.py +330 -0
  133. attocode_intel/confidence/redact.py +44 -0
  134. attocode_intel/confidence/settings.py +74 -0
  135. attocode_intel/config.py +256 -0
  136. attocode_intel/crypto.py +31 -0
  137. attocode_intel/db/__init__.py +7 -0
  138. attocode_intel/db/base.py +33 -0
  139. attocode_intel/db/engine.py +61 -0
  140. attocode_intel/db/models.py +529 -0
  141. attocode_intel/entrypoint.py +185 -0
  142. attocode_intel/focused_evidence.py +130 -0
  143. attocode_intel/freshness.py +154 -0
  144. attocode_intel/gateway.py +734 -0
  145. attocode_intel/git/__init__.py +7 -0
  146. attocode_intel/git/credentials.py +75 -0
  147. attocode_intel/git/manager.py +493 -0
  148. attocode_intel/git/models.py +96 -0
  149. attocode_intel/git/storage.py +98 -0
  150. attocode_intel/graph_query_parser.py +761 -0
  151. attocode_intel/helpers.py +712 -0
  152. attocode_intel/indexing/__init__.py +29 -0
  153. attocode_intel/indexing/debouncer.py +136 -0
  154. attocode_intel/indexing/delta_indexer.py +151 -0
  155. attocode_intel/indexing/full_indexer.py +323 -0
  156. attocode_intel/indexing/incremental.py +240 -0
  157. attocode_intel/indexing/parser.py +403 -0
  158. attocode_intel/indexing/summarizer.py +264 -0
  159. attocode_intel/installer.py +1896 -0
  160. attocode_intel/knowledge.py +269 -0
  161. attocode_intel/local_knowledge.py +77 -0
  162. attocode_intel/migrations/alembic.ini +36 -0
  163. attocode_intel/migrations/env.py +71 -0
  164. attocode_intel/migrations/script.py.mako +27 -0
  165. attocode_intel/migrations/versions/001_initial_schema.py +219 -0
  166. attocode_intel/migrations/versions/002_branch_version_and_embeddings.py +61 -0
  167. attocode_intel/migrations/versions/003_embedding_uniqueness_and_cascade.py +51 -0
  168. attocode_intel/migrations/versions/004_audit_events.py +38 -0
  169. attocode_intel/migrations/versions/005_user_preferences.py +30 -0
  170. attocode_intel/migrations/versions/006_google_oauth.py +27 -0
  171. attocode_intel/migrations/versions/007_pgvector_embeddings.py +28 -0
  172. attocode_intel/migrations/versions/008_symbol_references.py +37 -0
  173. attocode_intel/migrations/versions/009_webhook_secret_encryption.py +25 -0
  174. attocode_intel/migrations/versions/010_add_commits_table.py +40 -0
  175. attocode_intel/migrations/versions/011_commit_changed_files.py +25 -0
  176. attocode_intel/migrations/versions/012_branch_merged_at.py +25 -0
  177. attocode_intel/migrations/versions/013_revoked_tokens.py +32 -0
  178. attocode_intel/migrations/versions/014_blame_hunks.py +40 -0
  179. attocode_intel/migrations/versions/015_commit_file_stats.py +40 -0
  180. attocode_intel/migrations/versions/016_embedding_provenance_columns.py +179 -0
  181. attocode_intel/migrations/versions/017_provenance_table.py +102 -0
  182. attocode_intel/migrations/versions/018_repo_snapshots.py +178 -0
  183. attocode_intel/migrations/versions/019_symref_caller.py +42 -0
  184. attocode_intel/migrations/versions/020_shared_knowledge.py +29 -0
  185. attocode_intel/onboarding.py +258 -0
  186. attocode_intel/output.py +178 -0
  187. attocode_intel/precision.py +133 -0
  188. attocode_intel/presence.py +160 -0
  189. attocode_intel/probe.py +110 -0
  190. attocode_intel/project_dir.py +97 -0
  191. attocode_intel/pubsub.py +113 -0
  192. attocode_intel/query_ranking.py +242 -0
  193. attocode_intel/readiness.py +972 -0
  194. attocode_intel/redis.py +51 -0
  195. attocode_intel/reference_pages.py +60 -0
  196. attocode_intel/remote.py +131 -0
  197. attocode_intel/repo_ranker.py +246 -0
  198. attocode_intel/request_context.py +82 -0
  199. attocode_intel/rules/__init__.py +28 -0
  200. attocode_intel/rules/ci.py +320 -0
  201. attocode_intel/rules/combinators.py +293 -0
  202. attocode_intel/rules/enricher.py +127 -0
  203. attocode_intel/rules/evolution.py +452 -0
  204. attocode_intel/rules/executor.py +531 -0
  205. attocode_intel/rules/filters/__init__.py +5 -0
  206. attocode_intel/rules/filters/pipeline.py +151 -0
  207. attocode_intel/rules/formatter.py +198 -0
  208. attocode_intel/rules/hygiene.py +242 -0
  209. attocode_intel/rules/importers/__init__.py +1 -0
  210. attocode_intel/rules/importers/semgrep.py +382 -0
  211. attocode_intel/rules/loader.py +292 -0
  212. attocode_intel/rules/marketplace.py +375 -0
  213. attocode_intel/rules/metavar.py +181 -0
  214. attocode_intel/rules/model.py +317 -0
  215. attocode_intel/rules/packs/__init__.py +1 -0
  216. attocode_intel/rules/packs/community/_license_templates/Apache-2.0.txt +176 -0
  217. attocode_intel/rules/packs/community/_license_templates/MIT.txt +21 -0
  218. attocode_intel/rules/packs/community/bandit-python/LICENSE +176 -0
  219. attocode_intel/rules/packs/community/bandit-python/NOTICE +13 -0
  220. attocode_intel/rules/packs/community/bandit-python/PORTING.md +23 -0
  221. attocode_intel/rules/packs/community/bandit-python/manifest.yaml +14 -0
  222. attocode_intel/rules/packs/community/bandit-python/rules/B105-hardcoded-password.yaml +23 -0
  223. attocode_intel/rules/packs/community/bandit-python/rules/B303-weak-md5.yaml +21 -0
  224. attocode_intel/rules/packs/community/bandit-python/rules/B304-weak-des.yaml +20 -0
  225. attocode_intel/rules/packs/community/bandit-python/rules/B307-use-of-eval.yaml +22 -0
  226. attocode_intel/rules/packs/community/bandit-python/rules/B324-weak-sha1.yaml +21 -0
  227. attocode_intel/rules/packs/community/bandit-python/rules/B501-no-cert-validation.yaml +23 -0
  228. attocode_intel/rules/packs/community/bandit-python/rules/B602-shell-true.yaml +22 -0
  229. attocode_intel/rules/packs/community/bandit-python/rules/B608-sqli.yaml +22 -0
  230. attocode_intel/rules/packs/community/eslint-typescript/LICENSE +21 -0
  231. attocode_intel/rules/packs/community/eslint-typescript/NOTICE +13 -0
  232. attocode_intel/rules/packs/community/eslint-typescript/PORTING.md +23 -0
  233. attocode_intel/rules/packs/community/eslint-typescript/manifest.yaml +14 -0
  234. attocode_intel/rules/packs/community/eslint-typescript/rules/eqeqeq.yaml +20 -0
  235. attocode_intel/rules/packs/community/eslint-typescript/rules/no-eval.yaml +22 -0
  236. attocode_intel/rules/packs/community/eslint-typescript/rules/no-implied-eval.yaml +20 -0
  237. attocode_intel/rules/packs/community/eslint-typescript/rules/no-new-func.yaml +21 -0
  238. attocode_intel/rules/packs/community/eslint-typescript/rules/no-throw-literal.yaml +19 -0
  239. attocode_intel/rules/packs/community/eslint-typescript/rules/no-var.yaml +20 -0
  240. attocode_intel/rules/packs/community/eslint-typescript/rules/no-with.yaml +18 -0
  241. attocode_intel/rules/packs/community/gosec-go/LICENSE +176 -0
  242. attocode_intel/rules/packs/community/gosec-go/NOTICE +13 -0
  243. attocode_intel/rules/packs/community/gosec-go/PORTING.md +23 -0
  244. attocode_intel/rules/packs/community/gosec-go/manifest.yaml +14 -0
  245. attocode_intel/rules/packs/community/gosec-go/rules/G101-hardcoded-credentials.yaml +22 -0
  246. attocode_intel/rules/packs/community/gosec-go/rules/G102-bind-all-interfaces.yaml +21 -0
  247. attocode_intel/rules/packs/community/gosec-go/rules/G103-unsafe-block.yaml +21 -0
  248. attocode_intel/rules/packs/community/gosec-go/rules/G201-sql-format.yaml +21 -0
  249. attocode_intel/rules/packs/community/gosec-go/rules/G304-file-inclusion.yaml +23 -0
  250. attocode_intel/rules/packs/community/gosec-go/rules/G401-weak-cipher.yaml +20 -0
  251. attocode_intel/rules/packs/community/gosec-go/rules/G402-tls-min-version.yaml +20 -0
  252. attocode_intel/rules/packs/community/gosec-go/rules/G501-md5-import.yaml +21 -0
  253. attocode_intel/rules/packs/examples/cpp/manifest.yaml +4 -0
  254. attocode_intel/rules/packs/examples/cpp/rules/security.yaml +90 -0
  255. attocode_intel/rules/packs/examples/cpp/rules/structural.yaml +42 -0
  256. attocode_intel/rules/packs/examples/csharp/manifest.yaml +4 -0
  257. attocode_intel/rules/packs/examples/csharp/rules/security.yaml +69 -0
  258. attocode_intel/rules/packs/examples/csharp/rules/structural.yaml +42 -0
  259. attocode_intel/rules/packs/examples/go/manifest.yaml +4 -0
  260. attocode_intel/rules/packs/examples/go/rules/performance.yaml +136 -0
  261. attocode_intel/rules/packs/examples/go/rules/security.yaml +48 -0
  262. attocode_intel/rules/packs/examples/go/rules/structural.yaml +58 -0
  263. attocode_intel/rules/packs/examples/go/taint/sanitizers.yaml +24 -0
  264. attocode_intel/rules/packs/examples/go/taint/sinks.yaml +51 -0
  265. attocode_intel/rules/packs/examples/go/taint/sources.yaml +26 -0
  266. attocode_intel/rules/packs/examples/java/manifest.yaml +4 -0
  267. attocode_intel/rules/packs/examples/java/rules/correctness.yaml +55 -0
  268. attocode_intel/rules/packs/examples/java/rules/structural.yaml +48 -0
  269. attocode_intel/rules/packs/examples/java/taint/sinks.yaml +37 -0
  270. attocode_intel/rules/packs/examples/java/taint/sources.yaml +21 -0
  271. attocode_intel/rules/packs/examples/kotlin/manifest.yaml +4 -0
  272. attocode_intel/rules/packs/examples/kotlin/rules/correctness.yaml +50 -0
  273. attocode_intel/rules/packs/examples/kotlin/rules/structural.yaml +38 -0
  274. attocode_intel/rules/packs/examples/php/manifest.yaml +4 -0
  275. attocode_intel/rules/packs/examples/php/rules/security.yaml +66 -0
  276. attocode_intel/rules/packs/examples/php/rules/structural.yaml +41 -0
  277. attocode_intel/rules/packs/examples/python/manifest.yaml +4 -0
  278. attocode_intel/rules/packs/examples/python/rules/performance.yaml +90 -0
  279. attocode_intel/rules/packs/examples/python/rules/structural.yaml +55 -0
  280. attocode_intel/rules/packs/examples/ruby/manifest.yaml +4 -0
  281. attocode_intel/rules/packs/examples/ruby/rules/security.yaml +73 -0
  282. attocode_intel/rules/packs/examples/ruby/rules/structural.yaml +41 -0
  283. attocode_intel/rules/packs/examples/rust/manifest.yaml +4 -0
  284. attocode_intel/rules/packs/examples/rust/rules/correctness.yaml +51 -0
  285. attocode_intel/rules/packs/examples/rust/rules/structural.yaml +49 -0
  286. attocode_intel/rules/packs/examples/typescript/manifest.yaml +4 -0
  287. attocode_intel/rules/packs/examples/typescript/rules/performance.yaml +70 -0
  288. attocode_intel/rules/packs/examples/typescript/rules/structural.yaml +48 -0
  289. attocode_intel/rules/packs/pack_loader.py +244 -0
  290. attocode_intel/rules/plugins/__init__.py +1 -0
  291. attocode_intel/rules/plugins/plugin_loader.py +132 -0
  292. attocode_intel/rules/profiling.py +332 -0
  293. attocode_intel/rules/registry.py +213 -0
  294. attocode_intel/rules/sarif.py +187 -0
  295. attocode_intel/rules/synthesis.py +420 -0
  296. attocode_intel/rules/testing.py +326 -0
  297. attocode_intel/server.py +734 -0
  298. attocode_intel/service.py +3416 -0
  299. attocode_intel/sharing.py +57 -0
  300. attocode_intel/storage/__init__.py +6 -0
  301. attocode_intel/storage/branch_overlay.py +352 -0
  302. attocode_intel/storage/content_store.py +220 -0
  303. attocode_intel/storage/dependency_store.py +143 -0
  304. attocode_intel/storage/diff_engine.py +187 -0
  305. attocode_intel/storage/embedding_store.py +594 -0
  306. attocode_intel/storage/provenance_store.py +137 -0
  307. attocode_intel/storage/security_scanner_db.py +164 -0
  308. attocode_intel/storage/symbol_store.py +160 -0
  309. attocode_intel/structural_evidence.py +144 -0
  310. attocode_intel/symbol_inspection.py +141 -0
  311. attocode_intel/symbol_links.py +167 -0
  312. attocode_intel/telemetry.py +42 -0
  313. attocode_intel/test_ranking.py +73 -0
  314. attocode_intel/testing/__init__.py +43 -0
  315. attocode_intel/testing/fixtures.py +122 -0
  316. attocode_intel/testing/helpers.py +75 -0
  317. attocode_intel/testing/mocks.py +99 -0
  318. attocode_intel/tools/__init__.py +2 -0
  319. attocode_intel/tools/adr_tools.py +628 -0
  320. attocode_intel/tools/analysis_tools.py +382 -0
  321. attocode_intel/tools/composite_tools.py +695 -0
  322. attocode_intel/tools/cross_mode_tools.py +173 -0
  323. attocode_intel/tools/dead_code_tools.py +582 -0
  324. attocode_intel/tools/distill_tools.py +353 -0
  325. attocode_intel/tools/frecency_tools.py +220 -0
  326. attocode_intel/tools/fuzzy_tools.py +255 -0
  327. attocode_intel/tools/history_tools.py +486 -0
  328. attocode_intel/tools/learning_tools.py +222 -0
  329. attocode_intel/tools/lsp_tools.py +218 -0
  330. attocode_intel/tools/maintenance_tools.py +1662 -0
  331. attocode_intel/tools/navigation_tools.py +220 -0
  332. attocode_intel/tools/overlay_tools.py +540 -0
  333. attocode_intel/tools/pin_store.py +432 -0
  334. attocode_intel/tools/pin_tools.py +266 -0
  335. attocode_intel/tools/query_constraints_tools.py +262 -0
  336. attocode_intel/tools/query_history_tools.py +158 -0
  337. attocode_intel/tools/readiness_tools.py +44 -0
  338. attocode_intel/tools/rule_tools.py +992 -0
  339. attocode_intel/tools/search_tools.py +770 -0
  340. attocode_intel/tools/snapshot_tools.py +624 -0
  341. attocode_intel/workers/__init__.py +1 -0
  342. attocode_intel/workers/job_utils.py +111 -0
  343. attocode_intel/workers/jobs.py +573 -0
  344. attocode_intel/workers/run.py +30 -0
  345. attocode_intel/workers/settings.py +61 -0
@@ -0,0 +1,330 @@
1
+ """LLM-based false positive classifier for rule findings.
2
+
3
+ Uses a fast LLM (Haiku) to classify findings as true positive or false
4
+ positive based on code context, rule description, and file purpose.
5
+ Inspired by the Datadog approach to agentic SAST triage.
6
+
7
+ Measured against the rule corpus this is the most precise scorer available
8
+ (P=0.96) and the worst calibrated (ECE=0.28) — a good gate, a poor probability.
9
+
10
+ Reference: https://www.datadoghq.com/blog/using-llms-to-filter-out-false-positives/
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import logging
16
+ from collections.abc import Callable
17
+ from concurrent.futures import ThreadPoolExecutor
18
+ from dataclasses import dataclass
19
+ from enum import StrEnum
20
+ from typing import Any
21
+
22
+ from attocode_intel.confidence.redact import redact
23
+
24
+ # (prompt, model, max_tokens) -> completion text
25
+ LLMCaller = Callable[[str, str, int], str]
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+ # Haiku for cost efficiency (~$0.01 per classification). The id differs by
30
+ # route, so "" means "let _cheap_model() pick for whichever key is present".
31
+ DEFAULT_MODEL = ""
32
+ _OPENROUTER_MODEL = "anthropic/claude-haiku-4.5"
33
+ _ANTHROPIC_MODEL = "claude-haiku-4-5-20251001"
34
+ # Measured: still scaling at 32 (16.9x) — per-call latency is ~1.6s, so there
35
+ # is far more of it to hide than the noul head has.
36
+ _WORKERS = 32
37
+
38
+
39
+ def _default_llm_caller() -> LLMCaller:
40
+ """The project's shared client. Only importable inside this repo."""
41
+ try:
42
+ from eval.meta_harness._llm_client import call_llm
43
+ except ModuleNotFoundError as exc: # pragma: no cover - depends on checkout
44
+ raise RuntimeError(
45
+ "No LLM caller available. Pass llm_caller=..., or run inside the "
46
+ "attocode repo where eval.meta_harness is importable."
47
+ ) from exc
48
+
49
+ def call(prompt: str, model: str, max_tokens: int) -> str:
50
+ return call_llm(prompt, model=model, max_tokens=max_tokens)
51
+
52
+ return call
53
+
54
+
55
+ def _load_env() -> None:
56
+ """Populate keys from the project .env when running inside the repo."""
57
+ try:
58
+ from eval.meta_harness._llm_client import load_env
59
+ except ModuleNotFoundError: # pragma: no cover - depends on checkout
60
+ return
61
+ load_env()
62
+
63
+
64
+ def _cheap_model() -> str:
65
+ """The Haiku id for whichever provider call_llm() will route to."""
66
+ import os
67
+
68
+ return _OPENROUTER_MODEL if os.environ.get("OPENROUTER_API_KEY") else _ANTHROPIC_MODEL
69
+
70
+
71
+ class FPVerdict(StrEnum):
72
+ TRUE_POSITIVE = "true_positive"
73
+ FALSE_POSITIVE = "false_positive"
74
+ UNCERTAIN = "uncertain"
75
+
76
+
77
+ @dataclass(slots=True)
78
+ class FPClassification:
79
+ """Result of LLM-based TP/FP classification."""
80
+
81
+ rule_id: str
82
+ file: str
83
+ line: int
84
+ verdict: FPVerdict
85
+ confidence: float # 0.0-1.0
86
+ reasoning: str
87
+ tokens_used: int = 0
88
+ latency_ms: float = 0.0
89
+
90
+
91
+ _CLASSIFICATION_PROMPT = """\
92
+ You are a security code reviewer. Analyze whether the following static analysis finding is a TRUE POSITIVE (real security issue) or FALSE POSITIVE (benign code incorrectly flagged).
93
+
94
+ ## Finding
95
+ - **Rule**: {rule_id}
96
+ - **Severity**: {severity}
97
+ - **Description**: {description}
98
+ - **CWE**: {cwe}
99
+ - **File**: {file}:{line}
100
+
101
+ ## Code Context
102
+ ```
103
+ {code_context}
104
+ ```
105
+
106
+ ## Matched Line
107
+ ```
108
+ {matched_line}
109
+ ```
110
+
111
+ ## Rule Explanation
112
+ {explanation}
113
+
114
+ ## Instructions
115
+ Determine if this is a true positive or false positive. Consider:
116
+ 1. Is the flagged pattern actually exploitable in this context?
117
+ 2. Is there input validation, sanitization, or other mitigation nearby?
118
+ 3. Is this test code, example code, or documentation?
119
+ 4. Could the value be controlled by an attacker?
120
+
121
+ Respond with EXACTLY this format (no other text):
122
+ VERDICT: TRUE_POSITIVE or FALSE_POSITIVE or UNCERTAIN
123
+ CONFIDENCE: 0.0 to 1.0
124
+ REASONING: one sentence explanation
125
+ """
126
+
127
+
128
+ def classify_finding(
129
+ rule_id: str,
130
+ severity: str,
131
+ description: str,
132
+ cwe: str,
133
+ file: str,
134
+ line: int,
135
+ matched_line: str,
136
+ code_context: str,
137
+ explanation: str = "",
138
+ *,
139
+ model: str = DEFAULT_MODEL,
140
+ llm_caller: LLMCaller | None = None,
141
+ ) -> FPClassification:
142
+ """Classify a single finding as TP/FP using an LLM.
143
+
144
+ Args:
145
+ rule_id: The rule that produced the finding.
146
+ severity: Finding severity.
147
+ description: What was found.
148
+ cwe: CWE identifier.
149
+ file: Source file path.
150
+ line: Line number.
151
+ matched_line: The matched source line.
152
+ code_context: ~30 lines of surrounding code.
153
+ explanation: Rule explanation (why it matters).
154
+ model: LLM model to use. Defaults to Haiku on whichever route is keyed.
155
+ llm_caller: Override the provider call. Defaults to the project's
156
+ shared client, which is only importable inside this repo.
157
+
158
+ Returns:
159
+ FPClassification with verdict, confidence, and reasoning.
160
+ """
161
+ import os
162
+ import time
163
+
164
+ _load_env()
165
+ if not (os.environ.get("OPENROUTER_API_KEY") or os.environ.get("ANTHROPIC_API_KEY")):
166
+ return FPClassification(
167
+ rule_id=rule_id, file=file, line=line,
168
+ verdict=FPVerdict.UNCERTAIN, confidence=0.0,
169
+ reasoning="No API key available",
170
+ )
171
+
172
+ prompt = _CLASSIFICATION_PROMPT.format(
173
+ rule_id=redact(rule_id),
174
+ severity=redact(severity),
175
+ description=redact(description)[:1000],
176
+ cwe=redact(cwe or "N/A"),
177
+ file=redact(file),
178
+ line=line,
179
+ code_context=redact(code_context)[:2000],
180
+ matched_line=redact(matched_line)[:200],
181
+ explanation=redact(explanation or description)[:2000],
182
+ )
183
+
184
+ start = time.monotonic()
185
+ try:
186
+ text = (llm_caller or _default_llm_caller())(
187
+ prompt, model or _cheap_model(), 200,
188
+ )
189
+ elapsed = (time.monotonic() - start) * 1000
190
+ # call_llm returns text only; token counts are not available on this path.
191
+ return _parse_response(rule_id, file, line, text, 0, elapsed)
192
+
193
+ except ImportError:
194
+ return FPClassification(
195
+ rule_id=rule_id, file=file, line=line,
196
+ verdict=FPVerdict.UNCERTAIN, confidence=0.0,
197
+ reasoning="LLM SDK not installed (need openai for OpenRouter)",
198
+ )
199
+ except Exception as exc:
200
+ elapsed = (time.monotonic() - start) * 1000
201
+ return FPClassification(
202
+ rule_id=rule_id, file=file, line=line,
203
+ verdict=FPVerdict.UNCERTAIN, confidence=0.0,
204
+ reasoning=f"API error: {exc}",
205
+ latency_ms=elapsed,
206
+ )
207
+
208
+
209
+ def _parse_response(
210
+ rule_id: str, file: str, line: int,
211
+ text: str, tokens: int, latency_ms: float,
212
+ ) -> FPClassification:
213
+ """Parse the LLM response into a classification."""
214
+ verdict = FPVerdict.UNCERTAIN
215
+ confidence = 0.5
216
+ reasoning = text.strip()
217
+
218
+ for resp_line in text.strip().splitlines():
219
+ resp_line = resp_line.strip()
220
+ if resp_line.startswith("VERDICT:"):
221
+ v = resp_line.split(":", 1)[1].strip().upper()
222
+ if "TRUE_POSITIVE" in v:
223
+ verdict = FPVerdict.TRUE_POSITIVE
224
+ elif "FALSE_POSITIVE" in v:
225
+ verdict = FPVerdict.FALSE_POSITIVE
226
+ elif resp_line.startswith("CONFIDENCE:"):
227
+ try:
228
+ confidence = float(resp_line.split(":", 1)[1].strip())
229
+ confidence = max(0.0, min(1.0, confidence))
230
+ except ValueError:
231
+ pass
232
+ elif resp_line.startswith("REASONING:"):
233
+ reasoning = resp_line.split(":", 1)[1].strip()
234
+
235
+ return FPClassification(
236
+ rule_id=rule_id,
237
+ file=file,
238
+ line=line,
239
+ verdict=verdict,
240
+ confidence=confidence,
241
+ reasoning=reasoning,
242
+ tokens_used=tokens,
243
+ latency_ms=latency_ms,
244
+ )
245
+
246
+
247
+ # ---------------------------------------------------------------------------
248
+ # Batch classification for benchmark evaluation
249
+ # ---------------------------------------------------------------------------
250
+
251
+
252
+ def classify_findings_batch(
253
+ findings_with_context: list[dict[str, Any]],
254
+ *,
255
+ model: str = DEFAULT_MODEL,
256
+ llm_caller: LLMCaller | None = None,
257
+ ) -> list[FPClassification]:
258
+ """Classify multiple findings in batch.
259
+
260
+ Args:
261
+ findings_with_context: List of dicts with keys matching
262
+ classify_finding() parameters.
263
+ model: LLM model.
264
+
265
+ Returns:
266
+ List of classifications, one per finding.
267
+ """
268
+ def one(f: dict[str, Any]) -> FPClassification:
269
+ return classify_finding(
270
+ rule_id=f.get("rule_id", ""),
271
+ severity=f.get("severity", ""),
272
+ description=f.get("description", ""),
273
+ cwe=f.get("cwe", ""),
274
+ file=f.get("file", ""),
275
+ line=f.get("line", 0),
276
+ matched_line=f.get("matched_line", ""),
277
+ code_context=f.get("code_context", ""),
278
+ explanation=f.get("explanation", ""),
279
+ model=model,
280
+ llm_caller=llm_caller,
281
+ )
282
+
283
+ # Concurrent so a wall-clock comparison against other scorers measures the
284
+ # model, not this loop. map() preserves input order.
285
+ with ThreadPoolExecutor(max_workers=_WORKERS) as pool:
286
+ return list(pool.map(one, findings_with_context))
287
+
288
+
289
+ # ---------------------------------------------------------------------------
290
+ # Scorer interface
291
+ # ---------------------------------------------------------------------------
292
+
293
+
294
+ def estimate(findings: list[Any], *, min_confidence: float) -> list[float | None]:
295
+ """Return p(true positive) per finding, or None where the model had none.
296
+
297
+ ``min_confidence`` is unused here: the classifier does not see the
298
+ incumbent constant, unlike the jev scorer which records it for comparison.
299
+ """
300
+ if not findings:
301
+ return []
302
+
303
+ payload = [
304
+ {
305
+ "rule_id": getattr(f, "rule_id", ""),
306
+ "severity": str(getattr(f, "severity", "")),
307
+ "description": getattr(f, "description", ""),
308
+ "cwe": getattr(f, "cwe", ""),
309
+ "file": f.file,
310
+ "line": f.line,
311
+ "matched_line": getattr(f, "code_snippet", ""),
312
+ "code_context": "\n".join([
313
+ *getattr(f, "context_before", []),
314
+ getattr(f, "code_snippet", ""),
315
+ *getattr(f, "context_after", []),
316
+ ]),
317
+ "explanation": getattr(f, "explanation", ""),
318
+ }
319
+ for f in findings
320
+ ]
321
+
322
+ out: list[float | None] = []
323
+ for c in classify_findings_batch(payload):
324
+ if c.verdict == FPVerdict.TRUE_POSITIVE:
325
+ out.append(c.confidence)
326
+ elif c.verdict == FPVerdict.FALSE_POSITIVE:
327
+ out.append(1.0 - c.confidence)
328
+ else:
329
+ out.append(None) # no opinion; the constant stands
330
+ return out
@@ -0,0 +1,44 @@
1
+ """Strip credentials out of code before it leaves the machine.
2
+
3
+ Every scorer in this package sends source context to a third-party API. Repo
4
+ code is exactly where hardcoded keys live, so redaction happens on the way out,
5
+ once, rather than in each scorer.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import TYPE_CHECKING
11
+
12
+ from attocode_intel._internal.integrations.security.patterns import SECRET_PATTERNS
13
+
14
+ if TYPE_CHECKING:
15
+ import re
16
+
17
+ def _placeholder(secret: str) -> str:
18
+ """Describe the secret without disclosing it.
19
+
20
+ A scorer judging a hardcoded-credential finding still needs to know that a
21
+ literal of credential shape is present. Length alone carries that, and the
22
+ surrounding assignment survives, so the evidence is intact and the value
23
+ never leaves the machine.
24
+ """
25
+ return f"[REDACTED:{len(secret)} chars]"
26
+
27
+
28
+ def _blank(match: re.Match[str]) -> str:
29
+ """Replace the secret itself, keeping the surrounding code readable."""
30
+ whole = match.group(0)
31
+ if match.lastindex: # the pattern captures just the value
32
+ start = match.start(1) - match.start(0)
33
+ end = match.end(1) - match.start(0)
34
+ return whole[:start] + _placeholder(match.group(1)) + whole[end:]
35
+ return _placeholder(whole)
36
+
37
+
38
+ def redact(text: str) -> str:
39
+ """Return text with anything matching a known secret pattern blanked out."""
40
+ if not text:
41
+ return text
42
+ for pattern in SECRET_PATTERNS:
43
+ text = pattern.pattern.sub(_blank, text)
44
+ return text
@@ -0,0 +1,74 @@
1
+ """Workspace-scoped scorer settings; never copy repository keys into os.environ."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import os
7
+ import tomllib
8
+ from contextlib import contextmanager
9
+ from contextvars import ContextVar
10
+ from pathlib import Path
11
+
12
+ from attocode_intel._internal.integrations.feature_flags import registry
13
+
14
+ logger = logging.getLogger(__name__)
15
+ _project: ContextVar[str] = ContextVar("confidence_project", default="")
16
+ _disabled: ContextVar[bool] = ContextVar("confidence_disabled", default=False)
17
+
18
+
19
+ def project_dir() -> str:
20
+ from attocode_intel.request_context import current_request
21
+
22
+ request = current_request.get()
23
+ if request is not None and request.source != "local":
24
+ return "" # Hosted repositories cannot configure provider credentials.
25
+ return _project.get() or (request.project_dir if request else "")
26
+
27
+
28
+ @contextmanager
29
+ def workspace(path: str):
30
+ token = _project.set(path)
31
+ try:
32
+ yield
33
+ finally:
34
+ _project.reset(token)
35
+
36
+
37
+ @contextmanager
38
+ def disabled():
39
+ """Rule assertion tests must remain deterministic even in a live workspace."""
40
+ token = _disabled.set(True)
41
+ try:
42
+ yield
43
+ finally:
44
+ _disabled.reset(token)
45
+
46
+
47
+ def local_settings() -> dict:
48
+ root = project_dir()
49
+ if not root:
50
+ return {}
51
+ try:
52
+ value = tomllib.loads((Path(root) / ".attocode/config.toml").read_text())["confidence"]
53
+ return value if isinstance(value, dict) else {}
54
+ except (OSError, ValueError, KeyError):
55
+ return {}
56
+
57
+
58
+ def selected() -> str:
59
+ if _disabled.get():
60
+ return "off"
61
+ return registry.resolve_with_default("CONFIDENCE", local_settings().get("scorer", "off"))
62
+
63
+
64
+ def mode() -> str:
65
+ return registry.resolve_with_default("CONFIDENCE_MODE", local_settings().get("mode", "shadow"))
66
+
67
+
68
+ def environment() -> dict[str, str]:
69
+ """Credentials/settings for this request, with process environment precedence."""
70
+ from dotenv import dotenv_values
71
+
72
+ root = project_dir()
73
+ values = dotenv_values(Path(root) / ".env") if root else {}
74
+ return {**{k: v for k, v in values.items() if v is not None}, **os.environ}