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,320 @@
1
+ """Fuzzy string matching using Smith-Waterman algorithm.
2
+
3
+ Provides typo-resistant search that can find "mtxlk" matching "mutex_lock".
4
+ Uses the Smith-Waterman algorithm for local sequence alignment with
5
+ affine gaps for optimal matching.
6
+
7
+ Based on fff.nvim's fuzzy grep implementation.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from dataclasses import dataclass
13
+ from typing import TYPE_CHECKING
14
+
15
+ if TYPE_CHECKING:
16
+ from collections.abc import Sequence
17
+
18
+ # ---------------------------------------------------------------------------
19
+ # Scoring constants
20
+ # ---------------------------------------------------------------------------
21
+
22
+ _MATCH_SCORE: int = 2
23
+ _MISMATCH_SCORE: int = -1
24
+ _GAP_OPEN: int = -3
25
+ _GAP_EXTEND: int = -1
26
+
27
+ # Minimum score threshold (normalized to 0-100)
28
+ _MIN_SCORE_THRESHOLD: float = 30.0
29
+
30
+ # ---------------------------------------------------------------------------
31
+ # Data structures
32
+ # ---------------------------------------------------------------------------
33
+
34
+
35
+ @dataclass
36
+ class FuzzyMatch:
37
+ """A fuzzy match result."""
38
+ text: str
39
+ score: float # 0-100 normalized score
40
+ matched_indices: list[int] # Positions in text that matched the pattern
41
+
42
+
43
+ @dataclass
44
+ class FuzzyResult:
45
+ """Result of a fuzzy search."""
46
+ query: str
47
+ matches: list[FuzzyMatch]
48
+ mode: str = "fuzzy"
49
+
50
+
51
+ # ---------------------------------------------------------------------------
52
+ # Smith-Waterman implementation
53
+ # ---------------------------------------------------------------------------
54
+
55
+
56
+ def _smith_waterman(
57
+ pattern: str,
58
+ text: str,
59
+ *,
60
+ case_sensitive: bool = True,
61
+ ) -> tuple[float, list[int]]:
62
+ """Perform Smith-Waterman local alignment.
63
+
64
+ Args:
65
+ pattern: The search pattern (query).
66
+ text: The text to search in.
67
+ case_sensitive: Whether to match case-sensitively.
68
+
69
+ Returns:
70
+ Tuple of (normalized_score 0-100, list of matched character indices).
71
+ """
72
+ if not pattern or not text:
73
+ return 0.0, []
74
+
75
+ p_len = len(pattern)
76
+ t_len = len(text)
77
+
78
+ # Convert to uppercase for case-insensitive matching
79
+ if not case_sensitive:
80
+ p_upper = pattern.upper()
81
+ t_upper = text.upper()
82
+ else:
83
+ p_upper = pattern
84
+ t_upper = text
85
+
86
+ # Initialize matrices
87
+ # We only need two rows for memory efficiency
88
+ prev_row = [0] * (t_len + 1)
89
+ curr_row = [0] * (t_len + 1)
90
+
91
+ # Traceback matrix (only for matched indices)
92
+ # Store: 0=end, 1=diag, 2=up, 3=left
93
+ trace: list[list[int]] = [[0] * (t_len + 1) for _ in range(p_len + 1)]
94
+
95
+ max_score = 0
96
+ max_pos = (0, 0)
97
+
98
+ # Fill matrices
99
+ for i in range(1, p_len + 1):
100
+ for j in range(1, t_len + 1):
101
+ p_char = p_upper[i - 1]
102
+ t_char = t_upper[j - 1]
103
+
104
+ # Match/mismatch score
105
+ if p_char == t_char:
106
+ diag_score = prev_row[j - 1] + _MATCH_SCORE
107
+ else:
108
+ diag_score = prev_row[j - 1] + _MISMATCH_SCORE
109
+
110
+ # Gap open (from top)
111
+ up_score = prev_row[j] + _GAP_OPEN if prev_row[j] < 0 else prev_row[j] + _GAP_EXTEND
112
+
113
+ # Gap extend (from left)
114
+ left_score = curr_row[j - 1] + _GAP_OPEN if curr_row[j - 1] < 0 else curr_row[j - 1] + _GAP_EXTEND
115
+
116
+ # Take maximum (local alignment allows 0)
117
+ scores = [0, diag_score, up_score, left_score]
118
+ curr_row[j] = max(scores)
119
+ trace[i][j] = scores.index(curr_row[j])
120
+
121
+ if curr_row[j] > max_score:
122
+ max_score = curr_row[j]
123
+ max_pos = (i, j)
124
+
125
+ # Swap rows
126
+ prev_row, curr_row = curr_row, prev_row
127
+
128
+ if max_score <= 0:
129
+ return 0.0, []
130
+
131
+ # Traceback to find matched indices
132
+ matched_indices: list[int] = []
133
+ i, j = max_pos
134
+
135
+ while trace[i][j] != 0 and i > 0 and j > 0:
136
+ if trace[i][j] == 1: # Diagonal (match/mismatch)
137
+ matched_indices.append(j - 1)
138
+ i -= 1
139
+ j -= 1
140
+ elif trace[i][j] == 2: # Up (gap in text)
141
+ i -= 1
142
+ else: # Left (gap in pattern)
143
+ j -= 1
144
+
145
+ matched_indices.reverse()
146
+
147
+ # Normalize score to 0-100 range
148
+ # Maximum possible score is len(pattern) * MATCH_SCORE
149
+ max_possible = p_len * _MATCH_SCORE
150
+ normalized = (max_score / max_possible) * 100.0 if max_possible > 0 else 0.0
151
+
152
+ return normalized, matched_indices
153
+
154
+
155
+ def fuzzy_match(
156
+ pattern: str,
157
+ text: str,
158
+ *,
159
+ case_sensitive: bool = True,
160
+ min_score: float = _MIN_SCORE_THRESHOLD,
161
+ ) -> FuzzyMatch | None:
162
+ """Check if pattern fuzzy-matches text.
163
+
164
+ Args:
165
+ pattern: The pattern to search for.
166
+ text: The text to search in.
167
+ case_sensitive: Whether matching is case-sensitive.
168
+ min_score: Minimum score threshold (0-100).
169
+
170
+ Returns:
171
+ FuzzyMatch if score >= min_score, None otherwise.
172
+ """
173
+ score, indices = _smith_waterman(pattern, text, case_sensitive=case_sensitive)
174
+
175
+ if score >= min_score:
176
+ return FuzzyMatch(text=text, score=score, matched_indices=indices)
177
+ return None
178
+
179
+
180
+ def fuzzy_match_in_lines(
181
+ pattern: str,
182
+ lines: Sequence[str],
183
+ *,
184
+ case_sensitive: bool = True,
185
+ min_score: float = _MIN_SCORE_THRESHOLD,
186
+ max_results: int = 100,
187
+ ) -> list[FuzzyMatch]:
188
+ """Find fuzzy matches of pattern in lines.
189
+
190
+ Args:
191
+ pattern: The pattern to search for.
192
+ lines: Lines to search in.
193
+ case_sensitive: Whether matching is case-sensitive.
194
+ min_score: Minimum score threshold (0-100).
195
+ max_results: Maximum number of results to return.
196
+
197
+ Returns:
198
+ List of FuzzyMatch objects for matching lines.
199
+ """
200
+ results: list[FuzzyMatch] = []
201
+
202
+ for line in lines:
203
+ match = fuzzy_match(pattern, line, case_sensitive=case_sensitive, min_score=min_score)
204
+ if match is not None:
205
+ results.append(match)
206
+ if len(results) >= max_results:
207
+ break
208
+
209
+ # Sort by score descending
210
+ results.sort(key=lambda m: m.score, reverse=True)
211
+ return results
212
+
213
+
214
+ def fuzzy_search(
215
+ pattern: str,
216
+ text: str,
217
+ *,
218
+ case_sensitive: bool = False,
219
+ min_score: float = _MIN_SCORE_THRESHOLD,
220
+ ) -> FuzzyResult:
221
+ """Perform fuzzy search on text.
222
+
223
+ This splits text into lines and finds fuzzy matches.
224
+
225
+ Args:
226
+ pattern: The pattern to search for.
227
+ text: The text to search in.
228
+ case_sensitive: Whether matching is case-sensitive.
229
+ min_score: Minimum score threshold (0-100).
230
+
231
+ Returns:
232
+ FuzzyResult with all matching lines.
233
+ """
234
+ lines = text.splitlines()
235
+ matches = fuzzy_match_in_lines(
236
+ pattern,
237
+ lines,
238
+ case_sensitive=case_sensitive,
239
+ min_score=min_score,
240
+ )
241
+ return FuzzyResult(query=pattern, matches=matches)
242
+
243
+
244
+ # ---------------------------------------------------------------------------
245
+ # Quick pattern matching for file names
246
+ # ---------------------------------------------------------------------------
247
+
248
+
249
+ def fuzzy_match_filename(pattern: str, filename: str) -> float:
250
+ """Check how well a pattern matches a filename.
251
+
252
+ Uses a simpler scoring for filename matching.
253
+
254
+ Args:
255
+ pattern: The search pattern.
256
+ filename: The filename to match against.
257
+
258
+ Returns:
259
+ Score 0-100 indicating match quality.
260
+ """
261
+ pattern_lower = pattern.lower()
262
+ filename_lower = filename.lower()
263
+
264
+ # Exact substring match gets high score
265
+ if pattern_lower in filename_lower:
266
+ # Longer patterns that match are better
267
+ return 50.0 + (len(pattern) / len(filename)) * 50.0
268
+
269
+ # Fall back to Smith-Waterman for fuzzy matching
270
+ score, _ = _smith_waterman(pattern, filename, case_sensitive=False)
271
+ return score
272
+
273
+
274
+ # ---------------------------------------------------------------------------
275
+ # High-level API for search integration
276
+ # ---------------------------------------------------------------------------
277
+
278
+
279
+ class FuzzyMatcher:
280
+ """Fuzzy matcher for text search."""
281
+
282
+ def __init__(
283
+ self,
284
+ pattern: str,
285
+ *,
286
+ case_sensitive: bool = False,
287
+ min_score: float = _MIN_SCORE_THRESHOLD,
288
+ ) -> None:
289
+ self.pattern = pattern
290
+ self.case_sensitive = case_sensitive
291
+ self.min_score = min_score
292
+
293
+ def matches(self, text: str) -> bool:
294
+ """Check if text matches the pattern."""
295
+ match = fuzzy_match(
296
+ self.pattern,
297
+ text,
298
+ case_sensitive=self.case_sensitive,
299
+ min_score=self.min_score,
300
+ )
301
+ return match is not None
302
+
303
+ def get_score(self, text: str) -> float:
304
+ """Get the match score for text."""
305
+ match = fuzzy_match(
306
+ self.pattern,
307
+ text,
308
+ case_sensitive=self.case_sensitive,
309
+ min_score=0.0, # Get score even if below threshold
310
+ )
311
+ return match.score if match else 0.0
312
+
313
+ def search(self, lines: Sequence[str]) -> list[FuzzyMatch]:
314
+ """Find all matching lines."""
315
+ return fuzzy_match_in_lines(
316
+ self.pattern,
317
+ lines,
318
+ case_sensitive=self.case_sensitive,
319
+ min_score=self.min_score,
320
+ )
@@ -0,0 +1,331 @@
1
+ """Hierarchical BFS codebase explorer.
2
+
3
+ Provides drill-down navigation through the repository, showing one
4
+ directory level at a time with statistics and importance scoring.
5
+ This avoids overwhelming the LLM context with a flat repo map on
6
+ large codebases (1000+ files).
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import os
12
+ from collections import OrderedDict
13
+ from dataclasses import dataclass, field
14
+ from typing import Any
15
+
16
+
17
+ @dataclass(slots=True)
18
+ class DirectoryNode:
19
+ """A directory in the explorer view."""
20
+
21
+ name: str
22
+ path: str
23
+ file_count: int = 0
24
+ total_lines: int = 0
25
+ languages: dict[str, int] = field(default_factory=dict)
26
+ subdirs: int = 0
27
+
28
+
29
+ @dataclass(slots=True)
30
+ class FileNode:
31
+ """A file in the explorer view."""
32
+
33
+ name: str
34
+ path: str
35
+ relative_path: str
36
+ importance: float = 0.0
37
+ line_count: int = 0
38
+ language: str = ""
39
+ top_symbols: list[str] = field(default_factory=list)
40
+ tags: list[str] = field(default_factory=list)
41
+
42
+
43
+ @dataclass(slots=True)
44
+ class ExplorerResult:
45
+ """Result of exploring a directory."""
46
+
47
+ path: str
48
+ breadcrumbs: list[str]
49
+ total_files: int
50
+ total_lines: int
51
+ directories: list[DirectoryNode]
52
+ files: list[FileNode]
53
+ omitted_files: int = 0
54
+ omitted_dirs: int = 0
55
+
56
+
57
+ class HierarchicalExplorer:
58
+ """Interactive BFS codebase explorer with drill-down navigation.
59
+
60
+ Exactly ONE directory level deep per call, with predictable
61
+ token cost (~500-2000 tokens per result).
62
+
63
+ Usage::
64
+
65
+ explorer = HierarchicalExplorer(codebase_context)
66
+
67
+ # Top-level overview
68
+ result = explorer.explore()
69
+
70
+ # Drill into a directory
71
+ result = explorer.explore("src/attocode/integrations")
72
+
73
+ # With filters
74
+ result = explorer.explore("src", max_items=50, importance_threshold=0.3)
75
+ """
76
+
77
+ def __init__(self, context_manager: Any, ast_service: Any = None) -> None:
78
+ self._ctx = context_manager
79
+ self._ast = ast_service
80
+ self._cache: OrderedDict[str, ExplorerResult] = OrderedDict()
81
+ self._max_cache = 100
82
+
83
+ def explore(
84
+ self,
85
+ path: str = "",
86
+ *,
87
+ max_items: int = 30,
88
+ importance_threshold: float = 0.3,
89
+ task_scores: dict[str, float] | None = None,
90
+ ) -> ExplorerResult:
91
+ """Explore one level of a directory.
92
+
93
+ Args:
94
+ path: Relative path to explore ("" for root).
95
+ max_items: Maximum items (dirs + files) to return.
96
+ importance_threshold: Minimum importance for files to be shown.
97
+
98
+ Returns:
99
+ ExplorerResult with dirs, files, and breadcrumbs.
100
+ """
101
+ cache_key = f"{path}|{max_items}|{importance_threshold}" if not task_scores else None
102
+ if cache_key is not None and cache_key in self._cache:
103
+ self._cache.move_to_end(cache_key)
104
+ return self._cache[cache_key]
105
+
106
+ self._ctx._ensure_fresh()
107
+ files = self._ctx._files
108
+
109
+ # Normalize path
110
+ path = path.strip("/").rstrip("/") if path else ""
111
+
112
+ # Filter files to those in this directory (direct children or descendants)
113
+ if path:
114
+ prefix = path + "/"
115
+ relevant = [f for f in files if f.relative_path.startswith(prefix)]
116
+ else:
117
+ relevant = list(files)
118
+
119
+ if not relevant and path:
120
+ # Try exact match (file, not directory)
121
+ exact = [f for f in files if f.relative_path == path]
122
+ if exact:
123
+ return self._file_detail(exact[0], path)
124
+
125
+ # Group into direct children (dirs and files at this level)
126
+ dir_stats: dict[str, _DirAccum] = {}
127
+ direct_files: list[Any] = []
128
+ _depth = path.count("/") + 1 if path else 0
129
+
130
+ for f in relevant:
131
+ rel = f.relative_path
132
+ child = rel[len(path) + 1:] if path else rel
133
+
134
+ parts = child.split("/")
135
+ if len(parts) == 1:
136
+ # Direct file child
137
+ direct_files.append(f)
138
+ else:
139
+ # In a subdirectory
140
+ subdir_name = parts[0]
141
+ subdir_path = f"{path}/{subdir_name}" if path else subdir_name
142
+ if subdir_path not in dir_stats:
143
+ dir_stats[subdir_path] = _DirAccum(name=subdir_name, path=subdir_path)
144
+ acc = dir_stats[subdir_path]
145
+ acc.file_count += 1
146
+ acc.total_lines += f.line_count
147
+ if f.language:
148
+ acc.languages[f.language] = acc.languages.get(f.language, 0) + 1
149
+ # Count unique immediate subdirectories within this subdir
150
+ if len(parts) > 2:
151
+ acc.seen_subdirs.add(parts[1])
152
+
153
+ # Build directory nodes
154
+ directories: list[DirectoryNode] = []
155
+ for _sp, acc in sorted(dir_stats.items()):
156
+ directories.append(DirectoryNode(
157
+ name=acc.name,
158
+ path=acc.path,
159
+ file_count=acc.file_count,
160
+ total_lines=acc.total_lines,
161
+ languages=dict(acc.languages),
162
+ subdirs=len(acc.seen_subdirs),
163
+ ))
164
+ if task_scores:
165
+ directories.sort(key=lambda node: (
166
+ -max((score for file_path, score in task_scores.items()
167
+ if file_path.startswith(node.path + "/")), default=0.0),
168
+ node.path,
169
+ ))
170
+
171
+ # Build file nodes (filtered by importance threshold)
172
+ file_nodes: list[FileNode] = []
173
+ for f in sorted(direct_files, key=lambda x: (
174
+ -(task_scores or {}).get(x.relative_path, 0.0), -x.importance, x.relative_path,
175
+ )):
176
+ if (f.importance < importance_threshold and len(file_nodes) >= 5
177
+ and not (task_scores or {}).get(f.relative_path)):
178
+ break
179
+ tags: list[str] = []
180
+ if f.is_config:
181
+ tags.append("config")
182
+ if f.is_test:
183
+ tags.append("test")
184
+ name = os.path.basename(f.relative_path).lower()
185
+ if name in ("main.py", "main.ts", "app.py", "cli.py", "__init__.py",
186
+ "index.ts", "index.js"):
187
+ tags.append("entry")
188
+
189
+ # Get top symbols if AST service is available
190
+ top_syms: list[str] = []
191
+ if self._ast and self._ast.initialized:
192
+ cached = self._ast._ast_cache.get(f.relative_path)
193
+ if cached:
194
+ top_syms = cached.get_symbols()[:5]
195
+
196
+ file_nodes.append(FileNode(
197
+ name=os.path.basename(f.relative_path),
198
+ path=f.path,
199
+ relative_path=f.relative_path,
200
+ importance=round(f.importance, 2),
201
+ line_count=f.line_count,
202
+ language=f.language,
203
+ top_symbols=top_syms,
204
+ tags=tags,
205
+ ))
206
+
207
+ # Enforce max_items limit
208
+ total_items = len(directories) + len(file_nodes)
209
+ omitted_dir_count = 0
210
+ if total_items > max_items:
211
+ # Prioritize directories, then high-importance files
212
+ dir_budget = min(len(directories), max_items // 2)
213
+ file_budget = max_items - dir_budget
214
+ omitted_dir_count = max(0, len(directories) - dir_budget)
215
+ directories = directories[:dir_budget]
216
+ file_nodes = file_nodes[:file_budget]
217
+
218
+ omitted = len(direct_files) - len(file_nodes)
219
+
220
+ # Build breadcrumbs
221
+ breadcrumbs = ["(root)"]
222
+ if path:
223
+ parts = path.split("/")
224
+ for i in range(len(parts)):
225
+ breadcrumbs.append("/".join(parts[:i + 1]))
226
+
227
+ result = ExplorerResult(
228
+ path=path or "(root)",
229
+ breadcrumbs=breadcrumbs,
230
+ total_files=len(relevant),
231
+ total_lines=sum(f.line_count for f in relevant),
232
+ directories=directories,
233
+ files=file_nodes,
234
+ omitted_files=max(0, omitted),
235
+ omitted_dirs=omitted_dir_count,
236
+ )
237
+
238
+ # Cache with LRU eviction
239
+ if cache_key is not None:
240
+ self._cache[cache_key] = result
241
+ if len(self._cache) > self._max_cache:
242
+ self._cache.popitem(last=False)
243
+
244
+ return result
245
+
246
+ def _file_detail(self, file_info: Any, path: str) -> ExplorerResult:
247
+ """Return a single file as an explorer result."""
248
+ tags: list[str] = []
249
+ if file_info.is_config:
250
+ tags.append("config")
251
+ if file_info.is_test:
252
+ tags.append("test")
253
+ fn = FileNode(
254
+ name=os.path.basename(file_info.relative_path),
255
+ path=file_info.path,
256
+ relative_path=file_info.relative_path,
257
+ importance=round(file_info.importance, 2),
258
+ line_count=file_info.line_count,
259
+ language=file_info.language,
260
+ tags=tags,
261
+ )
262
+ return ExplorerResult(
263
+ path=path,
264
+ breadcrumbs=["(root)"] + path.split("/"),
265
+ total_files=1,
266
+ total_lines=file_info.line_count,
267
+ directories=[],
268
+ files=[fn],
269
+ )
270
+
271
+ def invalidate(self, path: str = "") -> None:
272
+ """Invalidate cached results for a path or all paths."""
273
+ if not path:
274
+ self._cache.clear()
275
+ return
276
+ keys_to_remove = [k for k in self._cache if k.startswith(path + "|") or k.startswith(path + "/")]
277
+ for k in keys_to_remove:
278
+ del self._cache[k]
279
+
280
+ def format_result(self, result: ExplorerResult) -> str:
281
+ """Format an ExplorerResult as a human-readable string."""
282
+ lines: list[str] = []
283
+
284
+ # Header
285
+ lines.append(f"{result.path} ({result.total_files} files, {result.total_lines:,} lines)")
286
+ lines.append(f"Breadcrumb: {' > '.join(result.breadcrumbs)}")
287
+ lines.append("")
288
+
289
+ # Directories
290
+ for d in result.directories:
291
+ lang_str = ""
292
+ if d.languages:
293
+ top_langs = sorted(d.languages.items(), key=lambda x: -x[1])[:3]
294
+ lang_str = f" [{', '.join(line for line, _ in top_langs)}]"
295
+ lines.append(
296
+ f" [dir] {d.name}/ "
297
+ f"{d.file_count} files {d.total_lines:,} lines{lang_str}"
298
+ )
299
+
300
+ # Files
301
+ for f in result.files:
302
+ tags_str = f" [{', '.join(f.tags)}]" if f.tags else ""
303
+ syms_str = ""
304
+ if f.top_symbols:
305
+ syms_str = f" [{', '.join(f.top_symbols)}]"
306
+ lines.append(
307
+ f" [file] {f.name} "
308
+ f"{f.line_count}L imp={f.importance}{tags_str}{syms_str}"
309
+ )
310
+
311
+ if result.omitted_files > 0 or result.omitted_dirs > 0:
312
+ parts = []
313
+ if result.omitted_files > 0:
314
+ parts.append(f"{result.omitted_files} more files")
315
+ if result.omitted_dirs > 0:
316
+ parts.append(f"{result.omitted_dirs} more directories")
317
+ lines.append(f" ... +{', '.join(parts)}")
318
+
319
+ return "\n".join(lines)
320
+
321
+
322
+ @dataclass(slots=True)
323
+ class _DirAccum:
324
+ """Accumulator for directory statistics."""
325
+
326
+ name: str
327
+ path: str
328
+ file_count: int = 0
329
+ total_lines: int = 0
330
+ languages: dict[str, int] = field(default_factory=dict)
331
+ seen_subdirs: set[str] = field(default_factory=set)