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,351 @@
1
+ """Security compliance scanner.
2
+
3
+ Scans source files for secrets, anti-patterns, and dependency issues.
4
+ All scanning is local — no external API calls for basic mode.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import logging
10
+ import os
11
+ import time
12
+ from dataclasses import dataclass, field
13
+ from pathlib import Path
14
+
15
+ from attocode_intel._internal.integrations.security.matcher import iter_pattern_matches
16
+ from attocode_intel._internal.integrations.security.patterns import (
17
+ ANTI_PATTERNS,
18
+ SECRET_PATTERNS,
19
+ Category,
20
+ SecurityPattern,
21
+ Severity,
22
+ )
23
+ from attocode_intel._internal.integrations.security.custom_rules import (
24
+ get_autofix_from_rules,
25
+ load_custom_rules,
26
+ )
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+ # Extensions worth scanning (source code, not binary/media)
31
+ _SCANNABLE_EXTENSIONS = {
32
+ ".py", ".pyi", ".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs",
33
+ ".rs", ".go", ".java", ".kt", ".rb", ".php", ".swift", ".cs",
34
+ ".c", ".cpp", ".h", ".hpp", ".lua", ".sh", ".bash", ".zsh",
35
+ ".yaml", ".yml", ".toml", ".json", ".env", ".cfg", ".ini", ".conf",
36
+ }
37
+
38
+ # Dot-files that should still be scanned (e.g. .env contains secrets)
39
+ _SCANNABLE_DOTFILES = {".env", ".envrc", ".env.local", ".env.production", ".env.staging"}
40
+ _EXTRA_IGNORED_DIRS = {"site"}
41
+ _PATTERN_DEFINITION_FILES = {
42
+ os.path.normpath("src/attocode/integrations/security/patterns.py"),
43
+ os.path.normpath("tests/unit/code_intel/test_supply_chain_rules.py"),
44
+ }
45
+
46
+
47
+ @dataclass(slots=True)
48
+ class SecurityFinding:
49
+ """A single security finding."""
50
+
51
+ severity: Severity
52
+ category: Category
53
+ file_path: str
54
+ line: int
55
+ message: str
56
+ recommendation: str
57
+ cwe_id: str = ""
58
+ pattern_name: str = ""
59
+ fix_diff: str = "" # unified diff suggestion for mechanical fixes
60
+
61
+
62
+ # Autofix templates: pattern_name -> (search, replace) for mechanical fixes.
63
+ # These are safe, deterministic transformations for common patterns.
64
+ _AUTOFIX_TEMPLATES: dict[str, tuple[str, str]] = {
65
+ "python_yaml_unsafe": ("yaml.load(", "yaml.safe_load("),
66
+ "python_shell_true": ("shell=True", "shell=False"),
67
+ "python_tempfile_insecure": ("tempfile.mktemp(", "tempfile.mkstemp("),
68
+ "python_verify_false": ("verify=False", "verify=True"),
69
+ }
70
+
71
+
72
+ @dataclass(slots=True)
73
+ class SecurityReport:
74
+ """Full security scan report."""
75
+
76
+ findings: list[SecurityFinding]
77
+ scan_time_ms: float
78
+ files_scanned: int
79
+ summary: dict[str, int] = field(default_factory=dict) # severity -> count
80
+ compliance_score: int = 100
81
+
82
+
83
+ @dataclass(slots=True)
84
+ class SecurityScanner:
85
+ """Scan a project for security issues.
86
+
87
+ Three scan modes:
88
+ - ``quick``: secrets only, fast
89
+ - ``full``: secrets + anti-patterns + dependencies
90
+ - ``secrets``: only secret detection
91
+ - ``patterns``: only code anti-patterns
92
+ - ``dependencies``: only dependency audit
93
+ """
94
+
95
+ root_dir: str
96
+ _language_map: dict[str, str] = field(default_factory=dict, repr=False)
97
+ _custom_patterns: list[SecurityPattern] = field(default_factory=list, repr=False)
98
+ _custom_autofixes: dict[str, tuple[str, str]] = field(default_factory=dict, repr=False)
99
+
100
+ def __post_init__(self) -> None:
101
+ from attocode_intel._internal.integrations.context.codebase_context import EXTENSION_LANGUAGES
102
+ self._language_map = dict(EXTENSION_LANGUAGES)
103
+ self._custom_patterns = load_custom_rules(self.root_dir)
104
+ self._custom_autofixes = get_autofix_from_rules(self.root_dir)
105
+
106
+ def scan(
107
+ self,
108
+ mode: str = "full",
109
+ path: str = "",
110
+ ) -> SecurityReport:
111
+ """Run a security scan.
112
+
113
+ Args:
114
+ mode: Scan mode — 'quick', 'full', 'secrets', 'patterns', 'dependencies'.
115
+ path: Optional subdirectory to scan (relative to root).
116
+
117
+ Returns:
118
+ SecurityReport with findings and compliance score.
119
+ """
120
+ start = time.monotonic()
121
+ findings: list[SecurityFinding] = []
122
+ files_scanned = 0
123
+
124
+ # Resolve and validate path stays inside root_dir to prevent traversal
125
+ if path:
126
+ resolved = (Path(self.root_dir) / path).resolve()
127
+ root_resolved = Path(self.root_dir).resolve()
128
+ if not str(resolved).startswith(str(root_resolved) + os.sep) and resolved != root_resolved:
129
+ return SecurityReport(findings=[], scan_time_ms=0, files_scanned=0, summary={}, compliance_score=100)
130
+ scan_root = str(resolved)
131
+ else:
132
+ scan_root = self.root_dir
133
+
134
+ run_secrets = mode in ("quick", "full", "secrets")
135
+ run_patterns = mode in ("full", "patterns")
136
+ run_deps = mode in ("full", "dependencies")
137
+
138
+ if run_secrets or run_patterns:
139
+ files_scanned, file_findings = self._scan_files(
140
+ scan_root, run_secrets=run_secrets, run_patterns=run_patterns,
141
+ )
142
+ findings.extend(file_findings)
143
+
144
+ if run_deps:
145
+ from attocode_intel._internal.integrations.security.dependency_audit import DependencyAuditor
146
+ auditor = DependencyAuditor(root_dir=self.root_dir)
147
+ dep_findings = auditor.audit()
148
+ for df in dep_findings:
149
+ findings.append(SecurityFinding(
150
+ severity=df.severity,
151
+ category=df.category,
152
+ file_path=df.source_file,
153
+ line=0,
154
+ message=df.message,
155
+ recommendation=df.recommendation,
156
+ cwe_id=df.cwe_id,
157
+ pattern_name=df.package,
158
+ ))
159
+
160
+ # Compute summary
161
+ summary: dict[str, int] = {}
162
+ for f in findings:
163
+ summary[f.severity] = summary.get(f.severity, 0) + 1
164
+
165
+ # Compute compliance score
166
+ score = self._compute_score(summary)
167
+
168
+ elapsed = (time.monotonic() - start) * 1000
169
+
170
+ return SecurityReport(
171
+ findings=findings,
172
+ scan_time_ms=round(elapsed, 1),
173
+ files_scanned=files_scanned,
174
+ summary=summary,
175
+ compliance_score=score,
176
+ )
177
+
178
+ def _scan_files(
179
+ self,
180
+ scan_root: str,
181
+ *,
182
+ run_secrets: bool = True,
183
+ run_patterns: bool = True,
184
+ ) -> tuple[int, list[SecurityFinding]]:
185
+ """Scan all source files for secrets and/or anti-patterns."""
186
+ findings: list[SecurityFinding] = []
187
+ files_scanned = 0
188
+
189
+ from attocode_intel._internal.integrations.context.codebase_context import (
190
+ DEFAULT_IGNORES,
191
+ SKIP_EXTENSIONS,
192
+ SKIP_FILENAMES,
193
+ )
194
+
195
+ for dirpath, dirnames, filenames in os.walk(scan_root):
196
+ rel_dir = os.path.relpath(dirpath, self.root_dir)
197
+ rel_parts = set(Path(rel_dir).parts) if rel_dir not in (".", "") else set()
198
+ # Filter ignored directories
199
+ dirnames[:] = [
200
+ d for d in dirnames
201
+ if d not in DEFAULT_IGNORES
202
+ and d not in _EXTRA_IGNORED_DIRS
203
+ and d not in rel_parts
204
+ and not d.startswith(".")
205
+ ]
206
+
207
+ for filename in filenames:
208
+ if filename.startswith(".") and filename not in _SCANNABLE_DOTFILES:
209
+ continue
210
+ if filename in SKIP_FILENAMES:
211
+ continue
212
+ ext = Path(filename).suffix.lower()
213
+ if ext in SKIP_EXTENSIONS:
214
+ continue
215
+ if ext not in _SCANNABLE_EXTENSIONS:
216
+ continue
217
+
218
+ full_path = os.path.join(dirpath, filename)
219
+ try:
220
+ rel_path = os.path.relpath(full_path, self.root_dir)
221
+ except ValueError:
222
+ continue
223
+ rel_path_norm = os.path.normpath(rel_path)
224
+
225
+ if rel_path_norm in _PATTERN_DEFINITION_FILES:
226
+ continue
227
+
228
+ try:
229
+ content = Path(full_path).read_text(
230
+ encoding="utf-8", errors="replace",
231
+ )
232
+ except OSError:
233
+ continue
234
+
235
+ files_scanned += 1
236
+ language = self._language_map.get(ext, "")
237
+
238
+ if run_secrets:
239
+ findings.extend(
240
+ self._scan_content(content, rel_path, SECRET_PATTERNS, language),
241
+ )
242
+
243
+ if run_patterns:
244
+ findings.extend(
245
+ self._scan_content(content, rel_path, ANTI_PATTERNS, language),
246
+ )
247
+ if self._custom_patterns:
248
+ findings.extend(
249
+ self._scan_content(content, rel_path, self._custom_patterns, language),
250
+ )
251
+
252
+ return files_scanned, findings
253
+
254
+ def _scan_content(
255
+ self,
256
+ content: str,
257
+ file_path: str,
258
+ patterns: list[SecurityPattern],
259
+ language: str,
260
+ ) -> list[SecurityFinding]:
261
+ """Scan file content against a set of patterns."""
262
+ findings: list[SecurityFinding] = []
263
+ for line_no, line_text, pat in iter_pattern_matches(content, patterns, language):
264
+ fix_diff = ""
265
+ template = _AUTOFIX_TEMPLATES.get(pat.name) or self._custom_autofixes.get(pat.name)
266
+ if template and template[0] in line_text:
267
+ old_line = line_text
268
+ new_line = line_text.replace(template[0], template[1], 1)
269
+ fix_diff = (
270
+ f"--- a/{file_path}\n"
271
+ f"+++ b/{file_path}\n"
272
+ f"@@ -{line_no},1 +{line_no},1 @@\n"
273
+ f"-{old_line}\n"
274
+ f"+{new_line}"
275
+ )
276
+ findings.append(SecurityFinding(
277
+ severity=pat.severity,
278
+ category=pat.category,
279
+ file_path=file_path,
280
+ line=line_no,
281
+ message=pat.message,
282
+ recommendation=pat.recommendation,
283
+ cwe_id=pat.cwe_id,
284
+ pattern_name=pat.name,
285
+ fix_diff=fix_diff,
286
+ ))
287
+ return findings
288
+
289
+ @staticmethod
290
+ def _compute_score(summary: dict[str, int]) -> int:
291
+ """Compute compliance score (0-100).
292
+
293
+ score = 100 - (critical * 20 + high * 10 + medium * 3 + low * 1)
294
+ """
295
+ deductions = (
296
+ summary.get("critical", 0) * 20
297
+ + summary.get("high", 0) * 10
298
+ + summary.get("medium", 0) * 3
299
+ + summary.get("low", 0) * 1
300
+ )
301
+ return max(0, min(100, 100 - deductions))
302
+
303
+ def format_report(self, report: SecurityReport) -> str:
304
+ """Format a SecurityReport as human-readable text."""
305
+ lines: list[str] = []
306
+
307
+ # Header
308
+ lines.append("Security Scan Report")
309
+ lines.append(f"Score: {report.compliance_score}/100 | "
310
+ f"Files: {report.files_scanned} | "
311
+ f"Time: {report.scan_time_ms:.0f}ms")
312
+ lines.append("")
313
+
314
+ # Summary
315
+ if report.summary:
316
+ parts = []
317
+ for sev in ("critical", "high", "medium", "low", "info"):
318
+ count = report.summary.get(sev, 0)
319
+ if count > 0:
320
+ parts.append(f"{sev}: {count}")
321
+ lines.append(f"Findings: {' | '.join(parts)}")
322
+ lines.append("")
323
+
324
+ if not report.findings:
325
+ lines.append("No security issues found.")
326
+ return "\n".join(lines)
327
+
328
+ # Group findings by severity
329
+ by_severity: dict[str, list[SecurityFinding]] = {}
330
+ for f in report.findings:
331
+ by_severity.setdefault(f.severity, []).append(f)
332
+
333
+ for sev in ("critical", "high", "medium", "low", "info"):
334
+ group = by_severity.get(sev, [])
335
+ if not group:
336
+ continue
337
+ lines.append(f"## {sev.upper()} ({len(group)})")
338
+ for f in group[:20]: # Cap per-severity to avoid huge output
339
+ cwe = f" [{f.cwe_id}]" if f.cwe_id else ""
340
+ lines.append(f" {f.file_path}:{f.line}{cwe}")
341
+ lines.append(f" {f.message}")
342
+ lines.append(f" → {f.recommendation}")
343
+ if f.fix_diff:
344
+ lines.append(f" Autofix:")
345
+ for dl in f.fix_diff.splitlines():
346
+ lines.append(f" {dl}")
347
+ if len(group) > 20:
348
+ lines.append(f" ... and {len(group) - 20} more {sev} findings")
349
+ lines.append("")
350
+
351
+ return "\n".join(lines)
@@ -0,0 +1 @@
1
+ """Internal code intelligence support; not a public API."""
@@ -0,0 +1,64 @@
1
+ """Gitignore-compatible path filtering using pathspec."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ import pathspec
8
+
9
+
10
+ class IgnoreManager:
11
+ """Manages .gitignore-style path filtering.
12
+
13
+ Loads patterns from .gitignore files and provides matching.
14
+ """
15
+
16
+ def __init__(self, root: str | Path = ".") -> None:
17
+ self._root = Path(root)
18
+ self._spec: pathspec.PathSpec | None = None
19
+ self._load()
20
+
21
+ def _load(self) -> None:
22
+ """Load .gitignore patterns from the root directory."""
23
+ patterns: list[str] = []
24
+
25
+ # Always ignore these
26
+ patterns.extend([
27
+ ".git/",
28
+ "node_modules/",
29
+ "__pycache__/",
30
+ ".venv/",
31
+ "*.pyc",
32
+ ".DS_Store",
33
+ ])
34
+
35
+ # Load project .gitignore
36
+ gitignore = self._root / ".gitignore"
37
+ if gitignore.is_file():
38
+ try:
39
+ text = gitignore.read_text(encoding="utf-8", errors="replace")
40
+ for line in text.splitlines():
41
+ line = line.strip()
42
+ if line and not line.startswith("#"):
43
+ patterns.append(line)
44
+ except OSError:
45
+ pass
46
+
47
+ self._spec = pathspec.PathSpec.from_lines("gitignore", patterns)
48
+
49
+ def is_ignored(self, path: str | Path) -> bool:
50
+ """Check if a path should be ignored."""
51
+ if self._spec is None:
52
+ return False
53
+ rel = str(path)
54
+ # pathspec expects forward slashes
55
+ rel = rel.replace("\\", "/")
56
+ return self._spec.match_file(rel)
57
+
58
+ def filter_paths(self, paths: list[str]) -> list[str]:
59
+ """Filter a list of paths, returning only non-ignored ones."""
60
+ return [p for p in paths if not self.is_ignored(p)]
61
+
62
+ def reload(self) -> None:
63
+ """Reload patterns from disk."""
64
+ self._load()
@@ -0,0 +1,51 @@
1
+ """Token estimation using tiktoken."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import tiktoken
6
+
7
+ # Default encoding for Claude / GPT-4 class models
8
+ _DEFAULT_ENCODING = "cl100k_base"
9
+ _encoder: tiktoken.Encoding | None = None
10
+
11
+ # Shared ratio for quick estimation without tiktoken
12
+ CHARS_PER_TOKEN = 3.5
13
+
14
+
15
+ def _get_encoder() -> tiktoken.Encoding:
16
+ global _encoder
17
+ if _encoder is None:
18
+ _encoder = tiktoken.get_encoding(_DEFAULT_ENCODING)
19
+ return _encoder
20
+
21
+
22
+ def count_tokens(text: str) -> int:
23
+ """Count tokens in text using tiktoken.
24
+
25
+ Falls back to character-based estimation if tiktoken fails.
26
+ """
27
+ if not text:
28
+ return 0
29
+ try:
30
+ return len(_get_encoder().encode(text))
31
+ except Exception:
32
+ return estimate_tokens(text)
33
+
34
+
35
+ def estimate_tokens(text: str) -> int:
36
+ """Quick character-based token estimation without tiktoken."""
37
+ if not text:
38
+ return 0
39
+ return max(1, int(len(text) / CHARS_PER_TOKEN))
40
+
41
+
42
+ def estimate_messages_tokens(messages: list[dict[str, str]]) -> int:
43
+ """Estimate total tokens for a list of message dicts."""
44
+ total = 0
45
+ for msg in messages:
46
+ # Each message has ~4 token overhead for role/formatting
47
+ total += 4
48
+ content = msg.get("content", "")
49
+ if content:
50
+ total += count_tokens(content)
51
+ return total
@@ -0,0 +1 @@
1
+ """Internal code intelligence support; not a public API."""