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,528 @@
1
+ """Code analyzer using optional tree-sitter for AST parsing.
2
+
3
+ Extracts code chunks (functions, classes, imports) from source files
4
+ for intelligent context selection. Falls back to regex-based parsing
5
+ when tree-sitter is not available.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import re
11
+ from dataclasses import dataclass, field
12
+ from pathlib import Path
13
+ from typing import Any
14
+
15
+
16
+ def _djb2_hash(s: str) -> int:
17
+ """DJB2 hash function for content-based cache invalidation."""
18
+ h = 5381
19
+ for ch in s:
20
+ h = ((h << 5) + h + ord(ch)) & 0xFFFFFFFF
21
+ return h
22
+
23
+
24
+ @dataclass(slots=True)
25
+ class CodeChunk:
26
+ """A chunk of code extracted from a file."""
27
+
28
+ name: str
29
+ kind: str # 'function', 'class', 'method', 'import', 'constant'
30
+ start_line: int
31
+ end_line: int
32
+ content: str
33
+ file_path: str = ""
34
+ language: str = ""
35
+ signature: str = "" # Function/method signature
36
+ parent: str = "" # Parent class for methods
37
+ docstring: str = ""
38
+ importance: float = 0.5
39
+
40
+ @property
41
+ def line_count(self) -> int:
42
+ return self.end_line - self.start_line + 1
43
+
44
+
45
+ @dataclass(slots=True)
46
+ class FileAnalysis:
47
+ """Analysis results for a single file."""
48
+
49
+ path: str
50
+ language: str
51
+ chunks: list[CodeChunk]
52
+ imports: list[str] = field(default_factory=list)
53
+ exports: list[str] = field(default_factory=list)
54
+ line_count: int = 0
55
+ has_main: bool = False
56
+
57
+
58
+ # Tree-sitter availability flag
59
+ _TREE_SITTER_AVAILABLE = False
60
+ try:
61
+ import tree_sitter # type: ignore[import-untyped] # noqa: F401
62
+ _TREE_SITTER_AVAILABLE = True
63
+ except ImportError:
64
+ pass
65
+
66
+
67
+ def is_tree_sitter_available() -> bool:
68
+ """Check if tree-sitter is available."""
69
+ return _TREE_SITTER_AVAILABLE
70
+
71
+
72
+ class CodeAnalyzer:
73
+ """Analyzes source code files to extract structured chunks.
74
+
75
+ Uses tree-sitter when available, falls back to regex-based
76
+ extraction otherwise. Caches results using content hashing
77
+ to avoid re-analysis of unchanged files.
78
+ """
79
+
80
+ def __init__(self) -> None:
81
+ self._parsers: dict[str, Any] = {}
82
+ self._cache: dict[str, tuple[int, FileAnalysis]] = {} # path -> (hash, result)
83
+ self._cache_hits: int = 0
84
+ self._cache_misses: int = 0
85
+
86
+ @property
87
+ def cache_stats(self) -> dict[str, int]:
88
+ """Return cache hit/miss statistics."""
89
+ return {
90
+ "hits": self._cache_hits,
91
+ "misses": self._cache_misses,
92
+ "entries": len(self._cache),
93
+ }
94
+
95
+ def clear_cache(self) -> None:
96
+ """Clear the analysis cache."""
97
+ self._cache.clear()
98
+ self._cache_hits = 0
99
+ self._cache_misses = 0
100
+
101
+ def invalidate(self, file_path: str) -> None:
102
+ """Remove cached analysis for a specific file.
103
+
104
+ Args:
105
+ file_path: Path of the file to invalidate.
106
+ """
107
+ self._cache.pop(file_path, None)
108
+
109
+ def update_file(self, file_path: str, content: str, language: str = "") -> FileAnalysis:
110
+ """Re-analyze a single file with provided content and update cache.
111
+
112
+ Unlike analyze_file() which reads from disk, this accepts content
113
+ directly — useful when the caller already has the new content
114
+ (e.g. after an edit tool produces it).
115
+
116
+ Args:
117
+ file_path: File path (for language detection and cache key).
118
+ content: The new file content to analyze.
119
+ language: Language hint (auto-detected if empty).
120
+
121
+ Returns:
122
+ Updated FileAnalysis.
123
+ """
124
+ # Invalidate old entry
125
+ self.invalidate(file_path)
126
+
127
+ if not language:
128
+ language = _detect_language(Path(file_path).suffix)
129
+
130
+ lines = content.splitlines()
131
+ content_hash = _djb2_hash(content)
132
+
133
+ if _TREE_SITTER_AVAILABLE and language in ("python", "javascript", "typescript"):
134
+ chunks = self._analyze_with_tree_sitter(content, language, file_path)
135
+ else:
136
+ chunks = self._analyze_with_regex(content, language, file_path)
137
+
138
+ imports = _extract_imports(content, language)
139
+ has_main = _has_main_guard(content, language)
140
+
141
+ result = FileAnalysis(
142
+ path=file_path,
143
+ language=language,
144
+ chunks=chunks,
145
+ imports=imports,
146
+ line_count=len(lines),
147
+ has_main=has_main,
148
+ )
149
+
150
+ self._cache[file_path] = (content_hash, result)
151
+ self._cache_misses += 1
152
+ return result
153
+
154
+ def analyze_file(self, path: str, language: str = "") -> FileAnalysis:
155
+ """Analyze a source file.
156
+
157
+ Uses content-hash caching: if the file content hasn't changed
158
+ since the last analysis, returns the cached result.
159
+
160
+ Args:
161
+ path: File path to analyze.
162
+ language: Language hint (auto-detected from extension if empty).
163
+
164
+ Returns:
165
+ FileAnalysis with extracted chunks.
166
+ """
167
+ p = Path(path)
168
+ if not p.exists() or not p.is_file():
169
+ return FileAnalysis(path=path, language=language, chunks=[])
170
+
171
+ try:
172
+ content = p.read_text(encoding="utf-8", errors="ignore")
173
+ except OSError:
174
+ return FileAnalysis(path=path, language=language, chunks=[])
175
+
176
+ # Check content-hash cache
177
+ content_hash = _djb2_hash(content)
178
+ cached = self._cache.get(path)
179
+ if cached is not None and cached[0] == content_hash:
180
+ self._cache_hits += 1
181
+ return cached[1]
182
+
183
+ self._cache_misses += 1
184
+
185
+ if not language:
186
+ language = _detect_language(p.suffix)
187
+
188
+ lines = content.splitlines()
189
+
190
+ if _TREE_SITTER_AVAILABLE and language in ("python", "javascript", "typescript"):
191
+ chunks = self._analyze_with_tree_sitter(content, language, path)
192
+ else:
193
+ chunks = self._analyze_with_regex(content, language, path)
194
+
195
+ # Extract imports
196
+ imports = _extract_imports(content, language)
197
+
198
+ # Check for main entry point
199
+ has_main = _has_main_guard(content, language)
200
+
201
+ result = FileAnalysis(
202
+ path=path,
203
+ language=language,
204
+ chunks=chunks,
205
+ imports=imports,
206
+ line_count=len(lines),
207
+ has_main=has_main,
208
+ )
209
+
210
+ # Store in cache
211
+ self._cache[path] = (content_hash, result)
212
+ return result
213
+
214
+ def analyze_files(self, paths: list[str]) -> list[FileAnalysis]:
215
+ """Analyze multiple files.
216
+
217
+ Args:
218
+ paths: List of file paths.
219
+
220
+ Returns:
221
+ List of FileAnalysis results.
222
+ """
223
+ return [self.analyze_file(p) for p in paths]
224
+
225
+ def _analyze_with_tree_sitter(
226
+ self, content: str, language: str, path: str
227
+ ) -> list[CodeChunk]:
228
+ """Analyze using tree-sitter (when available)."""
229
+ try:
230
+ import tree_sitter # type: ignore[import-untyped]
231
+
232
+ # Get or create parser
233
+ if language not in self._parsers:
234
+ if language == "python":
235
+ import tree_sitter_python as ts_python # type: ignore[import-untyped]
236
+ lang = tree_sitter.Language(ts_python.language())
237
+ elif language in ("javascript", "typescript"):
238
+ import tree_sitter_javascript as ts_js # type: ignore[import-untyped]
239
+ lang = tree_sitter.Language(ts_js.language())
240
+ else:
241
+ return self._analyze_with_regex(content, language, path)
242
+
243
+ parser = tree_sitter.Parser(lang)
244
+ self._parsers[language] = parser
245
+
246
+ parser = self._parsers[language]
247
+ tree = parser.parse(bytes(content, "utf-8"))
248
+
249
+ chunks: list[CodeChunk] = []
250
+ self._walk_tree_sitter_node(tree.root_node, content, language, path, chunks)
251
+ return chunks
252
+
253
+ except Exception:
254
+ # Fall back to regex on any tree-sitter error
255
+ return self._analyze_with_regex(content, language, path)
256
+
257
+ def _walk_tree_sitter_node(
258
+ self,
259
+ node: Any,
260
+ content: str,
261
+ language: str,
262
+ path: str,
263
+ chunks: list[CodeChunk],
264
+ parent_name: str = "",
265
+ ) -> None:
266
+ """Walk tree-sitter AST and extract chunks."""
267
+ lines = content.splitlines()
268
+
269
+ for child in node.children:
270
+ kind = ""
271
+ name = ""
272
+
273
+ if language == "python":
274
+ if child.type == "function_definition":
275
+ kind = "method" if parent_name else "function"
276
+ name_node = child.child_by_field_name("name")
277
+ name = name_node.text.decode() if name_node else ""
278
+ elif child.type == "class_definition":
279
+ kind = "class"
280
+ name_node = child.child_by_field_name("name")
281
+ name = name_node.text.decode() if name_node else ""
282
+ elif language in ("javascript", "typescript"):
283
+ if child.type in ("function_declaration", "arrow_function"):
284
+ kind = "function"
285
+ name_node = child.child_by_field_name("name")
286
+ name = name_node.text.decode() if name_node else ""
287
+ elif child.type == "class_declaration":
288
+ kind = "class"
289
+ name_node = child.child_by_field_name("name")
290
+ name = name_node.text.decode() if name_node else ""
291
+
292
+ if kind and name:
293
+ start_line = child.start_point[0] + 1
294
+ end_line = child.end_point[0] + 1
295
+ chunk_content = "\n".join(lines[start_line - 1:end_line])
296
+
297
+ # Extract signature (first line)
298
+ signature = lines[start_line - 1].strip() if start_line <= len(lines) else ""
299
+
300
+ # Extract docstring
301
+ docstring = _extract_docstring(chunk_content, language)
302
+
303
+ chunks.append(CodeChunk(
304
+ name=name,
305
+ kind=kind,
306
+ start_line=start_line,
307
+ end_line=end_line,
308
+ content=chunk_content,
309
+ file_path=path,
310
+ language=language,
311
+ signature=signature,
312
+ parent=parent_name,
313
+ docstring=docstring,
314
+ ))
315
+
316
+ # Recurse into class bodies
317
+ if kind == "class":
318
+ self._walk_tree_sitter_node(
319
+ child, content, language, path, chunks, parent_name=name
320
+ )
321
+ else:
322
+ # Recurse into other nodes
323
+ self._walk_tree_sitter_node(
324
+ child, content, language, path, chunks, parent_name=parent_name
325
+ )
326
+
327
+ def _analyze_with_regex(
328
+ self, content: str, language: str, path: str
329
+ ) -> list[CodeChunk]:
330
+ """Fallback regex-based code analysis."""
331
+ if language == "python":
332
+ return _regex_analyze_python(content, path)
333
+ elif language in ("javascript", "typescript"):
334
+ return _regex_analyze_js_ts(content, path, language)
335
+ elif language in ("rust", "go", "java"):
336
+ return _regex_analyze_generic(content, path, language)
337
+ return []
338
+
339
+
340
+ def _regex_analyze_python(content: str, path: str) -> list[CodeChunk]:
341
+ """Extract Python chunks using regex."""
342
+ chunks: list[CodeChunk] = []
343
+ lines = content.splitlines()
344
+
345
+ # Match function and class definitions
346
+ patterns = [
347
+ (r"^(class)\s+(\w+)", "class"),
348
+ (r"^(def)\s+(\w+)", "function"),
349
+ (r"^(\s+def)\s+(\w+)", "method"),
350
+ ]
351
+
352
+ i = 0
353
+ while i < len(lines):
354
+ line = lines[i]
355
+ for pattern, kind in patterns:
356
+ match = re.match(pattern, line)
357
+ if match:
358
+ name = match.group(2)
359
+ start_line = i + 1
360
+
361
+ # Find end of block (next line with same or less indentation)
362
+ indent = len(line) - len(line.lstrip())
363
+ end_line = start_line
364
+ for j in range(i + 1, len(lines)):
365
+ stripped = lines[j].strip()
366
+ if not stripped:
367
+ continue
368
+ line_indent = len(lines[j]) - len(lines[j].lstrip())
369
+ if line_indent <= indent and stripped:
370
+ end_line = j
371
+ break
372
+ else:
373
+ end_line = len(lines)
374
+
375
+ chunk_content = "\n".join(lines[i:end_line])
376
+ signature = line.strip()
377
+ docstring = _extract_docstring(chunk_content, "python")
378
+
379
+ # Determine parent
380
+ parent = ""
381
+ if kind == "method" and indent > 0:
382
+ # Look backwards for class definition
383
+ for k in range(i - 1, -1, -1):
384
+ cls_match = re.match(r"^class\s+(\w+)", lines[k])
385
+ if cls_match:
386
+ parent = cls_match.group(1)
387
+ break
388
+
389
+ chunks.append(CodeChunk(
390
+ name=name,
391
+ kind=kind,
392
+ start_line=start_line,
393
+ end_line=end_line,
394
+ content=chunk_content,
395
+ file_path=path,
396
+ language="python",
397
+ signature=signature,
398
+ parent=parent,
399
+ docstring=docstring,
400
+ ))
401
+ break
402
+ i += 1
403
+
404
+ return chunks
405
+
406
+
407
+ def _regex_analyze_js_ts(content: str, path: str, language: str) -> list[CodeChunk]:
408
+ """Extract JS/TS chunks using regex."""
409
+ chunks: list[CodeChunk] = []
410
+ lines = content.splitlines()
411
+
412
+ patterns = [
413
+ (r"(?:export\s+)?(?:async\s+)?function\s+(\w+)", "function"),
414
+ (r"(?:export\s+)?class\s+(\w+)", "class"),
415
+ (r"(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\(", "function"),
416
+ ]
417
+
418
+ for i, line in enumerate(lines):
419
+ for pattern, kind in patterns:
420
+ match = re.search(pattern, line)
421
+ if match:
422
+ name = match.group(1)
423
+ start_line = i + 1
424
+ # Simple heuristic: find matching brace
425
+ brace_count = line.count("{") - line.count("}")
426
+ end_line = start_line
427
+ for j in range(i + 1, min(i + 500, len(lines))):
428
+ brace_count += lines[j].count("{") - lines[j].count("}")
429
+ if brace_count <= 0:
430
+ end_line = j + 1
431
+ break
432
+ else:
433
+ end_line = min(i + 50, len(lines))
434
+
435
+ chunk_content = "\n".join(lines[i:end_line])
436
+ chunks.append(CodeChunk(
437
+ name=name,
438
+ kind=kind,
439
+ start_line=start_line,
440
+ end_line=end_line,
441
+ content=chunk_content,
442
+ file_path=path,
443
+ language=language,
444
+ signature=line.strip(),
445
+ ))
446
+ break
447
+
448
+ return chunks
449
+
450
+
451
+ def _regex_analyze_generic(content: str, path: str, language: str) -> list[CodeChunk]:
452
+ """Basic chunk extraction for other languages."""
453
+ chunks: list[CodeChunk] = []
454
+ lines = content.splitlines()
455
+
456
+ # Generic function pattern
457
+ func_pattern = re.compile(r"(?:pub\s+)?(?:fn|func|function|def)\s+(\w+)")
458
+
459
+ for i, line in enumerate(lines):
460
+ match = func_pattern.search(line)
461
+ if match:
462
+ name = match.group(1)
463
+ start_line = i + 1
464
+ brace_count = line.count("{") - line.count("}")
465
+ end_line = start_line
466
+ for j in range(i + 1, min(i + 500, len(lines))):
467
+ brace_count += lines[j].count("{") - lines[j].count("}")
468
+ if brace_count <= 0:
469
+ end_line = j + 1
470
+ break
471
+ else:
472
+ end_line = min(i + 50, len(lines))
473
+
474
+ chunks.append(CodeChunk(
475
+ name=name,
476
+ kind="function",
477
+ start_line=start_line,
478
+ end_line=end_line,
479
+ content="\n".join(lines[i:end_line]),
480
+ file_path=path,
481
+ language=language,
482
+ signature=line.strip(),
483
+ ))
484
+
485
+ return chunks
486
+
487
+
488
+ def _detect_language(ext: str) -> str:
489
+ """Detect language from file extension."""
490
+ from attocode_intel._internal.integrations.context.codebase_ast import LANG_EXTENSIONS
491
+ return LANG_EXTENSIONS.get(ext, "")
492
+
493
+
494
+ def _extract_imports(content: str, language: str) -> list[str]:
495
+ """Extract import statements from source code."""
496
+ imports: list[str] = []
497
+ if language == "python":
498
+ for line in content.splitlines():
499
+ stripped = line.strip()
500
+ if stripped.startswith("import ") or stripped.startswith("from "):
501
+ imports.append(stripped)
502
+ elif language in ("javascript", "typescript"):
503
+ import_re = re.compile(r"^import\s+.+", re.MULTILINE)
504
+ imports = import_re.findall(content)
505
+ return imports[:50] # Limit
506
+
507
+
508
+ def _extract_docstring(content: str, language: str) -> str:
509
+ """Extract docstring from a code chunk."""
510
+ if language == "python":
511
+ match = re.search(r'"""(.*?)"""', content, re.DOTALL)
512
+ if match:
513
+ return match.group(1).strip()[:200]
514
+ match = re.search(r"'''(.*?)'''", content, re.DOTALL)
515
+ if match:
516
+ return match.group(1).strip()[:200]
517
+ elif language in ("javascript", "typescript"):
518
+ match = re.search(r"/\*\*(.*?)\*/", content, re.DOTALL)
519
+ if match:
520
+ return match.group(1).strip()[:200]
521
+ return ""
522
+
523
+
524
+ def _has_main_guard(content: str, language: str) -> bool:
525
+ """Check if file has a main guard."""
526
+ if language == "python":
527
+ return 'if __name__' in content
528
+ return False