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,57 @@
1
+ """Explicitly copy selected knowledge to a team; never send source files."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import os
8
+ from pathlib import Path
9
+
10
+
11
+ def share(argv):
12
+ parser = argparse.ArgumentParser(prog="attocode-code-intel share")
13
+ parser.add_argument("--project", default=".")
14
+ parser.add_argument("--learning", type=int, required=True, help="Local learning ID to copy")
15
+ parser.add_argument("--server", required=True)
16
+ parser.add_argument("--repo", required=True)
17
+ parser.add_argument("--token-env", default="ATTOCODE_API_KEY")
18
+ parser.add_argument("--dry-run", action="store_true")
19
+ args = parser.parse_args(argv)
20
+ from attocode_intel._internal.integrations.context.memory_store import MemoryStore
21
+
22
+ root = Path(args.project).resolve()
23
+ with MemoryStore(str(root)) as store:
24
+ row = next((row for row in store.list_all() if row["id"] == args.learning), None)
25
+ if row is None:
26
+ parser.error("Active learning not found")
27
+ payload = {
28
+ key: row[key]
29
+ for key in ("type", "description", "details", "scope", "confidence", "anchor_blob_oid")
30
+ }
31
+ payload["workspace"] = args.repo
32
+ if args.dry_run:
33
+ print(json.dumps({"operation": "record_learning", "arguments": payload}, indent=2))
34
+ return
35
+ from urllib.parse import urlsplit
36
+
37
+ parsed = urlsplit(args.server)
38
+ if (
39
+ parsed.scheme not in {"http", "https"}
40
+ or not parsed.netloc
41
+ or parsed.username
42
+ or parsed.password
43
+ ):
44
+ parser.error("Server must be an HTTP(S) URL without embedded credentials")
45
+ token = os.environ.get(args.token_env)
46
+ if not token:
47
+ parser.error(f"Set {args.token_env} to a team API key with intelligence:write scope")
48
+ import httpx
49
+
50
+ response = httpx.post(
51
+ args.server.rstrip("/") + "/api/v2/operations/record_learning",
52
+ json=payload,
53
+ headers={"Authorization": "Bearer " + token},
54
+ timeout=30,
55
+ )
56
+ response.raise_for_status()
57
+ print(json.dumps(response.json(), indent=2))
@@ -0,0 +1,6 @@
1
+ """Content-addressed storage and branch overlay model.
2
+
3
+ These modules require SQLAlchemy (service mode dependencies).
4
+ Import them directly when needed:
5
+ from attocode_intel.storage.content_store import ContentStore
6
+ """
@@ -0,0 +1,352 @@
1
+ """Branch overlay model — multi-branch indexing with parent chain resolution."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import uuid
7
+ from typing import TYPE_CHECKING
8
+
9
+ if TYPE_CHECKING:
10
+ from sqlalchemy.ext.asyncio import AsyncSession
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class BranchOverlay:
16
+ """Manages branch-level file overlays on top of a default branch.
17
+
18
+ Resolution algorithm:
19
+ 1. Load default branch manifest (all file paths → content SHAs)
20
+ 2. Walk from target branch → parent → default via parent_branch_id chain
21
+ 3. Apply overlays in order: default → intermediate → target
22
+ 4. Additions/modifications replace entries; deletions remove them
23
+ 5. Result: complete dict[str, str] (path → content_sha)
24
+
25
+ Branch version is incremented on every write for consistency tracking.
26
+ Clients can pass If-Match headers with branch version for strict consistency.
27
+ """
28
+
29
+ def __init__(self, session: AsyncSession) -> None:
30
+ self._session = session
31
+
32
+ async def _bump_version(self, branch_id: uuid.UUID) -> int:
33
+ """Increment branch version counter. Returns new version.
34
+
35
+ Raises ValueError if ``branch_id`` doesn't exist.
36
+ """
37
+ from sqlalchemy import update
38
+
39
+ from attocode_intel.db.models import Branch
40
+
41
+ result = await self._session.execute(
42
+ update(Branch)
43
+ .where(Branch.id == branch_id)
44
+ .values(version=Branch.version + 1)
45
+ .returning(Branch.version)
46
+ )
47
+ new_version = result.scalar_one_or_none()
48
+ if new_version is None:
49
+ raise ValueError(f"Branch not found: {branch_id}")
50
+ return new_version
51
+
52
+ async def get_version(self, branch_id: uuid.UUID) -> int:
53
+ """Get current branch version for consistency checks."""
54
+ from sqlalchemy import select
55
+
56
+ from attocode_intel.db.models import Branch
57
+
58
+ result = await self._session.execute(
59
+ select(Branch.version).where(Branch.id == branch_id)
60
+ )
61
+ return result.scalar_one_or_none() or 0
62
+
63
+ async def check_version(self, branch_id: uuid.UUID, expected: int) -> None:
64
+ """Verify branch version matches expected. Raises ValueError on mismatch.
65
+
66
+ Used for If-Match optimistic concurrency control.
67
+ """
68
+ current = await self.get_version(branch_id)
69
+ if current != expected:
70
+ raise ValueError(
71
+ f"Branch version mismatch: expected {expected}, current {current}. "
72
+ "Another update occurred — retry with the latest version."
73
+ )
74
+
75
+ async def set_file(
76
+ self,
77
+ branch_id: uuid.UUID,
78
+ path: str,
79
+ content_sha: str,
80
+ status: str = "modified",
81
+ ) -> None:
82
+ """Set a file in the branch overlay. Bumps branch version."""
83
+ from sqlalchemy import select
84
+
85
+ from attocode_intel.db.models import BranchFile
86
+
87
+ result = await self._session.execute(
88
+ select(BranchFile).where(
89
+ BranchFile.branch_id == branch_id,
90
+ BranchFile.path == path,
91
+ )
92
+ )
93
+ existing = result.scalar_one_or_none()
94
+ if existing:
95
+ existing.content_sha = content_sha
96
+ existing.status = status
97
+ else:
98
+ bf = BranchFile(
99
+ branch_id=branch_id,
100
+ path=path,
101
+ content_sha=content_sha,
102
+ status=status,
103
+ )
104
+ self._session.add(bf)
105
+
106
+ await self._bump_version(branch_id)
107
+ await self._session.flush()
108
+
109
+ async def set_files_batch(
110
+ self,
111
+ branch_id: uuid.UUID,
112
+ files: list[tuple[str, str, str]],
113
+ expected_version: int | None = None,
114
+ ) -> None:
115
+ """Set multiple files in one operation. Each tuple: (path, content_sha, status).
116
+
117
+ Single version bump for the batch.
118
+ """
119
+ from sqlalchemy import select
120
+
121
+ from attocode_intel.db.models import BranchFile
122
+
123
+ if not files:
124
+ return
125
+
126
+ if expected_version is not None:
127
+ await self.check_version(branch_id, expected_version)
128
+
129
+ # Load existing entries for these paths in one query
130
+ paths = [f[0] for f in files]
131
+ result = await self._session.execute(
132
+ select(BranchFile).where(
133
+ BranchFile.branch_id == branch_id,
134
+ BranchFile.path.in_(paths),
135
+ )
136
+ )
137
+ existing_map = {bf.path: bf for bf in result.scalars()}
138
+
139
+ for path, content_sha, status in files:
140
+ if path in existing_map:
141
+ existing_map[path].content_sha = content_sha
142
+ existing_map[path].status = status
143
+ else:
144
+ bf = BranchFile(
145
+ branch_id=branch_id,
146
+ path=path,
147
+ content_sha=content_sha,
148
+ status=status,
149
+ )
150
+ self._session.add(bf)
151
+
152
+ await self._bump_version(branch_id)
153
+ await self._session.flush()
154
+
155
+ async def delete_file(self, branch_id: uuid.UUID, path: str) -> None:
156
+ """Mark a file as deleted in the branch overlay. Bumps branch version."""
157
+ from sqlalchemy import select
158
+
159
+ from attocode_intel.db.models import BranchFile
160
+
161
+ result = await self._session.execute(
162
+ select(BranchFile).where(
163
+ BranchFile.branch_id == branch_id,
164
+ BranchFile.path == path,
165
+ )
166
+ )
167
+ existing = result.scalar_one_or_none()
168
+ if existing:
169
+ existing.content_sha = None
170
+ existing.status = "deleted"
171
+ else:
172
+ bf = BranchFile(
173
+ branch_id=branch_id,
174
+ path=path,
175
+ content_sha=None,
176
+ status="deleted",
177
+ )
178
+ self._session.add(bf)
179
+
180
+ await self._bump_version(branch_id)
181
+ await self._session.flush()
182
+
183
+ async def resolve_file(
184
+ self,
185
+ branch_id: uuid.UUID,
186
+ path: str,
187
+ ) -> str | None:
188
+ """Resolve a single file through the overlay chain.
189
+
190
+ Returns the content_sha or None if file doesn't exist.
191
+ """
192
+ manifest = await self.resolve_manifest(branch_id)
193
+ return manifest.get(path)
194
+
195
+ async def resolve_manifest(
196
+ self,
197
+ branch_id: uuid.UUID,
198
+ ) -> dict[str, str]:
199
+ """Resolve the complete file manifest for a branch.
200
+
201
+ Walks the overlay chain from default → intermediate → target branch.
202
+ C3 fix: Uses visited set to detect and break cyclic parent_branch_id chains.
203
+ """
204
+ from sqlalchemy import select
205
+
206
+ from attocode_intel.db.models import Branch, BranchFile
207
+
208
+ # Build the chain from target to default
209
+ chain: list[uuid.UUID] = []
210
+ visited: set[uuid.UUID] = set()
211
+ current_id: uuid.UUID | None = branch_id
212
+
213
+ while current_id is not None:
214
+ if current_id in visited:
215
+ logger.warning(
216
+ "Cyclic parent_branch_id detected at %s in chain %s — breaking",
217
+ current_id, [str(b) for b in chain],
218
+ )
219
+ break
220
+ visited.add(current_id)
221
+ chain.append(current_id)
222
+ result = await self._session.execute(
223
+ select(Branch.parent_branch_id).where(Branch.id == current_id)
224
+ )
225
+ parent_id = result.scalar_one_or_none()
226
+ current_id = parent_id
227
+
228
+ # Reverse: default first, target last
229
+ chain.reverse()
230
+
231
+ # Build manifest by applying overlays in order
232
+ manifest: dict[str, str] = {}
233
+
234
+ for bid in chain:
235
+ result = await self._session.execute(
236
+ select(BranchFile).where(BranchFile.branch_id == bid)
237
+ )
238
+ for bf in result.scalars():
239
+ if bf.status == "deleted":
240
+ manifest.pop(bf.path, None)
241
+ elif bf.content_sha:
242
+ manifest[bf.path] = bf.content_sha
243
+
244
+ return manifest
245
+
246
+ async def diff_branches(
247
+ self,
248
+ branch_a_id: uuid.UUID,
249
+ branch_b_id: uuid.UUID,
250
+ ) -> dict[str, str]:
251
+ """Compare two branches. Returns dict of path → change_type.
252
+
253
+ change_type: added|modified|deleted
254
+ """
255
+ manifest_a = await self.resolve_manifest(branch_a_id)
256
+ manifest_b = await self.resolve_manifest(branch_b_id)
257
+
258
+ diff: dict[str, str] = {}
259
+ all_paths = set(manifest_a.keys()) | set(manifest_b.keys())
260
+
261
+ for path in all_paths:
262
+ sha_a = manifest_a.get(path)
263
+ sha_b = manifest_b.get(path)
264
+
265
+ if sha_a is None and sha_b is not None:
266
+ diff[path] = "added"
267
+ elif sha_a is not None and sha_b is None:
268
+ diff[path] = "deleted"
269
+ elif sha_a != sha_b:
270
+ diff[path] = "modified"
271
+
272
+ return diff
273
+
274
+ async def merge_branch(
275
+ self,
276
+ source_id: uuid.UUID,
277
+ target_id: uuid.UUID,
278
+ *,
279
+ delete_source: bool = False,
280
+ ) -> dict:
281
+ """Merge source branch overlay into target branch.
282
+
283
+ Resolves both manifests, computes the diff, and applies source changes
284
+ to the target overlay. Optionally deletes the source branch after merge.
285
+
286
+ Returns merge statistics.
287
+ """
288
+ from datetime import datetime, timezone
289
+
290
+ from sqlalchemy import select
291
+
292
+ from attocode_intel.db.models import Branch
293
+
294
+ diff = await self.diff_branches(target_id, source_id)
295
+
296
+ # Apply source changes to target overlay
297
+ files_to_set: list[tuple[str, str, str]] = []
298
+ source_manifest = await self.resolve_manifest(source_id)
299
+ deleted = 0
300
+
301
+ for path, change_type in diff.items():
302
+ if change_type == "added":
303
+ sha = source_manifest.get(path)
304
+ if sha:
305
+ files_to_set.append((path, sha, "added"))
306
+ elif change_type == "modified":
307
+ sha = source_manifest.get(path)
308
+ if sha:
309
+ files_to_set.append((path, sha, "modified"))
310
+ elif change_type == "deleted":
311
+ await self.delete_file(target_id, path)
312
+ deleted += 1
313
+
314
+ if files_to_set:
315
+ await self.set_files_batch(target_id, files_to_set)
316
+
317
+ stats = {
318
+ "added": sum(1 for _, _, s in files_to_set if s == "added"),
319
+ "modified": sum(1 for _, _, s in files_to_set if s == "modified"),
320
+ "deleted": deleted,
321
+ "total": len(diff),
322
+ }
323
+
324
+ if delete_source:
325
+ # Mark source as merged and delete
326
+ result = await self._session.execute(
327
+ select(Branch).where(Branch.id == source_id)
328
+ )
329
+ source_branch = result.scalar_one_or_none()
330
+ if source_branch:
331
+ source_branch.merged_at = datetime.now(timezone.utc)
332
+ await self._session.delete(source_branch)
333
+ await self._session.flush()
334
+
335
+ return stats
336
+
337
+ async def get_overlay_stats(self, branch_id: uuid.UUID) -> dict:
338
+ """Get statistics about a branch's overlay (files added/modified/deleted)."""
339
+ from sqlalchemy import func, select
340
+
341
+ from attocode_intel.db.models import BranchFile
342
+
343
+ result = await self._session.execute(
344
+ select(BranchFile.status, func.count())
345
+ .where(BranchFile.branch_id == branch_id)
346
+ .group_by(BranchFile.status)
347
+ )
348
+ stats = {"added": 0, "modified": 0, "deleted": 0, "total": 0}
349
+ for status, count in result:
350
+ stats[status] = count
351
+ stats["total"] += count
352
+ return stats
@@ -0,0 +1,220 @@
1
+ """Content-addressed file storage — SHA-256 keyed deduplication."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import logging
7
+ from typing import TYPE_CHECKING
8
+
9
+ if TYPE_CHECKING:
10
+ import uuid
11
+
12
+ from sqlalchemy.ext.asyncio import AsyncSession
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class ContentStore:
18
+ """SHA-256 keyed content storage with deduplication.
19
+
20
+ Files are stored once by content hash. Multiple branches can reference
21
+ the same content without duplication.
22
+ """
23
+
24
+ def __init__(self, session: AsyncSession) -> None:
25
+ self._session = session
26
+
27
+ @staticmethod
28
+ def hash_content(content: bytes) -> str:
29
+ """Compute SHA-256 hash of content."""
30
+ return hashlib.sha256(content).hexdigest()
31
+
32
+ async def store(
33
+ self,
34
+ content: bytes,
35
+ language: str | None = None,
36
+ content_type: str = "source",
37
+ ) -> str:
38
+ """Store content and return its SHA-256 hash. Idempotent (INSERT ON CONFLICT DO NOTHING)."""
39
+ from sqlalchemy.dialects.postgresql import insert
40
+
41
+ from attocode_intel.db.models import FileContent
42
+
43
+ sha = self.hash_content(content)
44
+
45
+ # C4 fix: atomic INSERT ON CONFLICT DO NOTHING — eliminates TOCTOU race
46
+ stmt = insert(FileContent).values(
47
+ sha256=sha,
48
+ content=content,
49
+ size_bytes=len(content),
50
+ language=language,
51
+ content_type=content_type,
52
+ ).on_conflict_do_nothing(index_elements=["sha256"])
53
+ await self._session.execute(stmt)
54
+ await self._session.flush()
55
+ return sha
56
+
57
+ async def store_batch(
58
+ self,
59
+ items: list[tuple[bytes, str | None]],
60
+ ) -> list[str]:
61
+ """Store multiple items efficiently. Returns list of SHA-256 hashes.
62
+
63
+ Deduplicates within batch and uses INSERT ON CONFLICT DO NOTHING.
64
+ """
65
+ from sqlalchemy.dialects.postgresql import insert
66
+
67
+ from attocode_intel.db.models import FileContent
68
+
69
+ # Pre-compute all hashes
70
+ entries = [(self.hash_content(content), content, lang) for content, lang in items]
71
+
72
+ # Deduplicate within the batch by SHA.
73
+ seen: dict[str, tuple[str, bytes, str | None]] = {}
74
+ hashes = []
75
+ for sha, content, language in entries:
76
+ hashes.append(sha)
77
+ if sha not in seen:
78
+ seen[sha] = (sha, content, language)
79
+
80
+ # Bulk existence check on deduplicated set
81
+ unique_shas = list(seen.keys())
82
+ existing = await self.batch_exists(unique_shas)
83
+
84
+ # Only insert new content using ON CONFLICT DO NOTHING
85
+ for sha, content, language in seen.values():
86
+ if sha in existing:
87
+ continue
88
+ stmt = insert(FileContent).values(
89
+ sha256=sha,
90
+ content=content,
91
+ size_bytes=len(content),
92
+ language=language,
93
+ ).on_conflict_do_nothing(index_elements=["sha256"])
94
+ await self._session.execute(stmt)
95
+
96
+ await self._session.flush()
97
+ return hashes
98
+
99
+ async def get(self, sha: str) -> bytes | None:
100
+ """Retrieve content by SHA-256 hash."""
101
+ from sqlalchemy import select
102
+
103
+ from attocode_intel.db.models import FileContent
104
+
105
+ result = await self._session.execute(
106
+ select(FileContent.content).where(FileContent.sha256 == sha)
107
+ )
108
+ row = result.scalar_one_or_none()
109
+ return row if row is not None else None
110
+
111
+ async def exists(self, sha: str) -> bool:
112
+ """Check if content exists by hash."""
113
+ from sqlalchemy import select
114
+
115
+ from attocode_intel.db.models import FileContent
116
+
117
+ result = await self._session.execute(
118
+ select(FileContent.sha256).where(FileContent.sha256 == sha)
119
+ )
120
+ return result.scalar_one_or_none() is not None
121
+
122
+ async def batch_exists(self, shas: list[str]) -> set[str]:
123
+ """Check existence of multiple hashes in one query. Returns set of existing SHAs."""
124
+ if not shas:
125
+ return set()
126
+
127
+ from sqlalchemy import select
128
+
129
+ from attocode_intel.db.models import FileContent
130
+
131
+ result = await self._session.execute(
132
+ select(FileContent.sha256).where(FileContent.sha256.in_(shas))
133
+ )
134
+ return {row[0] for row in result}
135
+
136
+ async def gc_unreferenced(
137
+ self,
138
+ min_age_minutes: int = 5,
139
+ *,
140
+ repo_id: uuid.UUID | None = None,
141
+ ) -> int:
142
+ """Delete file_contents not referenced by any branch_files, symbols, dependencies, or embeddings.
143
+
144
+ Only deletes content older than min_age_minutes to avoid racing
145
+ with concurrent indexers still writing references.
146
+
147
+ When ``repo_id`` is provided, scopes the delete to content
148
+ referenced *only* from branches of that repo. A blob still
149
+ referenced from a different repo's branches is never deleted.
150
+ ``repo_id=None`` runs a global sweep (background worker).
151
+
152
+ Returns count of deleted rows.
153
+ """
154
+ from sqlalchemy import text
155
+
156
+ if repo_id is None:
157
+ result = await self._session.execute(
158
+ text("""
159
+ DELETE FROM file_contents
160
+ WHERE created_at < NOW() - make_interval(mins => :age)
161
+ AND sha256 NOT IN (
162
+ SELECT DISTINCT content_sha FROM branch_files WHERE content_sha IS NOT NULL
163
+ UNION
164
+ SELECT DISTINCT content_sha FROM symbols
165
+ UNION
166
+ SELECT DISTINCT source_sha FROM dependencies
167
+ UNION
168
+ SELECT DISTINCT target_sha FROM dependencies
169
+ UNION
170
+ SELECT DISTINCT content_sha FROM embeddings
171
+ )
172
+ """).bindparams(age=min_age_minutes)
173
+ )
174
+ else:
175
+ # Scoped GC: delete content whose *only* live references live in
176
+ # branch_files of the target repo, AND which is no longer in any
177
+ # of the target repo's branches. We still require the content to
178
+ # be globally unreferenced from symbols/deps/embeddings because
179
+ # those are not repo-scoped; otherwise we'd break indexing
180
+ # caches for other repos that happened to share the same blob.
181
+ result = await self._session.execute(
182
+ text("""
183
+ DELETE FROM file_contents
184
+ WHERE created_at < NOW() - make_interval(mins => :age)
185
+ AND sha256 IN (
186
+ -- blobs currently or previously associated with this repo
187
+ SELECT DISTINCT bf.content_sha
188
+ FROM branch_files bf
189
+ JOIN branches b ON b.id = bf.branch_id
190
+ WHERE b.repo_id = :repo_id AND bf.content_sha IS NOT NULL
191
+ )
192
+ AND sha256 NOT IN (
193
+ -- still live in any branch (any repo)
194
+ SELECT DISTINCT bf.content_sha
195
+ FROM branch_files bf
196
+ WHERE bf.content_sha IS NOT NULL
197
+ UNION
198
+ SELECT DISTINCT content_sha FROM symbols
199
+ UNION
200
+ SELECT DISTINCT source_sha FROM dependencies
201
+ UNION
202
+ SELECT DISTINCT target_sha FROM dependencies
203
+ UNION
204
+ SELECT DISTINCT content_sha FROM embeddings
205
+ )
206
+ """).bindparams(age=min_age_minutes, repo_id=str(repo_id))
207
+ )
208
+ count = result.rowcount
209
+ if count:
210
+ if repo_id is None:
211
+ logger.info(
212
+ "GC: removed %d unreferenced file contents (older than %dm, global)",
213
+ count, min_age_minutes,
214
+ )
215
+ else:
216
+ logger.info(
217
+ "GC: removed %d unreferenced file contents (older than %dm, repo=%s)",
218
+ count, min_age_minutes, repo_id,
219
+ )
220
+ return count