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,734 @@
1
+ """MCP server exposing Attocode's code intelligence capabilities.
2
+
3
+ Tool families (full list registered at runtime; query via your MCP
4
+ client's tool-listing endpoint):
5
+
6
+ - **Navigation & repo map**: bootstrap, relevant_context, repo_map,
7
+ symbols, search_symbols, file_analysis, project_summary,
8
+ explore_codebase, find_related, community_detection
9
+ - **Cross-refs & impact**: dependencies, impact_analysis,
10
+ cross_references, dependency_graph, graph_query, graph_dsl,
11
+ call_graph, explain_impact
12
+ - **LSP**: lsp_definition, lsp_references, lsp_hover, lsp_diagnostics
13
+ - **Search**: semantic_search, semantic_search_status, fast_search,
14
+ fuzzy_search, frecency_search, regex_search, cross_mode_search
15
+ - **Rules engine**: analyze, list_rules, list_packs, install_pack,
16
+ install_community_pack, register_rule, test_rules, ci_scan,
17
+ rule_stats, rule_feedback, rule_hygiene, synthesize_rule,
18
+ evolve_rules, import_rules
19
+ - **Quality / hygiene**: hotspots, conventions, dead_code, distill,
20
+ security_scan, suggest_tests, review_change, readiness_report
21
+ - **History**: code_evolution, recent_changes, churn_hotspots,
22
+ change_coupling
23
+ - **Memory & learning**: recall, record_learning, learning_feedback,
24
+ list_learnings, record_adr, list_adrs, get_adr, update_adr_status
25
+
26
+ Language packs shipped: **go, python, typescript, rust, java, cpp,
27
+ csharp, php, ruby, kotlin** (10 packs, ~91 rules — Tier-1 regex plus
28
+ Tier-2 ast-grep structural patterns where ast-grep is on PATH).
29
+
30
+ Usage::
31
+
32
+ attocode-code-intel --project /path/to/repo
33
+
34
+ Remote mode (proxy through a remote code-intel HTTP server)::
35
+
36
+ attocode-code-intel --project . --remote https://code.example.com --remote-token <jwt> --remote-repo <uuid>
37
+
38
+ Remote connection can also be auto-loaded from ``.attocode/config.toml``
39
+ (created by ``attocode code-intel connect``).
40
+ """
41
+
42
+ from __future__ import annotations
43
+
44
+ import logging
45
+ import os
46
+ import sys
47
+ import threading
48
+ from pathlib import Path
49
+
50
+ import attocode_intel._shared as _shared # noqa: F401
51
+
52
+ # ---------------------------------------------------------------------------
53
+ # Shared deps (mcp instance, lazy singletons, getters) live in _shared.py
54
+ # to break the circular import: server.py → tool modules → server.py.
55
+ # Re-exported here for backward compatibility.
56
+ # ---------------------------------------------------------------------------
57
+ from attocode_intel._shared import ( # noqa: F401
58
+ _get_ast_service,
59
+ _get_code_analyzer,
60
+ _get_context_mgr,
61
+ _get_explorer,
62
+ _get_project_dir,
63
+ _walk_up,
64
+ clear_remote_service,
65
+ configure_remote_service,
66
+ enable_remote_if_configured,
67
+ mcp,
68
+ )
69
+
70
+ logger = logging.getLogger(__name__)
71
+
72
+ # Backward-compat: tests and internal code set server._ast_service = mock.
73
+ # The getters (in _shared) read _shared._ast_service, so we must ensure
74
+ # that setting server._ast_service also sets _shared._ast_service.
75
+ # We do this by NOT declaring local variables here and using __getattr__
76
+ # to proxy reads, plus __setattr__ to proxy writes via setattr().
77
+ _SINGLETON_VARS = frozenset({
78
+ "_ast_service", "_context_mgr", "_code_analyzer",
79
+ "_semantic_search", "_memory_store", "_explorer",
80
+ "_service", "_remote_service",
81
+ })
82
+
83
+ def __getattr__(name: str):
84
+ if name in _SINGLETON_VARS:
85
+ return getattr(_shared, name, None)
86
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
87
+
88
+ # ---------------------------------------------------------------------------
89
+ # MCP Resource: Agent Guidelines
90
+ # ---------------------------------------------------------------------------
91
+
92
+ _GUIDELINES_PATH = Path(__file__).parent / "GUIDELINES.md"
93
+
94
+
95
+ @mcp.resource("attocode://guidelines")
96
+ def guidelines_resource() -> str:
97
+ """Agent guidelines for using code intelligence tools effectively."""
98
+ try:
99
+ return _GUIDELINES_PATH.read_text(encoding="utf-8")
100
+ except OSError:
101
+ return "Guidelines file not found."
102
+
103
+
104
+ # ---------------------------------------------------------------------------
105
+ # MCP Resources: Project files and symbol lookup
106
+ # ---------------------------------------------------------------------------
107
+
108
+
109
+ @mcp.resource("attocode://project/{path}")
110
+ def project_file_resource(path: str) -> str:
111
+ """File content with line numbers. Path-traversal protected.
112
+
113
+ Args:
114
+ path: Relative file path within the project.
115
+ """
116
+ project_dir = _get_project_dir()
117
+ root = Path(os.path.realpath(project_dir))
118
+ full = Path(os.path.realpath(os.path.join(project_dir, path)))
119
+ if not full.is_relative_to(root):
120
+ return "Error: path traversal not allowed."
121
+ if not full.is_file():
122
+ return f"Error: file not found: {path}"
123
+ try:
124
+ content = full.read_text(encoding="utf-8", errors="replace")
125
+ lines = content.split("\n")
126
+ numbered = [f"{i + 1:>5} | {line}" for i, line in enumerate(lines)]
127
+ return "\n".join(numbered)
128
+ except Exception as e:
129
+ return f"Error reading file: {e}"
130
+
131
+
132
+ @mcp.resource("attocode://symbols/{name}")
133
+ def symbol_resource(name: str) -> str:
134
+ """Symbol definition lookup with source snippets.
135
+
136
+ Args:
137
+ name: Symbol name to look up.
138
+ """
139
+ svc = _get_ast_service()
140
+ defs = svc.find_symbol(name)
141
+ if not defs:
142
+ return f"No definitions found for '{name}'."
143
+
144
+ project_dir = _get_project_dir()
145
+ root = Path(os.path.realpath(project_dir))
146
+
147
+ lines = [f"Definitions for '{name}':"]
148
+ for d in defs[:10]:
149
+ lines.append(f"\n {d.file_path}:{d.start_line}")
150
+ lines.append(f" Qualified: {d.qualified_name}")
151
+ # Try to read source snippet (path-traversal protected)
152
+ try:
153
+ full = Path(os.path.realpath(os.path.join(project_dir, d.file_path)))
154
+ if not full.is_relative_to(root):
155
+ continue
156
+ src_lines = full.read_text(encoding="utf-8", errors="replace").split("\n")
157
+ start = max(0, d.start_line - 1)
158
+ end = min(len(src_lines), d.end_line + 1)
159
+ for i in range(start, end):
160
+ lines.append(f" {i + 1:>5} | {src_lines[i]}")
161
+ except Exception:
162
+ pass
163
+ return "\n".join(lines)
164
+
165
+
166
+ @mcp.resource("attocode://learnings")
167
+ def learnings_resource() -> str:
168
+ """All active project learnings. Read this for full project knowledge base."""
169
+ from attocode_intel.tools.learning_tools import _get_memory_store
170
+
171
+ store = _get_memory_store()
172
+ results = store.list_all(status="active")
173
+ if not results:
174
+ return "No project learnings recorded yet."
175
+
176
+ lines = ["# Project Learnings\n"]
177
+ by_type: dict[str, list[dict]] = {}
178
+ for r in results:
179
+ by_type.setdefault(r["type"], []).append(r)
180
+
181
+ for type_name, entries in sorted(by_type.items()):
182
+ lines.append(f"\n## {type_name.title()} ({len(entries)})\n")
183
+ for r in entries:
184
+ scope_tag = f" [{r['scope']}]" if r["scope"] else ""
185
+ conf = f"{r['confidence']:.0%}"
186
+ lines.append(f"- **{r['description']}**{scope_tag} (id: {r['id']}, confidence: {conf})")
187
+ if r["details"]:
188
+ lines.append(f" {r['details']}")
189
+ return "\n".join(lines)
190
+
191
+
192
+ # ---------------------------------------------------------------------------
193
+ # Notification tool (explicit index update)
194
+ # ---------------------------------------------------------------------------
195
+
196
+
197
+ @mcp.tool()
198
+ def notify_file_changed(files: list[str]) -> str:
199
+ """Tell the server which files changed, so that the index and search read the new content.
200
+
201
+ A server that watches the project finds edits without this call.
202
+
203
+ Args:
204
+ files: Changed file paths, relative or absolute.
205
+ """
206
+ if not files:
207
+ return "No files specified."
208
+
209
+ project_dir = _get_project_dir()
210
+ svc = _get_ast_service()
211
+ updated = 0
212
+
213
+ for f in files:
214
+ try:
215
+ p = Path(f)
216
+ rel = os.path.relpath(str(p), project_dir) if p.is_absolute() else str(p)
217
+ # Guard against path traversal
218
+ rel = os.path.normpath(rel)
219
+ if rel.startswith(".."):
220
+ continue
221
+ svc.notify_file_changed(rel)
222
+ # Also invalidate semantic search embeddings
223
+ try:
224
+ smgr = _semantic_search
225
+ if smgr is None:
226
+ from attocode_intel.tools.search_tools import _get_semantic_search
227
+ smgr = _get_semantic_search()
228
+ abs_path = os.path.join(project_dir, rel)
229
+ smgr.invalidate_file(abs_path)
230
+ smgr.queue_reindex(abs_path)
231
+ except Exception:
232
+ pass
233
+ # Also invalidate trigram index
234
+ try:
235
+ from attocode_intel.tools.search_tools import _get_trigram_index
236
+ tri_idx = _get_trigram_index()
237
+ if tri_idx is not None:
238
+ abs_path = os.path.join(project_dir, rel)
239
+ tri_idx.update_file(rel, b"")
240
+ except Exception:
241
+ pass
242
+ updated += 1
243
+ except Exception as exc:
244
+ logger.debug("notify_file_changed: error for %s: %s", f, exc)
245
+
246
+ # Also drain the notification queue (CLI-written)
247
+ queued = _process_notification_queue()
248
+
249
+ total = updated + queued
250
+ return f"Updated {total} file(s). AST index refreshed."
251
+
252
+
253
+ # ---------------------------------------------------------------------------
254
+ # Notification queue (CLI -> server communication)
255
+ # ---------------------------------------------------------------------------
256
+
257
+
258
+ def _get_queue_path() -> Path:
259
+ """Return the path to the notification queue file."""
260
+ return Path(_get_project_dir()) / ".attocode" / "cache" / "file_changes"
261
+
262
+
263
+ _queue_lock = threading.Lock()
264
+
265
+
266
+ def _process_notification_queue() -> int:
267
+ """Check the notification queue file and process pending changes.
268
+
269
+ Returns number of files processed.
270
+ """
271
+ with _queue_lock:
272
+ return _process_notification_queue_locked()
273
+
274
+
275
+ def _process_notification_queue_locked() -> int:
276
+ """Inner implementation -- must be called under ``_queue_lock``."""
277
+ try:
278
+ queue_path = _get_queue_path()
279
+ except RuntimeError:
280
+ return 0
281
+
282
+ if not queue_path.exists():
283
+ return 0
284
+
285
+ try:
286
+ # Atomic read-and-truncate under exclusive lock to avoid TOCTOU
287
+ try:
288
+ import fcntl
289
+ with open(queue_path, "r+", encoding="utf-8") as fh:
290
+ fcntl.flock(fh, fcntl.LOCK_EX)
291
+ content = fh.read()
292
+ fh.seek(0)
293
+ fh.truncate()
294
+ except ImportError:
295
+ # Windows: fall back to non-locked read+truncate
296
+ content = queue_path.read_text(encoding="utf-8")
297
+ queue_path.write_text("", encoding="utf-8")
298
+ except OSError:
299
+ return 0
300
+
301
+ paths = [line.strip() for line in content.splitlines() if line.strip()]
302
+ if not paths:
303
+ return 0
304
+
305
+ project_dir = _get_project_dir()
306
+ svc = _get_ast_service()
307
+ count = 0
308
+
309
+ for rel in paths:
310
+ # Guard against path traversal (e.g. ../../etc/passwd)
311
+ norm = os.path.normpath(rel)
312
+ if norm.startswith(".."):
313
+ continue
314
+ try:
315
+ svc.notify_file_changed(norm)
316
+ try:
317
+ smgr = _semantic_search
318
+ if smgr is None:
319
+ from attocode_intel.tools.search_tools import _get_semantic_search
320
+ smgr = _get_semantic_search()
321
+ abs_path = os.path.join(project_dir, norm)
322
+ smgr.invalidate_file(abs_path)
323
+ smgr.queue_reindex(abs_path)
324
+ except Exception:
325
+ pass
326
+ try:
327
+ from attocode_intel.tools.search_tools import _get_trigram_index
328
+ tri_idx = _get_trigram_index()
329
+ if tri_idx is not None:
330
+ tri_idx.update_file(norm, b"")
331
+ except Exception:
332
+ pass
333
+ count += 1
334
+ except Exception:
335
+ pass
336
+
337
+ if count:
338
+ logger.debug("Processed %d queued file notification(s)", count)
339
+ return count
340
+
341
+
342
+ _queue_thread: threading.Thread | None = None
343
+
344
+
345
+ def _start_queue_poller(project_dir: str) -> None:
346
+ """Start a background thread that polls the notification queue every 2s."""
347
+ global _queue_thread
348
+
349
+ if _queue_thread is not None:
350
+ return
351
+
352
+ def _poll_loop() -> None:
353
+ while not _watcher_stop.is_set():
354
+ try:
355
+ _process_notification_queue()
356
+ except Exception:
357
+ pass
358
+ _watcher_stop.wait(2.0)
359
+
360
+ _queue_thread = threading.Thread(
361
+ target=_poll_loop, daemon=True, name="code-intel-queue-poller"
362
+ )
363
+ _queue_thread.start()
364
+ logger.debug("Notification queue poller started")
365
+
366
+
367
+ # ---------------------------------------------------------------------------
368
+ # File watcher (background thread)
369
+ # ---------------------------------------------------------------------------
370
+
371
+ _CODE_EXTENSIONS = {
372
+ ".py", ".js", ".jsx", ".ts", ".tsx", ".go", ".rs", ".java",
373
+ ".rb", ".c", ".cpp", ".h", ".hpp", ".cs", ".swift", ".kt",
374
+ ".scala", ".sc", ".ex", ".exs", ".zig", ".hs", ".tf", ".tfvars",
375
+ ".hcl", ".lua", ".sh", ".bash", ".php",
376
+ }
377
+
378
+ _watcher_thread: threading.Thread | None = None
379
+ _watcher_stop = threading.Event()
380
+
381
+
382
+ def _start_file_watcher(project_dir: str, *, debounce_ms: int = 500) -> None:
383
+ """Start a background file watcher that updates the AST index on changes.
384
+
385
+ Uses watchfiles (Rust-backed) if available, otherwise skips silently.
386
+ Calls ASTService.notify_file_changed() and queues background reindex for
387
+ modified code files. Second call is a no-op (idempotent).
388
+
389
+ Args:
390
+ project_dir: Absolute path to the project root.
391
+ debounce_ms: Debounce interval in milliseconds (default 500ms).
392
+ """
393
+ global _watcher_thread
394
+
395
+ if _watcher_thread is not None:
396
+ return # Already running
397
+
398
+ try:
399
+ from watchfiles import Change, watch
400
+ except ImportError:
401
+ logger.debug("watchfiles not installed — file watcher disabled")
402
+ return
403
+
404
+ def _watcher_loop() -> None:
405
+ try:
406
+ for changes in watch(
407
+ project_dir,
408
+ stop_event=_watcher_stop,
409
+ recursive=True,
410
+ debounce=debounce_ms,
411
+ # Ignore hidden dirs, node_modules, __pycache__, .git
412
+ watch_filter=lambda _, path: (
413
+ not any(
414
+ part.startswith(".") or part in ("node_modules", "__pycache__", ".git")
415
+ for part in Path(path).parts
416
+ )
417
+ and Path(path).suffix.lower() in _CODE_EXTENSIONS
418
+ ),
419
+ ):
420
+ if _watcher_stop.is_set():
421
+ break
422
+
423
+ svc = _get_ast_service()
424
+ for change_type, path_str in changes:
425
+ if change_type in (Change.modified, Change.added, Change.deleted):
426
+ try:
427
+ rel = os.path.relpath(path_str, project_dir)
428
+ svc.notify_file_changed(rel)
429
+ # Also invalidate semantic search embeddings
430
+ try:
431
+ from attocode_intel.tools.search_tools import (
432
+ _get_semantic_search,
433
+ )
434
+
435
+ smgr = _get_semantic_search()
436
+ smgr.invalidate_file(path_str)
437
+ smgr.queue_reindex(path_str)
438
+ except Exception:
439
+ pass
440
+ try:
441
+ from attocode_intel.tools.search_tools import (
442
+ _get_trigram_index,
443
+ )
444
+
445
+ tri_idx = _get_trigram_index()
446
+ if tri_idx is not None:
447
+ tri_idx.update_file(rel, b"")
448
+ except Exception:
449
+ pass
450
+ logger.debug("File watcher: updated %s", rel)
451
+ except Exception:
452
+ pass # Best-effort -- don't crash the watcher
453
+ except Exception:
454
+ logger.debug("File watcher stopped", exc_info=True)
455
+
456
+ _watcher_thread = threading.Thread(
457
+ target=_watcher_loop, daemon=True, name="code-intel-watcher"
458
+ )
459
+ _watcher_thread.start()
460
+ logger.info("File watcher started for %s (debounce=%dms)", project_dir, debounce_ms)
461
+
462
+
463
+ def _stop_file_watcher() -> None:
464
+ """Stop the background file watcher and queue poller."""
465
+ global _watcher_thread, _queue_thread
466
+ _watcher_stop.set()
467
+ for t in (_watcher_thread, _queue_thread):
468
+ if t is not None:
469
+ t.join(timeout=2.0)
470
+ _watcher_thread = None
471
+ _queue_thread = None
472
+
473
+
474
+ # ---------------------------------------------------------------------------
475
+ # Tool instrumentation — wraps MCP tool functions with metrics recording
476
+ # ---------------------------------------------------------------------------
477
+
478
+
479
+ def _instrument_tool(tool_fn, tool_name: str):
480
+ """Wrap an MCP tool function to record call metrics.
481
+
482
+ Records duration and success/failure for each invocation via the
483
+ module-level ``metrics_collector`` singleton.
484
+
485
+ Args:
486
+ tool_fn: The original tool function (sync).
487
+ tool_name: Human-readable tool name for metric labels.
488
+
489
+ Returns:
490
+ Wrapped function that records metrics then delegates to *tool_fn*.
491
+ """
492
+ import functools
493
+ import time as _time
494
+
495
+ @functools.wraps(tool_fn)
496
+ def _wrapper(*args, **kwargs):
497
+ from attocode_intel.api.middleware import metrics_collector
498
+
499
+ start = _time.monotonic()
500
+ success = True
501
+ try:
502
+ result = tool_fn(*args, **kwargs)
503
+ return result
504
+ except Exception:
505
+ success = False
506
+ raise
507
+ finally:
508
+ duration_ms = (_time.monotonic() - start) * 1000
509
+ metrics_collector.record_tool_call(tool_name, duration_ms, success)
510
+
511
+ return _wrapper
512
+
513
+
514
+ def _instrument_all_tools() -> None:
515
+ """Wrap all registered MCP tools with metrics instrumentation.
516
+
517
+ Must be called after all tool modules have been imported (so that
518
+ ``@mcp.tool()`` decorators have fired). Patches the internal tool
519
+ registry in-place.
520
+ """
521
+ try:
522
+ # FastMCP stores tools in mcp._tool_manager._tools (dict[str, Tool])
523
+ tool_manager = getattr(mcp, "_tool_manager", None)
524
+ if tool_manager is None:
525
+ return
526
+ tools_dict = getattr(tool_manager, "_tools", None)
527
+ if tools_dict is None:
528
+ return
529
+
530
+ for name, tool_obj in tools_dict.items():
531
+ original_fn = getattr(tool_obj, "fn", None)
532
+ if original_fn is None:
533
+ continue
534
+ tool_obj.fn = _instrument_tool(original_fn, name)
535
+ logger.info("Instrumented %d MCP tools with metrics recording", len(tools_dict))
536
+ except Exception:
537
+ logger.debug("Failed to instrument MCP tools", exc_info=True)
538
+
539
+
540
+ # ---------------------------------------------------------------------------
541
+ # Register all tool modules (decorators fire on import)
542
+ # ---------------------------------------------------------------------------
543
+
544
+ import attocode_intel.tools.adr_tools as _adr_tools # noqa: E402, F401
545
+ import attocode_intel.tools.analysis_tools as _analysis_tools # noqa: E402, F401
546
+ import attocode_intel.tools.cross_mode_tools as _cross_mode_tools # noqa: E402, F401
547
+ import attocode_intel.tools.dead_code_tools as _dead_code_tools # noqa: E402, F401
548
+ import attocode_intel.tools.distill_tools as _distill_tools # noqa: E402, F401
549
+ import attocode_intel.tools.frecency_tools as _frecency_tools # noqa: E402, F401
550
+ import attocode_intel.tools.fuzzy_tools as _fuzzy_tools # noqa: E402, F401
551
+ import attocode_intel.tools.history_tools as _history_tools # noqa: E402, F401
552
+ import attocode_intel.tools.learning_tools as _learning_tools # noqa: E402, F401
553
+ import attocode_intel.tools.lsp_tools as _lsp_tools # noqa: E402, F401
554
+ import attocode_intel.tools.navigation_tools as _navigation_tools # noqa: E402, F401
555
+ import attocode_intel.tools.query_constraints_tools as _query_constraints_tools # noqa: E402, F401
556
+ import attocode_intel.tools.query_history_tools as _query_history_tools # noqa: E402, F401
557
+ import attocode_intel.tools.readiness_tools as _readiness_tools # noqa: E402, F401
558
+ import attocode_intel.tools.composite_tools as _composite_tools # noqa: E402, F401
559
+ import attocode_intel.tools.search_tools as _search_tools # noqa: E402, F401
560
+ # pin_tools must come first because maintenance_tools imports
561
+ # _stamp_pin / _STORE_DEFS from it.
562
+ import attocode_intel.tools.pin_tools as _pin_tools # noqa: E402, F401
563
+ import attocode_intel.tools.maintenance_tools as _maintenance_tools # noqa: E402, F401
564
+ import attocode_intel.tools.snapshot_tools as _snapshot_tools # noqa: E402, F401
565
+ import attocode_intel.tools.overlay_tools as _overlay_tools # noqa: E402, F401
566
+ # Rule-based analysis engine (PerfInsights-inspired)
567
+ import attocode_intel.tools.rule_tools as _rule_tools # noqa: E402, F401
568
+ from attocode_intel.helpers import ( # noqa: E402, F401
569
+ _compute_file_metrics,
570
+ _percentile_ranks,
571
+ )
572
+
573
+ # Backward-compatible re-exports via attribute alias (avoids circular import)
574
+ file_analysis = _analysis_tools.file_analysis # noqa: E402
575
+ impact_analysis = _analysis_tools.impact_analysis # noqa: E402
576
+ dependency_graph = _analysis_tools.dependency_graph # noqa: E402
577
+ hotspots = _analysis_tools.hotspots # noqa: E402
578
+ cross_references = _analysis_tools.cross_references # noqa: E402
579
+ dependencies = _analysis_tools.dependencies # noqa: E402
580
+ explore_codebase = _navigation_tools.explore_codebase # noqa: E402
581
+ find_related = _analysis_tools.find_related # noqa: E402
582
+ community_detection = _analysis_tools.community_detection # noqa: E402
583
+ graph_dsl = _analysis_tools.graph_dsl # noqa: E402
584
+
585
+ semantic_search = _search_tools.semantic_search # noqa: E402
586
+ semantic_search_status = _search_tools.semantic_search_status # noqa: E402
587
+ security_scan = _search_tools.security_scan # noqa: E402
588
+ fast_search = _search_tools.fast_search # noqa: E402
589
+
590
+ recall = _learning_tools.recall # noqa: E402
591
+ record_learning = _learning_tools.record_learning # noqa: E402
592
+ learning_feedback = _learning_tools.learning_feedback # noqa: E402
593
+ list_learnings = _learning_tools.list_learnings # noqa: E402
594
+
595
+ record_adr = _adr_tools.record_adr # noqa: E402
596
+ list_adrs = _adr_tools.list_adrs # noqa: E402
597
+ get_adr = _adr_tools.get_adr # noqa: E402
598
+ update_adr_status = _adr_tools.update_adr_status # noqa: E402
599
+
600
+ dead_code = _dead_code_tools.dead_code # noqa: E402
601
+ distill = _distill_tools.distill # noqa: E402
602
+ readiness_report = _readiness_tools.readiness_report # noqa: E402
603
+
604
+ code_evolution = _history_tools.code_evolution # noqa: E402
605
+ recent_changes = _history_tools.recent_changes # noqa: E402
606
+
607
+ review_change = _composite_tools.review_change # noqa: E402
608
+ explain_impact = _composite_tools.explain_impact # noqa: E402
609
+ suggest_tests = _composite_tools.suggest_tests # noqa: E402
610
+ architecture_drift = _composite_tools.architecture_drift # noqa: E402
611
+
612
+ bootstrap = _navigation_tools.bootstrap # noqa: E402
613
+ conventions = _navigation_tools.conventions # noqa: E402
614
+ project_summary = _navigation_tools.project_summary # noqa: E402
615
+ relevant_context = _navigation_tools.relevant_context # noqa: E402
616
+ repo_map = _navigation_tools.repo_map # noqa: E402
617
+ search_symbols = _navigation_tools.search_symbols # noqa: E402
618
+ symbols = _navigation_tools.symbols # noqa: E402
619
+
620
+ # Instrument all registered tools with metrics recording
621
+ _instrument_all_tools()
622
+
623
+ # ---------------------------------------------------------------------------
624
+ # Entry point
625
+ # ---------------------------------------------------------------------------
626
+
627
+ # Subcommands that should be dispatched to the CLI handler instead of
628
+ # starting the MCP server.
629
+ _CLI_SUBCOMMANDS = {
630
+ "install", "uninstall", "serve", "status", "probe-install", "notify",
631
+ "connect", "test-connection", "watch", "help", "--help", "-h",
632
+ "query", "symbols", "impact", "hotspots", "deps", "dead-code",
633
+ "gc", "verify", "reindex", "bundle",
634
+ }
635
+
636
+
637
+ def main() -> None:
638
+ """CLI entry point for the MCP server.
639
+
640
+ If the first positional argument is a known subcommand (install, uninstall,
641
+ notify, status, serve, help), delegates to ``cli.dispatch_code_intel``.
642
+ Otherwise starts the MCP server on stdio.
643
+ """
644
+ args = sys.argv[1:]
645
+
646
+ # Detect subcommands -- delegate to CLI dispatcher
647
+ if args and args[0] in _CLI_SUBCOMMANDS:
648
+ from attocode_intel.cli import dispatch_code_intel
649
+
650
+ dispatch_code_intel(args)
651
+ return
652
+
653
+ # No subcommand -- start MCP server
654
+ from attocode_intel.project_dir import find_project_root
655
+
656
+ project_dir = find_project_root(os.path.abspath("."))
657
+
658
+ transport = "stdio"
659
+ host = "127.0.0.1"
660
+ port = 8080
661
+ remote_url = ""
662
+ remote_token = ""
663
+ remote_repo_id = ""
664
+ local_only = os.environ.get("ATTOCODE_LOCAL_ONLY") == "1"
665
+ for i, arg in enumerate(args):
666
+ if arg == "--project" and i + 1 < len(args):
667
+ project_dir = args[i + 1]
668
+ elif arg.startswith("--project="):
669
+ project_dir = arg.split("=", 1)[1]
670
+ elif arg == "--transport" and i + 1 < len(args):
671
+ transport = args[i + 1]
672
+ elif arg.startswith("--transport="):
673
+ transport = arg.split("=", 1)[1]
674
+ elif arg == "--host" and i + 1 < len(args):
675
+ host = args[i + 1]
676
+ elif arg.startswith("--host="):
677
+ host = arg.split("=", 1)[1]
678
+ elif arg == "--port" and i + 1 < len(args):
679
+ port = int(args[i + 1])
680
+ elif arg.startswith("--port="):
681
+ port = int(arg.split("=", 1)[1])
682
+ elif arg == "--remote" and i + 1 < len(args):
683
+ remote_url = args[i + 1]
684
+ elif arg.startswith("--remote="):
685
+ remote_url = arg.split("=", 1)[1]
686
+ elif arg == "--remote-token" and i + 1 < len(args):
687
+ remote_token = args[i + 1]
688
+ elif arg.startswith("--remote-token="):
689
+ remote_token = arg.split("=", 1)[1]
690
+ elif arg == "--remote-repo" and i + 1 < len(args):
691
+ remote_repo_id = args[i + 1]
692
+ elif arg.startswith("--remote-repo="):
693
+ remote_repo_id = arg.split("=", 1)[1]
694
+ elif arg == "--local-only":
695
+ local_only = True
696
+
697
+ project_dir = os.path.abspath(project_dir)
698
+ os.environ["ATTOCODE_PROJECT_DIR"] = project_dir
699
+ if local_only:
700
+ os.environ["ATTOCODE_LOCAL_ONLY"] = "1"
701
+
702
+ # Enable remote mode from CLI flags / config.toml / env (shared with the
703
+ # CLI serve path); gracefully falls back to local on a dead/expired remote.
704
+ enable_remote_if_configured(
705
+ project_dir,
706
+ local_only=local_only,
707
+ remote_url=remote_url,
708
+ remote_token=remote_token,
709
+ remote_repo_id=remote_repo_id,
710
+ )
711
+
712
+ # Start file watcher and notification queue poller in background
713
+ _start_file_watcher(project_dir)
714
+ _start_queue_poller(project_dir)
715
+
716
+ logger.info("Starting attocode-code-intel for %s (transport=%s)", project_dir, transport)
717
+ try:
718
+ if transport == "http":
719
+ from attocode_intel.cli import _serve_http
720
+
721
+ _serve_http(project_dir, host=host, port=port, debug=False)
722
+ elif transport == "sse":
723
+ mcp.run(transport="sse", host=host, port=port)
724
+ else:
725
+ from attocode_intel.entrypoint import exit_when_client_exits
726
+
727
+ exit_when_client_exits()
728
+ mcp.run(transport="stdio")
729
+ finally:
730
+ _stop_file_watcher()
731
+
732
+
733
+ if __name__ == "__main__":
734
+ main()