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,466 @@
1
+ """Content-addressable cache for code-intel derived artifacts.
2
+
3
+ A single shared store (default ``~/.cache/attocode/cas/``) holds both raw
4
+ source blobs and derived artifacts (symbol sets, embeddings, dep graphs,
5
+ stack-graph partials, etc.) keyed by content hash. Cross-project dedup: if
6
+ two projects reference the same source file or the same (file, indexer,
7
+ version, config) tuple, they share the cache entry.
8
+
9
+ Design notes:
10
+ - On-disk layout:
11
+ <cas_root>/
12
+ blobs/<aa>/<bb>/<rest> # raw file contents (opt-in)
13
+ derived/<artifact_type>/<aa>/<bb>/<rest>
14
+ manifest.db # SQLite index: fast exists / gc
15
+ - Keys are hex digests (sha256 or git-style sha1) prefixed with algo
16
+ (``"sha256:..."``, ``"git:..."``). The prefix is stripped before the
17
+ two-level directory sharding.
18
+ - Reference counting is application-level: callers ``incref()`` on write
19
+ and ``decref()`` when a downstream store drops its reference. GC only
20
+ removes entries whose refcount is zero *and* whose last-accessed time
21
+ is older than the min_age threshold (default 7 days), so briefly
22
+ unreferenced entries aren't prematurely evicted.
23
+ - Directly operating on the filesystem keeps the store easy to inspect
24
+ with ``ls`` / ``sha256sum`` — no proprietary format.
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import logging
30
+ import os
31
+ import sqlite3
32
+ import threading
33
+ import time
34
+ from dataclasses import dataclass, field
35
+ from typing import TYPE_CHECKING, Any
36
+
37
+ from attocode_intel.artifacts import Provenance
38
+
39
+ if TYPE_CHECKING:
40
+ from collections.abc import Iterator
41
+
42
+ logger = logging.getLogger(__name__)
43
+
44
+ CAS_SCHEMA_VERSION = 1
45
+ # Default GC age floor — unreferenced entries younger than this are kept.
46
+ DEFAULT_GC_MIN_AGE_SECONDS = 7 * 24 * 3600 # 7 days
47
+
48
+
49
+ def _default_cas_root() -> str:
50
+ """XDG-style default CAS location, overridable via ``ATTOCODE_CAS_DIR``."""
51
+ env = os.environ.get("ATTOCODE_CAS_DIR")
52
+ if env:
53
+ return os.path.expanduser(env)
54
+ xdg_cache = os.environ.get("XDG_CACHE_HOME")
55
+ if xdg_cache:
56
+ return os.path.join(os.path.expanduser(xdg_cache), "attocode", "cas")
57
+ return os.path.expanduser("~/.cache/attocode/cas")
58
+
59
+
60
+ def _normalize_key(key: str) -> tuple[str, str]:
61
+ """Return (algo, hex) for a CAS key like ``sha256:abc...`` or ``git:def...``.
62
+
63
+ Raises ``ValueError`` if the key has no algo prefix — we want loud
64
+ failure rather than silently hashing in one format on writes and
65
+ looking it up in another format on reads.
66
+ """
67
+ if ":" not in key:
68
+ raise ValueError(
69
+ f"CAS key must be prefixed with an algo (e.g. 'sha256:'); got {key!r}"
70
+ )
71
+ algo, _, hexdigest = key.partition(":")
72
+ if not hexdigest:
73
+ raise ValueError(f"CAS key is empty after algo prefix: {key!r}")
74
+ if algo not in {"sha256", "git"}:
75
+ raise ValueError(f"unsupported CAS algo {algo!r}; use 'sha256' or 'git'")
76
+ # Defensive: strip whitespace, lowercase.
77
+ return algo, hexdigest.strip().lower()
78
+
79
+
80
+ def _shard_path(root: str, subdir: str, key: str) -> str:
81
+ """Two-level sharding to avoid one giant directory."""
82
+ _, hexdigest = _normalize_key(key)
83
+ return os.path.join(root, subdir, hexdigest[:2], hexdigest[2:4], hexdigest[4:])
84
+
85
+
86
+ @dataclass(slots=True)
87
+ class CasEntry:
88
+ """One row from the CAS manifest, returned by :meth:`ContentAddressedCache.stat`."""
89
+
90
+ key: str
91
+ artifact_type: str
92
+ action_hash: str
93
+ size_bytes: int
94
+ created_at: float
95
+ last_accessed_at: float
96
+ refcount: int
97
+ provenance: Provenance | None
98
+
99
+
100
+ @dataclass(slots=True)
101
+ class ContentAddressedCache:
102
+ """Content-addressable cache for source blobs and derived artifacts.
103
+
104
+ Usage::
105
+
106
+ cas = ContentAddressedCache()
107
+ cas.put(
108
+ key="sha256:abc...",
109
+ data=symbol_set_bytes,
110
+ artifact_type="symbols",
111
+ provenance=Provenance.create(...),
112
+ )
113
+ if cas.exists("sha256:abc...", "symbols"):
114
+ data = cas.get("sha256:abc...", "symbols")
115
+ """
116
+
117
+ cas_root: str = field(default_factory=_default_cas_root)
118
+ _conn: sqlite3.Connection | None = field(default=None, repr=False)
119
+ _lock: threading.Lock = field(default_factory=threading.Lock, repr=False)
120
+
121
+ def __post_init__(self) -> None:
122
+ os.makedirs(self.cas_root, exist_ok=True)
123
+ os.makedirs(os.path.join(self.cas_root, "blobs"), exist_ok=True)
124
+ os.makedirs(os.path.join(self.cas_root, "derived"), exist_ok=True)
125
+ self._conn = sqlite3.connect(
126
+ os.path.join(self.cas_root, "manifest.db"),
127
+ check_same_thread=False,
128
+ )
129
+ self._conn.execute("PRAGMA journal_mode=WAL")
130
+ self._conn.execute("PRAGMA synchronous=NORMAL")
131
+ self._create_tables()
132
+
133
+ def _create_tables(self) -> None:
134
+ assert self._conn is not None
135
+ self._conn.executescript(
136
+ """
137
+ CREATE TABLE IF NOT EXISTS cas_meta (
138
+ key TEXT PRIMARY KEY,
139
+ value TEXT NOT NULL
140
+ );
141
+
142
+ CREATE TABLE IF NOT EXISTS cas_entries (
143
+ key TEXT NOT NULL,
144
+ artifact_type TEXT NOT NULL,
145
+ action_hash TEXT NOT NULL DEFAULT '',
146
+ size_bytes INTEGER NOT NULL,
147
+ created_at REAL NOT NULL,
148
+ last_accessed_at REAL NOT NULL,
149
+ refcount INTEGER NOT NULL DEFAULT 0,
150
+ provenance_json TEXT NOT NULL DEFAULT '{}',
151
+ PRIMARY KEY (key, artifact_type)
152
+ );
153
+
154
+ CREATE INDEX IF NOT EXISTS ix_cas_type ON cas_entries(artifact_type);
155
+ CREATE INDEX IF NOT EXISTS ix_cas_atime ON cas_entries(last_accessed_at);
156
+ CREATE INDEX IF NOT EXISTS ix_cas_action ON cas_entries(action_hash);
157
+ """
158
+ )
159
+ self._conn.execute(
160
+ "INSERT OR REPLACE INTO cas_meta (key, value) VALUES ('schema_version', ?)",
161
+ (str(CAS_SCHEMA_VERSION),),
162
+ )
163
+ self._conn.commit()
164
+
165
+ # ------------------------------------------------------------------
166
+ # Put / Get
167
+ # ------------------------------------------------------------------
168
+
169
+ def _path_for(self, key: str, artifact_type: str) -> str:
170
+ subdir = "blobs" if artifact_type == "blob" else f"derived/{artifact_type}"
171
+ return _shard_path(self.cas_root, subdir, key)
172
+
173
+ def put(
174
+ self,
175
+ key: str,
176
+ data: bytes,
177
+ *,
178
+ artifact_type: str,
179
+ provenance: Provenance | None = None,
180
+ action_hash: str = "",
181
+ ) -> str:
182
+ """Store ``data`` under ``key`` + ``artifact_type``.
183
+
184
+ Returns the on-disk path of the stored file. Idempotent: re-putting
185
+ the same key overwrites the file (should produce identical bytes
186
+ anyway) and refreshes ``last_accessed_at``.
187
+ """
188
+ path = self._path_for(key, artifact_type)
189
+ os.makedirs(os.path.dirname(path), exist_ok=True)
190
+ tmp = path + ".tmp"
191
+ with open(tmp, "wb") as f:
192
+ f.write(data)
193
+ os.replace(tmp, path)
194
+
195
+ now = time.time()
196
+ provenance_json = provenance.to_json() if provenance is not None else "{}"
197
+ size = len(data)
198
+
199
+ assert self._conn is not None
200
+ with self._lock:
201
+ self._conn.execute(
202
+ """INSERT INTO cas_entries
203
+ (key, artifact_type, action_hash, size_bytes,
204
+ created_at, last_accessed_at, refcount, provenance_json)
205
+ VALUES (?, ?, ?, ?, ?, ?, 0, ?)
206
+ ON CONFLICT(key, artifact_type) DO UPDATE SET
207
+ size_bytes = excluded.size_bytes,
208
+ last_accessed_at = excluded.last_accessed_at,
209
+ action_hash = excluded.action_hash,
210
+ provenance_json = excluded.provenance_json
211
+ """,
212
+ (
213
+ key, artifact_type, action_hash, size,
214
+ now, now, provenance_json,
215
+ ),
216
+ )
217
+ self._conn.commit()
218
+ return path
219
+
220
+ def get(self, key: str, artifact_type: str) -> bytes | None:
221
+ """Return stored bytes, or None if the entry does not exist."""
222
+ path = self._path_for(key, artifact_type)
223
+ if not os.path.exists(path):
224
+ return None
225
+ with open(path, "rb") as f:
226
+ data = f.read()
227
+ # Refresh access time (best-effort; a failure here shouldn't break reads).
228
+ try:
229
+ self._touch(key, artifact_type)
230
+ except sqlite3.OperationalError:
231
+ pass
232
+ return data
233
+
234
+ def exists(self, key: str, artifact_type: str) -> bool:
235
+ """Cheap existence check: manifest.db + filesystem."""
236
+ assert self._conn is not None
237
+ with self._lock:
238
+ row = self._conn.execute(
239
+ "SELECT 1 FROM cas_entries WHERE key = ? AND artifact_type = ?",
240
+ (key, artifact_type),
241
+ ).fetchone()
242
+ if row is None:
243
+ return False
244
+ return os.path.exists(self._path_for(key, artifact_type))
245
+
246
+ def stat(self, key: str, artifact_type: str) -> CasEntry | None:
247
+ """Return metadata for one entry, or None."""
248
+ assert self._conn is not None
249
+ with self._lock:
250
+ row = self._conn.execute(
251
+ """SELECT key, artifact_type, action_hash, size_bytes, created_at,
252
+ last_accessed_at, refcount, provenance_json
253
+ FROM cas_entries WHERE key = ? AND artifact_type = ?""",
254
+ (key, artifact_type),
255
+ ).fetchone()
256
+ if row is None:
257
+ return None
258
+ provenance: Provenance | None
259
+ if row[7] and row[7] != "{}":
260
+ try:
261
+ import json
262
+ provenance = Provenance.from_dict(json.loads(row[7]))
263
+ except (ValueError, KeyError):
264
+ provenance = None
265
+ else:
266
+ provenance = None
267
+ return CasEntry(
268
+ key=row[0],
269
+ artifact_type=row[1],
270
+ action_hash=row[2],
271
+ size_bytes=row[3],
272
+ created_at=row[4],
273
+ last_accessed_at=row[5],
274
+ refcount=row[6],
275
+ provenance=provenance,
276
+ )
277
+
278
+ def _touch(self, key: str, artifact_type: str) -> None:
279
+ assert self._conn is not None
280
+ with self._lock:
281
+ self._conn.execute(
282
+ "UPDATE cas_entries SET last_accessed_at = ? "
283
+ "WHERE key = ? AND artifact_type = ?",
284
+ (time.time(), key, artifact_type),
285
+ )
286
+ self._conn.commit()
287
+
288
+ # ------------------------------------------------------------------
289
+ # Reference counting
290
+ # ------------------------------------------------------------------
291
+
292
+ def incref(self, key: str, artifact_type: str) -> int:
293
+ """Increment refcount; returns new value. Returns 0 if entry not found."""
294
+ assert self._conn is not None
295
+ with self._lock:
296
+ cur = self._conn.execute(
297
+ "UPDATE cas_entries SET refcount = refcount + 1 "
298
+ "WHERE key = ? AND artifact_type = ?",
299
+ (key, artifact_type),
300
+ )
301
+ self._conn.commit()
302
+ if cur.rowcount == 0:
303
+ return 0
304
+ row = self._conn.execute(
305
+ "SELECT refcount FROM cas_entries "
306
+ "WHERE key = ? AND artifact_type = ?",
307
+ (key, artifact_type),
308
+ ).fetchone()
309
+ return int(row[0]) if row else 0
310
+
311
+ def decref(self, key: str, artifact_type: str) -> int:
312
+ """Decrement refcount (not below zero); returns new value."""
313
+ assert self._conn is not None
314
+ with self._lock:
315
+ self._conn.execute(
316
+ "UPDATE cas_entries "
317
+ "SET refcount = CASE WHEN refcount > 0 THEN refcount - 1 ELSE 0 END "
318
+ "WHERE key = ? AND artifact_type = ?",
319
+ (key, artifact_type),
320
+ )
321
+ self._conn.commit()
322
+ row = self._conn.execute(
323
+ "SELECT refcount FROM cas_entries "
324
+ "WHERE key = ? AND artifact_type = ?",
325
+ (key, artifact_type),
326
+ ).fetchone()
327
+ return int(row[0]) if row else 0
328
+
329
+ # ------------------------------------------------------------------
330
+ # GC
331
+ # ------------------------------------------------------------------
332
+
333
+ def iter_orphans(
334
+ self, min_age_seconds: float = DEFAULT_GC_MIN_AGE_SECONDS,
335
+ ) -> Iterator[tuple[str, str, int]]:
336
+ """Yield (key, artifact_type, size_bytes) for entries with refcount=0
337
+ older than the age floor.
338
+ """
339
+ cutoff = time.time() - min_age_seconds
340
+ assert self._conn is not None
341
+ with self._lock:
342
+ rows = self._conn.execute(
343
+ """SELECT key, artifact_type, size_bytes FROM cas_entries
344
+ WHERE refcount <= 0 AND last_accessed_at <= ?
345
+ ORDER BY last_accessed_at""",
346
+ (cutoff,),
347
+ ).fetchall()
348
+ for row in rows:
349
+ yield row[0], row[1], int(row[2])
350
+
351
+ def gc(
352
+ self,
353
+ min_age_seconds: float = DEFAULT_GC_MIN_AGE_SECONDS,
354
+ dry_run: bool = True,
355
+ ) -> dict[str, Any]:
356
+ """Delete orphaned entries (refcount=0, older than age floor).
357
+
358
+ Returns a summary dict with ``{would_delete_count, would_free_bytes,
359
+ deleted_count, freed_bytes, dry_run, examples}``.
360
+
361
+ ``examples`` is a short list of entries (up to 10) so callers can
362
+ show a preview of what would (or did) get deleted.
363
+ """
364
+ would_count = 0
365
+ would_bytes = 0
366
+ examples: list[dict[str, Any]] = []
367
+
368
+ to_delete: list[tuple[str, str]] = []
369
+ for key, artifact_type, size in self.iter_orphans(min_age_seconds):
370
+ would_count += 1
371
+ would_bytes += size
372
+ if len(examples) < 10:
373
+ examples.append({
374
+ "key": key,
375
+ "artifact_type": artifact_type,
376
+ "size_bytes": size,
377
+ })
378
+ to_delete.append((key, artifact_type))
379
+
380
+ if dry_run:
381
+ return {
382
+ "dry_run": True,
383
+ "would_delete_count": would_count,
384
+ "would_free_bytes": would_bytes,
385
+ "deleted_count": 0,
386
+ "freed_bytes": 0,
387
+ "examples": examples,
388
+ }
389
+
390
+ deleted = 0
391
+ freed = 0
392
+ assert self._conn is not None
393
+ for key, artifact_type in to_delete:
394
+ path = self._path_for(key, artifact_type)
395
+ try:
396
+ size = os.path.getsize(path)
397
+ except OSError:
398
+ size = 0
399
+ try:
400
+ os.unlink(path)
401
+ except FileNotFoundError:
402
+ pass
403
+ except OSError as exc:
404
+ logger.warning("cas: failed to unlink %s: %s", path, exc)
405
+ continue
406
+ with self._lock:
407
+ self._conn.execute(
408
+ "DELETE FROM cas_entries WHERE key = ? AND artifact_type = ?",
409
+ (key, artifact_type),
410
+ )
411
+ self._conn.commit()
412
+ deleted += 1
413
+ freed += size
414
+
415
+ return {
416
+ "dry_run": False,
417
+ "would_delete_count": would_count,
418
+ "would_free_bytes": would_bytes,
419
+ "deleted_count": deleted,
420
+ "freed_bytes": freed,
421
+ "examples": examples,
422
+ }
423
+
424
+ # ------------------------------------------------------------------
425
+ # Stats / introspection
426
+ # ------------------------------------------------------------------
427
+
428
+ def stats(self) -> dict[str, Any]:
429
+ """Return summary stats for the CAS: per-type counts + total bytes."""
430
+ assert self._conn is not None
431
+ with self._lock:
432
+ rows = self._conn.execute(
433
+ """SELECT artifact_type, COUNT(*), SUM(size_bytes),
434
+ SUM(CASE WHEN refcount <= 0 THEN 1 ELSE 0 END)
435
+ FROM cas_entries GROUP BY artifact_type"""
436
+ ).fetchall()
437
+ by_type = {
438
+ row[0]: {
439
+ "count": int(row[1] or 0),
440
+ "bytes": int(row[2] or 0),
441
+ "orphans": int(row[3] or 0),
442
+ }
443
+ for row in rows
444
+ }
445
+ total = {
446
+ "count": sum(v["count"] for v in by_type.values()),
447
+ "bytes": sum(v["bytes"] for v in by_type.values()),
448
+ "orphans": sum(v["orphans"] for v in by_type.values()),
449
+ }
450
+ return {
451
+ "cas_root": self.cas_root,
452
+ "schema_version": CAS_SCHEMA_VERSION,
453
+ "by_type": by_type,
454
+ "total": total,
455
+ }
456
+
457
+ def close(self) -> None:
458
+ if self._conn:
459
+ self._conn.close()
460
+ self._conn = None
461
+
462
+ def __del__(self) -> None:
463
+ try:
464
+ self.close()
465
+ except Exception: # noqa: BLE001
466
+ pass