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,586 @@
1
+ """SQLite-backed persistent index for symbols, references, and dependencies.
2
+
3
+ Stores parsed AST data so that ASTService can skip re-parsing unchanged
4
+ files on restart. Modeled after ``VectorStore`` (WAL mode, mtime tracking).
5
+
6
+ Database location: ``.attocode/index/symbols.db``
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import logging
12
+ import os
13
+ import sqlite3
14
+ import threading
15
+ import time
16
+ from dataclasses import dataclass, field
17
+ from typing import Any
18
+
19
+ from filelock import FileLock
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+ SCHEMA_VERSION = "6"
24
+ SQLITE_BUSY_TIMEOUT_MS = 30_000
25
+ INDEX_INIT_LOCK_TIMEOUT_SECONDS = 30
26
+
27
+
28
+ class IndexSchemaVersionError(RuntimeError):
29
+ """The persisted structural index cannot be opened by this client version."""
30
+
31
+
32
+ @dataclass(slots=True)
33
+ class StoredFile:
34
+ """Cached file metadata."""
35
+
36
+ path: str
37
+ mtime: float
38
+ size: int
39
+ language: str
40
+ line_count: int
41
+ content_hash: str
42
+
43
+
44
+ @dataclass(slots=True)
45
+ class StoredSymbol:
46
+ """A persisted symbol definition."""
47
+
48
+ id: int
49
+ file_path: str
50
+ name: str
51
+ qualified_name: str
52
+ kind: str
53
+ line: int
54
+ end_line: int
55
+ source: str # "tree-sitter" | "lsp"
56
+
57
+
58
+ @dataclass(slots=True)
59
+ class StoredReference:
60
+ """A persisted symbol reference."""
61
+
62
+ id: int
63
+ file_path: str
64
+ symbol_name: str
65
+ ref_kind: str
66
+ line: int
67
+ column: int
68
+ source: str # "tree-sitter" | "lsp"
69
+ syntax_name: str = "" # identifier/member expression as written in source
70
+ caller_qualified_name: str = "" # enclosing function/method (call-graph edge)
71
+
72
+
73
+ @dataclass(slots=True)
74
+ class IndexStore:
75
+ """SQLite-backed persistent index store.
76
+
77
+ Usage::
78
+
79
+ store = IndexStore(db_path=".attocode/index/symbols.db")
80
+ store.save_file(StoredFile(...))
81
+ store.save_symbols("src/main.py", [StoredSymbol(...)])
82
+ stale = store.get_stale_files({"src/main.py": 1234567.0})
83
+ """
84
+
85
+ db_path: str
86
+ _conn: sqlite3.Connection | None = field(default=None, repr=False)
87
+ _lock: threading.Lock = field(default_factory=threading.Lock, repr=False)
88
+ # When the on-disk schema differed from ``SCHEMA_VERSION`` at startup,
89
+ # we wipe and rebuild — this records the prior version so callers
90
+ # (e.g. ``readiness_report``) can surface a "schema migrated, full
91
+ # re-index pending" hint instead of leaving users guessing why the
92
+ # first request after upgrade is slow.
93
+ _schema_rebuilt_from: str | None = field(default=None, repr=False)
94
+
95
+ def __post_init__(self) -> None:
96
+ os.makedirs(os.path.dirname(self.db_path) or ".", exist_ok=True)
97
+ try:
98
+ if self.db_path == ":memory:":
99
+ self._initialize_connection()
100
+ else:
101
+ # Multiple MCP clients commonly share one workspace. Serialize
102
+ # schema inspection/rebuild so simultaneous restarts do not race
103
+ # over DDL while hydration is opening the same SQLite store.
104
+ with FileLock(f"{self.db_path}.init.lock", timeout=INDEX_INIT_LOCK_TIMEOUT_SECONDS):
105
+ self._initialize_connection()
106
+ except Exception:
107
+ if self._conn is not None:
108
+ self._conn.close()
109
+ self._conn = None
110
+ raise
111
+
112
+ def _initialize_connection(self) -> None:
113
+ self._conn = sqlite3.connect(
114
+ self.db_path,
115
+ check_same_thread=False,
116
+ timeout=SQLITE_BUSY_TIMEOUT_MS / 1000,
117
+ )
118
+ self._conn.execute(f"PRAGMA busy_timeout={SQLITE_BUSY_TIMEOUT_MS}")
119
+ self._conn.execute("PRAGMA journal_mode=WAL")
120
+ self._conn.execute("PRAGMA synchronous=NORMAL")
121
+ self._conn.execute("PRAGMA foreign_keys=ON")
122
+ self._migrate_stale_schema()
123
+ self._create_tables()
124
+ self._check_schema_version()
125
+
126
+ @staticmethod
127
+ def _compare_schema_version(stored: str) -> int:
128
+ try:
129
+ stored_version = int(stored)
130
+ current_version = int(SCHEMA_VERSION)
131
+ except ValueError as error:
132
+ raise IndexSchemaVersionError(
133
+ f"Unrecognized structural index schema {stored!r}; "
134
+ "upgrade Attocode or rebuild the workspace index."
135
+ ) from error
136
+ return (stored_version > current_version) - (stored_version < current_version)
137
+
138
+ def _get_conn(self) -> sqlite3.Connection:
139
+ if self._conn is None:
140
+ raise RuntimeError("IndexStore connection is closed")
141
+ return self._conn
142
+
143
+ def _migrate_stale_schema(self) -> None:
144
+ """Drop stale data tables BEFORE (re)creating them when the on-disk
145
+ schema predates the current version.
146
+
147
+ ``_create_tables`` builds the v3 indexes (e.g. ``ix_refs_caller`` on
148
+ ``refs(caller_qualified_name)``). On a pre-v3 DB whose ``refs`` table
149
+ lacks that column, creating the index raises ``OperationalError`` and
150
+ the store can't even open — so the wipe in ``_check_schema_version``
151
+ never runs. Dropping the stale tables here keeps the upgrade path
152
+ working; data is rebuilt from source on the next index. Children are
153
+ dropped before ``files`` to respect FK ordering.
154
+ """
155
+ conn = self._get_conn()
156
+ try:
157
+ row = conn.execute(
158
+ "SELECT value FROM metadata WHERE key = 'schema_version'"
159
+ ).fetchone()
160
+ except sqlite3.OperationalError:
161
+ return # no metadata table => brand-new DB, nothing to migrate
162
+ if not row or row[0] == SCHEMA_VERSION:
163
+ return
164
+ if self._compare_schema_version(row[0]) > 0:
165
+ raise IndexSchemaVersionError(
166
+ f"Structural index schema {row[0]} is newer than this client's "
167
+ f"schema {SCHEMA_VERSION}; restart using the upgraded Attocode executable."
168
+ )
169
+ logger.warning(
170
+ "Index schema is stale (%s, expected %s); dropping data tables in "
171
+ "%r so the new schema can be created — a full re-index will run on "
172
+ "next access.",
173
+ row[0], SCHEMA_VERSION, self.db_path,
174
+ )
175
+ conn.executescript(
176
+ "DROP TABLE IF EXISTS refs;"
177
+ "DROP TABLE IF EXISTS symbols;"
178
+ "DROP TABLE IF EXISTS dependencies;"
179
+ "DROP TABLE IF EXISTS files;"
180
+ )
181
+ conn.commit()
182
+
183
+ def _create_tables(self) -> None:
184
+ conn = self._get_conn()
185
+ conn.executescript("""
186
+ CREATE TABLE IF NOT EXISTS metadata (
187
+ key TEXT PRIMARY KEY,
188
+ value TEXT NOT NULL
189
+ );
190
+
191
+ CREATE TABLE IF NOT EXISTS files (
192
+ path TEXT PRIMARY KEY,
193
+ mtime REAL NOT NULL,
194
+ size INTEGER NOT NULL,
195
+ language TEXT NOT NULL DEFAULT '',
196
+ line_count INTEGER NOT NULL DEFAULT 0,
197
+ content_hash TEXT NOT NULL DEFAULT ''
198
+ );
199
+
200
+ CREATE TABLE IF NOT EXISTS symbols (
201
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
202
+ file_path TEXT NOT NULL REFERENCES files(path) ON DELETE CASCADE,
203
+ name TEXT NOT NULL,
204
+ qualified_name TEXT NOT NULL,
205
+ kind TEXT NOT NULL,
206
+ line INTEGER NOT NULL DEFAULT 0,
207
+ end_line INTEGER NOT NULL DEFAULT 0,
208
+ source TEXT NOT NULL DEFAULT 'tree-sitter'
209
+ );
210
+
211
+ CREATE TABLE IF NOT EXISTS refs (
212
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
213
+ file_path TEXT NOT NULL REFERENCES files(path) ON DELETE CASCADE,
214
+ symbol_name TEXT NOT NULL,
215
+ ref_kind TEXT NOT NULL DEFAULT 'call',
216
+ line INTEGER NOT NULL DEFAULT 0,
217
+ col INTEGER NOT NULL DEFAULT 0,
218
+ source TEXT NOT NULL DEFAULT 'tree-sitter',
219
+ syntax_name TEXT NOT NULL DEFAULT '',
220
+ caller_qualified_name TEXT NOT NULL DEFAULT ''
221
+ );
222
+
223
+ CREATE TABLE IF NOT EXISTS dependencies (
224
+ source_path TEXT NOT NULL,
225
+ target_path TEXT NOT NULL,
226
+ PRIMARY KEY (source_path, target_path)
227
+ );
228
+
229
+ CREATE INDEX IF NOT EXISTS ix_symbols_name ON symbols(name);
230
+ CREATE INDEX IF NOT EXISTS ix_symbols_qname ON symbols(qualified_name);
231
+ CREATE INDEX IF NOT EXISTS ix_symbols_file ON symbols(file_path);
232
+ CREATE INDEX IF NOT EXISTS ix_refs_symbol ON refs(symbol_name);
233
+ CREATE INDEX IF NOT EXISTS ix_refs_file ON refs(file_path);
234
+ CREATE INDEX IF NOT EXISTS ix_refs_caller ON refs(caller_qualified_name);
235
+ CREATE INDEX IF NOT EXISTS ix_deps_target ON dependencies(target_path);
236
+ """)
237
+ conn.commit()
238
+
239
+ def _check_schema_version(self) -> None:
240
+ conn = self._get_conn()
241
+ row = conn.execute(
242
+ "SELECT value FROM metadata WHERE key = 'schema_version'"
243
+ ).fetchone()
244
+ if row:
245
+ if row[0] != SCHEMA_VERSION:
246
+ if self._compare_schema_version(row[0]) > 0:
247
+ raise IndexSchemaVersionError(
248
+ f"Structural index schema {row[0]} is newer than this client's "
249
+ f"schema {SCHEMA_VERSION}; restart using the upgraded Attocode executable."
250
+ )
251
+ # Schema migrations clear all persisted index data — the
252
+ # first request after upgrade will trigger a full
253
+ # re-index that may take a while on big repos. We log
254
+ # this prominently and stash the prior version on the
255
+ # store instance + in the SQLite metadata table so a
256
+ # future readiness probe can attribute a slow first
257
+ # request to the rebuild rather than to indexing bugs.
258
+ logger.warning(
259
+ "Index schema changed (%s -> %s); clearing %r and "
260
+ "rebuilding from source on next access — full "
261
+ "re-index will run on demand. Subsequent requests "
262
+ "may be slow until indexing catches up.",
263
+ row[0], SCHEMA_VERSION, self.db_path,
264
+ )
265
+ self.clear_all()
266
+ conn.execute(
267
+ "INSERT OR REPLACE INTO metadata (key, value) VALUES ('schema_version', ?)",
268
+ (SCHEMA_VERSION,),
269
+ )
270
+ conn.execute(
271
+ "INSERT OR REPLACE INTO metadata (key, value) "
272
+ "VALUES ('schema_rebuilt_from_version', ?)",
273
+ (row[0],),
274
+ )
275
+ conn.commit()
276
+ self._schema_rebuilt_from = row[0]
277
+ else:
278
+ conn.execute(
279
+ "INSERT OR REPLACE INTO metadata (key, value) VALUES ('schema_version', ?)",
280
+ (SCHEMA_VERSION,),
281
+ )
282
+ conn.commit()
283
+
284
+ # ------------------------------------------------------------------
285
+ # Metadata
286
+ # ------------------------------------------------------------------
287
+
288
+ def get_meta(self, key: str) -> str | None:
289
+ conn = self._get_conn()
290
+ with self._lock:
291
+ row = conn.execute(
292
+ "SELECT value FROM metadata WHERE key = ?", (key,)
293
+ ).fetchone()
294
+ return row[0] if row else None
295
+
296
+ def set_meta(self, key: str, value: str) -> None:
297
+ conn = self._get_conn()
298
+ with self._lock:
299
+ conn.execute(
300
+ "INSERT OR REPLACE INTO metadata (key, value) VALUES (?, ?)",
301
+ (key, value),
302
+ )
303
+ conn.commit()
304
+
305
+ # ------------------------------------------------------------------
306
+ # Files
307
+ # ------------------------------------------------------------------
308
+
309
+ def save_file(self, f: StoredFile) -> None:
310
+ conn = self._get_conn()
311
+ with self._lock:
312
+ conn.execute(
313
+ """INSERT OR REPLACE INTO files
314
+ (path, mtime, size, language, line_count, content_hash)
315
+ VALUES (?, ?, ?, ?, ?, ?)""",
316
+ (f.path, f.mtime, f.size, f.language, f.line_count, f.content_hash),
317
+ )
318
+ conn.commit()
319
+
320
+ def save_files_batch(self, files: list[StoredFile]) -> None:
321
+ conn = self._get_conn()
322
+ rows = [
323
+ (f.path, f.mtime, f.size, f.language, f.line_count, f.content_hash)
324
+ for f in files
325
+ ]
326
+ with self._lock:
327
+ conn.executemany(
328
+ """INSERT OR REPLACE INTO files
329
+ (path, mtime, size, language, line_count, content_hash)
330
+ VALUES (?, ?, ?, ?, ?, ?)""",
331
+ rows,
332
+ )
333
+ conn.commit()
334
+
335
+ def get_file(self, path: str) -> StoredFile | None:
336
+ conn = self._get_conn()
337
+ with self._lock:
338
+ row = conn.execute(
339
+ "SELECT path, mtime, size, language, line_count, content_hash "
340
+ "FROM files WHERE path = ?",
341
+ (path,),
342
+ ).fetchone()
343
+ if row:
344
+ return StoredFile(*row)
345
+ return None
346
+
347
+ def get_all_files(self) -> list[StoredFile]:
348
+ conn = self._get_conn()
349
+ with self._lock:
350
+ rows = conn.execute(
351
+ "SELECT path, mtime, size, language, line_count, content_hash FROM files"
352
+ ).fetchall()
353
+ return [StoredFile(*r) for r in rows]
354
+
355
+ def get_stale_files(self, file_mtimes: dict[str, float]) -> list[str]:
356
+ """Return file paths where current mtime > stored mtime (or not indexed)."""
357
+ conn = self._get_conn()
358
+ with self._lock:
359
+ rows = conn.execute("SELECT path, mtime FROM files").fetchall()
360
+ indexed = {row[0]: row[1] for row in rows}
361
+ stale: list[str] = []
362
+ for fpath, current_mtime in file_mtimes.items():
363
+ stored_mtime = indexed.get(fpath)
364
+ if stored_mtime is None or current_mtime > stored_mtime:
365
+ stale.append(fpath)
366
+ return stale
367
+
368
+ def get_deleted_files(self, current_files: set[str]) -> list[str]:
369
+ """Return indexed files no longer present on disk."""
370
+ conn = self._get_conn()
371
+ with self._lock:
372
+ rows = conn.execute("SELECT path FROM files").fetchall()
373
+ return [r[0] for r in rows if r[0] not in current_files]
374
+
375
+ def remove_file(self, path: str) -> None:
376
+ """Remove a file and all its symbols/refs (CASCADE)."""
377
+ conn = self._get_conn()
378
+ with self._lock:
379
+ conn.execute("DELETE FROM files WHERE path = ?", (path,))
380
+ conn.execute(
381
+ "DELETE FROM dependencies WHERE source_path = ? OR target_path = ?",
382
+ (path, path),
383
+ )
384
+ conn.commit()
385
+
386
+ # ------------------------------------------------------------------
387
+ # Symbols
388
+ # ------------------------------------------------------------------
389
+
390
+ def save_symbols(self, file_path: str, symbols: list[dict[str, Any]]) -> None:
391
+ """Save symbol definitions for a file (replaces existing)."""
392
+ conn = self._get_conn()
393
+ rows = [
394
+ (
395
+ file_path,
396
+ s["name"],
397
+ s["qualified_name"],
398
+ s["kind"],
399
+ s.get("line", 0),
400
+ s.get("end_line", 0),
401
+ s.get("source", "tree-sitter"),
402
+ )
403
+ for s in symbols
404
+ ]
405
+ with self._lock:
406
+ conn.execute("DELETE FROM symbols WHERE file_path = ?", (file_path,))
407
+ conn.executemany(
408
+ """INSERT INTO symbols
409
+ (file_path, name, qualified_name, kind, line, end_line, source)
410
+ VALUES (?, ?, ?, ?, ?, ?, ?)""",
411
+ rows,
412
+ )
413
+ conn.commit()
414
+
415
+ def load_symbols(self, file_path: str | None = None) -> list[StoredSymbol]:
416
+ """Load symbols, optionally filtered by file."""
417
+ conn = self._get_conn()
418
+ with self._lock:
419
+ if file_path:
420
+ rows = conn.execute(
421
+ "SELECT id, file_path, name, qualified_name, kind, line, end_line, source "
422
+ "FROM symbols WHERE file_path = ?",
423
+ (file_path,),
424
+ ).fetchall()
425
+ else:
426
+ rows = conn.execute(
427
+ "SELECT id, file_path, name, qualified_name, kind, line, end_line, source "
428
+ "FROM symbols"
429
+ ).fetchall()
430
+ return [
431
+ StoredSymbol(
432
+ id=r[0], file_path=r[1], name=r[2], qualified_name=r[3],
433
+ kind=r[4], line=r[5], end_line=r[6], source=r[7],
434
+ )
435
+ for r in rows
436
+ ]
437
+
438
+ # ------------------------------------------------------------------
439
+ # References
440
+ # ------------------------------------------------------------------
441
+
442
+ def save_references(self, file_path: str, refs: list[dict[str, Any]]) -> None:
443
+ """Save references for a file (replaces existing)."""
444
+ conn = self._get_conn()
445
+ rows = [
446
+ (
447
+ file_path,
448
+ r["symbol_name"],
449
+ r.get("ref_kind", "call"),
450
+ r.get("line", 0),
451
+ r.get("column", 0),
452
+ r.get("source", "tree-sitter"),
453
+ r.get("syntax_name", ""),
454
+ r.get("caller_qualified_name", ""),
455
+ )
456
+ for r in refs
457
+ ]
458
+ with self._lock:
459
+ conn.execute("DELETE FROM refs WHERE file_path = ?", (file_path,))
460
+ conn.executemany(
461
+ """INSERT INTO refs
462
+ (file_path, symbol_name, ref_kind, line, col, source,
463
+ syntax_name, caller_qualified_name)
464
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)""",
465
+ rows,
466
+ )
467
+ conn.commit()
468
+
469
+ def load_references(self, file_path: str | None = None) -> list[StoredReference]:
470
+ """Load references, optionally filtered by file."""
471
+ conn = self._get_conn()
472
+ with self._lock:
473
+ if file_path:
474
+ rows = conn.execute(
475
+ "SELECT id, file_path, symbol_name, ref_kind, line, col, "
476
+ "source, syntax_name, caller_qualified_name FROM refs WHERE file_path = ?",
477
+ (file_path,),
478
+ ).fetchall()
479
+ else:
480
+ rows = conn.execute(
481
+ "SELECT id, file_path, symbol_name, ref_kind, line, col, "
482
+ "source, syntax_name, caller_qualified_name FROM refs"
483
+ ).fetchall()
484
+ return [
485
+ StoredReference(
486
+ id=r[0], file_path=r[1], symbol_name=r[2], ref_kind=r[3],
487
+ line=r[4], column=r[5], source=r[6], syntax_name=r[7],
488
+ caller_qualified_name=r[8],
489
+ )
490
+ for r in rows
491
+ ]
492
+
493
+ # ------------------------------------------------------------------
494
+ # Dependencies
495
+ # ------------------------------------------------------------------
496
+
497
+ def save_dependencies(self, source_path: str, targets: set[str]) -> None:
498
+ """Save dependency edges from source to targets (replaces existing)."""
499
+ conn = self._get_conn()
500
+ with self._lock:
501
+ conn.execute(
502
+ "DELETE FROM dependencies WHERE source_path = ?", (source_path,)
503
+ )
504
+ if targets:
505
+ conn.executemany(
506
+ "INSERT OR IGNORE INTO dependencies (source_path, target_path) VALUES (?, ?)",
507
+ [(source_path, t) for t in targets],
508
+ )
509
+ conn.commit()
510
+
511
+ def save_dependencies_batch(self, edges: list[tuple[str, str]]) -> None:
512
+ """Bulk save dependency edges."""
513
+ conn = self._get_conn()
514
+ with self._lock:
515
+ conn.executemany(
516
+ "INSERT OR IGNORE INTO dependencies (source_path, target_path) VALUES (?, ?)",
517
+ edges,
518
+ )
519
+ conn.commit()
520
+
521
+ def load_dependencies(self) -> dict[str, set[str]]:
522
+ """Load all dependency edges as forward map."""
523
+ conn = self._get_conn()
524
+ with self._lock:
525
+ rows = conn.execute(
526
+ "SELECT source_path, target_path FROM dependencies"
527
+ ).fetchall()
528
+ forward: dict[str, set[str]] = {}
529
+ for src, tgt in rows:
530
+ forward.setdefault(src, set()).add(tgt)
531
+ return forward
532
+
533
+ def clear_dependencies(self) -> None:
534
+ conn = self._get_conn()
535
+ with self._lock:
536
+ conn.execute("DELETE FROM dependencies")
537
+ conn.commit()
538
+
539
+ # ------------------------------------------------------------------
540
+ # Bulk operations
541
+ # ------------------------------------------------------------------
542
+
543
+ def clear_all(self) -> None:
544
+ """Drop all data (keeps schema)."""
545
+ conn = self._get_conn()
546
+ with self._lock:
547
+ conn.executescript("""
548
+ DELETE FROM refs;
549
+ DELETE FROM symbols;
550
+ DELETE FROM dependencies;
551
+ DELETE FROM files;
552
+ DELETE FROM metadata WHERE key = 'structural_snapshot_v1';
553
+ """)
554
+ conn.commit()
555
+
556
+ def stats(self) -> dict[str, int]:
557
+ """Return counts of files, symbols, refs, and dependencies."""
558
+ conn = self._get_conn()
559
+ with self._lock:
560
+ files = conn.execute("SELECT COUNT(*) FROM files").fetchone()[0]
561
+ symbols = conn.execute("SELECT COUNT(*) FROM symbols").fetchone()[0]
562
+ refs = conn.execute("SELECT COUNT(*) FROM refs").fetchone()[0]
563
+ deps = conn.execute("SELECT COUNT(*) FROM dependencies").fetchone()[0]
564
+ return {
565
+ "files": files,
566
+ "symbols": symbols,
567
+ "references": refs,
568
+ "dependencies": deps,
569
+ }
570
+
571
+ def record_scan_time(self) -> None:
572
+ """Record current time as last full scan timestamp."""
573
+ self.set_meta("last_full_scan", str(time.time()))
574
+
575
+ def get_last_scan_time(self) -> float | None:
576
+ """Return last full scan timestamp, or None if never scanned."""
577
+ val = self.get_meta("last_full_scan")
578
+ return float(val) if val else None
579
+
580
+ def close(self) -> None:
581
+ if self._conn:
582
+ self._conn.close()
583
+ self._conn = None
584
+
585
+ def __del__(self) -> None:
586
+ self.close()