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,375 @@
1
+ """Community rule registry — search, install, validate, and publish packs.
2
+
3
+ Phase 1: GitHub-based registry with a static YAML index.
4
+ Packs are Git repositories or subdirectories that follow the standard
5
+ pack structure (manifest.yaml + rules/*.yaml).
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import logging
11
+ import re
12
+ import shutil
13
+ import tempfile
14
+ from dataclasses import dataclass, field
15
+ from pathlib import Path
16
+
17
+ from attocode_intel.rules.packs.pack_loader import _load_manifest
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+ # Default registry index URL (GitHub raw)
22
+ DEFAULT_REGISTRY_URL = (
23
+ "https://raw.githubusercontent.com/attocode/rules/main/index.yaml"
24
+ )
25
+
26
+
27
+ # ---------------------------------------------------------------------------
28
+ # Registry index
29
+ # ---------------------------------------------------------------------------
30
+
31
+
32
+ @dataclass(slots=True)
33
+ class RegistryEntry:
34
+ """A pack listed in the community registry."""
35
+
36
+ name: str
37
+ url: str # Git clone URL
38
+ languages: list[str] = field(default_factory=list)
39
+ rules_count: int = 0
40
+ description: str = ""
41
+ author: str = ""
42
+ version: str = "1.0.0"
43
+ tags: list[str] = field(default_factory=list)
44
+
45
+
46
+ def fetch_registry_index(
47
+ registry_url: str = DEFAULT_REGISTRY_URL,
48
+ ) -> list[RegistryEntry]:
49
+ """Fetch the community registry index.
50
+
51
+ Returns:
52
+ List of RegistryEntry from the remote index.
53
+ Returns empty list on network errors (graceful degradation).
54
+ """
55
+ try:
56
+ import urllib.request
57
+ import yaml # type: ignore[import-untyped]
58
+
59
+ with urllib.request.urlopen(registry_url, timeout=10) as resp:
60
+ data = yaml.safe_load(resp.read().decode("utf-8"))
61
+ except Exception as exc:
62
+ logger.debug("Failed to fetch registry index: %s", exc)
63
+ return []
64
+
65
+ if not isinstance(data, dict) or "packs" not in data:
66
+ return []
67
+
68
+ entries: list[RegistryEntry] = []
69
+ for item in data["packs"]:
70
+ if not isinstance(item, dict) or "name" not in item:
71
+ continue
72
+ entries.append(RegistryEntry(
73
+ name=str(item["name"]),
74
+ url=str(item.get("url", "")),
75
+ languages=list(item.get("languages", [])),
76
+ rules_count=int(item.get("rules_count", 0)),
77
+ description=str(item.get("description", "")),
78
+ author=str(item.get("author", "")),
79
+ version=str(item.get("version", "1.0.0")),
80
+ tags=list(item.get("tags", [])),
81
+ ))
82
+
83
+ return entries
84
+
85
+
86
+ def search_packs(
87
+ query: str = "",
88
+ *,
89
+ language: str = "",
90
+ registry_url: str = DEFAULT_REGISTRY_URL,
91
+ ) -> list[RegistryEntry]:
92
+ """Search the community registry for packs matching a query.
93
+
94
+ Args:
95
+ query: Free-text search (matches name, description, tags).
96
+ language: Filter to packs supporting this language.
97
+ registry_url: Override registry URL.
98
+
99
+ Returns:
100
+ Matching registry entries.
101
+ """
102
+ entries = fetch_registry_index(registry_url)
103
+ if not entries:
104
+ return []
105
+
106
+ results: list[RegistryEntry] = []
107
+ query_lower = query.lower()
108
+
109
+ for entry in entries:
110
+ if language and language.lower() not in [l.lower() for l in entry.languages]:
111
+ continue
112
+ if query_lower:
113
+ searchable = f"{entry.name} {entry.description} {' '.join(entry.tags)}".lower()
114
+ if query_lower not in searchable:
115
+ continue
116
+ results.append(entry)
117
+
118
+ return results
119
+
120
+
121
+ # ---------------------------------------------------------------------------
122
+ # Remote pack installation
123
+ # ---------------------------------------------------------------------------
124
+
125
+
126
+ def install_remote_pack(
127
+ pack_url: str,
128
+ project_dir: str,
129
+ *,
130
+ pack_name: str = "",
131
+ ) -> str:
132
+ """Install a pack from a remote Git URL into .attocode/packs/.
133
+
134
+ Clones the repository at depth=1 and validates the pack before installing.
135
+
136
+ Args:
137
+ pack_url: URL to clone or download.
138
+ project_dir: Project root directory.
139
+ pack_name: Override pack name (default: derived from URL).
140
+
141
+ Returns:
142
+ Status message.
143
+ """
144
+ if not pack_name:
145
+ # Derive name from URL: github.com/user/repo -> repo
146
+ pack_name = pack_url.rstrip("/").rsplit("/", 1)[-1]
147
+ pack_name = pack_name.removesuffix(".git")
148
+
149
+ # Sanitize pack_name to prevent path traversal
150
+ pack_name = re.sub(r"[^a-zA-Z0-9_-]", "_", pack_name)
151
+
152
+ dest = Path(project_dir) / ".attocode" / "packs" / pack_name
153
+ if dest.is_dir():
154
+ return f"Pack '{pack_name}' already installed at {dest}. Delete to reinstall."
155
+
156
+ # Try git clone first
157
+ import subprocess
158
+
159
+ with tempfile.TemporaryDirectory() as tmpdir:
160
+ clone_dir = Path(tmpdir) / pack_name
161
+ try:
162
+ subprocess.run(
163
+ ["git", "clone", "--depth=1", pack_url, str(clone_dir)],
164
+ capture_output=True, text=True, timeout=60,
165
+ check=True,
166
+ )
167
+ except (subprocess.CalledProcessError, FileNotFoundError, subprocess.TimeoutExpired) as exc:
168
+ return f"Failed to clone '{pack_url}': {exc}"
169
+
170
+ # Validate before installing
171
+ errors = validate_pack(str(clone_dir))
172
+ if errors:
173
+ return f"Pack validation failed:\n" + "\n".join(f" - {e}" for e in errors)
174
+
175
+ # Copy to destination
176
+ dest.parent.mkdir(parents=True, exist_ok=True)
177
+ shutil.copytree(str(clone_dir), str(dest), dirs_exist_ok=True)
178
+ # Remove .git directory
179
+ git_dir = dest / ".git"
180
+ if git_dir.is_dir():
181
+ shutil.rmtree(str(git_dir))
182
+
183
+ manifest = _load_manifest(dest)
184
+ return (
185
+ f"Installed community pack '{pack_name}' to {dest}\n"
186
+ f" Version: {manifest.version if manifest else 'unknown'}\n"
187
+ f" Languages: {', '.join(manifest.languages) if manifest else pack_name}"
188
+ )
189
+
190
+
191
+ # ---------------------------------------------------------------------------
192
+ # Pack validation
193
+ # ---------------------------------------------------------------------------
194
+
195
+
196
+ def validate_pack(pack_dir: str) -> list[str]:
197
+ """Validate a pack directory for correctness and completeness.
198
+
199
+ Checks:
200
+ 1. manifest.yaml present and parseable
201
+ 2. At least one rule file in rules/
202
+ 3. All rule IDs are unique
203
+ 4. Required fields present (message, severity)
204
+ 5. All regex patterns compile
205
+ 6. Inline test_cases pass (if present)
206
+
207
+ Returns:
208
+ List of error messages (empty = valid).
209
+ """
210
+ errors: list[str] = []
211
+ pack_path = Path(pack_dir)
212
+
213
+ # 1. Manifest — check existence first
214
+ manifest_file = pack_path / "manifest.yaml"
215
+ if not manifest_file.is_file():
216
+ errors.append("Missing manifest.yaml (create one with name, version, languages)")
217
+ return errors
218
+
219
+ manifest = _load_manifest(pack_path)
220
+ if manifest is None:
221
+ errors.append("Failed to parse manifest.yaml")
222
+ return errors
223
+
224
+ if not manifest.name:
225
+ errors.append("Manifest missing 'name' field")
226
+
227
+ # 2. Rules directory
228
+ rules_dir = pack_path / "rules"
229
+ if not rules_dir.is_dir():
230
+ errors.append("Missing rules/ directory")
231
+ return errors
232
+
233
+ yaml_files = list(rules_dir.glob("*.yaml"))
234
+ if not yaml_files:
235
+ errors.append("No *.yaml rule files in rules/")
236
+ return errors
237
+
238
+ # 3-6. Load and validate rules
239
+ try:
240
+ import yaml as _yaml # type: ignore[import-untyped]
241
+ except ImportError:
242
+ errors.append("PyYAML not installed — cannot validate rules")
243
+ return errors
244
+
245
+ seen_ids: set[str] = set()
246
+ rule_count = 0
247
+
248
+ for yaml_file in yaml_files:
249
+ try:
250
+ content = yaml_file.read_text(encoding="utf-8")
251
+ data = _yaml.safe_load(content)
252
+ if data is None:
253
+ continue
254
+ items = [data] if isinstance(data, dict) else data if isinstance(data, list) else []
255
+
256
+ for i, item in enumerate(items):
257
+ if not isinstance(item, dict):
258
+ errors.append(f"{yaml_file.name}[{i}]: expected dict, got {type(item).__name__}")
259
+ continue
260
+
261
+ rule_id = item.get("id", "")
262
+ if not rule_id:
263
+ errors.append(f"{yaml_file.name}[{i}]: missing 'id' field")
264
+ continue
265
+
266
+ # 3. Unique IDs
267
+ if rule_id in seen_ids:
268
+ errors.append(f"{yaml_file.name}[{i}]: duplicate rule ID '{rule_id}'")
269
+ seen_ids.add(rule_id)
270
+
271
+ # 4. Check required fields
272
+ for req in ("pattern", "message", "severity"):
273
+ if req not in item and "patterns" not in item and "pattern-either" not in item:
274
+ if req == "pattern":
275
+ continue # composite patterns don't need top-level pattern
276
+ errors.append(f"{yaml_file.name}[{i}]: missing '{req}' field")
277
+
278
+ # 5. Regex compilation
279
+ pattern_str = item.get("pattern", "")
280
+ if pattern_str:
281
+ try:
282
+ re.compile(pattern_str)
283
+ except re.error as exc:
284
+ errors.append(f"{yaml_file.name}[{i}]: invalid regex '{pattern_str[:40]}': {exc}")
285
+
286
+ # 6. Inline test_cases (per-line matching, consistent with executor)
287
+ test_cases = item.get("test_cases", [])
288
+ if test_cases and pattern_str:
289
+ try:
290
+ compiled = re.compile(pattern_str)
291
+ for j, tc in enumerate(test_cases):
292
+ code = str(tc.get("code", ""))
293
+ should_match = bool(tc.get("should_match", True))
294
+ matched = any(compiled.search(ln) for ln in code.splitlines()) if code else False
295
+ if should_match and not matched:
296
+ errors.append(
297
+ f"{yaml_file.name}[{i}].test_cases[{j}]: "
298
+ f"expected match on '{code[:40]}'"
299
+ )
300
+ elif not should_match and matched:
301
+ errors.append(
302
+ f"{yaml_file.name}[{i}].test_cases[{j}]: "
303
+ f"unexpected match on '{code[:40]}'"
304
+ )
305
+ except re.error:
306
+ pass # already reported above
307
+
308
+ rule_count += 1
309
+
310
+ except Exception as exc:
311
+ errors.append(f"Failed to parse {yaml_file.name}: {exc}")
312
+
313
+ if rule_count == 0:
314
+ errors.append("No valid rules found")
315
+
316
+ return errors
317
+
318
+
319
+ # ---------------------------------------------------------------------------
320
+ # Pack publishing helper
321
+ # ---------------------------------------------------------------------------
322
+
323
+
324
+ def prepare_pack_for_publish(pack_dir: str) -> str:
325
+ """Validate and prepare a pack for community sharing.
326
+
327
+ Returns a status message with validation results and next steps.
328
+ """
329
+ errors = validate_pack(pack_dir)
330
+
331
+ if errors:
332
+ return (
333
+ "Pack validation FAILED — fix these before publishing:\n"
334
+ + "\n".join(f" - {e}" for e in errors)
335
+ )
336
+
337
+ manifest = _load_manifest(Path(pack_dir))
338
+ from attocode_intel.rules.loader import load_yaml_rules
339
+
340
+ rules_dir = Path(pack_dir) / "rules"
341
+ rules = load_yaml_rules(str(rules_dir))
342
+
343
+ return (
344
+ f"Pack '{manifest.name}' is ready for publishing!\n"
345
+ f" Version: {manifest.version}\n"
346
+ f" Languages: {', '.join(manifest.languages)}\n"
347
+ f" Rules: {len(rules)}\n"
348
+ f"\nNext steps:\n"
349
+ f" 1. Push to a public Git repository\n"
350
+ f" 2. Submit a PR to the attocode/rules registry to list your pack\n"
351
+ f" 3. Others can install with: install_community_pack('{manifest.name}')"
352
+ )
353
+
354
+
355
+ # ---------------------------------------------------------------------------
356
+ # Formatting
357
+ # ---------------------------------------------------------------------------
358
+
359
+
360
+ def format_registry_search(entries: list[RegistryEntry]) -> str:
361
+ """Format registry search results as markdown."""
362
+ if not entries:
363
+ return "No packs found matching your query."
364
+
365
+ lines = ["## Community Rule Packs\n"]
366
+ for e in entries:
367
+ langs = ", ".join(e.languages) if e.languages else "any"
368
+ tags = " ".join(f"`{t}`" for t in e.tags) if e.tags else ""
369
+ lines.append(
370
+ f"- **{e.name}** v{e.version} ({langs}) — {e.description}\n"
371
+ f" {e.rules_count} rules | by {e.author or 'community'} {tags}\n"
372
+ f" Install: `install_community_pack('{e.name}')`"
373
+ )
374
+
375
+ return "\n".join(lines)
@@ -0,0 +1,181 @@
1
+ """Metavariable support for regex-tier rules.
2
+
3
+ Translates Semgrep-style ``$VAR`` placeholders in pattern strings into
4
+ named capture groups, enabling extraction of matched fragments and
5
+ template-based autofix.
6
+
7
+ Metavariable type inference:
8
+ $FUNC, $NAME, $VAR, $IDENT -> identifier (``\\w+``)
9
+ $ARG, $EXPR, $VALUE -> expression within parens (``[^,)]+``)
10
+ $STR -> string literal (``["'][^"']*["']``)
11
+ $NUM -> number (``\\d+(?:\\.\\d+)?``)
12
+ $TYPE -> type annotation (``[\\w.\\[\\]]+``)
13
+ $... -> any (non-capturing ``.*?``)
14
+ All others -> identifier (``\\w+``)
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import re
20
+
21
+ # Pattern that matches $IDENTIFIER tokens in a rule pattern string
22
+ _METAVAR_RE = re.compile(r"\$(\.\.\.|[A-Z_][A-Z0-9_]*)")
23
+
24
+ # Named groups map — root names (no numeric suffix) to regex fragment
25
+ _TYPE_FRAGMENTS: dict[str, str] = {
26
+ "FUNC": r"\w+",
27
+ "NAME": r"\w+",
28
+ "VAR": r"\w+",
29
+ "IDENT": r"\w+",
30
+ "ARG": r"[^,)]+",
31
+ "EXPR": r".+?",
32
+ "VALUE": r"[^,)]+",
33
+ "STR": r"""["'][^"']*["']""",
34
+ "NUM": r"\d+(?:\.\d+)?",
35
+ "TYPE": r"[\w.\[\]]+",
36
+ }
37
+
38
+ # Default regex for any $X not in _TYPE_FRAGMENTS
39
+ _DEFAULT_FRAGMENT = r"\w+"
40
+
41
+ # Comparison operators for metavariable-comparison constraints
42
+ _CMP_OPS = {
43
+ ">": float.__gt__, ">=": float.__ge__,
44
+ "<": float.__lt__, "<=": float.__le__,
45
+ "==": float.__eq__, "!=": float.__ne__,
46
+ }
47
+
48
+
49
+ def _infer_fragment(name: str) -> str:
50
+ """Infer the regex fragment for a metavariable name.
51
+
52
+ Strips trailing digits to find the root type hint:
53
+ ``$FUNC2`` -> root ``FUNC`` -> ``\\w+``.
54
+ """
55
+ root = name.rstrip("0123456789")
56
+ return _TYPE_FRAGMENTS.get(root, _DEFAULT_FRAGMENT)
57
+
58
+
59
+ def has_metavars(pattern_str: str) -> bool:
60
+ """Return True if *pattern_str* contains any ``$VAR`` metavariable tokens."""
61
+ return bool(_METAVAR_RE.search(pattern_str))
62
+
63
+
64
+ def compile_metavar_pattern(
65
+ pattern_str: str,
66
+ ) -> tuple[re.Pattern[str], list[str]]:
67
+ """Compile a pattern string with ``$VAR`` metavariables into a regex.
68
+
69
+ Returns:
70
+ (compiled_regex, metavar_names) where *metavar_names* lists the
71
+ metavariable names in order of appearance (``"..."`` for ellipsis).
72
+ """
73
+ metavar_names: list[str] = []
74
+ seen: set[str] = set()
75
+ pos = 0
76
+ parts: list[str] = []
77
+
78
+ for m in _METAVAR_RE.finditer(pattern_str):
79
+ # Escape the literal text between the previous match end and this match start
80
+ parts.append(re.escape(pattern_str[pos:m.start()]))
81
+ pos = m.end()
82
+
83
+ token = m.group(1) # e.g. "FUNC", "...", "ARG2"
84
+
85
+ if token == "...":
86
+ parts.append(r".*?")
87
+ metavar_names.append("...")
88
+ elif token in seen:
89
+ # Back-reference — must match the same text as the first occurrence
90
+ parts.append(rf"(?P={token})")
91
+ metavar_names.append(token)
92
+ else:
93
+ fragment = _infer_fragment(token)
94
+ parts.append(rf"(?P<{token}>{fragment})")
95
+ metavar_names.append(token)
96
+ seen.add(token)
97
+
98
+ # Trailing literal after the last metavar
99
+ parts.append(re.escape(pattern_str[pos:]))
100
+
101
+ combined = "".join(parts)
102
+ return re.compile(combined), metavar_names
103
+
104
+
105
+ def interpolate_message(template: str, captures: dict[str, str]) -> str:
106
+ """Substitute ``$VAR`` references in a message template with captured values."""
107
+ def _replace(m: re.Match[str]) -> str:
108
+ token = m.group(1)
109
+ if token == "...":
110
+ return "$..."
111
+ return captures.get(token, m.group(0))
112
+
113
+ return _METAVAR_RE.sub(_replace, template)
114
+
115
+
116
+ def apply_metavar_fix(
117
+ search_template: str,
118
+ replace_template: str,
119
+ captures: dict[str, str],
120
+ ) -> tuple[str, str]:
121
+ """Resolve ``$VAR`` references in autofix search/replace templates.
122
+
123
+ Returns:
124
+ (concrete_search, concrete_replace) with metavariables substituted.
125
+ """
126
+ return (
127
+ interpolate_message(search_template, captures),
128
+ interpolate_message(replace_template, captures),
129
+ )
130
+
131
+
132
+ # ---------------------------------------------------------------------------
133
+ # Metavariable constraints (post-match filters)
134
+ # ---------------------------------------------------------------------------
135
+
136
+
137
+ def check_metavar_constraints(
138
+ captures: dict[str, str],
139
+ metavar_regex: dict[str, str] | None = None,
140
+ metavar_comparison: dict[str, str] | None = None,
141
+ ) -> bool:
142
+ """Evaluate metavariable constraints against captured values.
143
+
144
+ Args:
145
+ captures: Captured metavar values from a regex match.
146
+ metavar_regex: Map of ``$VAR`` name to regex that the captured
147
+ value must match (e.g. ``{"FUNC": "^(query|execute)$"}``).
148
+ metavar_comparison: Map of ``$VAR`` name to comparison expression
149
+ (e.g. ``{"NUM": "> 1000"}``). Only numeric comparisons supported.
150
+
151
+ Returns:
152
+ True if all constraints pass (or no constraints given).
153
+ """
154
+ if metavar_regex:
155
+ for var, regex_str in metavar_regex.items():
156
+ name = var.lstrip("$")
157
+ val = captures.get(name, "")
158
+ if not re.search(regex_str, val):
159
+ return False
160
+
161
+ if metavar_comparison:
162
+ for var, expr in metavar_comparison.items():
163
+ name = var.lstrip("$")
164
+ val = captures.get(name, "")
165
+ try:
166
+ num = float(val)
167
+ except (ValueError, TypeError):
168
+ return False
169
+ # Parse operator + value from expr like "> 1000" or "<= 5"
170
+ cmp_match = re.match(r"([<>!=]+)\s*(-?\d+(?:\.\d+)?)", expr.strip())
171
+ if not cmp_match:
172
+ return False
173
+ op, threshold_str = cmp_match.group(1), cmp_match.group(2)
174
+ threshold = float(threshold_str)
175
+ op_fn = _CMP_OPS.get(op)
176
+ if op_fn is None:
177
+ return False # unrecognized operator — fail closed
178
+ if not op_fn(num, threshold):
179
+ return False
180
+
181
+ return True