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,63 @@
1
+ """Shared community detection helpers.
2
+
3
+ Used by both the MCP tool (analysis_tools.py) and the HTTP service (service.py).
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from collections import deque
9
+
10
+
11
+ def louvain_communities(
12
+ all_files: set[str],
13
+ adj: dict[str, set[str]],
14
+ weights: dict[tuple[str, str], float],
15
+ ) -> tuple[list[set[str]], float]:
16
+ """Run Louvain community detection using networkx.
17
+
18
+ Returns ``([], 0.0)`` for an empty graph (no edges).
19
+ Raises ``ImportError`` if networkx is not installed.
20
+ """
21
+ import networkx as nx
22
+ from networkx.algorithms.community import louvain_communities as _louvain
23
+ from networkx.algorithms.community import modularity
24
+
25
+ G = nx.Graph() # noqa: N806
26
+ G.add_nodes_from(all_files)
27
+ for src, neighbors in adj.items():
28
+ for tgt in neighbors:
29
+ if src < tgt: # avoid duplicate edges
30
+ w = weights.get((src, tgt), weights.get((tgt, src), 1.0))
31
+ G.add_edge(src, tgt, weight=w)
32
+
33
+ if G.number_of_edges() == 0:
34
+ return [{f} for f in all_files], 0.0
35
+
36
+ communities = [set(c) for c in _louvain(G, weight="weight", seed=42)]
37
+ mod = modularity(G, communities, weight="weight")
38
+ return communities, mod
39
+
40
+
41
+ def bfs_connected_components(
42
+ all_files: set[str],
43
+ adj: dict[str, set[str]],
44
+ ) -> tuple[list[set[str]], float]:
45
+ """Fallback: connected components via BFS. Modularity = 0."""
46
+ visited: set[str] = set()
47
+ communities: list[set[str]] = []
48
+ for start in all_files:
49
+ if start in visited:
50
+ continue
51
+ component: set[str] = set()
52
+ bfs_queue: deque[str] = deque([start])
53
+ while bfs_queue:
54
+ node = bfs_queue.popleft()
55
+ if node in visited:
56
+ continue
57
+ visited.add(node)
58
+ component.add(node)
59
+ for neighbor in adj.get(node, set()):
60
+ if neighbor not in visited:
61
+ bfs_queue.append(neighbor)
62
+ communities.append(component)
63
+ return communities, 0.0
@@ -0,0 +1,144 @@
1
+ """Where a finding's confidence comes from.
2
+
3
+ Most confidences in this package are literals somebody picked: the eight in
4
+ ``bug_finder._PATTERNS``, the ``0.6`` on synthesized rules, the ``0.7``/``0.8``
5
+ in ``dead_code_tools``. Two scorers can replace them, chosen with
6
+ ``ATTOCODE_FLAG_CONFIDENCE``: jev and llm. Historical experiments and their
7
+ limitations live in ``eval/rule_accuracy/demo``; their results are not
8
+ general accuracy guarantees.
9
+
10
+ ``ATTOCODE_FLAG_CONFIDENCE_MODE`` picks what happens with the estimate:
11
+ ``shadow`` logs it beside the constant and changes nothing, ``live`` uses it.
12
+ Both default to off/shadow, so nothing here runs unless asked.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import hashlib
18
+ import logging
19
+ import math
20
+ from contextvars import ContextVar
21
+ from copy import deepcopy
22
+ from typing import TYPE_CHECKING, Any
23
+
24
+ from attocode_intel.confidence import jev as jev_scorer
25
+ from attocode_intel.confidence import llm as llm_scorer
26
+ from attocode_intel.confidence import settings
27
+ from attocode_intel.confidence.redact import redact
28
+
29
+ if TYPE_CHECKING:
30
+ from collections.abc import Sequence
31
+
32
+ logger = logging.getLogger(__name__)
33
+
34
+ __all__ = ["MAX_CALLS", "last_report", "last_run", "mode", "redact", "rule_threshold", "score", "scorer_name", "summary"]
35
+
36
+ # ponytail: per-call cap. A corpus file yields a handful of findings; a wide
37
+ # diff can yield hundreds, and each one is a network round trip.
38
+ MAX_CALLS = 25
39
+
40
+ _SCORERS = {"jev": jev_scorer, "llm": llm_scorer}
41
+
42
+ _last: ContextVar[tuple[int, int]] = ContextVar("confidence_counts", default=(0, 0))
43
+ _report: ContextVar[dict | None] = ContextVar("confidence_report", default=None)
44
+
45
+
46
+ def last_run() -> tuple[int, int]:
47
+ """(estimated, considered) from the most recent score() call."""
48
+ return _last.get()
49
+
50
+
51
+ def last_report() -> dict:
52
+ """A detached request-local audit, including suppressed and unscored candidates."""
53
+ return deepcopy(_report.get() or {})
54
+
55
+
56
+ def summary() -> str:
57
+ report = _report.get() or {}
58
+ if not report or report["scorer"] == "off":
59
+ return ""
60
+ return (
61
+ f"Confidence: {report['scorer']} ({report['mode']}); "
62
+ f"{report['estimated']}/{report['total']} scored, "
63
+ f"{report['fallback']} fallback, {report['skipped']} skipped by cap. "
64
+ "Fallback and skipped findings retain their baseline confidence."
65
+ )
66
+
67
+
68
+ def rule_threshold(min_confidence: float) -> float:
69
+ """Live estimates can promote rules below their old constant threshold."""
70
+ return 0.0 if settings.selected() != "off" and mode() == "live" else min_confidence
71
+
72
+
73
+ def scorer_name() -> str:
74
+ """Which scorer is selected: "off", "jev" or "llm"."""
75
+ name = settings.selected()
76
+ if name == "jev":
77
+ try:
78
+ if not jev_scorer.available():
79
+ return "off"
80
+ except (ValueError, OSError):
81
+ return "off"
82
+ return name if name in _SCORERS else "off"
83
+
84
+
85
+ def mode() -> str:
86
+ """What to do with an estimate: "shadow" (log only) or "live" (use it)."""
87
+ value = settings.mode()
88
+ return "live" if value == "live" else "shadow"
89
+
90
+
91
+ def score(findings: Sequence[Any], *, min_confidence: float) -> tuple[int, int]:
92
+ """Estimate p(true positive) per finding and log it beside the constant.
93
+
94
+ In ``live`` mode the estimate replaces ``finding.confidence``. Findings the
95
+ scorer has no opinion on keep their constant. Never raises: a scorer outage
96
+ must not cost a caller its findings.
97
+
98
+ Returns (estimated, considered) so a caller can tell a real score from a
99
+ scorer that ran but never committed to anything.
100
+ """
101
+ requested = settings.selected()
102
+ name = scorer_name()
103
+ items = list(findings)
104
+ batch = items[:MAX_CALLS] if name != "off" else []
105
+ estimates: list = []
106
+ if batch:
107
+ try:
108
+ estimates = list(_SCORERS[name].estimate(batch, min_confidence=min_confidence))
109
+ except Exception:
110
+ logger.warning("confidence: %s scorer failed, keeping constants", name)
111
+ live = mode() == "live"
112
+ estimated = 0
113
+ rows = []
114
+ for index, finding in enumerate(items):
115
+ baseline = finding.confidence
116
+ estimate = estimates[index] if index < len(batch) and index < len(estimates) else None
117
+ if isinstance(estimate, bool) or not isinstance(estimate, int | float) or not 0 <= estimate <= 1 or not math.isfinite(estimate):
118
+ estimate = None
119
+ if estimate is not None:
120
+ estimated += 1
121
+ if live:
122
+ finding.confidence = estimate
123
+ status = ("off" if requested == "off" else "unavailable" if name == "off"
124
+ else "skipped_cap" if index >= len(batch)
125
+ else "estimated" if estimate is not None else "fallback")
126
+ identity = f"{finding.file}:{finding.line}:{getattr(finding, 'rule_id', '')}"
127
+ rows.append({
128
+ "id": hashlib.sha256(identity.encode()).hexdigest()[:20],
129
+ "file": finding.file, "line": finding.line,
130
+ "rule": getattr(finding, "rule_id", ""),
131
+ "baseline": baseline, "estimate": estimate, "effective": finding.confidence,
132
+ "status": status, "kept": finding.confidence >= min_confidence,
133
+ })
134
+ report = {
135
+ "scorer": requested, "mode": mode(), "total": len(items), "estimated": estimated,
136
+ "considered": len(batch), "skipped": sum(r["status"] == "skipped_cap" for r in rows),
137
+ "fallback": sum(r["status"] in {"fallback", "unavailable"} for r in rows),
138
+ "findings": rows,
139
+ }
140
+ _report.set(report)
141
+ _last.set((estimated, len(batch)))
142
+ if requested != "off":
143
+ logger.info("%s", summary())
144
+ return last_run()
@@ -0,0 +1,234 @@
1
+ """Jev scorer — a calibrated probability in place of a hardcoded constant.
2
+
3
+ Jev (TypeSafe System One) answers a yes/no question with a probability.
4
+ Historical comparisons and their limitations live in eval/rule_accuracy/demo.
5
+
6
+ It needs no client library. The backend is a public HTTP endpoint and one POST
7
+ is the whole protocol. Supported backends are OpenRouter, TypeSafe, and a
8
+ local server, with credentials selected for the configured backend.
9
+
10
+ If the standalone `jev` CLI happens to be importable, its decide() is used
11
+ outside workspace-scoped requests. It also appends a row to
12
+ ~/.jev/decisions.jsonl, which is what `jev label` and `jev report` read.
13
+
14
+ An unkeyed or unreachable backend yields no estimate, and the caller keeps its
15
+ constant.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import ipaddress
21
+ import json
22
+ import logging
23
+ import math
24
+ import os
25
+ import urllib.error
26
+ import urllib.request
27
+ from concurrent.futures import ThreadPoolExecutor
28
+ from typing import TYPE_CHECKING, Any
29
+ from urllib.parse import urlsplit
30
+
31
+ from attocode_intel.confidence import settings
32
+ from attocode_intel.confidence.redact import redact
33
+
34
+ if TYPE_CHECKING:
35
+ from collections.abc import Sequence
36
+
37
+ try: # optional: only for the decisions.jsonl log the jev CLI reads
38
+ import jev
39
+
40
+ _HAS_JEV = True
41
+ except ImportError:
42
+ jev = None
43
+ _HAS_JEV = False
44
+
45
+ logger = logging.getLogger(__name__)
46
+
47
+ _env_loaded = False
48
+
49
+
50
+ def _load_env() -> None:
51
+ """Populate keys once, from wherever this machine keeps them.
52
+
53
+ The jev CLI keeps its key in ~/.jev/env; the project keeps one in .env.
54
+ Neither is on a hook's or a subprocess's environment by default.
55
+ """
56
+ global _env_loaded
57
+ if settings.project_dir():
58
+ return # Scoped requests read .env without mutating process credentials.
59
+ if _env_loaded:
60
+ return
61
+ _env_loaded = True
62
+ if _HAS_JEV:
63
+ jev.load_env()
64
+ try:
65
+ from dotenv import load_dotenv
66
+ except ImportError: # pragma: no cover - declared dependency
67
+ return
68
+ load_dotenv()
69
+
70
+
71
+ # backend -> (url, model, key env var). Mirrors the jev CLI so a row logged by
72
+ # either route is directly comparable.
73
+ _BACKENDS = {
74
+ "openrouter": (
75
+ "https://openrouter.ai/api/alpha/decisions",
76
+ "typesafe/jev-1.13",
77
+ "OPENROUTER_API_KEY",
78
+ ),
79
+ "typesafe": ("https://api.typesafe.ai/v1/systemone", "jev-1.13.0", "TYPESAFE_API_KEY"),
80
+ "local": (None, "jev-latest", "OPENJEV_API_KEY"),
81
+ }
82
+
83
+ # Measured on the rule corpus: near-linear to 16 workers (9.8x), flat after.
84
+ WORKERS = 16
85
+
86
+ QUESTION = "Is this rule match a true positive at this location?"
87
+ CRITERIA = {
88
+ "true": "The problem the rule describes is really present in this code.",
89
+ "false": "Test code, a comment, a string literal, a dead branch, or the case "
90
+ "is already handled nearby.",
91
+ }
92
+
93
+
94
+ def available() -> bool:
95
+ """True when a backend can be reached: a key, or a local server."""
96
+ _load_env()
97
+ chosen = backend()
98
+ return bool(chosen == "local" and (
99
+ settings.environment().get("JEV_BASE_URL")
100
+ or settings.environment().get("JEV_BACKEND") == "local"
101
+ or settings.local_settings().get("backend") == "local"
102
+ or settings.environment().get("ATTOCODE_LOCAL_ONLY", "").lower() in {"1", "true", "yes", "on"}
103
+ ) or settings.environment().get(_BACKENDS[chosen][2])
104
+ or (_HAS_JEV and not settings.project_dir()))
105
+
106
+
107
+ def _loopback(url: str) -> bool:
108
+ # IP literals only, as in the SystemOne ranker: a hostname can re-resolve.
109
+ try:
110
+ return ipaddress.ip_address(urlsplit(url).hostname or "").is_loopback
111
+ except ValueError:
112
+ return False
113
+
114
+
115
+ class _NoRedirect(urllib.request.HTTPRedirectHandler):
116
+ def redirect_request(self, *args: Any, **kwargs: Any) -> None:
117
+ return None # urllib would resend the key to the new URL; the 3xx becomes an error
118
+
119
+
120
+ def _open(request: urllib.request.Request, timeout: float) -> Any:
121
+ """Send without redirects, and without an ambient proxy for a loopback server."""
122
+ handlers: list[urllib.request.BaseHandler] = [_NoRedirect()]
123
+ if _loopback(request.full_url):
124
+ handlers.append(urllib.request.ProxyHandler({}))
125
+ return urllib.request.build_opener(*handlers).open(request, timeout=timeout)
126
+
127
+
128
+ def _decide(site: str, state: dict[str, Any], questions: dict[str, Any],
129
+ incumbent: str, chosen: str) -> dict[str, Any]:
130
+ """One decision. Uses the jev CLI when present, else a direct POST."""
131
+ if _HAS_JEV and not settings.project_dir():
132
+ result: dict[str, Any] = jev.decide(
133
+ site, state, questions, incumbent=incumbent, backend=chosen,
134
+ )
135
+ return result
136
+
137
+ env = settings.environment()
138
+ url, model, keyvar = _BACKENDS[chosen]
139
+ if url is None:
140
+ # 127.0.0.1, not localhost: localhost resolves to ::1 first.
141
+ base = env.get("JEV_BASE_URL", "http://127.0.0.1:8000").rstrip("/")
142
+ if not _loopback(base) and (
143
+ "JEV_BASE_URL" not in os.environ # a repository .env may not pick the host
144
+ or env.get("ATTOCODE_LOCAL_ONLY", "").lower() in {"1", "true", "yes", "on"}
145
+ ):
146
+ raise ValueError("a local Jev server must use a loopback IP address")
147
+ url = base + "/v1/systemone"
148
+ headers = {"Content-Type": "application/json"}
149
+ key = env.get(keyvar)
150
+ if key:
151
+ headers["Authorization"] = "Bearer " + key
152
+ body = json.dumps(
153
+ {"model": env.get("JEV_MODEL", model), "state": state, "questions": questions}
154
+ ).encode()
155
+ request = urllib.request.Request(url, data=body, headers=headers) # noqa: S310
156
+ timeout = float(env.get("JEV_TIMEOUT", "20"))
157
+ with _open(request, timeout) as response:
158
+ parsed: dict[str, Any] = json.loads(response.read())
159
+ return {"answers": parsed.get("answers"), "error": None}
160
+
161
+
162
+ def backend() -> str:
163
+ """Pick a Jev backend: local when offline or unkeyed, else the configured one."""
164
+ _load_env()
165
+ env = settings.environment()
166
+ if env.get("ATTOCODE_LOCAL_ONLY", "").lower() in {"1", "true", "yes", "on"}:
167
+ return "local"
168
+ explicit = env.get("JEV_BACKEND") or settings.local_settings().get("backend")
169
+ if explicit:
170
+ if explicit not in _BACKENDS:
171
+ raise ValueError("Unsupported Jev backend")
172
+ return explicit
173
+ if env.get("OPENROUTER_API_KEY"):
174
+ return "openrouter"
175
+ if env.get("TYPESAFE_API_KEY"):
176
+ return "typesafe"
177
+ return "local"
178
+
179
+
180
+ def _state(finding: Any) -> dict[str, Any]:
181
+ """The evidence Jev sees. Redacted: this leaves the machine."""
182
+ code = "\n".join([
183
+ *getattr(finding, "context_before", []),
184
+ getattr(finding, "code_snippet", ""),
185
+ *getattr(finding, "context_after", []),
186
+ ])
187
+ return {
188
+ "file": redact(finding.file),
189
+ "line": finding.line,
190
+ "rule": redact(getattr(finding, "rule_id", "")),
191
+ "message": redact(getattr(finding, "description", ""))[:1000],
192
+ "code": redact(code)[:3000],
193
+ }
194
+
195
+
196
+ def estimate(findings: Sequence[Any], *, min_confidence: float) -> list[float | None]:
197
+ """Return p(true positive) per finding, or None where Jev gave no answer.
198
+
199
+ The incumbent recorded with each row is the constant's own verdict at
200
+ ``min_confidence``, which makes every logged row a paired comparison.
201
+ """
202
+ if not findings:
203
+ return []
204
+
205
+ _load_env()
206
+ questions = {"p": {"type": "noul", "instructions": QUESTION, "criteria": CRITERIA}}
207
+ chosen = backend()
208
+
209
+ def ask(finding: Any) -> float | None:
210
+ try:
211
+ row = _decide(
212
+ "attocode_rule_fp",
213
+ _state(finding),
214
+ questions,
215
+ "yes" if finding.confidence >= min_confidence else "no",
216
+ chosen,
217
+ )
218
+ except Exception: # thread plus network; a finding is not worth an outage
219
+ logger.debug("jev: decide failed for %s:%s", finding.file, finding.line)
220
+ return None
221
+ try:
222
+ if row.get("error"):
223
+ return None
224
+ value = ((row.get("answers") or {}).get("p") or {}).get("noul")
225
+ if isinstance(value, bool) or not isinstance(value, int | float):
226
+ return None
227
+ return float(value) if math.isfinite(value) and 0 <= value <= 1 else None
228
+ except (AttributeError, TypeError, ValueError, OverflowError):
229
+ return None
230
+
231
+ with ThreadPoolExecutor(max_workers=WORKERS) as pool:
232
+ from contextvars import copy_context
233
+ futures = [pool.submit(copy_context().run, ask, finding) for finding in findings]
234
+ return [future.result() for future in futures]