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,198 @@
1
+ """Cross-repository search endpoint — search across all repos in an org."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import uuid
7
+
8
+ from fastapi import APIRouter, Depends, HTTPException
9
+ from pydantic import BaseModel
10
+ from sqlalchemy import select
11
+ from sqlalchemy.ext.asyncio import AsyncSession
12
+
13
+ from attocode_intel.api.auth import resolve_auth
14
+ from attocode_intel.api.auth.context import AuthContext
15
+ from attocode_intel.api.deps import get_db_session
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ router = APIRouter(
20
+ prefix="/api/v2/orgs/{org_id}",
21
+ tags=["cross-repo-search"],
22
+ )
23
+
24
+
25
+ # --- Request/Response models ---
26
+
27
+
28
+ class CrossRepoSearchRequest(BaseModel):
29
+ query: str
30
+ repo_ids: list[str] = []
31
+ top_k: int = 20
32
+ file_filter: str = ""
33
+
34
+
35
+ class CrossRepoSearchResultItem(BaseModel):
36
+ repo_id: str = ""
37
+ repo_name: str = ""
38
+ file_path: str = ""
39
+ score: float = 0.0
40
+ snippet: str = ""
41
+
42
+
43
+ class CrossRepoSearchResponse(BaseModel):
44
+ query: str
45
+ results: list[CrossRepoSearchResultItem]
46
+ total: int
47
+ repos_searched: int = 0
48
+
49
+
50
+ # --- Endpoint ---
51
+
52
+
53
+ @router.post("/search", response_model=CrossRepoSearchResponse)
54
+ async def cross_repo_search(
55
+ org_id: uuid.UUID,
56
+ req: CrossRepoSearchRequest,
57
+ auth: AuthContext = Depends(resolve_auth),
58
+ session: AsyncSession = Depends(get_db_session),
59
+ ) -> CrossRepoSearchResponse:
60
+ """Semantic search across multiple repositories in an organization.
61
+
62
+ If repo_ids is empty, searches all repos in the org.
63
+ Results are sorted globally by cosine similarity score.
64
+ """
65
+ from attocode_intel.api.deps import get_config
66
+ from attocode_intel.db.models import Branch, OrgMembership, Repository
67
+ from attocode_intel.storage.branch_overlay import BranchOverlay
68
+ from attocode_intel.storage.embedding_store import EmbeddingStore
69
+ from attocode_intel._internal.integrations.context.embeddings import create_embedding_provider
70
+
71
+ # 1. Verify org membership
72
+ if not auth.user_id:
73
+ raise HTTPException(status_code=403, detail="Authentication required")
74
+
75
+ result = await session.execute(
76
+ select(OrgMembership).where(
77
+ OrgMembership.org_id == org_id,
78
+ OrgMembership.user_id == auth.user_id,
79
+ )
80
+ )
81
+ membership = result.scalar_one_or_none()
82
+ if membership is None:
83
+ raise HTTPException(status_code=403, detail="Not a member of this organization")
84
+
85
+ # 2. Resolve target repositories
86
+ if req.repo_ids:
87
+ # Filter to specified repos within the org
88
+ repo_uuids = []
89
+ for rid in req.repo_ids:
90
+ try:
91
+ repo_uuids.append(uuid.UUID(rid))
92
+ except ValueError:
93
+ raise HTTPException(
94
+ status_code=400,
95
+ detail=f"Invalid repo_id format: {rid}",
96
+ )
97
+
98
+ result = await session.execute(
99
+ select(Repository).where(
100
+ Repository.org_id == org_id,
101
+ Repository.id.in_(repo_uuids),
102
+ )
103
+ )
104
+ else:
105
+ # All repos in the org
106
+ result = await session.execute(
107
+ select(Repository).where(Repository.org_id == org_id)
108
+ )
109
+
110
+ repos = result.scalars().all()
111
+ if not repos:
112
+ return CrossRepoSearchResponse(
113
+ query=req.query, results=[], total=0, repos_searched=0,
114
+ )
115
+
116
+ # Build repo_id → name mapping
117
+ repo_name_map: dict[str, str] = {str(r.id): r.name for r in repos}
118
+
119
+ # 3. For each repo, get default branch and resolve manifest
120
+ overlay = BranchOverlay(session)
121
+ branch_manifests: dict[str, dict[str, str]] = {}
122
+
123
+ for repo in repos:
124
+ # Find the default branch
125
+ branch_result = await session.execute(
126
+ select(Branch).where(
127
+ Branch.repo_id == repo.id,
128
+ Branch.name == repo.default_branch,
129
+ )
130
+ )
131
+ branch = branch_result.scalar_one_or_none()
132
+ if branch is None:
133
+ # Try is_default flag as fallback
134
+ branch_result = await session.execute(
135
+ select(Branch).where(
136
+ Branch.repo_id == repo.id,
137
+ Branch.is_default == True, # noqa: E712
138
+ )
139
+ )
140
+ branch = branch_result.scalar_one_or_none()
141
+
142
+ if branch is None:
143
+ logger.debug(
144
+ "Skipping repo %s (%s): no default branch found",
145
+ repo.name, repo.id,
146
+ )
147
+ continue
148
+
149
+ manifest = await overlay.resolve_manifest(branch.id)
150
+ if manifest:
151
+ branch_manifests[str(repo.id)] = manifest
152
+
153
+ if not branch_manifests:
154
+ return CrossRepoSearchResponse(
155
+ query=req.query, results=[], total=0, repos_searched=0,
156
+ )
157
+
158
+ # 4. Embed query
159
+ config = get_config()
160
+ provider = create_embedding_provider(config.embedding_model)
161
+ if provider.name == "none":
162
+ raise HTTPException(
163
+ status_code=503,
164
+ detail="No embedding provider available for semantic search",
165
+ )
166
+
167
+ query_vectors = provider.embed([req.query])
168
+ query_vector = query_vectors[0]
169
+
170
+ # 5. Single pgvector query across all repos
171
+ store = EmbeddingStore(session)
172
+ results = await store.multi_branch_similarity_search(
173
+ session=session,
174
+ branch_manifests=branch_manifests,
175
+ query_vector=query_vector,
176
+ top_k=req.top_k,
177
+ model=provider.name,
178
+ file_filter=req.file_filter,
179
+ )
180
+
181
+ # 6. Build response with repo context
182
+ items = [
183
+ CrossRepoSearchResultItem(
184
+ repo_id=r["repo_id"],
185
+ repo_name=repo_name_map.get(r["repo_id"], "unknown"),
186
+ file_path=r["file"],
187
+ score=r["score"],
188
+ snippet=r.get("chunk_text", ""),
189
+ )
190
+ for r in results
191
+ ]
192
+
193
+ return CrossRepoSearchResponse(
194
+ query=req.query,
195
+ results=items,
196
+ total=len(items),
197
+ repos_searched=len(branch_manifests),
198
+ )
@@ -0,0 +1,386 @@
1
+ """Embedding and indexing status dashboard endpoints (service mode only)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import uuid
7
+
8
+ from fastapi import APIRouter, Depends, HTTPException, Query
9
+ from pydantic import BaseModel
10
+ from sqlalchemy import func, select
11
+ from sqlalchemy.ext.asyncio import AsyncSession
12
+
13
+ from attocode_intel.api.auth import resolve_auth
14
+ from attocode_intel.api.auth.context import AuthContext
15
+ from attocode_intel.api.deps import get_branch_context, get_db_session
16
+
17
+ router = APIRouter(prefix="/api/v2/projects/{project_id}", tags=["embeddings"])
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ # --- Response models ---
22
+
23
+
24
+ class EmbeddingStatusResponse(BaseModel):
25
+ total_files: int
26
+ embedded_files: int
27
+ coverage_pct: float
28
+ model: str
29
+ provider_available: bool = True
30
+ provider_hint: str = ""
31
+
32
+
33
+ class FileEmbeddingStatus(BaseModel):
34
+ path: str
35
+ content_sha: str
36
+ has_embedding: bool
37
+ chunk_count: int = 0
38
+ last_embedded_at: str | None = None
39
+
40
+
41
+ class FileEmbeddingListResponse(BaseModel):
42
+ files: list[FileEmbeddingStatus]
43
+ total: int
44
+ limit: int
45
+ offset: int
46
+ has_more: bool
47
+
48
+
49
+ class SimilarFileItem(BaseModel):
50
+ file_path: str
51
+ score: float
52
+ snippet: str = ""
53
+
54
+
55
+ class FindSimilarResponse(BaseModel):
56
+ source_file: str
57
+ similar: list[SimilarFileItem]
58
+
59
+
60
+ class IndexingStatusResponse(BaseModel):
61
+ index_status: str
62
+ last_indexed_at: str | None = None
63
+ active_jobs: int = 0
64
+ embedding_coverage_pct: float = 0.0
65
+ total_files: int = 0
66
+ embedded_files: int = 0
67
+
68
+
69
+ # --- Helpers ---
70
+
71
+
72
+ async def _get_repo(project_id: uuid.UUID, session: AsyncSession):
73
+ from attocode_intel.db.models import Repository
74
+
75
+ result = await session.execute(select(Repository).where(Repository.id == project_id))
76
+ repo = result.scalar_one_or_none()
77
+ if repo is None:
78
+ raise HTTPException(status_code=404, detail="Repository not found")
79
+ return repo
80
+
81
+
82
+ def _resolve_model_name(model: str) -> str:
83
+ """Resolve 'default' to the actual embedding provider name."""
84
+ if model != "default":
85
+ return model
86
+ try:
87
+ from attocode_intel.api.deps import get_config
88
+ from attocode_intel._internal.integrations.context.embeddings import create_embedding_provider
89
+ config = get_config()
90
+ provider = create_embedding_provider(config.embedding_model)
91
+ return provider.name
92
+ except Exception:
93
+ return model
94
+
95
+
96
+ # --- Endpoints ---
97
+
98
+
99
+ @router.get("/embeddings/status", response_model=EmbeddingStatusResponse)
100
+ async def get_embedding_status(
101
+ project_id: uuid.UUID,
102
+ ref: str = Query("", description="Git ref. Defaults to default branch."),
103
+ model: str = Query("default", description="Embedding model name."),
104
+ auth: AuthContext = Depends(resolve_auth),
105
+ session: AsyncSession = Depends(get_db_session),
106
+ ) -> EmbeddingStatusResponse:
107
+ """Get embedding coverage for a repo branch."""
108
+ repo = await _get_repo(project_id, session)
109
+ resolved_ref = ref if ref else repo.default_branch
110
+
111
+ try:
112
+ branch_ctx = await get_branch_context(project_id, resolved_ref, session)
113
+ except HTTPException:
114
+ return EmbeddingStatusResponse(
115
+ total_files=0, embedded_files=0, coverage_pct=0.0, model=model,
116
+ )
117
+
118
+ content_shas = branch_ctx.content_shas
119
+ total = len(content_shas)
120
+ if total == 0:
121
+ return EmbeddingStatusResponse(
122
+ total_files=0, embedded_files=0, coverage_pct=0.0, model=model,
123
+ )
124
+
125
+ from attocode_intel.storage.embedding_store import EmbeddingStore
126
+
127
+ model = _resolve_model_name(model)
128
+ store = EmbeddingStore(session)
129
+ embedded_shas = await store.batch_has_embeddings(content_shas, model)
130
+ embedded = len(embedded_shas)
131
+
132
+ response = EmbeddingStatusResponse(
133
+ total_files=total,
134
+ embedded_files=embedded,
135
+ coverage_pct=round(embedded / total * 100, 1) if total > 0 else 0.0,
136
+ model=model,
137
+ )
138
+
139
+ from attocode_intel._internal.integrations.context.embeddings import create_embedding_provider
140
+ from attocode_intel.api.deps import get_config
141
+
142
+ config = get_config()
143
+ try:
144
+ provider = create_embedding_provider(config.embedding_model)
145
+ if provider.name == "none":
146
+ response.provider_available = False
147
+ response.provider_hint = (
148
+ "No embedding provider detected. "
149
+ "Install sentence-transformers (`pip install attocode[semantic]`) or set OPENAI_API_KEY."
150
+ )
151
+ except (ImportError, RuntimeError) as exc:
152
+ response.provider_available = False
153
+ response.provider_hint = str(exc)
154
+
155
+ return response
156
+
157
+
158
+ @router.get("/embeddings/files", response_model=FileEmbeddingListResponse)
159
+ async def get_file_embedding_status(
160
+ project_id: uuid.UUID,
161
+ ref: str = Query("", description="Git ref. Defaults to default branch."),
162
+ model: str = Query("default", description="Embedding model name."),
163
+ limit: int = Query(50, ge=1, le=200),
164
+ offset: int = Query(0, ge=0),
165
+ search: str = Query("", description="Filter files by path substring (case-insensitive)."),
166
+ auth: AuthContext = Depends(resolve_auth),
167
+ session: AsyncSession = Depends(get_db_session),
168
+ ) -> FileEmbeddingListResponse:
169
+ """Get per-file embedding status (paginated)."""
170
+ repo = await _get_repo(project_id, session)
171
+ resolved_ref = ref if ref else repo.default_branch
172
+
173
+ try:
174
+ branch_ctx = await get_branch_context(project_id, resolved_ref, session)
175
+ except HTTPException:
176
+ return FileEmbeddingListResponse(
177
+ files=[], total=0, limit=limit, offset=offset, has_more=False,
178
+ )
179
+
180
+ manifest = branch_ctx.manifest
181
+ content_shas = branch_ctx.content_shas
182
+ total = len(manifest)
183
+
184
+ # Batch check which SHAs have embeddings
185
+ from attocode_intel.storage.embedding_store import EmbeddingStore
186
+
187
+ model = _resolve_model_name(model)
188
+ store = EmbeddingStore(session)
189
+ stats = await store.batch_embedding_stats(content_shas, model)
190
+
191
+ # Paginate over sorted manifest entries
192
+ sorted_paths = sorted(manifest.keys())
193
+ if search:
194
+ search_lower = search.lower()
195
+ sorted_paths = [p for p in sorted_paths if search_lower in p.lower()]
196
+ total = len(sorted_paths) # recalculate after filtering
197
+ page = sorted_paths[offset:offset + limit]
198
+
199
+ files = []
200
+ for path in page:
201
+ sha = manifest[path]
202
+ sha_stats = stats.get(sha)
203
+ files.append(FileEmbeddingStatus(
204
+ path=path,
205
+ content_sha=sha,
206
+ has_embedding=sha_stats is not None,
207
+ chunk_count=sha_stats["chunk_count"] if sha_stats else 0,
208
+ last_embedded_at=sha_stats["last_embedded"].isoformat() if sha_stats and sha_stats["last_embedded"] else None,
209
+ ))
210
+
211
+ return FileEmbeddingListResponse(
212
+ files=files,
213
+ total=total,
214
+ limit=limit,
215
+ offset=offset,
216
+ has_more=(offset + limit < total),
217
+ )
218
+
219
+
220
+ @router.post("/embeddings/similar", response_model=FindSimilarResponse)
221
+ async def find_similar_files(
222
+ project_id: uuid.UUID,
223
+ content_sha: str = Query(..., description="Content SHA of source file"),
224
+ ref: str = Query("", description="Git ref. Defaults to default branch."),
225
+ auth: AuthContext = Depends(resolve_auth),
226
+ session: AsyncSession = Depends(get_db_session),
227
+ ) -> FindSimilarResponse:
228
+ """Find files similar to a given file by its content SHA."""
229
+ repo = await _get_repo(project_id, session)
230
+ resolved_ref = ref if ref else repo.default_branch
231
+
232
+ try:
233
+ branch_ctx = await get_branch_context(project_id, resolved_ref, session)
234
+ except HTTPException:
235
+ return FindSimilarResponse(source_file="unknown", similar=[])
236
+
237
+ model = _resolve_model_name("default")
238
+
239
+ from attocode_intel.storage.embedding_store import EmbeddingStore
240
+
241
+ store = EmbeddingStore(session)
242
+ results = await store.find_similar_by_sha(
243
+ branch_ctx.branch_id, content_sha, top_k=10, model=model,
244
+ )
245
+
246
+ # Resolve source file path
247
+ sha_to_path = {sha: path for path, sha in branch_ctx.manifest.items()}
248
+ source_file = sha_to_path.get(content_sha, "unknown")
249
+
250
+ return FindSimilarResponse(
251
+ source_file=source_file,
252
+ similar=[
253
+ SimilarFileItem(
254
+ file_path=r["file"],
255
+ score=r["score"],
256
+ snippet=r.get("chunk_text", "")[:200],
257
+ )
258
+ for r in results
259
+ ],
260
+ )
261
+
262
+
263
+ class GenerateEmbeddingsRequest(BaseModel):
264
+ branch: str = "main"
265
+
266
+
267
+ class GenerateEmbeddingsResponse(BaseModel):
268
+ job_id: str
269
+ status: str
270
+ message: str
271
+
272
+
273
+ @router.post("/embeddings/generate", response_model=GenerateEmbeddingsResponse)
274
+ async def trigger_generate_embeddings(
275
+ project_id: uuid.UUID,
276
+ req: GenerateEmbeddingsRequest = GenerateEmbeddingsRequest(),
277
+ auth: AuthContext = Depends(resolve_auth),
278
+ session: AsyncSession = Depends(get_db_session),
279
+ ) -> GenerateEmbeddingsResponse:
280
+ """Trigger embedding generation for a branch.
281
+
282
+ Enqueues an ARQ background job to generate embeddings for all files
283
+ in the specified branch that don't already have them.
284
+ """
285
+ # Validate that the repository exists (raises 404 otherwise).
286
+ await _get_repo(project_id, session)
287
+
288
+ # Fail fast if no embedding provider is available
289
+ from attocode_intel._internal.integrations.context.embeddings import create_embedding_provider
290
+ from attocode_intel.api.deps import get_config
291
+
292
+ config = get_config()
293
+ try:
294
+ provider = create_embedding_provider(config.embedding_model)
295
+ except (ImportError, RuntimeError) as exc:
296
+ raise HTTPException(status_code=400, detail=str(exc))
297
+ if provider.name == "none":
298
+ raise HTTPException(
299
+ status_code=400,
300
+ detail="No embedding provider available. Install sentence-transformers or set OPENAI_API_KEY.",
301
+ )
302
+
303
+ # Enqueue the ARQ job
304
+ try:
305
+ from arq import create_pool
306
+ from arq.connections import RedisSettings
307
+
308
+ from attocode_intel.api.deps import get_config
309
+
310
+ config = get_config()
311
+ if not config.redis_url:
312
+ raise HTTPException(status_code=503, detail="Redis not configured — cannot enqueue jobs")
313
+
314
+ redis_settings = RedisSettings.from_dsn(config.redis_url)
315
+ pool = await create_pool(redis_settings)
316
+ job = await pool.enqueue_job(
317
+ "generate_embeddings",
318
+ str(project_id),
319
+ req.branch,
320
+ )
321
+ await pool.close()
322
+
323
+ return GenerateEmbeddingsResponse(
324
+ job_id=job.job_id if job else "unknown",
325
+ status="queued",
326
+ message=f"Embedding generation queued for branch '{req.branch}'",
327
+ )
328
+ except HTTPException:
329
+ raise
330
+ except Exception as e:
331
+ logger.exception("Failed to enqueue embedding generation job")
332
+ raise HTTPException(status_code=500, detail=f"Failed to enqueue job: {e}")
333
+
334
+
335
+ @router.get("/indexing/status", response_model=IndexingStatusResponse)
336
+ async def get_indexing_status(
337
+ project_id: uuid.UUID,
338
+ ref: str = Query("", description="Git ref. Defaults to default branch."),
339
+ auth: AuthContext = Depends(resolve_auth),
340
+ session: AsyncSession = Depends(get_db_session),
341
+ ) -> IndexingStatusResponse:
342
+ """Get combined indexing + embedding progress."""
343
+ from attocode_intel.db.models import IndexingJob
344
+
345
+ repo = await _get_repo(project_id, session)
346
+
347
+ # Count active indexing jobs
348
+ active_result = await session.execute(
349
+ select(func.count()).select_from(
350
+ select(IndexingJob.id).where(
351
+ IndexingJob.repo_id == project_id,
352
+ IndexingJob.status.in_(["queued", "running"]),
353
+ ).subquery()
354
+ )
355
+ )
356
+ active_jobs = active_result.scalar() or 0
357
+
358
+ # Get embedding coverage
359
+ resolved_ref = ref if ref else repo.default_branch
360
+ total_files = 0
361
+ embedded_files = 0
362
+ coverage_pct = 0.0
363
+
364
+ try:
365
+ branch_ctx = await get_branch_context(project_id, resolved_ref, session)
366
+ content_shas = branch_ctx.content_shas
367
+ total_files = len(content_shas)
368
+
369
+ if total_files > 0:
370
+ from attocode_intel.storage.embedding_store import EmbeddingStore
371
+
372
+ store = EmbeddingStore(session)
373
+ embedded_shas = await store.batch_has_embeddings(content_shas, _resolve_model_name("default"))
374
+ embedded_files = len(embedded_shas)
375
+ coverage_pct = round(embedded_files / total_files * 100, 1)
376
+ except HTTPException:
377
+ pass
378
+
379
+ return IndexingStatusResponse(
380
+ index_status=repo.index_status,
381
+ last_indexed_at=repo.last_indexed_at.isoformat() if repo.last_indexed_at else None,
382
+ active_jobs=active_jobs,
383
+ embedding_coverage_pct=coverage_pct,
384
+ total_files=total_files,
385
+ embedded_files=embedded_files,
386
+ )