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,433 @@
1
+ """Git operations endpoints — commits, blame, diffs (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 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, get_git_manager
16
+ from attocode_intel.api.utils import get_repo_by_id as _get_repo
17
+
18
+ router = APIRouter(prefix="/api/v2/projects/{project_id}", tags=["git-v2"])
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ # --- Response models ---
23
+
24
+
25
+ class CommitInfoResponse(BaseModel):
26
+ oid: str
27
+ message: str
28
+ author_name: str
29
+ author_email: str
30
+ timestamp: int
31
+ parent_oids: list[str] = []
32
+
33
+
34
+ class DiffLineResponse(BaseModel):
35
+ origin: str # '+'/'-'/' '
36
+ content: str
37
+ old_lineno: int | None = None
38
+ new_lineno: int | None = None
39
+
40
+
41
+ class DiffHunkResponse(BaseModel):
42
+ old_start: int
43
+ old_lines: int
44
+ new_start: int
45
+ new_lines: int
46
+ header: str
47
+ lines: list[DiffLineResponse]
48
+
49
+
50
+ class PatchEntryResponse(BaseModel):
51
+ path: str
52
+ status: str
53
+ old_path: str | None = None
54
+ additions: int = 0
55
+ deletions: int = 0
56
+ hunks: list[DiffHunkResponse] = []
57
+
58
+
59
+ class CommitDetailResponse(BaseModel):
60
+ commit: CommitInfoResponse
61
+ files: list[PatchEntryResponse]
62
+
63
+
64
+ class BlameHunkResponse(BaseModel):
65
+ commit_oid: str
66
+ author_name: str
67
+ author_email: str
68
+ timestamp: int
69
+ start_line: int
70
+ end_line: int
71
+
72
+
73
+ class CommitListResponse(BaseModel):
74
+ commits: list[CommitInfoResponse]
75
+ total: int
76
+ limit: int
77
+ offset: int
78
+ has_more: bool
79
+
80
+
81
+ class DiffResponse(BaseModel):
82
+ from_ref: str
83
+ to_ref: str
84
+ files: list[PatchEntryResponse]
85
+
86
+
87
+ # --- Helpers ---
88
+
89
+
90
+ def _patch_entry_from_db_diff(patch) -> PatchEntryResponse:
91
+ """Convert a diff_engine.PatchEntry to PatchEntryResponse."""
92
+ return PatchEntryResponse(
93
+ path=patch.path,
94
+ status=patch.status,
95
+ old_path=patch.old_path,
96
+ additions=patch.additions,
97
+ deletions=patch.deletions,
98
+ hunks=[
99
+ DiffHunkResponse(
100
+ old_start=h.old_start,
101
+ old_lines=h.old_lines,
102
+ new_start=h.new_start,
103
+ new_lines=h.new_lines,
104
+ header=h.header,
105
+ lines=[
106
+ DiffLineResponse(
107
+ origin=ln.origin,
108
+ content=ln.content,
109
+ old_lineno=ln.old_lineno,
110
+ new_lineno=ln.new_lineno,
111
+ )
112
+ for ln in h.lines
113
+ ],
114
+ )
115
+ for h in patch.hunks
116
+ ],
117
+ )
118
+
119
+
120
+ # --- Endpoints ---
121
+
122
+
123
+ @router.get("/commits", response_model=CommitListResponse)
124
+ async def get_commit_log(
125
+ project_id: uuid.UUID,
126
+ ref: str = Query("", description="Git ref. Defaults to default branch."),
127
+ path: str = Query("", description="Filter commits to those touching this path."),
128
+ limit: int = Query(50, ge=1, le=100),
129
+ offset: int = Query(0, ge=0),
130
+ auth: AuthContext = Depends(resolve_auth),
131
+ session: AsyncSession = Depends(get_db_session),
132
+ ) -> CommitListResponse:
133
+ """Get paginated commit log."""
134
+ repo = await _get_repo(project_id, session, auth)
135
+ resolved_ref = ref if ref else repo.default_branch
136
+
137
+ # DB fallback for repos without a bare clone (remote-connected repos)
138
+ if not repo.clone_path:
139
+ from attocode_intel.db.models import Commit
140
+
141
+ query = (
142
+ select(Commit)
143
+ .where(Commit.repo_id == project_id, Commit.branch_name == resolved_ref)
144
+ .order_by(Commit.timestamp.desc())
145
+ .limit(limit)
146
+ .offset(offset)
147
+ )
148
+ result = await session.execute(query)
149
+ rows = result.scalars().all()
150
+
151
+ items = [
152
+ CommitInfoResponse(
153
+ oid=c.oid,
154
+ message=c.message,
155
+ author_name=c.author_name,
156
+ author_email=c.author_email,
157
+ timestamp=c.timestamp,
158
+ parent_oids=c.parent_oids,
159
+ )
160
+ for c in rows
161
+ ]
162
+ return CommitListResponse(
163
+ commits=items,
164
+ total=len(items) + offset,
165
+ limit=limit,
166
+ offset=offset,
167
+ has_more=len(items) == limit,
168
+ )
169
+
170
+ git = get_git_manager()
171
+ try:
172
+ commits = git.get_commit_log(
173
+ str(project_id), resolved_ref,
174
+ path=path or None, limit=limit, offset=offset,
175
+ )
176
+ except (FileNotFoundError, ValueError) as e:
177
+ raise HTTPException(status_code=404, detail=str(e))
178
+
179
+ items = [
180
+ CommitInfoResponse(
181
+ oid=c.oid,
182
+ message=c.message,
183
+ author_name=c.author_name,
184
+ author_email=c.author_email,
185
+ timestamp=c.timestamp,
186
+ parent_oids=c.parent_oids,
187
+ )
188
+ for c in commits
189
+ ]
190
+
191
+ return CommitListResponse(
192
+ commits=items,
193
+ total=len(items) + offset, # approximate
194
+ limit=limit,
195
+ offset=offset,
196
+ has_more=len(items) == limit,
197
+ )
198
+
199
+
200
+ @router.get("/commits/{sha}", response_model=CommitDetailResponse)
201
+ async def get_commit_detail(
202
+ project_id: uuid.UUID,
203
+ sha: str,
204
+ auth: AuthContext = Depends(resolve_auth),
205
+ session: AsyncSession = Depends(get_db_session),
206
+ ) -> CommitDetailResponse:
207
+ """Get full commit detail with file diffs."""
208
+ repo = await _get_repo(project_id, session, auth)
209
+
210
+ if not repo.clone_path:
211
+ # DB fallback: return commit metadata with changed_files if available
212
+ from attocode_intel.db.models import Commit
213
+
214
+ result = await session.execute(
215
+ select(Commit).where(Commit.repo_id == project_id, Commit.oid == sha)
216
+ )
217
+ c = result.scalar_one_or_none()
218
+ if c is None:
219
+ raise HTTPException(status_code=404, detail=f"Commit {sha} not found")
220
+
221
+ files: list[PatchEntryResponse] = []
222
+ if c.changed_files:
223
+ files = [
224
+ PatchEntryResponse(
225
+ path=cf.get("path", ""),
226
+ status=cf.get("status", "M"),
227
+ )
228
+ for cf in c.changed_files
229
+ ]
230
+
231
+ return CommitDetailResponse(
232
+ commit=CommitInfoResponse(
233
+ oid=c.oid, message=c.message, author_name=c.author_name,
234
+ author_email=c.author_email, timestamp=c.timestamp, parent_oids=c.parent_oids,
235
+ ),
236
+ files=files,
237
+ )
238
+
239
+ git = get_git_manager()
240
+ try:
241
+ commit_info, patches = git.get_commit_detail(str(project_id), sha)
242
+ except (FileNotFoundError, ValueError) as e:
243
+ raise HTTPException(status_code=404, detail=str(e))
244
+
245
+ return CommitDetailResponse(
246
+ commit=CommitInfoResponse(
247
+ oid=commit_info.oid,
248
+ message=commit_info.message,
249
+ author_name=commit_info.author_name,
250
+ author_email=commit_info.author_email,
251
+ timestamp=commit_info.timestamp,
252
+ parent_oids=commit_info.parent_oids,
253
+ ),
254
+ files=[
255
+ PatchEntryResponse(
256
+ path=p.path,
257
+ status=p.status,
258
+ old_path=p.old_path,
259
+ additions=p.additions,
260
+ deletions=p.deletions,
261
+ hunks=[
262
+ DiffHunkResponse(
263
+ old_start=h.old_start,
264
+ old_lines=h.old_lines,
265
+ new_start=h.new_start,
266
+ new_lines=h.new_lines,
267
+ header=h.header,
268
+ lines=[
269
+ DiffLineResponse(
270
+ origin=ln.origin,
271
+ content=ln.content,
272
+ old_lineno=ln.old_lineno,
273
+ new_lineno=ln.new_lineno,
274
+ )
275
+ for ln in h.lines
276
+ ],
277
+ )
278
+ for h in p.hunks
279
+ ],
280
+ )
281
+ for p in patches
282
+ ],
283
+ )
284
+
285
+
286
+ @router.get("/diff", response_model=DiffResponse)
287
+ async def get_diff(
288
+ project_id: uuid.UUID,
289
+ from_ref: str = Query(..., alias="from", description="Base ref"),
290
+ to_ref: str = Query(..., alias="to", description="Target ref"),
291
+ path: str = Query("", description="Filter to specific path"),
292
+ auth: AuthContext = Depends(resolve_auth),
293
+ session: AsyncSession = Depends(get_db_session),
294
+ ) -> DiffResponse:
295
+ """Diff between two refs with patch content."""
296
+ repo = await _get_repo(project_id, session, auth)
297
+
298
+ if not repo.clone_path:
299
+ # DB-backed diff: resolve from_ref/to_ref as branch names
300
+ from attocode_intel.db.models import Branch
301
+ from attocode_intel.storage.diff_engine import compute_branch_diff
302
+
303
+ branch_a_result = await session.execute(
304
+ select(Branch).where(Branch.repo_id == project_id, Branch.name == from_ref)
305
+ )
306
+ branch_a = branch_a_result.scalar_one_or_none()
307
+ if branch_a is None:
308
+ raise HTTPException(status_code=404, detail=f"Branch '{from_ref}' not found")
309
+
310
+ branch_b_result = await session.execute(
311
+ select(Branch).where(Branch.repo_id == project_id, Branch.name == to_ref)
312
+ )
313
+ branch_b = branch_b_result.scalar_one_or_none()
314
+ if branch_b is None:
315
+ raise HTTPException(status_code=404, detail=f"Branch '{to_ref}' not found")
316
+
317
+ patches = await compute_branch_diff(
318
+ session, branch_a.id, branch_b.id, path_filter=path,
319
+ )
320
+
321
+ return DiffResponse(
322
+ from_ref=from_ref,
323
+ to_ref=to_ref,
324
+ files=[_patch_entry_from_db_diff(p) for p in patches],
325
+ )
326
+
327
+ git = get_git_manager()
328
+
329
+ try:
330
+ patches = git.get_patch(str(project_id), from_ref, to_ref, path=path or None)
331
+ except (FileNotFoundError, ValueError) as e:
332
+ raise HTTPException(status_code=404, detail=str(e))
333
+
334
+ return DiffResponse(
335
+ from_ref=from_ref,
336
+ to_ref=to_ref,
337
+ files=[
338
+ PatchEntryResponse(
339
+ path=p.path,
340
+ status=p.status,
341
+ old_path=p.old_path,
342
+ additions=p.additions,
343
+ deletions=p.deletions,
344
+ hunks=[
345
+ DiffHunkResponse(
346
+ old_start=h.old_start,
347
+ old_lines=h.old_lines,
348
+ new_start=h.new_start,
349
+ new_lines=h.new_lines,
350
+ header=h.header,
351
+ lines=[
352
+ DiffLineResponse(
353
+ origin=ln.origin,
354
+ content=ln.content,
355
+ old_lineno=ln.old_lineno,
356
+ new_lineno=ln.new_lineno,
357
+ )
358
+ for ln in h.lines
359
+ ],
360
+ )
361
+ for h in p.hunks
362
+ ],
363
+ )
364
+ for p in patches
365
+ ],
366
+ )
367
+
368
+
369
+ @router.get("/blame/{path:path}", response_model=list[BlameHunkResponse])
370
+ async def get_blame(
371
+ project_id: uuid.UUID,
372
+ path: str,
373
+ ref: str = Query("", description="Git ref. Defaults to default branch."),
374
+ auth: AuthContext = Depends(resolve_auth),
375
+ session: AsyncSession = Depends(get_db_session),
376
+ ) -> list[BlameHunkResponse]:
377
+ """Get line-level blame for a file."""
378
+ repo = await _get_repo(project_id, session, auth)
379
+
380
+ if ".." in path.split("/"):
381
+ raise HTTPException(status_code=400, detail="Path traversal not allowed")
382
+
383
+ if not repo.clone_path:
384
+ # DB fallback: query blame_hunks table
385
+ from attocode_intel.db.models import BlameHunk
386
+
387
+ resolved_ref = ref if ref else repo.default_branch
388
+ result = await session.execute(
389
+ select(BlameHunk)
390
+ .where(
391
+ BlameHunk.repo_id == project_id,
392
+ BlameHunk.path == path,
393
+ BlameHunk.branch_name == resolved_ref,
394
+ )
395
+ .order_by(BlameHunk.start_line)
396
+ )
397
+ hunks = result.scalars().all()
398
+ if not hunks:
399
+ raise HTTPException(
400
+ status_code=404,
401
+ detail=f"No blame data for {path}. Push blame data via notify/blame.",
402
+ )
403
+ return [
404
+ BlameHunkResponse(
405
+ commit_oid=h.commit_oid,
406
+ author_name=h.author_name,
407
+ author_email=h.author_email,
408
+ timestamp=h.timestamp,
409
+ start_line=h.start_line,
410
+ end_line=h.end_line,
411
+ )
412
+ for h in hunks
413
+ ]
414
+
415
+ git = get_git_manager()
416
+ resolved_ref = ref if ref else repo.default_branch
417
+
418
+ try:
419
+ hunks = git.get_blame(str(project_id), resolved_ref, path)
420
+ except (FileNotFoundError, ValueError, KeyError) as e:
421
+ raise HTTPException(status_code=404, detail=f"Cannot blame: {e}")
422
+
423
+ return [
424
+ BlameHunkResponse(
425
+ commit_oid=h.commit_oid,
426
+ author_name=h.author_name,
427
+ author_email=h.author_email,
428
+ timestamp=h.timestamp,
429
+ start_line=h.start_line,
430
+ end_line=h.end_line,
431
+ )
432
+ for h in hunks
433
+ ]
@@ -0,0 +1,174 @@
1
+ """Graph query endpoints — unified v1 (text) + v2 (structured JSON)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from fastapi import APIRouter, Depends
6
+
7
+ from attocode_intel.api.auth import verify_auth
8
+ from attocode_intel.api.deps import (
9
+ BranchParam,
10
+ ensure_branch_supported,
11
+ get_graph_provider,
12
+ get_service_or_404,
13
+ )
14
+ from attocode_intel.api.models import (
15
+ CommunityResponse,
16
+ FindRelatedRequest,
17
+ FindRelatedResponse,
18
+ GraphDSLRequest,
19
+ GraphQueryRequest,
20
+ GraphQueryResponse,
21
+ RelevantContextRequest,
22
+ TextResult,
23
+ )
24
+
25
+ # --- v1 router: text responses ---
26
+
27
+ router_v1 = APIRouter(
28
+ prefix="/api/v1/projects/{project_id}/graph",
29
+ tags=["graph"],
30
+ dependencies=[Depends(verify_auth)],
31
+ )
32
+
33
+ # --- v2 router: structured JSON ---
34
+
35
+ router_v2 = APIRouter(
36
+ prefix="/api/v2/projects/{project_id}/graph",
37
+ tags=["graph-v2"],
38
+ dependencies=[Depends(verify_auth)],
39
+ )
40
+
41
+
42
+ # ===================================================================
43
+ # v1 endpoints
44
+ # ===================================================================
45
+
46
+
47
+ @router_v1.post("/query", response_model=TextResult)
48
+ async def graph_query_v1(
49
+ project_id: str,
50
+ req: GraphQueryRequest,
51
+ branch: BranchParam = "",
52
+ ) -> TextResult:
53
+ """BFS traversal over dependency edges."""
54
+ ensure_branch_supported(branch)
55
+ svc = await get_service_or_404(project_id)
56
+ return TextResult(result=svc.graph_query(
57
+ file=req.file, edge_type=req.edge_type, direction=req.direction, depth=req.depth,
58
+ ))
59
+
60
+
61
+ @router_v1.post("/related", response_model=TextResult)
62
+ async def find_related_v1(
63
+ project_id: str,
64
+ req: FindRelatedRequest,
65
+ branch: BranchParam = "",
66
+ ) -> TextResult:
67
+ """Find structurally related files."""
68
+ ensure_branch_supported(branch)
69
+ svc = await get_service_or_404(project_id)
70
+ return TextResult(result=svc.find_related(file=req.file, top_k=req.top_k))
71
+
72
+
73
+ @router_v1.get("/communities", response_model=TextResult)
74
+ async def community_detection_v1(
75
+ project_id: str,
76
+ branch: BranchParam = "",
77
+ min_community_size: int = 3,
78
+ max_communities: int = 20,
79
+ ) -> TextResult:
80
+ """Detect file communities."""
81
+ ensure_branch_supported(branch)
82
+ svc = await get_service_or_404(project_id)
83
+ return TextResult(result=svc.community_detection(
84
+ min_community_size=min_community_size, max_communities=max_communities,
85
+ ))
86
+
87
+
88
+ @router_v1.post("/context", response_model=TextResult)
89
+ async def relevant_context_v1(
90
+ project_id: str,
91
+ req: RelevantContextRequest,
92
+ branch: BranchParam = "",
93
+ ) -> TextResult:
94
+ """Get subgraph capsule for files with neighbors."""
95
+ ensure_branch_supported(branch)
96
+ svc = await get_service_or_404(project_id)
97
+ return TextResult(result=svc.relevant_context(
98
+ files=req.files, depth=req.depth, max_tokens=req.max_tokens,
99
+ include_symbols=req.include_symbols,
100
+ ))
101
+
102
+
103
+ @router_v1.post("/dsl", response_model=TextResult)
104
+ async def graph_dsl_v1(
105
+ project_id: str,
106
+ req: GraphDSLRequest,
107
+ branch: BranchParam = "",
108
+ ) -> TextResult:
109
+ """Query the dependency graph using the graph DSL."""
110
+ from attocode_intel.tools.analysis_tools import graph_dsl
111
+
112
+ ensure_branch_supported(branch)
113
+ await get_service_or_404(project_id)
114
+ return TextResult(result=graph_dsl(req.query))
115
+
116
+
117
+ # ===================================================================
118
+ # v2 endpoints
119
+ # ===================================================================
120
+
121
+
122
+ @router_v2.post("/query", response_model=GraphQueryResponse)
123
+ async def graph_query_v2(
124
+ project_id: str,
125
+ req: GraphQueryRequest,
126
+ branch: BranchParam = "",
127
+ ) -> GraphQueryResponse:
128
+ """BFS traversal over dependency edges (structured)."""
129
+ provider = await get_graph_provider(project_id)
130
+ return await provider.graph_query(
131
+ file=req.file, edge_type=req.edge_type,
132
+ direction=req.direction, depth=req.depth, branch=branch,
133
+ )
134
+
135
+
136
+ @router_v2.post("/related", response_model=FindRelatedResponse)
137
+ async def find_related_v2(
138
+ project_id: str,
139
+ req: FindRelatedRequest,
140
+ branch: BranchParam = "",
141
+ ) -> FindRelatedResponse:
142
+ """Find structurally related files (structured)."""
143
+ provider = await get_graph_provider(project_id)
144
+ return await provider.find_related(file=req.file, top_k=req.top_k, branch=branch)
145
+
146
+
147
+ @router_v2.get("/communities", response_model=CommunityResponse)
148
+ async def community_detection_v2(
149
+ project_id: str,
150
+ branch: BranchParam = "",
151
+ min_community_size: int = 3,
152
+ max_communities: int = 20,
153
+ ) -> CommunityResponse:
154
+ """Detect file communities (structured)."""
155
+ provider = await get_graph_provider(project_id)
156
+ return await provider.community_detection(
157
+ branch=branch,
158
+ min_community_size=min_community_size,
159
+ max_communities=max_communities,
160
+ )
161
+
162
+
163
+ @router_v2.post("/context", response_model=TextResult)
164
+ async def relevant_context_v2(
165
+ project_id: str,
166
+ req: RelevantContextRequest,
167
+ branch: BranchParam = "",
168
+ ) -> TextResult:
169
+ """Get subgraph capsule (text — inherently text-shaped)."""
170
+ svc = await get_service_or_404(project_id)
171
+ return TextResult(result=svc.relevant_context(
172
+ files=req.files, depth=req.depth, max_tokens=req.max_tokens,
173
+ include_symbols=req.include_symbols,
174
+ ))