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,128 @@
1
+ """Authentication package — unified auth resolution for local and service modes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import time
6
+ from typing import TYPE_CHECKING
7
+
8
+ from fastapi import Header, HTTPException
9
+
10
+ from attocode_intel.api.deps import get_config
11
+
12
+ if TYPE_CHECKING:
13
+ from attocode_intel.api.auth.context import AuthContext
14
+
15
+ # In-memory revocation cache (jti → expiry_monotonic)
16
+ _revocation_cache: dict[str, float] = {}
17
+ _REVOCATION_CACHE_TTL = 30 # seconds
18
+ _revocation_cache_refreshed_at: float = 0.0
19
+
20
+
21
+ async def verify_api_key(authorization: str | None = Header(None)) -> None:
22
+ """Legacy API key verification (local mode).
23
+
24
+ If ATTOCODE_API_KEY is not set, all requests are allowed (local mode).
25
+ """
26
+ config = get_config()
27
+ if not config.api_key:
28
+ return
29
+ if not authorization:
30
+ raise HTTPException(status_code=401, detail="Authorization header required")
31
+ token = authorization.removeprefix("Bearer ").strip()
32
+ if token != config.api_key:
33
+ raise HTTPException(status_code=401, detail="Invalid API key")
34
+
35
+
36
+ async def verify_auth(authorization: str | None = Header(None)) -> None:
37
+ """Router-level auth guard — delegates to resolve_auth, discards AuthContext."""
38
+ await resolve_auth(authorization)
39
+
40
+
41
+ async def _is_token_revoked(jti: str) -> bool:
42
+ """Check if a JWT's jti is in the revocation blocklist.
43
+
44
+ Uses a 30-second in-memory cache to avoid per-request DB hits.
45
+ """
46
+ global _revocation_cache_refreshed_at
47
+
48
+ now = time.monotonic()
49
+
50
+ # Refresh cache if stale
51
+ if now - _revocation_cache_refreshed_at > _REVOCATION_CACHE_TTL:
52
+ try:
53
+ from attocode_intel.db.engine import get_session
54
+ from attocode_intel.db.models import RevokedToken
55
+
56
+ from sqlalchemy import select
57
+
58
+ async for session in get_session():
59
+ result = await session.execute(select(RevokedToken.jti))
60
+ _revocation_cache.clear()
61
+ for row in result:
62
+ _revocation_cache[row[0]] = now + _REVOCATION_CACHE_TTL
63
+ _revocation_cache_refreshed_at = now
64
+ break
65
+ except Exception:
66
+ pass # If DB is unavailable, use stale cache
67
+
68
+ return jti in _revocation_cache
69
+
70
+
71
+ async def resolve_auth(authorization: str | None = Header(None)) -> AuthContext:
72
+ """Unified auth resolver — delegates to legacy or service path based on config.
73
+
74
+ In local mode: verifies static API key, returns a stub AuthContext.
75
+ In service mode: tries JWT first, then service API key resolution.
76
+ """
77
+ from attocode_intel.api.auth.context import AuthContext
78
+
79
+ config = get_config()
80
+
81
+ if not config.is_service_mode:
82
+ # Local mode — delegate to legacy check
83
+ await verify_api_key(authorization)
84
+ return AuthContext(auth_method="legacy")
85
+
86
+ # Service mode — try JWT, then API key
87
+ if not authorization:
88
+ raise HTTPException(status_code=401, detail="Authorization header required")
89
+
90
+ token = authorization.removeprefix("Bearer ").strip()
91
+ if not token:
92
+ raise HTTPException(status_code=401, detail="Authorization header required")
93
+
94
+ # Try JWT first
95
+ from attocode_intel.api.auth.jwt import decode_token
96
+
97
+ payload = decode_token(token)
98
+ if payload is not None:
99
+ if payload.get("type", "access") != "access":
100
+ raise HTTPException(status_code=401, detail="Access token required")
101
+ import uuid
102
+
103
+ # Check revocation blocklist
104
+ jti = payload.get("jti")
105
+ if jti and await _is_token_revoked(jti):
106
+ raise HTTPException(status_code=401, detail="Token has been revoked")
107
+
108
+ try:
109
+ user_id = uuid.UUID(payload["sub"])
110
+ org_id = uuid.UUID(payload["org"]) if payload.get("org") else None
111
+ except (KeyError, TypeError, ValueError, AttributeError) as exc:
112
+ raise HTTPException(status_code=401, detail="Invalid token identity") from exc
113
+ return AuthContext(
114
+ user_id=user_id,
115
+ org_id=org_id,
116
+ scopes=payload.get("scopes", []),
117
+ auth_method="jwt",
118
+ plan=payload.get("plan", "free"),
119
+ )
120
+
121
+ # Try API key
122
+ from attocode_intel.api.auth.api_keys import resolve_api_key
123
+
124
+ ctx = await resolve_api_key(token)
125
+ if ctx is not None:
126
+ return ctx
127
+
128
+ raise HTTPException(status_code=401, detail="Invalid credentials")
@@ -0,0 +1,61 @@
1
+ """Service API key generation and resolution."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import secrets
7
+ from datetime import datetime, timezone
8
+ from typing import TYPE_CHECKING
9
+
10
+ if TYPE_CHECKING:
11
+ from attocode_intel.api.auth.context import AuthContext
12
+
13
+
14
+ def generate_api_key() -> tuple[str, str, str]:
15
+ """Generate a new API key.
16
+
17
+ Returns:
18
+ (plaintext_key, key_hash, key_prefix) — show plaintext once, store hash.
19
+ """
20
+ plaintext = f"aci_{secrets.token_urlsafe(32)}"
21
+ key_hash = hashlib.sha256(plaintext.encode()).hexdigest()
22
+ key_prefix = plaintext[:12]
23
+ return plaintext, key_hash, key_prefix
24
+
25
+
26
+ async def resolve_api_key(token: str) -> AuthContext | None:
27
+ """Look up a service API key by its SHA-256 hash. Returns AuthContext or None."""
28
+ import hashlib
29
+
30
+ from sqlalchemy import select
31
+
32
+ from attocode_intel.api.auth.context import AuthContext
33
+ from attocode_intel.db.engine import get_session
34
+ from attocode_intel.db.models import ApiKey
35
+
36
+ key_hash = hashlib.sha256(token.encode()).hexdigest()
37
+
38
+ async for session in get_session():
39
+ result = await session.execute(
40
+ select(ApiKey).where(ApiKey.key_hash == key_hash)
41
+ )
42
+ api_key = result.scalar_one_or_none()
43
+ if api_key is None:
44
+ return None
45
+
46
+ # Check expiry
47
+ if api_key.expires_at and api_key.expires_at < datetime.now(timezone.utc):
48
+ return None
49
+
50
+ # Update last_used_at
51
+ api_key.last_used_at = datetime.now(timezone.utc)
52
+ await session.commit()
53
+
54
+ return AuthContext(
55
+ user_id=api_key.user_id,
56
+ org_id=api_key.org_id,
57
+ scopes=list(api_key.scopes) if api_key.scopes else [],
58
+ auth_method="api_key",
59
+ plan="free",
60
+ )
61
+ return None
@@ -0,0 +1,34 @@
1
+ """Authentication context — carries identity info through request lifecycle."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import uuid
6
+ from dataclasses import dataclass, field
7
+
8
+
9
+ @dataclass(slots=True)
10
+ class AuthContext:
11
+ """Identity and authorization context for an authenticated request."""
12
+
13
+ user_id: uuid.UUID | None = None
14
+ org_id: uuid.UUID | None = None
15
+ scopes: list[str] = field(default_factory=list)
16
+ auth_method: str = "legacy" # legacy|jwt|api_key
17
+ plan: str = "free" # free|team|enterprise
18
+
19
+ def has_scope(self, scope: str) -> bool:
20
+ """Check if the context has a specific scope."""
21
+ if not self.scopes:
22
+ return True # Empty scopes = full access (legacy mode)
23
+ return scope in self.scopes
24
+
25
+ def require_scope(self, scope: str) -> None:
26
+ """Raise if the required scope is missing."""
27
+ if not self.has_scope(scope):
28
+ from fastapi import HTTPException
29
+
30
+ raise HTTPException(status_code=403, detail=f"Missing scope: {scope}")
31
+
32
+ def require_role(self, org_id: uuid.UUID, min_role: str) -> None:
33
+ """Placeholder for role checks — actual check done in route handlers."""
34
+ pass
@@ -0,0 +1,66 @@
1
+ """JWT token creation and validation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import uuid
7
+ from datetime import datetime, timedelta, timezone
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ def create_access_token(
13
+ user_id: uuid.UUID,
14
+ org_id: uuid.UUID | None = None,
15
+ scopes: list[str] | None = None,
16
+ plan: str = "free",
17
+ expires_minutes: int = 60,
18
+ ) -> str:
19
+ """Create a signed JWT access token."""
20
+ from jose import jwt
21
+
22
+ from attocode_intel.api.deps import get_config
23
+
24
+ config = get_config()
25
+ now = datetime.now(timezone.utc)
26
+ payload = {
27
+ "sub": str(user_id),
28
+ "type": "access",
29
+ "scopes": scopes or [],
30
+ "plan": plan,
31
+ "iat": now,
32
+ "exp": now + timedelta(minutes=expires_minutes),
33
+ }
34
+ if org_id:
35
+ payload["org"] = str(org_id)
36
+ return jwt.encode(payload, config.secret_key, algorithm="HS256")
37
+
38
+
39
+ def create_refresh_token(user_id: uuid.UUID, expires_days: int = 30) -> str:
40
+ """Create a signed JWT refresh token."""
41
+ from jose import jwt
42
+
43
+ from attocode_intel.api.deps import get_config
44
+
45
+ config = get_config()
46
+ now = datetime.now(timezone.utc)
47
+ payload = {
48
+ "sub": str(user_id),
49
+ "type": "refresh",
50
+ "iat": now,
51
+ "exp": now + timedelta(days=expires_days),
52
+ }
53
+ return jwt.encode(payload, config.secret_key, algorithm="HS256")
54
+
55
+
56
+ def decode_token(token: str) -> dict | None:
57
+ """Decode and validate a JWT token. Returns payload dict or None if invalid."""
58
+ from jose import JWTError, jwt
59
+
60
+ from attocode_intel.api.deps import get_config
61
+
62
+ config = get_config()
63
+ try:
64
+ return jwt.decode(token, config.secret_key, algorithms=["HS256"])
65
+ except JWTError:
66
+ return None
@@ -0,0 +1,146 @@
1
+ """OAuth 2.0 authorization code flows (GitHub + Google)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import TYPE_CHECKING
7
+
8
+ if TYPE_CHECKING:
9
+ pass
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+ GITHUB_AUTHORIZE_URL = "https://github.com/login/oauth/authorize"
14
+ GITHUB_TOKEN_URL = "https://github.com/login/oauth/access_token"
15
+ GITHUB_USER_URL = "https://api.github.com/user"
16
+
17
+ GOOGLE_AUTHORIZE_URL = "https://accounts.google.com/o/oauth2/v2/auth"
18
+ GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token"
19
+ GOOGLE_USERINFO_URL = "https://openidconnect.googleapis.com/v1/userinfo"
20
+
21
+
22
+ async def get_github_auth_url(client_id: str, redirect_uri: str, state: str) -> str:
23
+ """Build the GitHub OAuth authorization URL."""
24
+ import urllib.parse
25
+
26
+ params = {
27
+ "client_id": client_id,
28
+ "redirect_uri": redirect_uri,
29
+ "scope": "user:email",
30
+ "state": state,
31
+ }
32
+ return f"{GITHUB_AUTHORIZE_URL}?{urllib.parse.urlencode(params)}"
33
+
34
+
35
+ async def exchange_github_code(
36
+ code: str,
37
+ client_id: str,
38
+ client_secret: str,
39
+ ) -> dict:
40
+ """Exchange authorization code for access token and fetch user info.
41
+
42
+ Returns dict with keys: github_id, email, name, avatar_url.
43
+ """
44
+ import httpx
45
+
46
+ async with httpx.AsyncClient() as client:
47
+ # Exchange code for token
48
+ token_resp = await client.post(
49
+ GITHUB_TOKEN_URL,
50
+ data={
51
+ "client_id": client_id,
52
+ "client_secret": client_secret,
53
+ "code": code,
54
+ },
55
+ headers={"Accept": "application/json"},
56
+ )
57
+ token_resp.raise_for_status()
58
+ token_data = token_resp.json()
59
+ access_token = token_data["access_token"]
60
+
61
+ # Fetch user profile
62
+ user_resp = await client.get(
63
+ GITHUB_USER_URL,
64
+ headers={"Authorization": f"Bearer {access_token}"},
65
+ )
66
+ user_resp.raise_for_status()
67
+ user_data = user_resp.json()
68
+
69
+ # Fetch email if not public
70
+ email = user_data.get("email")
71
+ if not email:
72
+ email_resp = await client.get(
73
+ "https://api.github.com/user/emails",
74
+ headers={"Authorization": f"Bearer {access_token}"},
75
+ )
76
+ email_resp.raise_for_status()
77
+ emails = email_resp.json()
78
+ primary = next((e for e in emails if e.get("primary")), None)
79
+ email = primary["email"] if primary else emails[0]["email"]
80
+
81
+ return {
82
+ "github_id": user_data["id"],
83
+ "email": email,
84
+ "name": user_data.get("name") or user_data.get("login", ""),
85
+ "avatar_url": user_data.get("avatar_url", ""),
86
+ }
87
+
88
+
89
+ async def get_google_auth_url(client_id: str, redirect_uri: str, state: str) -> str:
90
+ """Build the Google OAuth authorization URL."""
91
+ import urllib.parse
92
+
93
+ params = {
94
+ "client_id": client_id,
95
+ "redirect_uri": redirect_uri,
96
+ "scope": "openid email profile",
97
+ "state": state,
98
+ "response_type": "code",
99
+ "access_type": "offline",
100
+ "prompt": "consent",
101
+ }
102
+ return f"{GOOGLE_AUTHORIZE_URL}?{urllib.parse.urlencode(params)}"
103
+
104
+
105
+ async def exchange_google_code(
106
+ code: str,
107
+ client_id: str,
108
+ client_secret: str,
109
+ redirect_uri: str,
110
+ ) -> dict:
111
+ """Exchange authorization code for access token and fetch user info.
112
+
113
+ Returns dict with keys: google_id, email, name, avatar_url.
114
+ """
115
+ import httpx
116
+
117
+ async with httpx.AsyncClient() as client:
118
+ # Exchange code for token (Google requires form-encoded, not JSON)
119
+ token_resp = await client.post(
120
+ GOOGLE_TOKEN_URL,
121
+ data={
122
+ "code": code,
123
+ "client_id": client_id,
124
+ "client_secret": client_secret,
125
+ "redirect_uri": redirect_uri,
126
+ "grant_type": "authorization_code",
127
+ },
128
+ )
129
+ token_resp.raise_for_status()
130
+ token_data = token_resp.json()
131
+ access_token = token_data["access_token"]
132
+
133
+ # Fetch user profile
134
+ user_resp = await client.get(
135
+ GOOGLE_USERINFO_URL,
136
+ headers={"Authorization": f"Bearer {access_token}"},
137
+ )
138
+ user_resp.raise_for_status()
139
+ user_data = user_resp.json()
140
+
141
+ return {
142
+ "google_id": user_data["sub"],
143
+ "email": user_data["email"],
144
+ "name": user_data.get("name", ""),
145
+ "avatar_url": user_data.get("picture", ""),
146
+ }
@@ -0,0 +1,15 @@
1
+ """Password hashing and verification via bcrypt."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import bcrypt
6
+
7
+
8
+ def hash_password(password: str) -> str:
9
+ """Hash a plaintext password with bcrypt."""
10
+ return bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
11
+
12
+
13
+ def verify_password(plain: str, hashed: str) -> bool:
14
+ """Verify a plaintext password against a bcrypt hash."""
15
+ return bcrypt.checkpw(plain.encode(), hashed.encode())