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 @@
1
+ """Internal code intelligence support; not a public API."""
@@ -0,0 +1,253 @@
1
+ """MCP (Model Context Protocol) client.
2
+
3
+ Implements a stdio-based JSON-RPC client for communicating
4
+ with MCP servers that provide additional tools.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import asyncio
10
+ import json
11
+ import logging
12
+ import os
13
+ import re
14
+ from dataclasses import dataclass, field
15
+ from typing import Any
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ def _expand_env(env: dict[str, str] | None) -> dict[str, str] | None:
21
+ """Expand ``${VAR}`` references and merge with the current environment.
22
+
23
+ When *env* is provided, we start from ``os.environ`` (so the child
24
+ process inherits PATH, HOME, etc.) and overlay the user-supplied
25
+ values after expanding any ``${VAR_NAME}`` patterns against
26
+ ``os.environ``.
27
+ """
28
+ if not env:
29
+ return None
30
+ base = os.environ.copy()
31
+ for key, val in env.items():
32
+ expanded = re.sub(
33
+ r"\$\{(?:env:)?(\w+)\}",
34
+ lambda m: os.environ.get(m.group(1), ""),
35
+ val,
36
+ )
37
+ base[key] = expanded
38
+ return base
39
+
40
+
41
+ @dataclass
42
+ class MCPTool:
43
+ """A tool provided by an MCP server."""
44
+
45
+ name: str
46
+ description: str
47
+ input_schema: dict[str, Any] = field(default_factory=dict)
48
+ server_name: str = ""
49
+
50
+
51
+ @dataclass
52
+ class MCPCallResult:
53
+ """Result of calling an MCP tool."""
54
+
55
+ success: bool
56
+ result: Any = None
57
+ error: str | None = None
58
+
59
+
60
+ class MCPClient:
61
+ """Stdio-based JSON-RPC MCP client.
62
+
63
+ Launches an MCP server process, communicates via stdin/stdout
64
+ using JSON-RPC 2.0 protocol.
65
+ """
66
+
67
+ def __init__(
68
+ self,
69
+ server_command: str,
70
+ server_args: list[str] | None = None,
71
+ server_name: str = "",
72
+ env: dict[str, str] | None = None,
73
+ cwd: str | None = None,
74
+ ) -> None:
75
+ self._command = server_command
76
+ self._args = server_args or []
77
+ self._server_name = server_name
78
+ self._env = env
79
+ self._cwd = cwd
80
+ self._process: asyncio.subprocess.Process | None = None
81
+ self._tools: list[MCPTool] = []
82
+ self._initialized = False
83
+ self._request_id = 0
84
+
85
+ async def connect(self) -> None:
86
+ """Start the MCP server process and initialize."""
87
+ self._process = await asyncio.create_subprocess_exec(
88
+ self._command,
89
+ *self._args,
90
+ stdin=asyncio.subprocess.PIPE,
91
+ stdout=asyncio.subprocess.PIPE,
92
+ stderr=asyncio.subprocess.PIPE,
93
+ env=_expand_env(self._env),
94
+ cwd=self._cwd,
95
+ )
96
+
97
+ # Send initialize request
98
+ result = await self._request("initialize", {
99
+ "protocolVersion": "2024-11-05",
100
+ "capabilities": {},
101
+ "clientInfo": {"name": "attocode", "version": "0.1.0"},
102
+ })
103
+
104
+ if result is not None:
105
+ self._initialized = True
106
+
107
+ # Send initialized notification
108
+ await self._notify("notifications/initialized", {})
109
+
110
+ # List tools
111
+ await self._refresh_tools()
112
+
113
+ async def disconnect(self) -> None:
114
+ """Disconnect and terminate the server process."""
115
+ if self._process:
116
+ try:
117
+ if self._process.stdin:
118
+ self._process.stdin.close()
119
+ self._process.terminate()
120
+ await asyncio.wait_for(self._process.wait(), timeout=5.0)
121
+ except (TimeoutError, ProcessLookupError):
122
+ self._process.kill()
123
+ finally:
124
+ self._process = None
125
+ self._initialized = False
126
+
127
+ async def call_tool(self, name: str, arguments: dict[str, Any]) -> MCPCallResult:
128
+ """Call an MCP tool."""
129
+ if not self._initialized:
130
+ return MCPCallResult(success=False, error="Not connected")
131
+
132
+ try:
133
+ result = await self._request("tools/call", {
134
+ "name": name,
135
+ "arguments": arguments,
136
+ })
137
+ if result is None:
138
+ return MCPCallResult(success=False, error="No response")
139
+
140
+ # MCP tool results have a 'content' array
141
+ content = result.get("content", [])
142
+ if content and isinstance(content, list):
143
+ text_parts = [
144
+ c.get("text", "")
145
+ for c in content
146
+ if c.get("type") == "text"
147
+ ]
148
+ return MCPCallResult(success=True, result="\n".join(text_parts))
149
+
150
+ return MCPCallResult(success=True, result=str(result))
151
+ except Exception as e:
152
+ return MCPCallResult(success=False, error=str(e))
153
+
154
+ @property
155
+ def tools(self) -> list[MCPTool]:
156
+ """Get available tools."""
157
+ return list(self._tools)
158
+
159
+ @property
160
+ def is_connected(self) -> bool:
161
+ return self._initialized
162
+
163
+ @property
164
+ def server_name(self) -> str:
165
+ return self._server_name
166
+
167
+ async def _refresh_tools(self) -> None:
168
+ """Refresh the tool list from the server."""
169
+ result = await self._request("tools/list", {})
170
+ if result and "tools" in result:
171
+ self._tools = [
172
+ MCPTool(
173
+ name=t["name"],
174
+ description=t.get("description", ""),
175
+ input_schema=t.get("inputSchema", {}),
176
+ server_name=self._server_name,
177
+ )
178
+ for t in result["tools"]
179
+ ]
180
+
181
+ async def _request(self, method: str, params: dict[str, Any]) -> Any:
182
+ """Send a JSON-RPC request and wait for response."""
183
+ if not self._process or not self._process.stdin or not self._process.stdout:
184
+ return None
185
+
186
+ self._request_id += 1
187
+ req_id = self._request_id
188
+ msg = {
189
+ "jsonrpc": "2.0",
190
+ "id": req_id,
191
+ "method": method,
192
+ "params": params,
193
+ }
194
+
195
+ data = json.dumps(msg) + "\n"
196
+ self._process.stdin.write(data.encode())
197
+ await self._process.stdin.drain()
198
+
199
+ # Read response
200
+ try:
201
+ line = await asyncio.wait_for(
202
+ self._process.stdout.readline(), timeout=30.0
203
+ )
204
+ if not line:
205
+ logger.warning(
206
+ "MCP server %s returned empty response for %s",
207
+ self._server_name, method,
208
+ )
209
+ return None
210
+ response = json.loads(line.decode())
211
+ # Validate JSON-RPC response ID matches request
212
+ resp_id = response.get("id")
213
+ if resp_id != req_id:
214
+ logger.warning(
215
+ "MCP server %s response ID mismatch for %s: expected %s, got %s",
216
+ self._server_name, method, req_id, resp_id,
217
+ )
218
+ if "error" in response:
219
+ err = response["error"]
220
+ logger.warning(
221
+ "MCP server %s RPC error for %s: code=%s message=%s",
222
+ self._server_name, method,
223
+ err.get("code", "?"), err.get("message", "unknown"),
224
+ )
225
+ return None
226
+ return response.get("result")
227
+ except TimeoutError:
228
+ logger.warning(
229
+ "MCP server %s timed out on %s (30s)",
230
+ self._server_name, method,
231
+ )
232
+ return None
233
+ except json.JSONDecodeError as exc:
234
+ logger.warning(
235
+ "MCP server %s returned invalid JSON for %s: %s",
236
+ self._server_name, method, exc,
237
+ )
238
+ return None
239
+
240
+ async def _notify(self, method: str, params: dict[str, Any]) -> None:
241
+ """Send a JSON-RPC notification (no response expected)."""
242
+ if not self._process or not self._process.stdin:
243
+ return
244
+
245
+ msg = {
246
+ "jsonrpc": "2.0",
247
+ "method": method,
248
+ "params": params,
249
+ }
250
+
251
+ data = json.dumps(msg) + "\n"
252
+ self._process.stdin.write(data.encode())
253
+ await self._process.stdin.drain()
@@ -0,0 +1 @@
1
+ """Internal code intelligence support; not a public API."""
@@ -0,0 +1,176 @@
1
+ """Load user-defined security rules from .attocode/rules/*.yaml files.
2
+
3
+ Custom rules use the same SecurityPattern dataclass as built-in patterns,
4
+ allowing them to be seamlessly merged into the scanning pipeline.
5
+
6
+ Rule format (YAML):
7
+ id: my_rule_name
8
+ pattern: "regex_pattern_here"
9
+ message: "What was detected"
10
+ severity: high # critical | high | medium | low | info
11
+ cwe: CWE-79 # optional
12
+ recommendation: "How to fix"
13
+ languages: # optional, empty = all languages
14
+ - python
15
+ - javascript
16
+ scan_comments: false # optional, default false
17
+ fix: # optional autofix
18
+ search: "old_code("
19
+ replace: "new_code("
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import logging
25
+ import re
26
+ from pathlib import Path
27
+
28
+ from attocode_intel._internal.integrations.security.patterns import (
29
+ Category,
30
+ SecurityPattern,
31
+ Severity,
32
+ )
33
+
34
+ logger = logging.getLogger(__name__)
35
+
36
+ _VALID_SEVERITIES = frozenset(s.value for s in Severity)
37
+
38
+
39
+ def load_custom_rules(project_dir: str) -> list[SecurityPattern]:
40
+ """Load custom security rules from .attocode/rules/*.yaml files.
41
+
42
+ Args:
43
+ project_dir: Project root directory.
44
+
45
+ Returns:
46
+ List of SecurityPattern instances from user-defined rules.
47
+ Invalid rules are logged and skipped.
48
+ """
49
+ rules_dir = Path(project_dir) / ".attocode" / "rules"
50
+ if not rules_dir.is_dir():
51
+ return []
52
+
53
+ patterns: list[SecurityPattern] = []
54
+ for yaml_file in sorted(rules_dir.glob("*.yaml")):
55
+ try:
56
+ file_patterns = _load_rules_file(yaml_file)
57
+ patterns.extend(file_patterns)
58
+ except Exception as exc:
59
+ logger.warning("Failed to load custom rules from %s: %s", yaml_file, exc)
60
+
61
+ if patterns:
62
+ logger.info("Loaded %d custom security rule(s) from %s", len(patterns), rules_dir)
63
+
64
+ return patterns
65
+
66
+
67
+ def _load_rules_file(path: Path) -> list[SecurityPattern]:
68
+ """Parse a single YAML rules file into SecurityPattern instances."""
69
+ try:
70
+ import yaml # type: ignore[import-untyped]
71
+ except ImportError:
72
+ logger.debug("PyYAML not installed — skipping custom rules file %s", path)
73
+ return []
74
+
75
+ content = path.read_text(encoding="utf-8")
76
+ data = yaml.safe_load(content)
77
+
78
+ if data is None:
79
+ return []
80
+
81
+ # Support both single-rule and multi-rule files
82
+ if isinstance(data, dict):
83
+ rules_list = [data]
84
+ elif isinstance(data, list):
85
+ rules_list = data
86
+ else:
87
+ logger.warning("Invalid rules file %s: expected dict or list", path)
88
+ return []
89
+
90
+ patterns: list[SecurityPattern] = []
91
+ for i, rule in enumerate(rules_list):
92
+ try:
93
+ pattern = _parse_rule(rule, source=f"{path.name}[{i}]")
94
+ if pattern is not None:
95
+ patterns.append(pattern)
96
+ except Exception as exc:
97
+ logger.warning("Invalid rule in %s at index %d: %s", path.name, i, exc)
98
+
99
+ return patterns
100
+
101
+
102
+ def _parse_rule(rule: dict, source: str = "") -> SecurityPattern | None:
103
+ """Parse a single rule dict into a SecurityPattern.
104
+
105
+ Required fields: id, pattern, message, severity, recommendation.
106
+ Optional fields: cwe, languages, scan_comments, fix.
107
+ """
108
+ # Validate required fields
109
+ missing = [f for f in ("id", "pattern", "message", "severity", "recommendation") if f not in rule]
110
+ if missing:
111
+ logger.warning("Rule %s missing required fields: %s", source, ", ".join(missing))
112
+ return None
113
+
114
+ # Validate severity
115
+ severity_str = str(rule["severity"]).lower()
116
+ if severity_str not in _VALID_SEVERITIES:
117
+ logger.warning(
118
+ "Rule %s has invalid severity '%s' (must be one of: %s)",
119
+ source, rule["severity"], ", ".join(sorted(_VALID_SEVERITIES)),
120
+ )
121
+ return None
122
+
123
+ # Compile regex
124
+ try:
125
+ compiled = re.compile(rule["pattern"])
126
+ except re.error as exc:
127
+ logger.warning("Rule %s has invalid regex '%s': %s", source, rule["pattern"], exc)
128
+ return None
129
+
130
+ return SecurityPattern(
131
+ name=str(rule["id"]),
132
+ pattern=compiled,
133
+ severity=Severity(severity_str),
134
+ category=Category.ANTI_PATTERN,
135
+ cwe_id=str(rule.get("cwe", "")),
136
+ message=str(rule["message"]),
137
+ recommendation=str(rule["recommendation"]),
138
+ languages=rule.get("languages", []),
139
+ scan_comments=bool(rule.get("scan_comments", False)),
140
+ )
141
+
142
+
143
+ def get_autofix_from_rules(
144
+ project_dir: str,
145
+ ) -> dict[str, tuple[str, str]]:
146
+ """Extract autofix templates from custom rules that define a 'fix' field.
147
+
148
+ Returns:
149
+ Dict mapping rule id -> (search, replace) for rules with fix defined.
150
+ """
151
+ rules_dir = Path(project_dir) / ".attocode" / "rules"
152
+ if not rules_dir.is_dir():
153
+ return {}
154
+
155
+ try:
156
+ import yaml # type: ignore[import-untyped]
157
+ except ImportError:
158
+ return {}
159
+
160
+ fixes: dict[str, tuple[str, str]] = {}
161
+ for yaml_file in sorted(rules_dir.glob("*.yaml")):
162
+ try:
163
+ content = yaml_file.read_text(encoding="utf-8")
164
+ data = yaml.safe_load(content)
165
+ if data is None:
166
+ continue
167
+ rules_list = [data] if isinstance(data, dict) else data if isinstance(data, list) else []
168
+ for rule in rules_list:
169
+ if isinstance(rule, dict) and "fix" in rule and "id" in rule:
170
+ fix = rule["fix"]
171
+ if isinstance(fix, dict) and "search" in fix and "replace" in fix:
172
+ fixes[str(rule["id"])] = (str(fix["search"]), str(fix["replace"]))
173
+ except Exception:
174
+ continue
175
+
176
+ return fixes