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,137 @@
1
+ """Provenance write path for derived code-intel artifacts.
2
+
3
+ Normalizes a ``Provenance`` dataclass (from the shared ``artifacts/``
4
+ package) into a row in the server-side ``provenance`` table.
5
+
6
+ - :func:`write_provenance_rows` takes a list of dicts so batched writes
7
+ (one per embedding chunk in the same upsert) collapse into a single
8
+ ``session.add_all`` call.
9
+ - Schema is loose: keys that don't match ``Provenance`` columns flow
10
+ into the ``extra`` JSONB bucket, so callers can tack on contextual
11
+ metadata without a migration.
12
+ - No commit — the caller owns the session lifecycle.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import logging
18
+ import socket
19
+ import time
20
+ from typing import TYPE_CHECKING, Any
21
+
22
+ if TYPE_CHECKING:
23
+ from sqlalchemy.ext.asyncio import AsyncSession
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+
28
+ # Columns that are first-class on the Provenance ORM (everything else
29
+ # in the input dict goes into ``extra``).
30
+ _TOP_LEVEL_COLS: frozenset[str] = frozenset({
31
+ "action_hash",
32
+ "artifact_type",
33
+ "input_blob_oid",
34
+ "input_tree_oid",
35
+ "indexer_name",
36
+ "indexer_version",
37
+ "config_digest",
38
+ "producer_service",
39
+ "producer_user_id",
40
+ "producer_job_id",
41
+ "producer_host",
42
+ })
43
+
44
+
45
+ def _split_known(data: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
46
+ """Partition a raw dict into (top-level, extra) based on ORM columns."""
47
+ top: dict[str, Any] = {}
48
+ extra: dict[str, Any] = {}
49
+ for k, v in data.items():
50
+ if k in _TOP_LEVEL_COLS:
51
+ top[k] = v
52
+ else:
53
+ extra[k] = v
54
+ return top, extra
55
+
56
+
57
+ async def write_provenance_rows(
58
+ session: AsyncSession,
59
+ rows: list[dict[str, Any]],
60
+ ) -> int:
61
+ """Insert provenance rows for a batch of derived artifacts.
62
+
63
+ Each ``row`` is a dict whose keys are either Provenance ORM column
64
+ names or arbitrary extras (which land in the ``extra`` JSONB bucket).
65
+ Missing required fields are filled from sensible defaults so callers
66
+ can write a provenance row with just an ``action_hash`` +
67
+ ``input_blob_oid`` if that's all they know.
68
+
69
+ Returns the number of rows inserted. Does NOT commit — the caller
70
+ is expected to be inside a larger unit of work that commits at its
71
+ own boundary.
72
+ """
73
+ if not rows:
74
+ return 0
75
+
76
+ from attocode_intel.db.models import Provenance
77
+
78
+ default_host = socket.gethostname() or "unknown"
79
+ _ = time # keep import alive for future use (e.g. produced_at override)
80
+
81
+ orm_rows: list[Provenance] = []
82
+ for raw in rows:
83
+ top, extra = _split_known(dict(raw))
84
+ orm_rows.append(
85
+ Provenance(
86
+ action_hash=str(top.get("action_hash", "")),
87
+ artifact_type=str(top.get("artifact_type", "unknown")),
88
+ input_blob_oid=str(top.get("input_blob_oid", "")),
89
+ input_tree_oid=top.get("input_tree_oid"),
90
+ indexer_name=str(top.get("indexer_name", "unknown")),
91
+ indexer_version=str(top.get("indexer_version", "0")),
92
+ config_digest=str(top.get("config_digest", "")),
93
+ producer_service=str(
94
+ top.get("producer_service", "attocode-server"),
95
+ ),
96
+ producer_user_id=top.get("producer_user_id"),
97
+ producer_job_id=top.get("producer_job_id"),
98
+ producer_host=str(top.get("producer_host") or default_host),
99
+ extra=extra,
100
+ )
101
+ )
102
+
103
+ session.add_all(orm_rows)
104
+ return len(orm_rows)
105
+
106
+
107
+ def embedding_provenance_dict(
108
+ *,
109
+ action_hash: str,
110
+ content_sha: str,
111
+ model_name: str,
112
+ model_version: str,
113
+ dimension: int | None,
114
+ indexer_name: str = "attocode-embedding-store",
115
+ indexer_version: str = "1",
116
+ config_digest: str = "",
117
+ **extra: Any,
118
+ ) -> dict[str, Any]:
119
+ """Build the dict that gets passed to :func:`write_provenance_rows`
120
+ for an embedding chunk.
121
+
122
+ Factored out so :class:`EmbeddingStore` doesn't have to hand-assemble
123
+ the same dict in every insert path.
124
+ """
125
+ row: dict[str, Any] = {
126
+ "action_hash": action_hash,
127
+ "artifact_type": "embedding_blob_v1",
128
+ "input_blob_oid": f"sha256:{content_sha}",
129
+ "indexer_name": indexer_name,
130
+ "indexer_version": indexer_version,
131
+ "config_digest": config_digest,
132
+ "model_name": model_name,
133
+ "model_version": model_version,
134
+ "dimension": dimension,
135
+ }
136
+ row.update(extra)
137
+ return row
@@ -0,0 +1,164 @@
1
+ """DB-backed security scanning — runs regex patterns on ContentStore content.
2
+
3
+ For remote repos without a local clone, this provides security scanning
4
+ by reading file content from the database and applying the same patterns
5
+ used by the local scanner.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import logging
11
+ import uuid
12
+ from typing import TYPE_CHECKING
13
+
14
+ from attocode_intel._internal.integrations.security.matcher import iter_pattern_matches
15
+
16
+ if TYPE_CHECKING:
17
+ from sqlalchemy.ext.asyncio import AsyncSession
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+ # Scannable file extensions (same as local scanner)
22
+ _SCANNABLE_EXTENSIONS = {
23
+ ".py", ".pyi", ".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs",
24
+ ".rs", ".go", ".java", ".kt", ".rb", ".php", ".swift", ".cs",
25
+ ".c", ".cpp", ".h", ".hpp", ".lua", ".sh", ".bash", ".zsh",
26
+ ".yaml", ".yml", ".toml", ".json", ".env", ".cfg", ".ini", ".conf",
27
+ }
28
+
29
+ # Max file size to scan (200KB)
30
+ _MAX_SCAN_SIZE = 200 * 1024
31
+
32
+ # Max files to scan per run
33
+ _MAX_SCAN_FILES = 500
34
+
35
+
36
+ def _detect_language(path: str) -> str:
37
+ """Detect language from file extension."""
38
+ import os
39
+ ext = os.path.splitext(path)[1].lower()
40
+ lang_map = {
41
+ ".py": "python", ".pyi": "python",
42
+ ".js": "javascript", ".jsx": "javascript", ".mjs": "javascript", ".cjs": "javascript",
43
+ ".ts": "typescript", ".tsx": "typescript",
44
+ ".go": "go", ".rs": "rust", ".java": "java", ".kt": "kotlin",
45
+ ".rb": "ruby", ".php": "php", ".swift": "swift", ".cs": "csharp",
46
+ ".c": "c", ".cpp": "cpp", ".h": "c", ".hpp": "cpp",
47
+ }
48
+ return lang_map.get(ext, "")
49
+
50
+
51
+ def _is_scannable(path: str) -> bool:
52
+ """Check if a file path is scannable by extension or filename."""
53
+ import os
54
+ basename = os.path.basename(path)
55
+ ext = os.path.splitext(path)[1].lower()
56
+ # Handle dotfiles like .env (ext is ".env", basename is ".env")
57
+ if not ext and basename.startswith("."):
58
+ ext = basename.lower()
59
+ return ext in _SCANNABLE_EXTENSIONS
60
+
61
+
62
+ async def db_security_scan(
63
+ session: AsyncSession,
64
+ branch_id: uuid.UUID,
65
+ manifest: dict[str, str],
66
+ *,
67
+ mode: str = "full",
68
+ path_filter: str = "",
69
+ ) -> dict:
70
+ """Run security scan on DB-stored file contents.
71
+
72
+ Args:
73
+ session: Async DB session.
74
+ branch_id: Branch UUID for scoping.
75
+ manifest: Resolved manifest (path → content_sha).
76
+ mode: Scan mode — "quick" (secrets only), "full" (secrets + patterns),
77
+ "secrets", "patterns".
78
+ path_filter: Optional path prefix filter.
79
+
80
+ Returns:
81
+ Dict with findings, total_findings, summary.
82
+ """
83
+ from attocode_intel.storage.content_store import ContentStore
84
+
85
+ # Import patterns
86
+ from attocode_intel._internal.integrations.security.patterns import (
87
+ ANTI_PATTERNS,
88
+ SECRET_PATTERNS,
89
+ )
90
+
91
+ content_store = ContentStore(session)
92
+
93
+ # Select which patterns to use based on mode
94
+ patterns = []
95
+ if mode in ("full", "quick", "secrets"):
96
+ patterns.extend(SECRET_PATTERNS)
97
+ if mode in ("full", "patterns"):
98
+ patterns.extend(ANTI_PATTERNS)
99
+
100
+ # Filter and limit files
101
+ scannable: list[tuple[str, str]] = [] # (path, sha)
102
+ for path, sha in sorted(manifest.items()):
103
+ if path_filter and not path.startswith(path_filter):
104
+ continue
105
+ if not _is_scannable(path):
106
+ continue
107
+ scannable.append((path, sha))
108
+ if len(scannable) >= _MAX_SCAN_FILES:
109
+ break
110
+
111
+ findings: list[dict] = []
112
+ files_scanned = 0
113
+ severity_counts = {"critical": 0, "high": 0, "medium": 0, "low": 0}
114
+
115
+ for path, sha in scannable:
116
+ content_bytes = await content_store.get(sha)
117
+ if content_bytes is None:
118
+ continue
119
+ if len(content_bytes) > _MAX_SCAN_SIZE:
120
+ continue
121
+
122
+ try:
123
+ text = content_bytes.decode("utf-8", errors="replace")
124
+ except Exception:
125
+ continue
126
+
127
+ files_scanned += 1
128
+ file_lang = _detect_language(path)
129
+
130
+ for line_no, line, pat in iter_pattern_matches(text, patterns, file_lang):
131
+ sev = str(pat.severity)
132
+ severity_counts[sev] = severity_counts.get(sev, 0) + 1
133
+ findings.append({
134
+ "pattern": pat.name,
135
+ "severity": sev,
136
+ "category": str(pat.category),
137
+ "cwe_id": pat.cwe_id,
138
+ "file": path,
139
+ "line": line_no,
140
+ "message": pat.message,
141
+ "recommendation": pat.recommendation,
142
+ "snippet": line.strip()[:200],
143
+ })
144
+
145
+ # Compute compliance score
146
+ score = 100 - (
147
+ severity_counts.get("critical", 0) * 20
148
+ + severity_counts.get("high", 0) * 10
149
+ + severity_counts.get("medium", 0) * 3
150
+ + severity_counts.get("low", 0) * 1
151
+ )
152
+ score = max(0, min(100, score))
153
+
154
+ return {
155
+ "mode": mode,
156
+ "path": path_filter,
157
+ "findings": findings,
158
+ "total_findings": len(findings),
159
+ "summary": {
160
+ "files_scanned": files_scanned,
161
+ "compliance_score": score,
162
+ **severity_counts,
163
+ },
164
+ }
@@ -0,0 +1,160 @@
1
+ """Symbol storage — content-addressed symbol indexing with branch resolution."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import uuid
6
+ from typing import TYPE_CHECKING
7
+
8
+ if TYPE_CHECKING:
9
+ from sqlalchemy.ext.asyncio import AsyncSession
10
+
11
+
12
+ class SymbolStore:
13
+ """Content-addressed symbol storage.
14
+
15
+ Symbols are keyed by content SHA, so identical files share symbol data.
16
+ Branch-aware queries resolve through BranchOverlay.
17
+ """
18
+
19
+ def __init__(self, session: AsyncSession) -> None:
20
+ self._session = session
21
+
22
+ async def has_symbols(self, content_sha: str) -> bool:
23
+ """Check if symbols already exist for a content SHA. Used for dedup gating."""
24
+ from sqlalchemy import select
25
+
26
+ from attocode_intel.db.models import Symbol
27
+
28
+ result = await self._session.execute(
29
+ select(Symbol.id).where(Symbol.content_sha == content_sha).limit(1)
30
+ )
31
+ return result.scalar_one_or_none() is not None
32
+
33
+ async def upsert_symbols(
34
+ self,
35
+ content_sha: str,
36
+ symbols: list[dict],
37
+ skip_if_exists: bool = False,
38
+ ) -> int:
39
+ """Insert or update symbols for a content hash.
40
+
41
+ Each symbol dict should have: name, kind, line_start, line_end,
42
+ signature (optional), exported (optional).
43
+
44
+ If skip_if_exists=True, returns 0 immediately when SHA already has symbols.
45
+ This is the content-hash dedup optimization — same content always produces
46
+ the same symbols.
47
+
48
+ Returns count of symbols stored.
49
+ """
50
+ from attocode_intel.db.models import Symbol
51
+
52
+ if skip_if_exists and await self.has_symbols(content_sha):
53
+ return 0
54
+
55
+ # Bulk DELETE — O(N) individual ORM deletes are too slow here.
56
+ from sqlalchemy import delete
57
+ await self._session.execute(
58
+ delete(Symbol).where(Symbol.content_sha == content_sha)
59
+ )
60
+
61
+ # Insert new
62
+ for s in symbols:
63
+ sym = Symbol(
64
+ content_sha=content_sha,
65
+ name=s["name"],
66
+ kind=s["kind"],
67
+ line_start=s.get("line_start"),
68
+ line_end=s.get("line_end"),
69
+ signature=s.get("signature"),
70
+ exported=s.get("exported", False),
71
+ metadata_=s.get("metadata", {}),
72
+ )
73
+ self._session.add(sym)
74
+
75
+ await self._session.flush()
76
+ return len(symbols)
77
+
78
+ async def search_symbols(
79
+ self,
80
+ repo_id: uuid.UUID,
81
+ branch_id: uuid.UUID,
82
+ pattern: str,
83
+ limit: int = 50,
84
+ ) -> list[dict]:
85
+ """Search symbols by name pattern within a branch context.
86
+
87
+ Resolves through the branch overlay to find relevant content SHAs.
88
+ """
89
+ from sqlalchemy import select
90
+
91
+ from attocode_intel.db.models import Symbol
92
+ from attocode_intel.storage.branch_overlay import BranchOverlay
93
+
94
+ overlay = BranchOverlay(self._session)
95
+ manifest = await overlay.resolve_manifest(branch_id)
96
+ content_shas = set(manifest.values())
97
+
98
+ if not content_shas:
99
+ return []
100
+
101
+ result = await self._session.execute(
102
+ select(Symbol)
103
+ .where(
104
+ Symbol.content_sha.in_(content_shas),
105
+ Symbol.name.ilike(f"%{pattern}%"),
106
+ )
107
+ .limit(limit)
108
+ )
109
+
110
+ # Build reverse map: sha → path
111
+ sha_to_path = {sha: path for path, sha in manifest.items()}
112
+
113
+ symbols = []
114
+ for sym in result.scalars():
115
+ symbols.append({
116
+ "name": sym.name,
117
+ "kind": sym.kind,
118
+ "file": sha_to_path.get(sym.content_sha, "unknown"),
119
+ "line_start": sym.line_start,
120
+ "line_end": sym.line_end,
121
+ "signature": sym.signature,
122
+ "exported": sym.exported,
123
+ })
124
+ return symbols
125
+
126
+ async def get_symbols_for_branch(
127
+ self,
128
+ branch_id: uuid.UUID,
129
+ ) -> list[dict]:
130
+ """Get all symbols for a branch, resolved through overlay."""
131
+ from sqlalchemy import select
132
+
133
+ from attocode_intel.db.models import Symbol
134
+ from attocode_intel.storage.branch_overlay import BranchOverlay
135
+
136
+ overlay = BranchOverlay(self._session)
137
+ manifest = await overlay.resolve_manifest(branch_id)
138
+ content_shas = set(manifest.values())
139
+
140
+ if not content_shas:
141
+ return []
142
+
143
+ sha_to_path = {sha: path for path, sha in manifest.items()}
144
+
145
+ result = await self._session.execute(
146
+ select(Symbol).where(Symbol.content_sha.in_(content_shas))
147
+ )
148
+
149
+ symbols = []
150
+ for sym in result.scalars():
151
+ symbols.append({
152
+ "name": sym.name,
153
+ "kind": sym.kind,
154
+ "file": sha_to_path.get(sym.content_sha, "unknown"),
155
+ "line_start": sym.line_start,
156
+ "line_end": sym.line_end,
157
+ "signature": sym.signature,
158
+ "exported": sym.exported,
159
+ })
160
+ return symbols
@@ -0,0 +1,144 @@
1
+ """Syntax-local evidence groups; this does not resolve data flow or earlier exits."""
2
+ from __future__ import annotations
3
+
4
+ import re
5
+ from pathlib import Path
6
+
7
+ LANGUAGES = {'.py': 'python', '.js': 'javascript', '.mjs': 'javascript', '.cjs': 'javascript',
8
+ '.ts': 'typescript', '.tsx': 'tsx', '.jsx': 'javascript'}
9
+ CONTROLS = {'if_statement', 'elif_clause', 'else_clause', 'for_statement', 'for_in_statement',
10
+ 'while_statement', 'with_statement', 'try_statement', 'except_clause',
11
+ 'catch_clause', 'finally_clause', 'switch_statement', 'switch_case', 'switch_default'}
12
+ BLOCKS = {'block', 'statement_block', 'switch_body'}
13
+ LEAVES = {'identifier', 'property_identifier', 'string', 'integer', 'float', 'number',
14
+ 'true', 'false', 'none', 'null'}
15
+
16
+
17
+ def evidence_terms(value):
18
+ from attocode_intel.focused_evidence import terms
19
+ return terms(value) | set(re.findall(r'--[\w-]*|(?<!\w)\d+(?!\w)', value))
20
+
21
+
22
+ def structural_excerpts(source, definition, hint, preview_end):
23
+ """Return None when structural context is unavailable, otherwise ranked groups.
24
+
25
+ Each list member owns its context so the response budget drops it atomically.
26
+ Context ranges describe enclosing syntax, not complete executable reachability.
27
+ """
28
+ language = LANGUAGES.get(Path(definition.file_path).suffix)
29
+ if not language:
30
+ return None
31
+ from attocode_intel._internal.integrations.context.ts_parser import _get_parser
32
+ parser = _get_parser(language)
33
+ if parser is None:
34
+ return None
35
+ raw = '\n'.join(source).encode()
36
+ tree = parser.parse(raw) # No awaits between parse and traversal of this local tree.
37
+ if tree.root_node.has_error:
38
+ return None
39
+ from attocode_intel.focused_evidence import task_terms
40
+ positive, _ = task_terms(hint)
41
+ query = positive | {term for term in evidence_terms(hint)
42
+ if term.startswith('--') or term.isdigit()}
43
+ first, last = definition.start_line, min(definition.end_line, len(source))
44
+
45
+ def bounds(node):
46
+ return node.start_point.row + 1, node.end_point.row + bool(node.end_point.column)
47
+
48
+ def header(node):
49
+ bodies = [c for c in node.children if c.type in BLOCKS]
50
+ for field in ('consequence', 'body'):
51
+ child = node.child_by_field_name(field)
52
+ if child is not None:
53
+ bodies.append(child)
54
+ a, b = bounds(node)
55
+ if bodies:
56
+ body = min(bodies, key=lambda c: c.start_byte)
57
+ # Keep a same-line statement verbatim; do not invent sliced source lines.
58
+ b = max(a, body.start_point.row + (body.type == 'statement_block'))
59
+ return a, b
60
+
61
+ def controls(node):
62
+ ranges = []
63
+ child, parent = node, node.parent
64
+ while parent is not None and bounds(parent)[0] >= first:
65
+ if parent.type in CONTROLS:
66
+ ranges.append(header(parent))
67
+ # Python elif/else are sibling clauses, so include preceding guards.
68
+ for sibling in parent.named_children:
69
+ if sibling.start_byte >= child.start_byte:
70
+ break
71
+ if sibling.type == 'elif_clause':
72
+ ranges.append(header(sibling))
73
+ child, parent = parent, parent.parent
74
+ return ranges
75
+
76
+ def tokens(node, interval=None):
77
+ if interval and (bounds(node)[1] < interval[0] or bounds(node)[0] > interval[1]):
78
+ return set()
79
+ if node.type in {'comment', 'comment_block'}:
80
+ return set()
81
+ # A bare Python string statement is documentation, not an executable literal.
82
+ if node.type == 'expression_statement' and node.named_children and node.named_children[0].type == 'string':
83
+ return set()
84
+ if node.type in LEAVES:
85
+ return evidence_terms(raw[node.start_byte:node.end_byte].decode())
86
+ result = set()
87
+ for child in node.named_children:
88
+ result.update(tokens(child, interval))
89
+ return result
90
+
91
+ candidates = []
92
+ stack = [tree.root_node]
93
+ while stack:
94
+ node = stack.pop()
95
+ a, b = bounds(node)
96
+ if b < first or a > last:
97
+ continue
98
+ stack.extend(reversed(node.named_children))
99
+ is_control = node.type in CONTROLS
100
+ is_statement = node.type.endswith('_statement') or node.type in {'lexical_declaration', 'variable_declaration'}
101
+ if not (is_control or is_statement) or a < first or b > last:
102
+ continue
103
+ # Compound statements contribute a header or a small complete branch.
104
+ if is_control:
105
+ effect = (a, b) if b - a < 18 and node.type in {'if_statement', 'elif_clause', 'else_clause'} else header(node)
106
+ else:
107
+ if any(c.type in BLOCKS for c in node.named_children):
108
+ continue
109
+ effect = (a, b)
110
+ if effect[1] <= preview_end:
111
+ continue
112
+ hits = tokens(node, effect) & query
113
+ if not hits:
114
+ continue
115
+ intervals = sorted(set([effect, *controls(node)]))
116
+ merged = []
117
+ for begin, end in intervals:
118
+ if merged and (begin <= merged[-1][1] + 1 or end - merged[-1][0] < 12):
119
+ merged[-1] = (merged[-1][0], max(end, merged[-1][1]))
120
+ else:
121
+ merged.append((begin, end))
122
+ # Prefer one small contiguous excerpt. Large gaps stay explicitly linked.
123
+ if merged[-1][1] - merged[0][0] < 12:
124
+ merged = [(merged[0][0], merged[-1][1])]
125
+ main = next(r for r in merged if r[0] <= effect[0] and r[1] >= effect[1])
126
+ context = [r for r in merged if r != main]
127
+ candidates.append((len(hits) + int(effect == (a, b)), hits, main, context))
128
+
129
+ def excerpt(interval):
130
+ a, b = interval
131
+ return {'file_path': definition.file_path, 'start_line': a, 'end_line': b,
132
+ 'text': '\n'.join(source[a-1:b])}
133
+
134
+ selected, covered, occupied = [], set(), set()
135
+ while candidates and len(selected) < 3:
136
+ score, hits, main, context = max(candidates, key=lambda r: (r[0] + 4 * len(r[1] - covered), -r[2][0]))
137
+ selected.append({**excerpt(main), 'matched_terms': sorted(hits),
138
+ 'context_ranges': [excerpt(r) for r in context],
139
+ 'context_relation': 'enclosing branch and preceding alternative guards',
140
+ 'context_incomplete': False})
141
+ covered.update(hits)
142
+ occupied.update(range(main[0], main[1] + 1))
143
+ candidates = [r for r in candidates if not occupied.intersection(range(r[2][0], r[2][1]+1))]
144
+ return selected