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,160 @@
1
+ """Redis-backed presence tracking for collaboration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import logging
7
+ import time
8
+ from dataclasses import dataclass
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+ # Presence entries expire after 120 seconds without heartbeat
13
+ _PRESENCE_TTL = 120
14
+
15
+
16
+ @dataclass(slots=True)
17
+ class PresenceEntry:
18
+ user_id: str
19
+ user_name: str
20
+ file: str
21
+ session_id: str
22
+ timestamp: float
23
+
24
+
25
+ async def set_presence(
26
+ repo_id: str,
27
+ user_id: str,
28
+ user_name: str,
29
+ file_path: str,
30
+ session_id: str,
31
+ ) -> None:
32
+ """Set or update presence for a user in a repo."""
33
+ from attocode_intel.redis import get_redis
34
+
35
+ redis = await get_redis()
36
+ key = f"presence:{repo_id}"
37
+ field = f"{user_id}:{session_id}"
38
+
39
+ # Check for file change to publish event
40
+ old_raw = await redis.hget(key, field)
41
+ old_file = ""
42
+ if old_raw:
43
+ try:
44
+ old_data = json.loads(old_raw)
45
+ old_file = old_data.get("file", "")
46
+ except (json.JSONDecodeError, TypeError):
47
+ pass
48
+
49
+ value = json.dumps({
50
+ "user_id": user_id,
51
+ "user_name": user_name,
52
+ "file": file_path,
53
+ "session_id": session_id,
54
+ "timestamp": time.time(),
55
+ })
56
+ await redis.hset(key, field, value)
57
+
58
+ # Set TTL on the hash key (renews on each update)
59
+ await redis.expire(key, _PRESENCE_TTL)
60
+
61
+ # Publish presence events
62
+ from attocode_intel.pubsub import publish_event
63
+
64
+ if not old_raw:
65
+ await publish_event(repo_id, "presence.joined", {
66
+ "user_id": user_id, "user_name": user_name, "file": file_path,
67
+ })
68
+ elif old_file != file_path:
69
+ await publish_event(repo_id, "presence.file_changed", {
70
+ "user_id": user_id, "user_name": user_name,
71
+ "from_file": old_file, "to_file": file_path,
72
+ })
73
+
74
+
75
+ async def remove_presence(
76
+ repo_id: str,
77
+ user_id: str,
78
+ session_id: str,
79
+ ) -> None:
80
+ """Remove presence for a user session."""
81
+ from attocode_intel.redis import get_redis
82
+
83
+ redis = await get_redis()
84
+ key = f"presence:{repo_id}"
85
+ field = f"{user_id}:{session_id}"
86
+
87
+ # Get user name before removing
88
+ raw = await redis.hget(key, field)
89
+ user_name = ""
90
+ if raw:
91
+ try:
92
+ data = json.loads(raw)
93
+ user_name = data.get("user_name", "")
94
+ except (json.JSONDecodeError, TypeError):
95
+ pass
96
+
97
+ await redis.hdel(key, field)
98
+
99
+ from attocode_intel.pubsub import publish_event
100
+
101
+ await publish_event(repo_id, "presence.left", {
102
+ "user_id": user_id, "user_name": user_name,
103
+ })
104
+
105
+
106
+ async def get_presence(repo_id: str) -> list[PresenceEntry]:
107
+ """Get all active presence entries for a repo."""
108
+ from attocode_intel.redis import get_redis
109
+
110
+ redis = await get_redis()
111
+ key = f"presence:{repo_id}"
112
+
113
+ all_entries = await redis.hgetall(key)
114
+ now = time.time()
115
+ result = []
116
+
117
+ for field, raw in all_entries.items():
118
+ try:
119
+ data = json.loads(raw)
120
+ except (json.JSONDecodeError, TypeError):
121
+ continue
122
+
123
+ # Filter out stale entries
124
+ ts = data.get("timestamp", 0)
125
+ if now - ts > _PRESENCE_TTL:
126
+ await redis.hdel(key, field)
127
+ continue
128
+
129
+ result.append(PresenceEntry(
130
+ user_id=data["user_id"],
131
+ user_name=data.get("user_name", ""),
132
+ file=data.get("file", ""),
133
+ session_id=data.get("session_id", ""),
134
+ timestamp=ts,
135
+ ))
136
+
137
+ return result
138
+
139
+
140
+ async def heartbeat(
141
+ repo_id: str,
142
+ user_id: str,
143
+ session_id: str,
144
+ ) -> None:
145
+ """Renew presence TTL via heartbeat."""
146
+ from attocode_intel.redis import get_redis
147
+
148
+ redis = await get_redis()
149
+ key = f"presence:{repo_id}"
150
+ field = f"{user_id}:{session_id}"
151
+
152
+ raw = await redis.hget(key, field)
153
+ if raw:
154
+ try:
155
+ data = json.loads(raw)
156
+ data["timestamp"] = time.time()
157
+ await redis.hset(key, field, json.dumps(data))
158
+ await redis.expire(key, _PRESENCE_TTL)
159
+ except (json.JSONDecodeError, TypeError):
160
+ pass
@@ -0,0 +1,110 @@
1
+ """Runtime MCP install probing for file-based assistant configurations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import os
7
+ import sys
8
+ from dataclasses import replace
9
+
10
+ from attocode_intel.installer import ResolvedInstallSpec, resolve_install_spec
11
+ from attocode_intel._internal.integrations.mcp.client import MCPClient
12
+
13
+
14
+ def _substitute_workspace_folder(value: str, project_dir: str) -> str:
15
+ return value.replace("${workspaceFolder}", project_dir)
16
+
17
+
18
+ def _materialize_spec(spec: ResolvedInstallSpec, project_dir: str) -> ResolvedInstallSpec:
19
+ """Resolve runtime placeholders before spawning the stdio process."""
20
+ env = {
21
+ key: _substitute_workspace_folder(value, project_dir)
22
+ for key, value in spec.env.items()
23
+ }
24
+ return replace(
25
+ spec,
26
+ command=_substitute_workspace_folder(spec.command, project_dir),
27
+ args=[_substitute_workspace_folder(arg, project_dir) for arg in spec.args],
28
+ env=env,
29
+ )
30
+
31
+
32
+ def _has_concrete_project_arg(args: list[str]) -> bool:
33
+ for idx, arg in enumerate(args):
34
+ if arg == "--project" and idx + 1 < len(args):
35
+ return True
36
+ if arg.startswith("--project="):
37
+ return True
38
+ return False
39
+
40
+
41
+ async def _run_probe(
42
+ spec: ResolvedInstallSpec,
43
+ *,
44
+ project_dir: str,
45
+ should_call_project_summary: bool,
46
+ ) -> tuple[int, int]:
47
+ client = MCPClient(
48
+ spec.command,
49
+ server_args=spec.args,
50
+ server_name=spec.target,
51
+ env=spec.env,
52
+ cwd=project_dir,
53
+ )
54
+ try:
55
+ await client.connect()
56
+ if not client.is_connected:
57
+ raise RuntimeError("MCP initialize did not complete")
58
+ if should_call_project_summary:
59
+ result = await client.call_tool("project_summary", {"max_tokens": 128})
60
+ if not result.success:
61
+ raise RuntimeError(result.error or "project_summary probe failed")
62
+ return len(client.tools), 0
63
+ finally:
64
+ await client.disconnect()
65
+
66
+
67
+ def probe_install(
68
+ target: str,
69
+ project_dir: str = ".",
70
+ scope: str = "local",
71
+ *,
72
+ force_project_probe: bool = False,
73
+ ) -> int:
74
+ """Run a runtime MCP probe for an installed assistant target.
75
+
76
+ Exit codes:
77
+ 0: success
78
+ 1: target missing or probe failed
79
+ 2: unsupported target for v1 probing
80
+ """
81
+ abs_project = os.path.abspath(project_dir)
82
+ resolved = resolve_install_spec(target, project_dir=abs_project, scope=scope)
83
+ if resolved is None:
84
+ print(f"{target}: attocode-code-intel is not installed for scope={scope}.", file=sys.stderr)
85
+ return 1
86
+ if not resolved.is_supported:
87
+ print(f"{target}: {resolved.unsupported_reason}", file=sys.stderr)
88
+ return 2
89
+
90
+ runtime_spec = _materialize_spec(resolved, abs_project)
91
+ should_call_project_summary = force_project_probe or _has_concrete_project_arg(runtime_spec.args)
92
+
93
+ try:
94
+ tools_count, _ = asyncio.run(
95
+ _run_probe(
96
+ runtime_spec,
97
+ project_dir=abs_project,
98
+ should_call_project_summary=should_call_project_summary,
99
+ )
100
+ )
101
+ except Exception as exc:
102
+ print(f"{target}: probe failed ({type(exc).__name__}: {exc})", file=sys.stderr)
103
+ return 1
104
+
105
+ project_note = " with project_summary probe" if should_call_project_summary else ""
106
+ print(
107
+ f"{target}: probe succeeded{project_note} "
108
+ f"({runtime_spec.command} {' '.join(runtime_spec.args)}; tools={tools_count})"
109
+ )
110
+ return 0
@@ -0,0 +1,97 @@
1
+ """Project-directory discovery — MCP-free neutral helpers.
2
+
3
+ These functions used to live in ``attocode_intel._shared``, but
4
+ ``_shared`` unconditionally imports ``mcp.server.fastmcp`` at module
5
+ load time and ``sys.exit(1)`` s if ``mcp`` isn't installed. The HTTP
6
+ API layer (``api/providers/``) needs to discover the current project
7
+ directory without dragging in the MCP runtime, so the logic is hosted
8
+ here.
9
+
10
+ ``_shared`` re-exports both symbols for backward compatibility with
11
+ the 25-odd call sites that already import them via ``_shared``.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import logging
17
+ import os
18
+ from pathlib import Path
19
+ from typing import TYPE_CHECKING
20
+
21
+ if TYPE_CHECKING:
22
+ from collections.abc import Iterator
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ def _walk_up(start: str, max_depth: int = 20) -> Iterator[str]:
28
+ """Walk upward from start, yielding parent directories up to max_depth.
29
+
30
+ Yields each ancestor directory starting from dirname(start) (the immediate
31
+ parent), then climbing upward until filesystem root.
32
+ """
33
+ path = os.path.dirname(start)
34
+ for _ in range(max_depth):
35
+ yield path
36
+ parent = os.path.dirname(path)
37
+ if parent == path:
38
+ break # Reached filesystem root
39
+ path = parent
40
+
41
+
42
+ def _get_project_dir() -> str:
43
+ """Get the project directory from env var, CLI arg, or auto-discovery.
44
+
45
+ Resolution order:
46
+ 1. ATTOCODE_PROJECT_DIR env var (set by --project CLI arg)
47
+ 2. Walk up from CWD looking for .git/ or .attocode/ marker
48
+ 3. Fall back to CWD
49
+ """
50
+ from attocode_intel.request_context import current_request
51
+ if (request := current_request.get()) is not None:
52
+ return request.project_dir
53
+ project_dir = os.environ.get("ATTOCODE_PROJECT_DIR", "")
54
+ if project_dir:
55
+ return os.path.abspath(project_dir)
56
+ return find_project_root(os.getcwd())
57
+
58
+
59
+ def find_project_root(start: str) -> str:
60
+ """Return the nearest directory at or above ``start`` with a project marker.
61
+
62
+ The markers are ``.git`` (a directory, or a file in a worktree) and
63
+ ``.attocode``. The search skips the home directory: ``~/.attocode`` holds
64
+ user-wide settings and caches, so it does not make home a project. Before
65
+ this rule, a server started in a folder without markers indexed the whole
66
+ home directory. Without a marker, ``start`` is the root.
67
+ """
68
+ home = os.path.realpath(os.path.expanduser("~"))
69
+ for dir_path in [start, *_walk_up(start)]:
70
+ if os.path.realpath(dir_path) == home:
71
+ continue
72
+ for marker in (".git", ".attocode"):
73
+ if os.path.exists(os.path.join(dir_path, marker)):
74
+ logger.debug(
75
+ "Auto-discovered project root: %s (marker: %s)",
76
+ dir_path, marker,
77
+ )
78
+ return dir_path
79
+
80
+ logger.debug("No project marker found, falling back to %s", start)
81
+ return start
82
+
83
+
84
+ def cache_folder(project_dir: str | os.PathLike) -> Path:
85
+ """Return ``<project>/.attocode``, and make it if it does not exist.
86
+
87
+ A folder that this function makes gets a ``.gitignore`` with ``*``, so the
88
+ index stays out of commits. A folder that exists keeps its own rules: some
89
+ projects commit settings in it.
90
+ """
91
+ folder = Path(project_dir) / ".attocode"
92
+ try:
93
+ folder.mkdir()
94
+ except FileExistsError:
95
+ return folder
96
+ (folder / ".gitignore").write_text("# Local index data of attocode-code-intel\n*\n", encoding="utf-8")
97
+ return folder
@@ -0,0 +1,113 @@
1
+ """Redis Streams — persistent event publishing and subscription for real-time updates.
2
+
3
+ Uses Redis Streams (XADD/XREAD) instead of ephemeral PubSub so that clients
4
+ that disconnect can resume from their last-seen event ID. Stream entries are
5
+ auto-trimmed to keep memory bounded.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import logging
12
+ from typing import AsyncGenerator
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+ # Max entries per stream before auto-trimming (MAXLEN ~)
17
+ _MAX_STREAM_LEN = 10_000
18
+
19
+
20
+ async def publish_event(repo_id: str, event_type: str, payload: dict) -> str | None:
21
+ """Publish an event to the repo's stream. Returns the stream entry ID, or None on error.
22
+
23
+ Fire-and-forget semantics — never raises.
24
+ """
25
+ try:
26
+ from attocode_intel.redis import get_redis
27
+
28
+ redis = await get_redis()
29
+ stream_key = f"repo:{repo_id}:stream"
30
+ message = json.dumps({"type": event_type, "payload": payload})
31
+ entry_id = await redis.xadd(
32
+ stream_key,
33
+ {"event": message},
34
+ maxlen=_MAX_STREAM_LEN,
35
+ approximate=True,
36
+ )
37
+ return entry_id
38
+ except Exception:
39
+ logger.warning("Failed to publish event %s for repo %s", event_type, repo_id, exc_info=True)
40
+ return None
41
+
42
+
43
+ async def subscribe(
44
+ repo_id: str,
45
+ last_event_id: str = "$",
46
+ block_ms: int = 5000,
47
+ ) -> AsyncGenerator[dict, None]:
48
+ """Subscribe to events for a repository using Redis Streams.
49
+
50
+ Yields event dicts. If last_event_id is provided, replays all events
51
+ after that ID before switching to live mode.
52
+
53
+ Args:
54
+ repo_id: Repository ID to subscribe to.
55
+ last_event_id: Resume from this event ID. "$" = new events only.
56
+ "0" = replay all available events.
57
+ block_ms: How long to block waiting for new events (milliseconds).
58
+ """
59
+ from attocode_intel.redis import get_redis
60
+
61
+ redis = await get_redis()
62
+ stream_key = f"repo:{repo_id}:stream"
63
+ cursor = last_event_id
64
+
65
+ try:
66
+ while True:
67
+ # XREAD blocks for up to block_ms waiting for new entries
68
+ results = await redis.xread(
69
+ {stream_key: cursor},
70
+ count=100,
71
+ block=block_ms,
72
+ )
73
+
74
+ if not results:
75
+ # Timeout — no new events, yield control and retry
76
+ continue
77
+
78
+ for _stream_name, entries in results:
79
+ for entry_id, fields in entries:
80
+ cursor = entry_id
81
+ event_data = fields.get("event") or fields.get(b"event")
82
+ if event_data is None:
83
+ continue
84
+ if isinstance(event_data, bytes):
85
+ event_data = event_data.decode("utf-8")
86
+ try:
87
+ data = json.loads(event_data)
88
+ # Include the stream entry ID so clients can resume
89
+ data["_stream_id"] = entry_id if isinstance(entry_id, str) else entry_id.decode("utf-8")
90
+ yield data
91
+ except (json.JSONDecodeError, TypeError):
92
+ continue
93
+ except GeneratorExit:
94
+ pass
95
+ except Exception:
96
+ logger.warning("Stream subscription ended for repo %s", repo_id, exc_info=True)
97
+
98
+
99
+ async def get_stream_info(repo_id: str) -> dict:
100
+ """Get info about the repo's event stream (length, first/last entry IDs)."""
101
+ try:
102
+ from attocode_intel.redis import get_redis
103
+
104
+ redis = await get_redis()
105
+ stream_key = f"repo:{repo_id}:stream"
106
+ info = await redis.xinfo_stream(stream_key)
107
+ return {
108
+ "length": info.get("length", 0),
109
+ "first_entry": info.get("first-entry", [None])[0] if info.get("first-entry") else None,
110
+ "last_entry": info.get("last-entry", [None])[0] if info.get("last-entry") else None,
111
+ }
112
+ except Exception:
113
+ return {"length": 0, "first_entry": None, "last_entry": None}