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,351 @@
1
+ """Cross-encoder reranking for search results.
2
+
3
+ Provides optional reranking using a cross-encoder model to improve
4
+ precision after initial retrieval. Gracefully degrades if the model
5
+ is not available.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import hashlib
11
+ import hmac
12
+ import logging
13
+ import math
14
+ import re
15
+ import threading
16
+ from dataclasses import dataclass
17
+ from pathlib import Path
18
+ from typing import Any, Protocol
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+ # Singleton cache for the cross-encoder model
23
+ _reranker_instance: CrossEncoderReranker | None = None
24
+ _reranker_lock = threading.Lock()
25
+
26
+
27
+ def get_reranker(
28
+ model_name: str = "cross-encoder/ms-marco-MiniLM-L-6-v2",
29
+ ) -> CrossEncoderReranker:
30
+ """Get or create the singleton CrossEncoderReranker instance."""
31
+ global _reranker_instance
32
+ if _reranker_instance is not None and _reranker_instance._model_name == model_name:
33
+ return _reranker_instance
34
+ with _reranker_lock:
35
+ # Double-check after acquiring lock
36
+ if _reranker_instance is not None and _reranker_instance._model_name == model_name:
37
+ return _reranker_instance
38
+ _reranker_instance = CrossEncoderReranker(model_name=model_name)
39
+ return _reranker_instance
40
+
41
+
42
+ class CrossEncoderReranker:
43
+ """Optional cross-encoder reranking for search results.
44
+
45
+ Uses a lightweight cross-encoder model to score (query, document) pairs
46
+ for more precise relevance ranking. Falls back to returning candidates
47
+ unchanged if the model cannot be loaded.
48
+
49
+ Usage::
50
+
51
+ reranker = get_reranker()
52
+ reranked = reranker.rerank(
53
+ query="authentication middleware",
54
+ candidates=[("id1", "text1", 0.8), ("id2", "text2", 0.6)],
55
+ top_k=10,
56
+ )
57
+ """
58
+
59
+ def __init__(
60
+ self, model_name: str = "cross-encoder/ms-marco-MiniLM-L-6-v2",
61
+ ) -> None:
62
+ self._model_name = model_name
63
+ self._model: Any = None
64
+ self._available = False
65
+ self._load_attempted = False
66
+ self._load_lock = threading.Lock()
67
+
68
+ def _ensure_loaded(self) -> bool:
69
+ """Lazy-load the cross-encoder model. Returns True if available."""
70
+ if self._load_attempted:
71
+ return self._available
72
+ with self._load_lock:
73
+ if self._load_attempted:
74
+ return self._available
75
+ self._load_attempted = True
76
+ try:
77
+ from sentence_transformers import CrossEncoder
78
+
79
+ self._model = CrossEncoder(self._model_name)
80
+ self._available = True
81
+ logger.info(
82
+ "Cross-encoder reranker loaded: %s", self._model_name,
83
+ )
84
+ except ImportError:
85
+ logger.warning(
86
+ "sentence-transformers not installed; "
87
+ "cross-encoder reranking disabled. "
88
+ "Install with: pip install sentence-transformers",
89
+ )
90
+ self._available = False
91
+ except Exception:
92
+ logger.warning(
93
+ "Failed to load cross-encoder model %s; "
94
+ "reranking disabled",
95
+ self._model_name,
96
+ exc_info=True,
97
+ )
98
+ self._available = False
99
+ return self._available
100
+
101
+ @property
102
+ def is_available(self) -> bool:
103
+ """Check if the reranker model is loaded and usable."""
104
+ return self._available
105
+
106
+ def rerank(
107
+ self,
108
+ query: str,
109
+ candidates: list[tuple[str, str, float]],
110
+ top_k: int = 10,
111
+ ) -> list[tuple[str, str, float]]:
112
+ """Rerank candidates using the cross-encoder model.
113
+
114
+ Args:
115
+ query: The search query.
116
+ candidates: List of (id, text, original_score) tuples.
117
+ top_k: Number of results to return after reranking.
118
+
119
+ Returns:
120
+ Reranked list of (id, text, reranker_score) tuples,
121
+ or the original candidates (truncated to top_k) if
122
+ reranking is not available.
123
+ """
124
+ if not candidates:
125
+ return []
126
+
127
+ if not self._ensure_loaded():
128
+ # Graceful degradation: return candidates as-is
129
+ return candidates[:top_k]
130
+
131
+ # Build (query, document) pairs for cross-encoder scoring
132
+ pairs = [(query, text) for _, text, _ in candidates]
133
+
134
+ try:
135
+ scores = self._model.predict(pairs)
136
+ except Exception:
137
+ logger.warning(
138
+ "Cross-encoder prediction failed; returning original ranking",
139
+ exc_info=True,
140
+ )
141
+ return candidates[:top_k]
142
+
143
+ # Attach scores and sort descending
144
+ scored = [
145
+ (cid, text, float(score))
146
+ for (cid, text, _), score in zip(candidates, scores, strict=False)
147
+ ]
148
+ scored.sort(key=lambda x: x[2], reverse=True)
149
+ return scored[:top_k]
150
+
151
+
152
+ def model_tree_sha256(model_path: str | Path) -> str:
153
+ """Fingerprint a locally installed model, including every file's contents.
154
+
155
+ The digest is for deployment pinning, not for use on a search request. The
156
+ caller can compute it once when installing weights and configure that value
157
+ as ``expected_sha256``. Symlinks are rejected so the fingerprint cannot
158
+ silently depend on files outside the chosen model directory.
159
+ """
160
+ path = Path(model_path)
161
+ if not path.is_absolute() or not path.is_dir() or path.is_symlink():
162
+ raise ValueError("model_path must be an existing absolute directory")
163
+ digest = hashlib.sha256()
164
+ files = sorted(path.rglob("*"))
165
+ if not files or not any(item.is_file() for item in files):
166
+ raise ValueError("model_path contains no files")
167
+ for item in files:
168
+ if item.is_symlink():
169
+ raise ValueError("model_path must not contain symlinks")
170
+ if not item.is_file():
171
+ continue
172
+ relative = item.relative_to(path).as_posix().encode("utf-8")
173
+ digest.update(len(relative).to_bytes(8, "big"))
174
+ digest.update(relative)
175
+ with item.open("rb") as source:
176
+ while chunk := source.read(1024 * 1024):
177
+ digest.update(chunk)
178
+ return digest.hexdigest()
179
+
180
+
181
+ @dataclass(frozen=True)
182
+ class LocalRerankOutcome:
183
+ """Result plus a compact, source-free fallback reason for provenance."""
184
+
185
+ candidates: list[tuple[str, str, float]]
186
+ reranked: bool
187
+ fallback_reason: str | None = None
188
+
189
+
190
+ RankingOutcome = LocalRerankOutcome
191
+
192
+
193
+ class RankingProvider(Protocol):
194
+ """Shared shortlist contract for local and SystemOne-compatible adapters."""
195
+
196
+ max_candidates: int
197
+
198
+ @property
199
+ def status(self) -> str: ...
200
+
201
+ @property
202
+ def is_available(self) -> bool: ...
203
+
204
+ def rerank_result(
205
+ self, query: str, candidates: list[tuple[str, str, float]], top_k: int = 10,
206
+ ) -> RankingOutcome: ...
207
+
208
+
209
+ class LocalCrossEncoderReranker:
210
+ """Opt-in offline reranking with preinstalled, fingerprint-pinned weights.
211
+
212
+ Construction never loads weights. ``start_prewarm`` does that in a daemon
213
+ worker; until it completes, search calls use the original order. At most
214
+ one prediction runs at a time, and a timed-out prediction keeps the slot
215
+ until it finishes, preventing an unbounded queue of background work.
216
+ This class deliberately does not replace the legacy lazy-loading reranker.
217
+ """
218
+
219
+ def __init__(
220
+ self,
221
+ model_path: str | Path,
222
+ expected_sha256: str,
223
+ *,
224
+ max_candidates: int = 24,
225
+ max_query_chars: int = 512,
226
+ max_excerpt_chars: int = 2048,
227
+ timeout_seconds: float = 1.5,
228
+ ) -> None:
229
+ path = Path(model_path)
230
+ if not path.is_absolute():
231
+ raise ValueError("model_path must be absolute")
232
+ if re.fullmatch(r"[0-9a-fA-F]{64}", expected_sha256) is None:
233
+ raise ValueError("expected_sha256 must be a SHA-256 hex digest")
234
+ if min(max_candidates, max_query_chars, max_excerpt_chars) < 1:
235
+ raise ValueError("input bounds must be positive")
236
+ if timeout_seconds <= 0:
237
+ raise ValueError("timeout_seconds must be positive")
238
+ self.model_path = path
239
+ self.expected_sha256 = expected_sha256.lower()
240
+ self.max_candidates = max_candidates
241
+ self.max_query_chars = max_query_chars
242
+ self.max_excerpt_chars = max_excerpt_chars
243
+ self.timeout_seconds = timeout_seconds
244
+ self._model: Any = None
245
+ self._status = "cold"
246
+ self._state_lock = threading.Lock()
247
+ self._prediction_lock = threading.Lock()
248
+
249
+ @property
250
+ def status(self) -> str:
251
+ """One of ``cold``, ``loading``, ``ready``, or ``failed``."""
252
+ with self._state_lock:
253
+ return self._status
254
+
255
+ @property
256
+ def is_available(self) -> bool:
257
+ return self.status == "ready"
258
+
259
+ def start_prewarm(self) -> bool:
260
+ """Begin one asynchronous, offline load. Return False if already begun."""
261
+ with self._state_lock:
262
+ if self._status != "cold":
263
+ return False
264
+ self._status = "loading"
265
+ threading.Thread(target=self._prewarm, name="local-reranker-prewarm", daemon=True).start()
266
+ return True
267
+
268
+ def _prewarm(self) -> None:
269
+ try:
270
+ actual_sha256 = model_tree_sha256(self.model_path)
271
+ if not hmac.compare_digest(actual_sha256, self.expected_sha256):
272
+ raise ValueError("model fingerprint mismatch")
273
+ from sentence_transformers import CrossEncoder
274
+
275
+ model = CrossEncoder(
276
+ str(self.model_path),
277
+ local_files_only=True,
278
+ trust_remote_code=False,
279
+ )
280
+ except Exception:
281
+ # Never log a source snippet or a model's local path.
282
+ logger.warning("Local cross-encoder prewarm failed; deterministic ranking remains active")
283
+ with self._state_lock:
284
+ self._status = "failed"
285
+ return
286
+ with self._state_lock:
287
+ self._model = model
288
+ self._status = "ready"
289
+
290
+ def rerank_result(
291
+ self,
292
+ query: str,
293
+ candidates: list[tuple[str, str, float]],
294
+ top_k: int = 10,
295
+ ) -> LocalRerankOutcome:
296
+ """Rerank bounded pairs, or immediately return deterministic order."""
297
+ fallback = candidates[:max(0, top_k)]
298
+ if top_k <= 0 or not candidates:
299
+ return LocalRerankOutcome(fallback, False, "empty")
300
+ if not query.strip():
301
+ return LocalRerankOutcome(fallback, False, "empty_query")
302
+ if not self.is_available:
303
+ return LocalRerankOutcome(fallback, False, self.status)
304
+ if not self._prediction_lock.acquire(blocking=False):
305
+ return LocalRerankOutcome(fallback, False, "busy")
306
+
307
+ bounded = candidates[: self.max_candidates]
308
+ pairs = [
309
+ (query[: self.max_query_chars], excerpt[: self.max_excerpt_chars])
310
+ for _, excerpt, _ in bounded
311
+ ]
312
+ done = threading.Event()
313
+ output: list[Any] = []
314
+
315
+ def predict() -> None:
316
+ try:
317
+ output.append(self._model.predict(pairs, batch_size=8, show_progress_bar=False))
318
+ except Exception:
319
+ output.append(None)
320
+ finally:
321
+ self._prediction_lock.release()
322
+ done.set()
323
+
324
+ threading.Thread(target=predict, name="local-reranker-predict", daemon=True).start()
325
+ if not done.wait(self.timeout_seconds):
326
+ return LocalRerankOutcome(fallback, False, "timeout")
327
+ scores = output[0] if output else None
328
+ try:
329
+ if scores is None or len(scores) != len(bounded):
330
+ raise ValueError("invalid score count")
331
+ numeric_scores = [float(score) for score in scores]
332
+ if not all(math.isfinite(score) for score in numeric_scores):
333
+ raise ValueError("non-finite score")
334
+ except (TypeError, ValueError, OverflowError):
335
+ return LocalRerankOutcome(fallback, False, "prediction_failed")
336
+ scored = [
337
+ (candidate_id, excerpt, score)
338
+ for (candidate_id, excerpt, _), score in zip(bounded, numeric_scores, strict=True)
339
+ ]
340
+ scored.sort(key=lambda item: item[2], reverse=True)
341
+ # Never drop an unscored tail if a caller requests more than the cap.
342
+ return LocalRerankOutcome((scored + candidates[self.max_candidates :])[:top_k], True)
343
+
344
+ def rerank(
345
+ self,
346
+ query: str,
347
+ candidates: list[tuple[str, str, float]],
348
+ top_k: int = 10,
349
+ ) -> list[tuple[str, str, float]]:
350
+ """Compatibility-shaped wrapper for callers that only need results."""
351
+ return self.rerank_result(query, candidates, top_k).candidates