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,40 @@
1
+ """Add blame_hunks table for DB-backed blame data.
2
+
3
+ Revision ID: 014
4
+ Revises: 013
5
+ Create Date: 2026-03-15
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from typing import Sequence, Union
10
+
11
+ import sqlalchemy as sa
12
+ from alembic import op
13
+
14
+ revision: str = "014"
15
+ down_revision: Union[str, None] = "013"
16
+ branch_labels: Union[str, Sequence[str], None] = None
17
+ depends_on: Union[str, Sequence[str], None] = None
18
+
19
+
20
+ def upgrade() -> None:
21
+ op.create_table(
22
+ "blame_hunks",
23
+ sa.Column("id", sa.dialects.postgresql.UUID(as_uuid=True), primary_key=True),
24
+ sa.Column("repo_id", sa.dialects.postgresql.UUID(as_uuid=True), sa.ForeignKey("repositories.id", ondelete="CASCADE"), nullable=False),
25
+ sa.Column("path", sa.Text(), nullable=False),
26
+ sa.Column("branch_name", sa.Text(), nullable=False, server_default="main"),
27
+ sa.Column("commit_oid", sa.Text(), nullable=False),
28
+ sa.Column("author_name", sa.Text(), nullable=False),
29
+ sa.Column("author_email", sa.Text(), nullable=False),
30
+ sa.Column("timestamp", sa.BigInteger(), nullable=False),
31
+ sa.Column("start_line", sa.Integer(), nullable=False),
32
+ sa.Column("end_line", sa.Integer(), nullable=False),
33
+ sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
34
+ )
35
+ op.create_index("ix_blame_hunks_repo_path_branch", "blame_hunks", ["repo_id", "path", "branch_name"])
36
+
37
+
38
+ def downgrade() -> None:
39
+ op.drop_index("ix_blame_hunks_repo_path_branch", table_name="blame_hunks")
40
+ op.drop_table("blame_hunks")
@@ -0,0 +1,40 @@
1
+ """Add commit_file_stats table for per-file commit statistics.
2
+
3
+ Revision ID: 015
4
+ Revises: 014
5
+ Create Date: 2026-03-21
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from typing import Sequence, Union
10
+
11
+ import sqlalchemy as sa
12
+ from alembic import op
13
+
14
+ revision: str = "015"
15
+ down_revision: Union[str, None] = "014"
16
+ branch_labels: Union[str, Sequence[str], None] = None
17
+ depends_on: Union[str, Sequence[str], None] = None
18
+
19
+
20
+ def upgrade() -> None:
21
+ op.create_table(
22
+ "commit_file_stats",
23
+ sa.Column("id", sa.dialects.postgresql.UUID(as_uuid=True), primary_key=True),
24
+ sa.Column("commit_id", sa.dialects.postgresql.UUID(as_uuid=True), sa.ForeignKey("commits.id", ondelete="CASCADE"), nullable=False),
25
+ sa.Column("repo_id", sa.dialects.postgresql.UUID(as_uuid=True), sa.ForeignKey("repositories.id", ondelete="CASCADE"), nullable=False),
26
+ sa.Column("path", sa.Text(), nullable=False),
27
+ sa.Column("lines_added", sa.Integer(), nullable=False, server_default="0"),
28
+ sa.Column("lines_removed", sa.Integer(), nullable=False, server_default="0"),
29
+ sa.Column("symbols_changed", sa.dialects.postgresql.JSONB(), nullable=False, server_default="[]"),
30
+ sa.Column("change_type", sa.Text(), nullable=False),
31
+ sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
32
+ )
33
+ op.create_index("ix_commit_file_stats_repo_path", "commit_file_stats", ["repo_id", "path"])
34
+ op.create_index("ix_commit_file_stats_commit_id", "commit_file_stats", ["commit_id"])
35
+
36
+
37
+ def downgrade() -> None:
38
+ op.drop_index("ix_commit_file_stats_commit_id", table_name="commit_file_stats")
39
+ op.drop_index("ix_commit_file_stats_repo_path", table_name="commit_file_stats")
40
+ op.drop_table("commit_file_stats")
@@ -0,0 +1,179 @@
1
+ """Add provenance columns to embeddings (non-destructive).
2
+
3
+ This migration addresses the "embedding model/dimension silently wipes all
4
+ vectors" footgun by introducing explicit provenance columns — *without*
5
+ touching the primary ``vector`` column. Re-dimensioning is handled via a
6
+ separate rotation flow in a later phase; this migration is purely additive.
7
+
8
+ Changes:
9
+ - Add ``embeddings.embedding_model_version`` (text, default '').
10
+ - Add ``embeddings.embedding_dim`` (int, nullable — backfilled from
11
+ ``vector_dims(vector)`` for rows where pgvector is available).
12
+ - Add ``embeddings.embedding_provenance`` (JSONB, default '{}').
13
+ - Replace the ``(content_sha, embedding_model, chunk_type)`` unique with
14
+ ``(content_sha, embedding_model, embedding_model_version, chunk_type)``
15
+ so two versions of the same model can coexist during rotation.
16
+
17
+ Revision ID: 016
18
+ Revises: 015
19
+ Create Date: 2026-04-10
20
+ """
21
+ from __future__ import annotations
22
+
23
+ from typing import TYPE_CHECKING
24
+
25
+ import sqlalchemy as sa
26
+ from alembic import op
27
+
28
+ if TYPE_CHECKING:
29
+ from collections.abc import Sequence
30
+
31
+ revision: str = "016"
32
+ down_revision: str | None = "015"
33
+ branch_labels: str | Sequence[str] | None = None
34
+ depends_on: str | Sequence[str] | None = None
35
+
36
+
37
+ def upgrade() -> None:
38
+ # 1. Add new columns with safe defaults so existing rows don't break.
39
+ op.add_column(
40
+ "embeddings",
41
+ sa.Column(
42
+ "embedding_model_version",
43
+ sa.Text(),
44
+ nullable=False,
45
+ server_default="",
46
+ ),
47
+ )
48
+ op.add_column(
49
+ "embeddings",
50
+ sa.Column(
51
+ "embedding_dim",
52
+ sa.Integer(),
53
+ nullable=True,
54
+ ),
55
+ )
56
+ op.add_column(
57
+ "embeddings",
58
+ sa.Column(
59
+ "embedding_provenance",
60
+ sa.dialects.postgresql.JSONB(),
61
+ nullable=False,
62
+ server_default="{}",
63
+ ),
64
+ )
65
+
66
+ # 2. Backfill embedding_dim from pgvector's vector_dims() where possible.
67
+ # Rows without a vector (vector IS NULL) stay NULL.
68
+ #
69
+ # Done in a DO block so a connection lacking the `vector` extension
70
+ # or the `vector` column still lets the migration run on brand-new
71
+ # databases where ensure_vector_column() hasn't fired yet.
72
+ op.execute(
73
+ """
74
+ DO $$
75
+ BEGIN
76
+ IF EXISTS (
77
+ SELECT 1 FROM information_schema.columns
78
+ WHERE table_name = 'embeddings' AND column_name = 'vector'
79
+ ) THEN
80
+ EXECUTE 'UPDATE embeddings SET embedding_dim = vector_dims(vector) '
81
+ 'WHERE vector IS NOT NULL';
82
+ END IF;
83
+ END $$;
84
+ """
85
+ )
86
+
87
+ # 3. Replace the unique constraint to include embedding_model_version.
88
+ # During rotation two versions of the same model coexist in place.
89
+ op.drop_constraint(
90
+ "uq_embedding_content_model_chunk",
91
+ "embeddings",
92
+ type_="unique",
93
+ )
94
+ op.create_unique_constraint(
95
+ "uq_embedding_content_model_version_chunk",
96
+ "embeddings",
97
+ ["content_sha", "embedding_model", "embedding_model_version", "chunk_type"],
98
+ )
99
+
100
+ # 4. Helpful secondary index for provenance lookups.
101
+ op.create_index(
102
+ "ix_embeddings_model_version",
103
+ "embeddings",
104
+ ["embedding_model", "embedding_model_version"],
105
+ )
106
+
107
+
108
+ def downgrade() -> None:
109
+ """Revert the non-destructive dual-column rewrite.
110
+
111
+ A naive downgrade (drop the wide unique key, recreate the narrow
112
+ one) fails outright when a dual-version rotation has left multiple
113
+ ``embedding_model_version`` values for the same
114
+ ``(content_sha, embedding_model, chunk_type)`` tuple — recreating
115
+ the narrow unique constraint raises at DDL time.
116
+
117
+ Safe default: REFUSE the downgrade unless the operator sets
118
+ ``ATTOCODE_ALLOW_DESTRUCTIVE_016_DOWNGRADE``. When set, delete every
119
+ row whose ``embedding_model_version`` is non-empty first (logging
120
+ the count) and then rewrite. Pre-rotation databases (every row has
121
+ ``embedding_model_version = ''``) downgrade cleanly without the
122
+ flag.
123
+ """
124
+ import logging
125
+ import os
126
+
127
+ logger = logging.getLogger("alembic.016")
128
+
129
+ bind = op.get_bind()
130
+ conflicts_row = bind.execute(
131
+ sa.text(
132
+ "SELECT COUNT(*) FROM embeddings "
133
+ "WHERE embedding_model_version IS NOT NULL "
134
+ " AND embedding_model_version != ''"
135
+ )
136
+ ).fetchone()
137
+ conflict_count = int(conflicts_row[0] or 0) if conflicts_row else 0
138
+
139
+ if conflict_count > 0:
140
+ allow = os.environ.get(
141
+ "ATTOCODE_ALLOW_DESTRUCTIVE_016_DOWNGRADE", ""
142
+ ).lower() in {"1", "true", "yes"}
143
+ if not allow:
144
+ raise RuntimeError(
145
+ f"Refusing to downgrade migration 016: {conflict_count} "
146
+ f"embedding row(s) carry a non-empty "
147
+ f"embedding_model_version, which would violate the "
148
+ f"recreated (content_sha, embedding_model, chunk_type) "
149
+ f"unique constraint. Set "
150
+ f"ATTOCODE_ALLOW_DESTRUCTIVE_016_DOWNGRADE=1 to confirm "
151
+ f"the destructive downgrade (this will DELETE those rows)."
152
+ )
153
+ logger.warning(
154
+ "016 downgrade: destructive flag set, deleting %d "
155
+ "embedding row(s) with non-empty embedding_model_version",
156
+ conflict_count,
157
+ )
158
+ bind.execute(
159
+ sa.text(
160
+ "DELETE FROM embeddings "
161
+ "WHERE embedding_model_version IS NOT NULL "
162
+ " AND embedding_model_version != ''"
163
+ )
164
+ )
165
+
166
+ op.drop_index("ix_embeddings_model_version", table_name="embeddings")
167
+ op.drop_constraint(
168
+ "uq_embedding_content_model_version_chunk",
169
+ "embeddings",
170
+ type_="unique",
171
+ )
172
+ op.create_unique_constraint(
173
+ "uq_embedding_content_model_chunk",
174
+ "embeddings",
175
+ ["content_sha", "embedding_model", "chunk_type"],
176
+ )
177
+ op.drop_column("embeddings", "embedding_provenance")
178
+ op.drop_column("embeddings", "embedding_dim")
179
+ op.drop_column("embeddings", "embedding_model_version")
@@ -0,0 +1,102 @@
1
+ """Add provenance table — one row per derived code-intel artifact.
2
+
3
+ A ``Provenance`` row records how a symbol set / embedding / dep graph /
4
+ repo-map slice was produced: which indexer, which version, which blob was
5
+ the input, when, and by whom. Joined into derived-artifact queries by
6
+ ``action_hash``.
7
+
8
+ Per-row storage here rather than a JSONB column on every derived table
9
+ because:
10
+ - Multiple artifact types want the same shape (no duplication).
11
+ - Queries like "find everything produced by indexer X version Y" are
12
+ common for audit / GC and would be O(n_tables) with per-table JSONB.
13
+ - Provenance should outlive the derived row it describes (for audit).
14
+
15
+ Revision ID: 017
16
+ Revises: 016
17
+ Create Date: 2026-04-10
18
+ """
19
+ from __future__ import annotations
20
+
21
+ from typing import TYPE_CHECKING
22
+
23
+ import sqlalchemy as sa
24
+ from alembic import op
25
+
26
+ if TYPE_CHECKING:
27
+ from collections.abc import Sequence
28
+
29
+ revision: str = "017"
30
+ down_revision: str | None = "016"
31
+ branch_labels: str | Sequence[str] | None = None
32
+ depends_on: str | Sequence[str] | None = None
33
+
34
+
35
+ def upgrade() -> None:
36
+ op.create_table(
37
+ "provenance",
38
+ sa.Column(
39
+ "id",
40
+ sa.dialects.postgresql.UUID(as_uuid=True),
41
+ primary_key=True,
42
+ server_default=sa.text("gen_random_uuid()"),
43
+ ),
44
+ sa.Column("action_hash", sa.Text(), nullable=False),
45
+ sa.Column("artifact_type", sa.Text(), nullable=False),
46
+ sa.Column("input_blob_oid", sa.Text(), nullable=False),
47
+ sa.Column("input_tree_oid", sa.Text(), nullable=True),
48
+ sa.Column("indexer_name", sa.Text(), nullable=False),
49
+ sa.Column("indexer_version", sa.Text(), nullable=False),
50
+ sa.Column("config_digest", sa.Text(), nullable=False),
51
+ sa.Column(
52
+ "produced_at",
53
+ sa.DateTime(timezone=True),
54
+ server_default=sa.func.now(),
55
+ nullable=False,
56
+ ),
57
+ sa.Column(
58
+ "producer_service",
59
+ sa.Text(),
60
+ nullable=False,
61
+ server_default="attocode-server",
62
+ ),
63
+ sa.Column(
64
+ "producer_user_id",
65
+ sa.dialects.postgresql.UUID(as_uuid=True),
66
+ sa.ForeignKey("users.id", ondelete="SET NULL"),
67
+ nullable=True,
68
+ ),
69
+ sa.Column(
70
+ "producer_job_id",
71
+ sa.dialects.postgresql.UUID(as_uuid=True),
72
+ sa.ForeignKey("indexing_jobs.id", ondelete="SET NULL"),
73
+ nullable=True,
74
+ ),
75
+ sa.Column("producer_host", sa.Text(), nullable=False, server_default=""),
76
+ sa.Column(
77
+ "extra",
78
+ sa.dialects.postgresql.JSONB(),
79
+ nullable=False,
80
+ server_default="{}",
81
+ ),
82
+ )
83
+ # action_hash is not quite unique: if the same artifact is re-produced
84
+ # (idempotent indexer rerun with identical inputs) we still want a new
85
+ # provenance row for audit purposes. So no UNIQUE, but an index for fast
86
+ # dedup joins.
87
+ op.create_index("ix_provenance_action_hash", "provenance", ["action_hash"])
88
+ op.create_index("ix_provenance_input_blob", "provenance", ["input_blob_oid"])
89
+ op.create_index("ix_provenance_job", "provenance", ["producer_job_id"])
90
+ op.create_index(
91
+ "ix_provenance_indexer_version",
92
+ "provenance",
93
+ ["indexer_name", "indexer_version"],
94
+ )
95
+
96
+
97
+ def downgrade() -> None:
98
+ op.drop_index("ix_provenance_indexer_version", table_name="provenance")
99
+ op.drop_index("ix_provenance_job", table_name="provenance")
100
+ op.drop_index("ix_provenance_input_blob", table_name="provenance")
101
+ op.drop_index("ix_provenance_action_hash", table_name="provenance")
102
+ op.drop_table("provenance")
@@ -0,0 +1,178 @@
1
+ """Add repo_snapshots + repo_snapshot_components tables.
2
+
3
+ A ``repo_snapshot`` row records a point-in-time manifest hash of a repo's
4
+ code-intel state (branch manifest + derived artifacts). Components
5
+ (symbols / embeddings / deps / content slice) are stored in a sibling
6
+ table so downstream tools can walk the component list, fetch individual
7
+ blobs from ``file_contents`` / related stores, and reassemble the
8
+ snapshot without unpacking a monolithic tarball.
9
+
10
+ The shape of this table is forward-compatible with Phase 3b's OCI
11
+ adapter: every row's ``manifest_hash`` matches the SHA-256 of a canonical
12
+ JSON manifest that maps 1:1 to an OCI image manifest (``config`` +
13
+ ``layers`` with media types) without schema changes.
14
+
15
+ Revision ID: 018
16
+ Revises: 017
17
+ Create Date: 2026-04-10
18
+ """
19
+ from __future__ import annotations
20
+
21
+ from typing import TYPE_CHECKING
22
+
23
+ import sqlalchemy as sa
24
+ from alembic import op
25
+
26
+ if TYPE_CHECKING:
27
+ from collections.abc import Sequence
28
+
29
+ revision: str = "018"
30
+ down_revision: str | None = "017"
31
+ branch_labels: str | Sequence[str] | None = None
32
+ depends_on: str | Sequence[str] | None = None
33
+
34
+
35
+ def upgrade() -> None:
36
+ op.create_table(
37
+ "repo_snapshots",
38
+ sa.Column(
39
+ "id",
40
+ sa.dialects.postgresql.UUID(as_uuid=True),
41
+ primary_key=True,
42
+ server_default=sa.text("gen_random_uuid()"),
43
+ ),
44
+ sa.Column(
45
+ "org_id",
46
+ sa.dialects.postgresql.UUID(as_uuid=True),
47
+ sa.ForeignKey("organizations.id", ondelete="CASCADE"),
48
+ nullable=False,
49
+ ),
50
+ sa.Column(
51
+ "repo_id",
52
+ sa.dialects.postgresql.UUID(as_uuid=True),
53
+ sa.ForeignKey("repositories.id", ondelete="CASCADE"),
54
+ nullable=False,
55
+ ),
56
+ sa.Column(
57
+ "branch_id",
58
+ sa.dialects.postgresql.UUID(as_uuid=True),
59
+ sa.ForeignKey("branches.id", ondelete="SET NULL"),
60
+ nullable=True,
61
+ ),
62
+ sa.Column("name", sa.Text(), nullable=False),
63
+ sa.Column("description", sa.Text(), nullable=False, server_default=""),
64
+ sa.Column("manifest_hash", sa.Text(), nullable=False),
65
+ # Total bytes across every component, for at-a-glance sizing in the
66
+ # list endpoint (no need to scan component rows on every list call).
67
+ sa.Column(
68
+ "total_bytes",
69
+ sa.BigInteger(),
70
+ nullable=False,
71
+ server_default="0",
72
+ ),
73
+ sa.Column(
74
+ "component_count",
75
+ sa.Integer(),
76
+ nullable=False,
77
+ server_default="0",
78
+ ),
79
+ # Commit pinning — stored as a plain string so even repos without a
80
+ # commits table entry (e.g. local_path only) can still be pinned.
81
+ sa.Column("commit_oid", sa.Text(), nullable=True),
82
+ sa.Column(
83
+ "created_at",
84
+ sa.DateTime(timezone=True),
85
+ server_default=sa.func.now(),
86
+ nullable=False,
87
+ ),
88
+ sa.Column(
89
+ "created_by_user_id",
90
+ sa.dialects.postgresql.UUID(as_uuid=True),
91
+ sa.ForeignKey("users.id", ondelete="SET NULL"),
92
+ nullable=True,
93
+ ),
94
+ sa.Column(
95
+ "extra",
96
+ sa.dialects.postgresql.JSONB(),
97
+ nullable=False,
98
+ server_default="{}",
99
+ ),
100
+ )
101
+ # Unique per (repo_id, name) so you can't shadow an existing named
102
+ # snapshot — forces the user to delete + recreate.
103
+ op.create_unique_constraint(
104
+ "uq_repo_snapshots_repo_name",
105
+ "repo_snapshots",
106
+ ["repo_id", "name"],
107
+ )
108
+ op.create_index(
109
+ "ix_repo_snapshots_repo_created",
110
+ "repo_snapshots",
111
+ ["repo_id", "created_at"],
112
+ )
113
+ op.create_index(
114
+ "ix_repo_snapshots_org",
115
+ "repo_snapshots",
116
+ ["org_id"],
117
+ )
118
+
119
+ op.create_table(
120
+ "repo_snapshot_components",
121
+ sa.Column(
122
+ "id",
123
+ sa.dialects.postgresql.UUID(as_uuid=True),
124
+ primary_key=True,
125
+ server_default=sa.text("gen_random_uuid()"),
126
+ ),
127
+ sa.Column(
128
+ "snapshot_id",
129
+ sa.dialects.postgresql.UUID(as_uuid=True),
130
+ sa.ForeignKey("repo_snapshots.id", ondelete="CASCADE"),
131
+ nullable=False,
132
+ ),
133
+ sa.Column("name", sa.Text(), nullable=False),
134
+ sa.Column("media_type", sa.Text(), nullable=False),
135
+ # Each component is content-addressed by a SHA-256 digest (in the
136
+ # OCI ``sha256:<hex>`` form). For symbol_set_v1 / embedding_blob_v1
137
+ # components this lines up with the content_store action hash.
138
+ sa.Column("digest", sa.Text(), nullable=False),
139
+ sa.Column("size_bytes", sa.BigInteger(), nullable=False, server_default="0"),
140
+ sa.Column(
141
+ "extra",
142
+ sa.dialects.postgresql.JSONB(),
143
+ nullable=False,
144
+ server_default="{}",
145
+ ),
146
+ )
147
+ op.create_index(
148
+ "ix_repo_snapshot_components_snapshot",
149
+ "repo_snapshot_components",
150
+ ["snapshot_id"],
151
+ )
152
+ op.create_index(
153
+ "ix_repo_snapshot_components_digest",
154
+ "repo_snapshot_components",
155
+ ["digest"],
156
+ )
157
+
158
+
159
+ def downgrade() -> None:
160
+ op.drop_index(
161
+ "ix_repo_snapshot_components_digest",
162
+ table_name="repo_snapshot_components",
163
+ )
164
+ op.drop_index(
165
+ "ix_repo_snapshot_components_snapshot",
166
+ table_name="repo_snapshot_components",
167
+ )
168
+ op.drop_table("repo_snapshot_components")
169
+ op.drop_index("ix_repo_snapshots_org", table_name="repo_snapshots")
170
+ op.drop_index(
171
+ "ix_repo_snapshots_repo_created", table_name="repo_snapshots",
172
+ )
173
+ op.drop_constraint(
174
+ "uq_repo_snapshots_repo_name",
175
+ "repo_snapshots",
176
+ type_="unique",
177
+ )
178
+ op.drop_table("repo_snapshots")
@@ -0,0 +1,42 @@
1
+ """Add caller_qualified_name to symbol_references for call-graph attribution.
2
+
3
+ Pairs with the function-level call graph in CrossRefIndex (Phase 1, A1).
4
+ Without this column, the full-indexer pipeline persists references but
5
+ loses the enclosing function/method, which makes call_graph queries over
6
+ HTTP-indexed repos return empty edges.
7
+
8
+ Revision ID: 019
9
+ Revises: 018
10
+ Create Date: 2026-05-03
11
+ """
12
+ from __future__ import annotations
13
+
14
+ from typing import Sequence, Union
15
+
16
+ import sqlalchemy as sa
17
+ from alembic import op
18
+
19
+ revision: str = "019"
20
+ down_revision: Union[str, None] = "018"
21
+ branch_labels: Union[str, Sequence[str], None] = None
22
+ depends_on: Union[str, Sequence[str], None] = None
23
+
24
+
25
+ def upgrade() -> None:
26
+ op.add_column(
27
+ "symbol_references",
28
+ sa.Column(
29
+ "caller_qualified_name",
30
+ sa.String(500),
31
+ nullable=False,
32
+ server_default="",
33
+ ),
34
+ )
35
+ op.create_index(
36
+ "ix_symref_caller", "symbol_references", ["caller_qualified_name"],
37
+ )
38
+
39
+
40
+ def downgrade() -> None:
41
+ op.drop_index("ix_symref_caller", "symbol_references")
42
+ op.drop_column("symbol_references", "caller_qualified_name")
@@ -0,0 +1,29 @@
1
+ """Durable repository knowledge shared across clients and committed revisions."""
2
+ from alembic import op
3
+ import sqlalchemy as sa
4
+
5
+ revision = "020"
6
+ down_revision = "019"
7
+ branch_labels = None
8
+ depends_on = None
9
+
10
+
11
+ def upgrade():
12
+ op.create_table("knowledge_entries",
13
+ sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True),
14
+ sa.Column("repo_id", sa.Uuid(), sa.ForeignKey("repositories.id", ondelete="CASCADE"), nullable=False),
15
+ sa.Column("author_id", sa.String(64), nullable=False),
16
+ sa.Column("kind", sa.String(16), nullable=False),
17
+ sa.Column("status", sa.String(16), nullable=False),
18
+ sa.Column("scope", sa.Text(), nullable=False),
19
+ sa.Column("payload", sa.JSON(), nullable=False),
20
+ sa.Column("revision", sa.String(64), nullable=False),
21
+ sa.Column("anchor_hash", sa.String(64), nullable=False),
22
+ sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
23
+ sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
24
+ )
25
+ op.create_index("ix_knowledge_entries_repo_id", "knowledge_entries", ["repo_id"])
26
+
27
+
28
+ def downgrade():
29
+ op.drop_table("knowledge_entries")