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,582 @@
1
+ """Dead code detection tools for the code-intel MCP server.
2
+
3
+ Provides a ``dead_code`` tool that identifies unreferenced symbols, files,
4
+ and modules using the cross-reference index. Three analysis levels:
5
+
6
+ - ``symbol``: Functions, classes, and methods with zero references.
7
+ - ``file``: Files with zero importers that are not entry points.
8
+ - ``module``: Directories with no external imports from outside.
9
+
10
+ Entry points (tests, CLI mains, framework routes, etc.) are automatically
11
+ excluded using filename and decorator heuristics.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import os
17
+ import time
18
+ from typing import TYPE_CHECKING
19
+
20
+ try:
21
+ from attocode_intel._shared import (
22
+ _get_ast_service,
23
+ _get_context_mgr,
24
+ _get_project_dir,
25
+ _get_remote_service,
26
+ mcp,
27
+ )
28
+ except (ImportError, AttributeError):
29
+ # Deferred: will be available when server.py finishes loading.
30
+ # Private helpers (_find_dead_*) don't need these imports.
31
+ _get_ast_service = None # type: ignore[assignment]
32
+ _get_context_mgr = None # type: ignore[assignment]
33
+ _get_project_dir = None # type: ignore[assignment]
34
+ _get_remote_service = None # type: ignore[assignment]
35
+
36
+ class _StubMCP:
37
+ """Stub so @mcp.tool() doesn't crash during deferred import."""
38
+ def tool(self):
39
+ return lambda f: f
40
+ mcp = _StubMCP() # type: ignore[assignment]
41
+
42
+ if TYPE_CHECKING:
43
+ from attocode_intel._internal.integrations.context.cross_references import CrossRefIndex, SymbolLocation
44
+
45
+
46
+ # ---------------------------------------------------------------------------
47
+ # Entry-point heuristics
48
+ # ---------------------------------------------------------------------------
49
+
50
+ _ENTRY_POINT_FILE_PATTERNS: set[str] = {
51
+ "main.py",
52
+ "__main__.py",
53
+ "__init__.py",
54
+ "setup.py",
55
+ "manage.py",
56
+ "conftest.py",
57
+ }
58
+
59
+ _ENTRY_POINT_FILE_PREFIXES = ("test_",)
60
+ _ENTRY_POINT_FILE_SUFFIXES = ("_test.py",)
61
+
62
+ _ENTRY_POINT_DECORATOR_KEYWORDS: set[str] = {
63
+ "route",
64
+ "endpoint",
65
+ "fixture",
66
+ "command",
67
+ "click",
68
+ }
69
+
70
+ _ENTRY_POINT_FUNCTION_NAMES: set[str] = {
71
+ "main",
72
+ "setup",
73
+ "teardown",
74
+ }
75
+
76
+
77
+ def _is_entry_point_file(rel_path: str) -> bool:
78
+ """Return True if *rel_path* looks like a test, CLI main, or config file."""
79
+ basename = os.path.basename(rel_path)
80
+ if basename in _ENTRY_POINT_FILE_PATTERNS:
81
+ return True
82
+ if any(basename.startswith(p) for p in _ENTRY_POINT_FILE_PREFIXES):
83
+ return True
84
+ return any(basename.endswith(s) for s in _ENTRY_POINT_FILE_SUFFIXES)
85
+
86
+
87
+ def _is_entry_point_symbol(
88
+ loc: SymbolLocation,
89
+ ast_cache: dict,
90
+ ) -> bool:
91
+ """Return True if the symbol at *loc* looks like a framework entry point.
92
+
93
+ Checks decorator names and special function names.
94
+ """
95
+ # Special function names
96
+ base_name = loc.name.rsplit(".", 1)[-1] if "." in loc.name else loc.name
97
+ if base_name in _ENTRY_POINT_FUNCTION_NAMES:
98
+ return True
99
+
100
+ # Symbols defined in __init__.py are considered re-exports
101
+ if os.path.basename(loc.file_path) == "__init__.py":
102
+ return True
103
+
104
+ # Check decorators from AST cache
105
+ file_ast = ast_cache.get(loc.file_path)
106
+ if file_ast is None:
107
+ return False
108
+
109
+ # Search functions and class methods for matching decorator keywords
110
+ for func in file_ast.functions:
111
+ if func.name == base_name or loc.qualified_name.endswith(f".{func.name}"):
112
+ for dec in func.decorators:
113
+ if any(kw in dec.lower() for kw in _ENTRY_POINT_DECORATOR_KEYWORDS):
114
+ return True
115
+
116
+ for cls in file_ast.classes:
117
+ if cls.name == base_name or loc.qualified_name.endswith(f".{cls.name}"):
118
+ for dec in cls.decorators:
119
+ if any(kw in dec.lower() for kw in _ENTRY_POINT_DECORATOR_KEYWORDS):
120
+ return True
121
+ # Check methods inside the class
122
+ for method in cls.methods:
123
+ qname_suffix = f"{cls.name}.{method.name}"
124
+ if loc.qualified_name.endswith(qname_suffix):
125
+ for dec in method.decorators:
126
+ if any(kw in dec.lower() for kw in _ENTRY_POINT_DECORATOR_KEYWORDS):
127
+ return True
128
+
129
+ return False
130
+
131
+
132
+ # ---------------------------------------------------------------------------
133
+ # Confidence scoring
134
+ # ---------------------------------------------------------------------------
135
+
136
+
137
+ def _compute_confidence(
138
+ loc: SymbolLocation,
139
+ index: CrossRefIndex,
140
+ project_dir: str,
141
+ ) -> float:
142
+ """Compute a confidence score (0.0-1.0) that *loc* is truly dead code.
143
+
144
+ Higher confidence = more likely to be safe to remove.
145
+ """
146
+ name = loc.name
147
+ is_private = name.startswith("_") and not name.startswith("__")
148
+
149
+ # Base score
150
+ confidence = 0.8 if is_private else 0.7
151
+
152
+ # Penalty: symbol defined in __init__.py (likely a re-export)
153
+ if os.path.basename(loc.file_path) == "__init__.py":
154
+ confidence -= 0.3
155
+
156
+ # Penalty: looks like a public API (no underscore prefix + exported)
157
+ if not name.startswith("_"):
158
+ confidence -= 0.2
159
+
160
+ # Bonus: file hasn't been modified recently (> 180 days)
161
+ try:
162
+ abs_path = os.path.join(project_dir, loc.file_path)
163
+ mtime = os.path.getmtime(abs_path)
164
+ age_days = (time.time() - mtime) / 86400
165
+ if age_days > 180:
166
+ confidence += 0.1
167
+ except OSError:
168
+ pass
169
+
170
+ return max(0.0, min(1.0, round(confidence, 2)))
171
+
172
+
173
+ # ---------------------------------------------------------------------------
174
+ # Dead-code finders
175
+ # ---------------------------------------------------------------------------
176
+
177
+
178
+ def _find_dead_symbols(
179
+ index: CrossRefIndex,
180
+ ast_cache: dict,
181
+ project_dir: str,
182
+ scope: str,
183
+ entry_points: set[str],
184
+ min_confidence: float,
185
+ top_n: int,
186
+ ) -> list[dict]:
187
+ """Find symbols with 0 references, excluding entry points."""
188
+ candidates: list[dict] = []
189
+
190
+ for qname, locs in index.definitions.items():
191
+ for loc in locs:
192
+ # Skip if outside scope
193
+ if scope and not loc.file_path.startswith(scope):
194
+ continue
195
+
196
+ # Skip user-specified entry points
197
+ if qname in entry_points or loc.name in entry_points:
198
+ continue
199
+
200
+ # Skip auto-detected entry points
201
+ if _is_entry_point_file(loc.file_path):
202
+ continue
203
+ if _is_entry_point_symbol(loc, ast_cache):
204
+ continue
205
+
206
+ # Check references: look up by both qualified name and base name
207
+ refs_by_qname = index.references.get(qname, [])
208
+ base_name = qname.rsplit(".", 1)[-1] if "." in qname else qname
209
+ refs_by_base = index.references.get(base_name, [])
210
+
211
+ # Filter out self-references (references from the same file)
212
+ external_refs_q = [
213
+ r for r in refs_by_qname if r.file_path != loc.file_path
214
+ ]
215
+ external_refs_b = [
216
+ r for r in refs_by_base if r.file_path != loc.file_path
217
+ ]
218
+
219
+ # If there are any external references, it's not dead
220
+ if external_refs_q or external_refs_b:
221
+ continue
222
+
223
+ # Also check if the base name has any references at all (suffix match)
224
+ # For methods like "ClassName.method", check if "method" is referenced
225
+ has_any_ref = False
226
+ if "." in qname:
227
+ for ref_name, refs in index.references.items():
228
+ if ref_name == base_name or ref_name.endswith(f".{base_name}"):
229
+ external = [
230
+ r for r in refs if r.file_path != loc.file_path
231
+ ]
232
+ if external:
233
+ has_any_ref = True
234
+ break
235
+ if has_any_ref:
236
+ continue
237
+
238
+ # Compute confidence
239
+ confidence = _compute_confidence(loc, index, project_dir)
240
+ if confidence < min_confidence:
241
+ continue
242
+
243
+ # Determine reason
244
+ reasons: list[str] = []
245
+ if loc.kind == "method":
246
+ reasons.append("method with no external callers")
247
+ elif loc.kind == "class":
248
+ reasons.append("class never instantiated or referenced")
249
+ else:
250
+ reasons.append("function never called or imported")
251
+
252
+ if loc.name.startswith("_"):
253
+ reasons.append("private symbol")
254
+
255
+ candidates.append({
256
+ "symbol": loc.qualified_name,
257
+ "name": loc.name,
258
+ "kind": loc.kind,
259
+ "file": loc.file_path,
260
+ "line": loc.start_line,
261
+ "end_line": loc.end_line,
262
+ "confidence": confidence,
263
+ "reason": "; ".join(reasons),
264
+ })
265
+
266
+ # Sort by confidence descending, then by file/line
267
+ candidates.sort(key=lambda c: (-c["confidence"], c["file"], c["line"]))
268
+ return candidates[:top_n]
269
+
270
+
271
+ def _find_dead_files(
272
+ index: CrossRefIndex,
273
+ all_file_paths: set[str],
274
+ scope: str,
275
+ entry_points: set[str],
276
+ min_confidence: float,
277
+ top_n: int,
278
+ project_dir: str,
279
+ ) -> list[dict]:
280
+ """Find files with 0 importers that are not entry points."""
281
+ candidates: list[dict] = []
282
+
283
+ for rel_path in sorted(all_file_paths):
284
+ # Skip if outside scope
285
+ if scope and not rel_path.startswith(scope):
286
+ continue
287
+
288
+ # Skip user-specified entry points
289
+ if rel_path in entry_points:
290
+ continue
291
+
292
+ # Skip auto-detected entry-point files
293
+ if _is_entry_point_file(rel_path):
294
+ continue
295
+
296
+ # Check importers
297
+ importers = index.file_dependents.get(rel_path, set())
298
+ if importers:
299
+ continue
300
+
301
+ # Compute file-level confidence
302
+ basename = os.path.basename(rel_path)
303
+ is_private = basename.startswith("_") and basename != "__init__.py"
304
+ confidence = 0.8 if is_private else 0.7
305
+
306
+ # Penalty for __init__.py
307
+ if basename == "__init__.py":
308
+ confidence -= 0.3
309
+
310
+ # Penalty for public-looking files
311
+ if not basename.startswith("_"):
312
+ confidence -= 0.2
313
+
314
+ # Bonus for old files
315
+ try:
316
+ abs_path = os.path.join(project_dir, rel_path)
317
+ mtime = os.path.getmtime(abs_path)
318
+ age_days = (time.time() - mtime) / 86400
319
+ if age_days > 180:
320
+ confidence += 0.1
321
+ except OSError:
322
+ pass
323
+
324
+ confidence = max(0.0, min(1.0, round(confidence, 2)))
325
+ if confidence < min_confidence:
326
+ continue
327
+
328
+ # Count symbols in the file
329
+ symbol_count = len(index.file_symbols.get(rel_path, set()))
330
+ deps_count = len(index.file_dependencies.get(rel_path, set()))
331
+
332
+ reasons: list[str] = ["no files import this module"]
333
+ if deps_count == 0:
334
+ reasons.append("also has no imports (orphan)")
335
+
336
+ candidates.append({
337
+ "file": rel_path,
338
+ "symbol_count": symbol_count,
339
+ "dependency_count": deps_count,
340
+ "confidence": confidence,
341
+ "reason": "; ".join(reasons),
342
+ })
343
+
344
+ candidates.sort(key=lambda c: (-c["confidence"], c["file"]))
345
+ return candidates[:top_n]
346
+
347
+
348
+ def _find_dead_modules(
349
+ index: CrossRefIndex,
350
+ all_file_paths: set[str],
351
+ scope: str,
352
+ entry_points: set[str],
353
+ min_confidence: float,
354
+ top_n: int,
355
+ project_dir: str,
356
+ ) -> list[dict]:
357
+ """Find directories where no file is imported from outside the directory."""
358
+ # Group files by directory
359
+ dir_files: dict[str, set[str]] = {}
360
+ for rel_path in all_file_paths:
361
+ dir_name = os.path.dirname(rel_path)
362
+ if not dir_name:
363
+ continue # skip root-level files
364
+ dir_files.setdefault(dir_name, set()).add(rel_path)
365
+
366
+ candidates: list[dict] = []
367
+
368
+ for dir_path, files_in_dir in sorted(dir_files.items()):
369
+ # Skip if outside scope
370
+ if scope and not dir_path.startswith(scope):
371
+ continue
372
+
373
+ # Skip user-specified entry points
374
+ if dir_path in entry_points:
375
+ continue
376
+
377
+ # Check if any file in this directory is imported from outside
378
+ has_external_importer = False
379
+ for f in files_in_dir:
380
+ importers = index.file_dependents.get(f, set())
381
+ for imp in importers:
382
+ if os.path.dirname(imp) != dir_path:
383
+ has_external_importer = True
384
+ break
385
+ if has_external_importer:
386
+ break
387
+
388
+ if has_external_importer:
389
+ continue
390
+
391
+ # Check if any file in the directory is an entry point
392
+ has_entry_point = any(_is_entry_point_file(f) for f in files_in_dir)
393
+
394
+ # Base confidence
395
+ confidence = 0.7
396
+ if has_entry_point:
397
+ confidence -= 0.3 # might be a standalone executable package
398
+
399
+ # Bonus for old directories
400
+ try:
401
+ newest_mtime = 0.0
402
+ for f in files_in_dir:
403
+ abs_path = os.path.join(project_dir, f)
404
+ try:
405
+ mt = os.path.getmtime(abs_path)
406
+ newest_mtime = max(newest_mtime, mt)
407
+ except OSError:
408
+ pass
409
+ if newest_mtime > 0:
410
+ age_days = (time.time() - newest_mtime) / 86400
411
+ if age_days > 180:
412
+ confidence += 0.1
413
+ except OSError:
414
+ pass
415
+
416
+ confidence = max(0.0, min(1.0, round(confidence, 2)))
417
+ if confidence < min_confidence:
418
+ continue
419
+
420
+ # Count total symbols across all files in directory
421
+ total_symbols = sum(
422
+ len(index.file_symbols.get(f, set())) for f in files_in_dir
423
+ )
424
+
425
+ reasons: list[str] = [
426
+ f"no external imports into this directory ({len(files_in_dir)} files)"
427
+ ]
428
+ if has_entry_point:
429
+ reasons.append("contains entry-point files (lower confidence)")
430
+
431
+ candidates.append({
432
+ "module": dir_path,
433
+ "file_count": len(files_in_dir),
434
+ "symbol_count": total_symbols,
435
+ "confidence": confidence,
436
+ "reason": "; ".join(reasons),
437
+ })
438
+
439
+ candidates.sort(key=lambda c: (-c["confidence"], c["module"]))
440
+ return candidates[:top_n]
441
+
442
+
443
+ # ---------------------------------------------------------------------------
444
+ # MCP Tool
445
+ # ---------------------------------------------------------------------------
446
+
447
+
448
+ @mcp.tool()
449
+ def dead_code(
450
+ scope: str = "",
451
+ entry_points: list[str] | None = None,
452
+ level: str = "symbol",
453
+ min_confidence: float = 0.5,
454
+ top_n: int = 30,
455
+ ) -> str:
456
+ """Detect potentially dead (unreferenced) code in the project.
457
+
458
+ Scans the cross-reference index for symbols, files, or modules that
459
+ have zero external references and are not detected as entry points.
460
+ Results are ranked by confidence score.
461
+
462
+ Args:
463
+ scope: Restrict analysis to files under this directory prefix
464
+ (e.g. "src/mypackage/"). Empty string means whole project.
465
+ entry_points: Additional qualified names or file paths to treat as
466
+ entry points (never flagged as dead). Auto-detected
467
+ entry points (tests, mains, framework routes) are
468
+ always excluded.
469
+ level: Analysis granularity. One of:
470
+ - "symbol": individual functions, classes, methods (default)
471
+ - "file": entire files with no importers
472
+ - "module": directories with no external imports
473
+ min_confidence: Minimum confidence threshold (0.0-1.0). Only report
474
+ items at or above this score. Default 0.5.
475
+ top_n: Maximum number of results to return. Default 30.
476
+ """
477
+ if _get_remote_service is not None:
478
+ remote = _get_remote_service()
479
+ if remote is not None:
480
+ return remote.dead_code(
481
+ scope=scope,
482
+ entry_points=entry_points,
483
+ level=level,
484
+ min_confidence=min_confidence,
485
+ top_n=top_n,
486
+ )
487
+
488
+ valid_levels = {"symbol", "file", "module"}
489
+ if level not in valid_levels:
490
+ opts = ", ".join(sorted(valid_levels))
491
+ return f"Error: invalid level '{level}'. Must be one of: {opts}"
492
+
493
+ min_confidence = max(0.0, min(1.0, min_confidence))
494
+ top_n = max(1, min(200, top_n))
495
+
496
+ svc = _get_ast_service()
497
+ index = svc._index
498
+ ast_cache = svc._ast_cache
499
+ project_dir = _get_project_dir()
500
+
501
+ ctx = _get_context_mgr()
502
+ all_file_paths = {fi.relative_path for fi in ctx._files}
503
+
504
+ ep_set = set(entry_points) if entry_points else set()
505
+
506
+ # Normalize scope
507
+ if scope and not scope.endswith("/"):
508
+ scope = scope + "/"
509
+
510
+ if level == "symbol":
511
+ items = _find_dead_symbols(
512
+ index, ast_cache, project_dir, scope, ep_set,
513
+ min_confidence, top_n,
514
+ )
515
+ return _format_symbol_results(items, scope)
516
+ elif level == "file":
517
+ items = _find_dead_files(
518
+ index, all_file_paths, scope, ep_set,
519
+ min_confidence, top_n, project_dir,
520
+ )
521
+ return _format_file_results(items, scope)
522
+ else: # module
523
+ items = _find_dead_modules(
524
+ index, all_file_paths, scope, ep_set,
525
+ min_confidence, top_n, project_dir,
526
+ )
527
+ return _format_module_results(items, scope)
528
+
529
+
530
+ # ---------------------------------------------------------------------------
531
+ # Output formatters
532
+ # ---------------------------------------------------------------------------
533
+
534
+
535
+ def _format_symbol_results(items: list[dict], scope: str) -> str:
536
+ """Format dead symbol results as ranked text output."""
537
+ scope_label = f" in {scope}" if scope else ""
538
+ if not items:
539
+ return f"No dead symbols detected{scope_label}."
540
+
541
+ lines = [f"Dead symbols detected{scope_label} ({len(items)} results):\n"]
542
+ for i, item in enumerate(items, 1):
543
+ lines.append(
544
+ f" {i:2d}. [{item['confidence']:.2f}] {item['kind']} {item['symbol']}\n"
545
+ f" {item['file']}:{item['line']}\n"
546
+ f" Reason: {item['reason']}"
547
+ )
548
+ return "\n".join(lines)
549
+
550
+
551
+ def _format_file_results(items: list[dict], scope: str) -> str:
552
+ """Format dead file results as ranked text output."""
553
+ scope_label = f" in {scope}" if scope else ""
554
+ if not items:
555
+ return f"No dead files detected{scope_label}."
556
+
557
+ lines = [f"Dead files detected{scope_label} ({len(items)} results):\n"]
558
+ for i, item in enumerate(items, 1):
559
+ lines.append(
560
+ f" {i:2d}. [{item['confidence']:.2f}] {item['file']}\n"
561
+ f" {item['symbol_count']} symbols, "
562
+ f"{item['dependency_count']} dependencies\n"
563
+ f" Reason: {item['reason']}"
564
+ )
565
+ return "\n".join(lines)
566
+
567
+
568
+ def _format_module_results(items: list[dict], scope: str) -> str:
569
+ """Format dead module results as ranked text output."""
570
+ scope_label = f" in {scope}" if scope else ""
571
+ if not items:
572
+ return f"No dead modules detected{scope_label}."
573
+
574
+ lines = [f"Dead modules detected{scope_label} ({len(items)} results):\n"]
575
+ for i, item in enumerate(items, 1):
576
+ lines.append(
577
+ f" {i:2d}. [{item['confidence']:.2f}] {item['module']}/\n"
578
+ f" {item['file_count']} files, "
579
+ f"{item['symbol_count']} symbols\n"
580
+ f" Reason: {item['reason']}"
581
+ )
582
+ return "\n".join(lines)