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,187 @@
1
+ """SARIF v2.1.0 output formatter for rule findings.
2
+
3
+ Produces Static Analysis Results Interchange Format (SARIF) JSON
4
+ compatible with GitHub Code Scanning, VS Code SARIF Viewer, and
5
+ Azure DevOps.
6
+
7
+ Reference: https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ from typing import Any
14
+
15
+ from attocode_intel.rules.model import EnrichedFinding, RuleSeverity
16
+
17
+ # SARIF severity mapping
18
+ _LEVEL_MAP: dict[str, str] = {
19
+ RuleSeverity.CRITICAL: "error",
20
+ RuleSeverity.HIGH: "error",
21
+ RuleSeverity.MEDIUM: "warning",
22
+ RuleSeverity.LOW: "note",
23
+ RuleSeverity.INFO: "note",
24
+ }
25
+
26
+
27
+ def _build_rule_descriptor(finding: EnrichedFinding) -> dict[str, Any]:
28
+ """Build a SARIF reportingDescriptor for a rule."""
29
+ descriptor: dict[str, Any] = {
30
+ "id": finding.rule_id,
31
+ "shortDescription": {"text": finding.rule_name},
32
+ }
33
+ if finding.description:
34
+ descriptor["fullDescription"] = {"text": finding.description}
35
+ if finding.explanation:
36
+ descriptor["help"] = {
37
+ "text": finding.explanation,
38
+ "markdown": finding.explanation,
39
+ }
40
+ if finding.recommendation:
41
+ descriptor.setdefault("help", {})
42
+ help_md = descriptor["help"].get("markdown", "")
43
+ if help_md:
44
+ help_md += "\n\n**Recommendation:** " + finding.recommendation
45
+ else:
46
+ help_md = "**Recommendation:** " + finding.recommendation
47
+ descriptor["help"]["markdown"] = help_md
48
+ descriptor["help"]["text"] = descriptor["help"].get("text", "") + " " + finding.recommendation
49
+
50
+ # CWE relationship
51
+ if finding.cwe:
52
+ cwe_id = finding.cwe.replace("CWE-", "")
53
+ descriptor["relationships"] = [{
54
+ "target": {
55
+ "id": finding.cwe,
56
+ "guid": f"cwe-{cwe_id}",
57
+ "toolComponent": {"name": "CWE"},
58
+ },
59
+ "kinds": ["superset"],
60
+ }]
61
+
62
+ # Tags
63
+ tags = list(finding.tags)
64
+ if finding.cwe:
65
+ tags.append(f"external/cwe/{finding.cwe.lower()}")
66
+ tags.append(f"security-severity/{finding.severity}")
67
+ if tags:
68
+ descriptor["properties"] = {"tags": tags}
69
+
70
+ # Few-shot examples in property bag
71
+ if finding.examples:
72
+ descriptor.setdefault("properties", {})["fewShotExamples"] = [
73
+ {
74
+ "bad": ex.bad_code,
75
+ "good": ex.good_code,
76
+ "explanation": ex.explanation,
77
+ }
78
+ for ex in finding.examples
79
+ ]
80
+
81
+ return descriptor
82
+
83
+
84
+ def _build_result(finding: EnrichedFinding) -> dict[str, Any]:
85
+ """Build a SARIF result object from an EnrichedFinding."""
86
+ result: dict[str, Any] = {
87
+ "ruleId": finding.rule_id,
88
+ "level": _LEVEL_MAP.get(finding.severity, "warning"),
89
+ "message": {"text": finding.description},
90
+ "locations": [{
91
+ "physicalLocation": {
92
+ "artifactLocation": {
93
+ "uri": finding.file.replace("\\", "/"),
94
+ "uriBaseId": "%SRCROOT%",
95
+ },
96
+ "region": {
97
+ "startLine": finding.line,
98
+ "startColumn": 1,
99
+ "snippet": {"text": finding.code_snippet[:200]},
100
+ },
101
+ },
102
+ }],
103
+ }
104
+
105
+ # Function scope
106
+ if finding.function_name:
107
+ result["locations"][0]["logicalLocations"] = [{
108
+ "name": finding.function_name,
109
+ "kind": "function",
110
+ }]
111
+
112
+ # Confidence + captures in property bag
113
+ props: dict[str, Any] = {"confidence": finding.confidence}
114
+ if finding.captures:
115
+ props["captures"] = finding.captures
116
+ if finding.suggested_fix:
117
+ props["suggestedFix"] = finding.suggested_fix
118
+ result["properties"] = props
119
+
120
+ # Related findings as relatedLocations
121
+ if finding.related_findings:
122
+ result["relatedLocations"] = [
123
+ {"id": i, "message": {"text": rid}}
124
+ for i, rid in enumerate(finding.related_findings)
125
+ ]
126
+
127
+ return result
128
+
129
+
130
+ def findings_to_sarif(
131
+ findings: list[EnrichedFinding],
132
+ *,
133
+ tool_name: str = "attocode-code-intel",
134
+ tool_version: str = "",
135
+ ) -> dict[str, Any]:
136
+ """Convert a list of EnrichedFinding into a SARIF v2.1.0 document.
137
+
138
+ Args:
139
+ findings: Analysis findings to convert.
140
+ tool_name: Name of the analysis tool.
141
+ tool_version: Tool version string.
142
+
143
+ Returns:
144
+ SARIF JSON-compatible dict.
145
+ """
146
+ # Deduplicate rules by rule_id
147
+ rules_by_id: dict[str, dict[str, Any]] = {}
148
+ for f in findings:
149
+ if f.rule_id not in rules_by_id:
150
+ rules_by_id[f.rule_id] = _build_rule_descriptor(f)
151
+
152
+ # Build rule index for ruleIndex references
153
+ rule_index: dict[str, int] = {}
154
+ rules_list = []
155
+ for i, (rid, desc) in enumerate(rules_by_id.items()):
156
+ rule_index[rid] = i
157
+ rules_list.append(desc)
158
+
159
+ # Build results
160
+ results = []
161
+ for f in findings:
162
+ result = _build_result(f)
163
+ result["ruleIndex"] = rule_index[f.rule_id]
164
+ results.append(result)
165
+
166
+ sarif: dict[str, Any] = {
167
+ "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json",
168
+ "version": "2.1.0",
169
+ "runs": [{
170
+ "tool": {
171
+ "driver": {
172
+ "name": tool_name,
173
+ "version": tool_version,
174
+ "informationUri": "https://github.com/attocode/code-intel",
175
+ "rules": rules_list,
176
+ },
177
+ },
178
+ "results": results,
179
+ }],
180
+ }
181
+
182
+ return sarif
183
+
184
+
185
+ def sarif_to_json(sarif: dict[str, Any], *, indent: int = 2) -> str:
186
+ """Serialize a SARIF document to JSON string."""
187
+ return json.dumps(sarif, indent=indent, ensure_ascii=False)
@@ -0,0 +1,420 @@
1
+ """Rule synthesis from positive / negative code samples.
2
+
3
+ Two paths:
4
+
5
+ * **Deterministic regex synthesis** — find the longest common contiguous
6
+ substring across positives, escape + word-boundary it, then validate
7
+ it doesn't match any negative. Cheap, no API needed, surprisingly
8
+ effective for shape-based patterns ("any call to ``eval``", "import
9
+ of a deprecated module", etc.).
10
+ * **LLM-assisted synthesis** — when the deterministic path can't find a
11
+ precise enough anchor, fall back to Claude. The prompt feeds samples
12
+ + description + language; the response is parsed as attocode YAML and
13
+ validated against the same samples before being accepted.
14
+
15
+ Public entry points:
16
+
17
+ * :func:`synthesize_rule` — top-level dispatch (``mode="auto"|"regex"|"llm"``)
18
+ * :func:`synthesize_regex_rule` — deterministic only
19
+
20
+ Both return a :class:`SynthesisResult` with the candidate rule (or None)
21
+ plus diagnostic messages explaining what was tried.
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ import logging
27
+ import re
28
+ from collections.abc import Callable
29
+ from dataclasses import dataclass, field
30
+
31
+ from attocode_intel.rules.model import (
32
+ RuleCategory,
33
+ RuleSeverity,
34
+ RuleSource,
35
+ RuleTier,
36
+ UnifiedRule,
37
+ )
38
+
39
+ logger = logging.getLogger(__name__)
40
+
41
+
42
+ # Length floor for the longest common substring before we accept it as a
43
+ # regex anchor. Below this we'd be matching common English/code fragments
44
+ # (e.g. "if ", "()") and producing high-FP rules.
45
+ DEFAULT_MIN_ANCHOR_LEN = 3
46
+
47
+
48
+ @dataclass(slots=True)
49
+ class SynthesisResult:
50
+ """Outcome of a synthesis attempt — None rule means nothing usable."""
51
+
52
+ rule: UnifiedRule | None
53
+ method: str # "regex" | "llm" | "none"
54
+ diagnostics: list[str] = field(default_factory=list)
55
+
56
+
57
+ # ---------------------------------------------------------------------------
58
+ # Deterministic regex synthesis
59
+ # ---------------------------------------------------------------------------
60
+
61
+
62
+ def _lcs_2way(a: str, b: str) -> str:
63
+ """Longest common contiguous substring of two strings (classic DP)."""
64
+ m, n = len(a), len(b)
65
+ if m == 0 or n == 0:
66
+ return ""
67
+ dp = [[0] * (n + 1) for _ in range(m + 1)]
68
+ end_i = max_len = 0
69
+ for i in range(1, m + 1):
70
+ for j in range(1, n + 1):
71
+ if a[i - 1] == b[j - 1]:
72
+ dp[i][j] = dp[i - 1][j - 1] + 1
73
+ if dp[i][j] > max_len:
74
+ max_len = dp[i][j]
75
+ end_i = i
76
+ return a[end_i - max_len:end_i]
77
+
78
+
79
+ def _lcs_across(strings: list[str]) -> str:
80
+ """N-way longest common substring via pairwise reduction."""
81
+ if not strings:
82
+ return ""
83
+ if len(strings) == 1:
84
+ return strings[0]
85
+ base = strings[0]
86
+ for s in strings[1:]:
87
+ base = _lcs_2way(base, s)
88
+ if not base:
89
+ return ""
90
+ return base
91
+
92
+
93
+ def _is_word_char(ch: str) -> bool:
94
+ return bool(ch) and (ch.isalnum() or ch == "_")
95
+
96
+
97
+ def _build_regex_pattern(common: str, positives: list[str] | None = None) -> str:
98
+ """``re.escape`` plus word boundaries when they're safe to add.
99
+
100
+ We only attach ``\\b`` at an edge that is a word character AND whose
101
+ actual neighbour in every positive sample is either out-of-string or
102
+ a non-word character. Otherwise ``\\b`` would prevent the pattern
103
+ from matching its own positives (e.g. anchor ``xy_`` followed by
104
+ another word char like ``a`` has no boundary between them).
105
+ """
106
+ pat = re.escape(common)
107
+ add_leading = False
108
+ add_trailing = False
109
+
110
+ if _is_word_char(common[:1]):
111
+ if positives:
112
+ add_leading = True
113
+ for p in positives:
114
+ idx = p.find(common)
115
+ if idx > 0 and _is_word_char(p[idx - 1]):
116
+ add_leading = False
117
+ break
118
+ else:
119
+ add_leading = True
120
+
121
+ if _is_word_char(common[-1:]):
122
+ if positives:
123
+ add_trailing = True
124
+ for p in positives:
125
+ idx = p.find(common)
126
+ end = idx + len(common)
127
+ if end < len(p) and _is_word_char(p[end]):
128
+ add_trailing = False
129
+ break
130
+ else:
131
+ add_trailing = True
132
+
133
+ if add_leading:
134
+ pat = r"\b" + pat
135
+ if add_trailing:
136
+ pat = pat + r"\b"
137
+ return pat
138
+
139
+
140
+ def synthesize_regex_rule(
141
+ positives: list[str],
142
+ negatives: list[str],
143
+ *,
144
+ rule_id: str,
145
+ language: str,
146
+ description: str = "",
147
+ min_anchor_len: int = DEFAULT_MIN_ANCHOR_LEN,
148
+ ) -> tuple[UnifiedRule | None, list[str]]:
149
+ """Build a regex rule from common shape across ``positives``.
150
+
151
+ Returns ``(rule, diagnostics)``. ``rule`` is ``None`` when no anchor
152
+ that satisfies both positives and negatives can be found.
153
+ """
154
+ diags: list[str] = []
155
+ if not positives:
156
+ diags.append("no positive samples provided")
157
+ return None, diags
158
+
159
+ common = _lcs_across(positives)
160
+ if len(common) < min_anchor_len:
161
+ diags.append(
162
+ f"longest common substring ({len(common)} chars) below "
163
+ f"min_anchor_len={min_anchor_len}"
164
+ )
165
+ return None, diags
166
+
167
+ pattern_str = _build_regex_pattern(common, positives=positives)
168
+ try:
169
+ compiled = re.compile(pattern_str)
170
+ except re.error as exc:
171
+ diags.append(f"regex compile failed: {exc}")
172
+ return None, diags
173
+
174
+ failed_pos = [p for p in positives if not compiled.search(p)]
175
+ if failed_pos:
176
+ diags.append(
177
+ f"synthesised pattern misses {len(failed_pos)}/{len(positives)} "
178
+ "positive(s) — anchor escaped a multi-byte boundary"
179
+ )
180
+ return None, diags
181
+
182
+ matched_neg = [n for n in negatives if compiled.search(n)]
183
+ if matched_neg:
184
+ diags.append(
185
+ f"pattern matches {len(matched_neg)}/{len(negatives)} negative "
186
+ "sample(s); common substring too generic for clean separation"
187
+ )
188
+ return None, diags
189
+
190
+ rule = UnifiedRule(
191
+ id=rule_id,
192
+ name=rule_id,
193
+ description=description or f"matches pattern {pattern_str}",
194
+ severity=RuleSeverity.MEDIUM,
195
+ category=RuleCategory.SUSPICIOUS,
196
+ languages=[language] if language else [],
197
+ pattern=compiled,
198
+ source=RuleSource.USER,
199
+ tier=RuleTier.REGEX,
200
+ confidence=0.6,
201
+ )
202
+ diags.append(f"synthesised regex pattern: {pattern_str}")
203
+ return rule, diags
204
+
205
+
206
+ # ---------------------------------------------------------------------------
207
+ # LLM-assisted synthesis
208
+ # ---------------------------------------------------------------------------
209
+
210
+
211
+ _LLM_SYNTH_PROMPT = """\
212
+ You generate a single static-analysis rule in attocode's YAML format.
213
+
214
+ Language: {language}
215
+ What this rule should detect: {description}
216
+
217
+ The rule MUST match every one of these positive samples (each is a single
218
+ line of code):
219
+ {positives}
220
+
221
+ The rule MUST NOT match any of these negative samples:
222
+ {negatives}
223
+
224
+ Output ONLY a YAML rule with these fields (no surrounding prose, no
225
+ markdown code fences):
226
+
227
+ - id: kebab-case identifier (use "{suggested_id}")
228
+ - message: short human-readable detection message
229
+ - severity: critical | high | medium | low | info
230
+ - category: correctness | suspicious | complexity | performance | style | security | deprecated
231
+ - languages: [{language}]
232
+ - pattern: a Python regex string. Use \\b word boundaries to avoid
233
+ over-matching. Keep the pattern minimal — match only the dangerous
234
+ shape, not the full samples literally.
235
+ """
236
+
237
+
238
+ def _strip_code_fences(text: str) -> str:
239
+ """Strip leading/trailing markdown fences if the LLM wrapped the YAML."""
240
+ cleaned = text.strip()
241
+ if not cleaned.startswith("```"):
242
+ return cleaned
243
+ lines = cleaned.splitlines()
244
+ # Drop the first fence line (```yaml or just ```)
245
+ body = lines[1:]
246
+ if body and body[-1].strip().startswith("```"):
247
+ body = body[:-1]
248
+ return "\n".join(body).strip()
249
+
250
+
251
+ def _validate_llm_yaml(
252
+ yaml_text: str,
253
+ positives: list[str],
254
+ negatives: list[str],
255
+ ) -> tuple[UnifiedRule | None, list[str]]:
256
+ """Parse + validate an LLM YAML response. Returns (rule, diagnostics).
257
+
258
+ A response may contain one or several rules; we accept the first one
259
+ that parses and matches all positives without matching any negative.
260
+ """
261
+ diags: list[str] = []
262
+ try:
263
+ import yaml
264
+ except ImportError:
265
+ return None, ["pyyaml not available — cannot parse LLM YAML"]
266
+
267
+ from attocode_intel.rules.loader import _parse_yaml_rule
268
+
269
+ cleaned = _strip_code_fences(yaml_text)
270
+ try:
271
+ data = yaml.safe_load(cleaned)
272
+ except Exception as exc:
273
+ return None, [f"YAML parse failed: {exc}"]
274
+
275
+ if isinstance(data, dict):
276
+ items = [data]
277
+ elif isinstance(data, list):
278
+ items = [d for d in data if isinstance(d, dict)]
279
+ else:
280
+ return None, [f"YAML must be dict or list, got {type(data).__name__}"]
281
+
282
+ if not items:
283
+ return None, ["LLM response contained no parseable rules"]
284
+
285
+ for i, item in enumerate(items):
286
+ rule = _parse_yaml_rule(item, source=RuleSource.USER, origin=f"llm[{i}]")
287
+ if rule is None:
288
+ diags.append(f"rule[{i}]: loader rejected the YAML")
289
+ continue
290
+ if rule.pattern is None:
291
+ diags.append(f"rule[{i}]: no regex pattern emitted")
292
+ continue
293
+ failed_pos = [p for p in positives if not rule.pattern.search(p)]
294
+ matched_neg = [n for n in negatives if rule.pattern.search(n)]
295
+ if failed_pos or matched_neg:
296
+ diags.append(
297
+ f"rule[{i}] failed validation: "
298
+ f"{len(failed_pos)}/{len(positives)} positive miss, "
299
+ f"{len(matched_neg)}/{len(negatives)} negative match"
300
+ )
301
+ continue
302
+ diags.append(f"rule[{i}] validated against samples")
303
+ return rule, diags
304
+
305
+ diags.append("no LLM-generated rule passed validation")
306
+ return None, diags
307
+
308
+
309
+ def _default_llm_caller(prompt: str) -> str:
310
+ """Resolve the project's shared LLM client lazily — keeps the
311
+ optional ``openai``/``anthropic`` import out of the import path for
312
+ callers that only want the deterministic synthesizer.
313
+
314
+ The default client lives under the dev-only ``eval/meta_harness/``
315
+ tree, which isn't bundled with the installed wheel. When that path
316
+ is unavailable we surface a clear error so the synthesizer's
317
+ ``except`` arm can attach it as a diagnostic, instead of letting an
318
+ opaque ``ModuleNotFoundError`` bubble out of the MCP tool.
319
+ """
320
+ try:
321
+ from eval.meta_harness._llm_client import call_llm, load_env
322
+ except ModuleNotFoundError as exc:
323
+ raise RuntimeError(
324
+ "LLM synthesis requested but no llm_caller was configured "
325
+ "and the dev meta-harness client is not importable from this "
326
+ "process. Pass llm_caller=... explicitly when calling "
327
+ "synthesize_rule(mode='llm' or 'auto') from production."
328
+ ) from exc
329
+
330
+ load_env()
331
+ return call_llm(prompt, max_tokens=2048)
332
+
333
+
334
+ # ---------------------------------------------------------------------------
335
+ # Top-level dispatch
336
+ # ---------------------------------------------------------------------------
337
+
338
+
339
+ def synthesize_rule(
340
+ positives: list[str],
341
+ negatives: list[str],
342
+ *,
343
+ language: str,
344
+ rule_id: str = "synth-rule",
345
+ description: str = "",
346
+ mode: str = "auto",
347
+ llm_caller: Callable[[str], str] | None = None,
348
+ ) -> SynthesisResult:
349
+ """Top-level synthesizer.
350
+
351
+ ``mode``:
352
+ * ``"regex"`` — deterministic regex only.
353
+ * ``"llm"`` — call Claude only.
354
+ * ``"auto"`` — try regex first; fall back to LLM if it fails.
355
+
356
+ ``llm_caller`` overrides the default LLM client (for testing without
357
+ network or to plug in a non-Claude provider).
358
+ """
359
+ diags: list[str] = []
360
+
361
+ if mode not in ("regex", "llm", "auto"):
362
+ return SynthesisResult(
363
+ rule=None,
364
+ method="none",
365
+ diagnostics=[f"unknown mode {mode!r}"],
366
+ )
367
+
368
+ if mode in ("regex", "auto"):
369
+ rule, regex_diags = synthesize_regex_rule(
370
+ positives, negatives,
371
+ rule_id=rule_id, language=language, description=description,
372
+ )
373
+ diags.extend(regex_diags)
374
+ if rule is not None:
375
+ return SynthesisResult(rule=rule, method="regex", diagnostics=diags)
376
+
377
+ if mode in ("llm", "auto"):
378
+ caller = llm_caller or _default_llm_caller
379
+ prompt = _LLM_SYNTH_PROMPT.format(
380
+ language=language,
381
+ description=description or "(no description)",
382
+ positives="\n".join(f"- {p!r}" for p in positives),
383
+ negatives="\n".join(f"- {n!r}" for n in negatives) or "(none)",
384
+ suggested_id=rule_id,
385
+ )
386
+ try:
387
+ response = caller(prompt)
388
+ except Exception as exc:
389
+ diags.append(f"LLM call failed: {exc}")
390
+ return SynthesisResult(rule=None, method="none", diagnostics=diags)
391
+
392
+ rule, llm_diags = _validate_llm_yaml(response, positives, negatives)
393
+ diags.extend(llm_diags)
394
+ if rule is not None:
395
+ return SynthesisResult(rule=rule, method="llm", diagnostics=diags)
396
+
397
+ return SynthesisResult(rule=None, method="none", diagnostics=diags)
398
+
399
+
400
+ def render_rule_yaml(rule: UnifiedRule) -> str:
401
+ """Render a synthesised rule as a YAML snippet ready to paste into a
402
+ pack file. Order mirrors the convention used by the example packs."""
403
+ lines = [f"- id: {rule.id}"]
404
+ lines.append(f" name: {rule.name}")
405
+ if rule.description:
406
+ # Quote the message; rule loader treats ``message`` as the canonical key.
407
+ msg = rule.description.replace('"', '\\"')
408
+ lines.append(f' message: "{msg}"')
409
+ if rule.pattern is not None:
410
+ pat = rule.pattern.pattern.replace("'", "''")
411
+ lines.append(f" pattern: '{pat}'")
412
+ if rule.structural_pattern:
413
+ sp = rule.structural_pattern.replace("'", "''")
414
+ lines.append(f" structural_pattern: '{sp}'")
415
+ lines.append(f" severity: {rule.severity}")
416
+ lines.append(f" category: {rule.category}")
417
+ if rule.languages:
418
+ lines.append(f" languages: [{', '.join(rule.languages)}]")
419
+ lines.append(f" confidence: {rule.confidence}")
420
+ return "\n".join(lines)