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,354 @@
1
+ """Architecture drift detection.
2
+
3
+ Compares actual file dependencies against declared architecture boundaries
4
+ to detect layering violations, circular dependencies, and unauthorized imports.
5
+
6
+ Loads rules from ``.attocode/architecture.yaml`` and checks them against the
7
+ real dependency graph built by the AST service.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import logging
13
+ import os
14
+ from dataclasses import dataclass, field
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ # ---------------------------------------------------------------------------
20
+ # Data classes
21
+ # ---------------------------------------------------------------------------
22
+
23
+
24
+ @dataclass(slots=True)
25
+ class ArchViolation:
26
+ """A single architecture boundary violation."""
27
+
28
+ source_file: str # file that has the import
29
+ target_file: str # file being imported
30
+ source_layer: str # layer the source belongs to
31
+ target_layer: str # layer the target belongs to
32
+ rule: str # human-readable rule description
33
+ severity: str = "high" # high for deny violations, medium for unlisted
34
+
35
+
36
+ @dataclass(slots=True)
37
+ class ArchReport:
38
+ """Architecture drift detection report."""
39
+
40
+ violations: list[ArchViolation]
41
+ layers_defined: int
42
+ rules_defined: int
43
+ files_checked: int
44
+ compliant_files: int
45
+
46
+
47
+ @dataclass
48
+ class ArchLayer:
49
+ """A named architecture layer with path patterns."""
50
+
51
+ name: str
52
+ paths: list[str]
53
+
54
+ def matches(self, file_path: str) -> bool:
55
+ """Check if a file belongs to this layer.
56
+
57
+ Uses forward-slash normalised prefix matching so that OS-specific
58
+ separators do not cause false negatives.
59
+ """
60
+ normalised = file_path.replace(os.sep, "/")
61
+ for path_prefix in self.paths:
62
+ prefix = path_prefix.replace(os.sep, "/")
63
+ if normalised.startswith(prefix):
64
+ return True
65
+ return False
66
+
67
+
68
+ @dataclass
69
+ class ArchRule:
70
+ """A dependency rule between layers."""
71
+
72
+ from_layer: str
73
+ allowed: list[str] = field(default_factory=list)
74
+ denied: list[str] = field(default_factory=list)
75
+
76
+
77
+ # ---------------------------------------------------------------------------
78
+ # Loading
79
+ # ---------------------------------------------------------------------------
80
+
81
+
82
+ def load_architecture(
83
+ project_dir: str,
84
+ ) -> tuple[list[ArchLayer], list[ArchRule], dict[str, list[str]]]:
85
+ """Load architecture definitions from ``.attocode/architecture.yaml``.
86
+
87
+ Returns:
88
+ A 3-tuple of ``(layers, rules, exceptions)`` where *exceptions* maps
89
+ a source file path to a list of explicitly-allowed target paths.
90
+
91
+ If the YAML file does not exist or PyYAML is not installed the function
92
+ returns empty collections without raising.
93
+ """
94
+ config_path = os.path.join(project_dir, ".attocode", "architecture.yaml")
95
+ if not os.path.isfile(config_path):
96
+ logger.debug("Architecture config not found: %s", config_path)
97
+ return [], [], {}
98
+
99
+ try:
100
+ import yaml # type: ignore[import-untyped]
101
+ except ImportError:
102
+ logger.warning(
103
+ "PyYAML is not installed -- cannot load architecture config. "
104
+ "Install it with: pip install pyyaml"
105
+ )
106
+ return [], [], {}
107
+
108
+ try:
109
+ with open(config_path, encoding="utf-8") as fh:
110
+ data = yaml.safe_load(fh) or {}
111
+ except Exception as exc:
112
+ logger.warning("Failed to parse architecture config: %s", exc)
113
+ return [], [], {}
114
+
115
+ # --- Layers ---
116
+ layers: list[ArchLayer] = []
117
+ for entry in data.get("layers", []):
118
+ name = entry.get("name", "")
119
+ paths = entry.get("paths", [])
120
+ if name and paths:
121
+ layers.append(ArchLayer(name=name, paths=paths))
122
+
123
+ # --- Rules ---
124
+ rules: list[ArchRule] = []
125
+ for entry in data.get("rules", []):
126
+ from_layer = entry.get("from", "")
127
+ if not from_layer:
128
+ continue
129
+ allowed = entry.get("to", [])
130
+ denied = entry.get("deny", [])
131
+ rules.append(ArchRule(from_layer=from_layer, allowed=allowed, denied=denied))
132
+
133
+ # --- Exceptions ---
134
+ exceptions: dict[str, list[str]] = {}
135
+ for entry in data.get("exceptions", []):
136
+ src = entry.get("file", "")
137
+ allowed_targets = entry.get("allowed", [])
138
+ if src and allowed_targets:
139
+ exceptions[src] = allowed_targets
140
+
141
+ return layers, rules, exceptions
142
+
143
+
144
+ # ---------------------------------------------------------------------------
145
+ # Classification
146
+ # ---------------------------------------------------------------------------
147
+
148
+
149
+ def classify_file(file_path: str, layers: list[ArchLayer]) -> str:
150
+ """Determine which layer *file_path* belongs to.
151
+
152
+ Returns the layer name, or ``""`` if the file does not match any layer.
153
+ Matches are evaluated in order; the first matching layer wins.
154
+ """
155
+ for layer in layers:
156
+ if layer.matches(file_path):
157
+ return layer.name
158
+ return ""
159
+
160
+
161
+ # ---------------------------------------------------------------------------
162
+ # Core check
163
+ # ---------------------------------------------------------------------------
164
+
165
+
166
+ def _build_deps_from_index(project_dir: str) -> dict[str, set[str]]:
167
+ """Build a dependency map from the AST service's CrossRefIndex."""
168
+ try:
169
+ from attocode_intel._internal.integrations.context.ast_service import ASTService
170
+
171
+ svc = ASTService.get_instance(project_dir)
172
+ if not svc.initialized:
173
+ svc.initialize_skeleton(indexing_depth="auto")
174
+ return dict(svc.index.file_dependencies)
175
+ except Exception as exc:
176
+ logger.debug("Could not build dependency map from AST index: %s", exc)
177
+ return {}
178
+
179
+
180
+ def check_drift(
181
+ project_dir: str,
182
+ dependencies: dict[str, set[str]] | None = None,
183
+ ) -> ArchReport:
184
+ """Check actual dependencies against architecture rules.
185
+
186
+ Args:
187
+ project_dir: Project root.
188
+ dependencies: Optional pre-computed dependency map
189
+ (file -> set of imported files). If *None*, will build from
190
+ the AST service's ``CrossRefIndex.file_dependencies``.
191
+
192
+ Returns:
193
+ An :class:`ArchReport` summarising all violations found.
194
+ """
195
+ layers, rules, exceptions = load_architecture(project_dir)
196
+ if not layers:
197
+ return ArchReport(
198
+ violations=[],
199
+ layers_defined=0,
200
+ rules_defined=0,
201
+ files_checked=0,
202
+ compliant_files=0,
203
+ )
204
+
205
+ if dependencies is None:
206
+ dependencies = _build_deps_from_index(project_dir)
207
+
208
+ # Build a quick rule lookup: from_layer -> ArchRule
209
+ rule_map: dict[str, ArchRule] = {}
210
+ for rule in rules:
211
+ rule_map[rule.from_layer] = rule
212
+
213
+ violations: list[ArchViolation] = []
214
+ files_checked = 0
215
+ compliant_files = 0
216
+
217
+ for source_file, targets in dependencies.items():
218
+ source_layer = classify_file(source_file, layers)
219
+ if not source_layer:
220
+ # File is outside any declared layer -- skip
221
+ continue
222
+
223
+ files_checked += 1
224
+ file_has_violation = False
225
+
226
+ for target_file in targets:
227
+ target_layer = classify_file(target_file, layers)
228
+ if not target_layer:
229
+ # Target outside all layers -- nothing to enforce
230
+ continue
231
+ if source_layer == target_layer:
232
+ # Intra-layer dependency -- always allowed
233
+ continue
234
+
235
+ # Check file-level exceptions
236
+ exception_list = exceptions.get(source_file, [])
237
+ if target_file in exception_list:
238
+ continue
239
+
240
+ rule = rule_map.get(source_layer)
241
+ if rule is None:
242
+ # No rule defined for this layer -- nothing to enforce
243
+ continue
244
+
245
+ # Check explicit deny list first (high severity)
246
+ if target_layer in rule.denied:
247
+ violations.append(
248
+ ArchViolation(
249
+ source_file=source_file,
250
+ target_file=target_file,
251
+ source_layer=source_layer,
252
+ target_layer=target_layer,
253
+ rule=(
254
+ f"Layer '{source_layer}' must NOT depend on "
255
+ f"'{target_layer}' (deny rule)"
256
+ ),
257
+ severity="high",
258
+ )
259
+ )
260
+ file_has_violation = True
261
+ continue
262
+
263
+ # Check allowed list (medium severity if not explicitly listed)
264
+ if rule.allowed and target_layer not in rule.allowed:
265
+ violations.append(
266
+ ArchViolation(
267
+ source_file=source_file,
268
+ target_file=target_file,
269
+ source_layer=source_layer,
270
+ target_layer=target_layer,
271
+ rule=(
272
+ f"Layer '{source_layer}' does not list "
273
+ f"'{target_layer}' as an allowed dependency"
274
+ ),
275
+ severity="medium",
276
+ )
277
+ )
278
+ file_has_violation = True
279
+
280
+ if not file_has_violation:
281
+ compliant_files += 1
282
+
283
+ # Sort: high severity first, then by source file for stability
284
+ _severity_order = {"high": 0, "medium": 1, "low": 2}
285
+ violations.sort(
286
+ key=lambda v: (_severity_order.get(v.severity, 9), v.source_file, v.target_file)
287
+ )
288
+
289
+ return ArchReport(
290
+ violations=violations,
291
+ layers_defined=len(layers),
292
+ rules_defined=len(rules),
293
+ files_checked=files_checked,
294
+ compliant_files=compliant_files,
295
+ )
296
+
297
+
298
+ # ---------------------------------------------------------------------------
299
+ # Formatting
300
+ # ---------------------------------------------------------------------------
301
+
302
+
303
+ def format_report(report: ArchReport) -> str:
304
+ """Format an :class:`ArchReport` as human-readable text."""
305
+ lines: list[str] = []
306
+ lines.append("# Architecture Drift Report\n")
307
+
308
+ lines.append(
309
+ f"Layers defined: {report.layers_defined} | "
310
+ f"Rules defined: {report.rules_defined}"
311
+ )
312
+ lines.append(
313
+ f"Files checked: {report.files_checked} | "
314
+ f"Compliant: {report.compliant_files}"
315
+ )
316
+
317
+ if not report.violations:
318
+ lines.append("\nNo architecture violations detected. All checked files comply.")
319
+ return "\n".join(lines)
320
+
321
+ # Group by severity
322
+ high: list[ArchViolation] = []
323
+ medium: list[ArchViolation] = []
324
+ other: list[ArchViolation] = []
325
+ for v in report.violations:
326
+ if v.severity == "high":
327
+ high.append(v)
328
+ elif v.severity == "medium":
329
+ medium.append(v)
330
+ else:
331
+ other.append(v)
332
+
333
+ total = len(report.violations)
334
+ lines.append(f"\n{total} violation(s) found.\n")
335
+
336
+ if high:
337
+ lines.append(f"## HIGH severity ({len(high)})\n")
338
+ for v in high:
339
+ lines.append(f" {v.source_file} -> {v.target_file}")
340
+ lines.append(f" [{v.source_layer} -> {v.target_layer}] {v.rule}")
341
+
342
+ if medium:
343
+ lines.append(f"\n## MEDIUM severity ({len(medium)})\n")
344
+ for v in medium:
345
+ lines.append(f" {v.source_file} -> {v.target_file}")
346
+ lines.append(f" [{v.source_layer} -> {v.target_layer}] {v.rule}")
347
+
348
+ if other:
349
+ lines.append(f"\n## OTHER ({len(other)})\n")
350
+ for v in other:
351
+ lines.append(f" {v.source_file} -> {v.target_file}")
352
+ lines.append(f" [{v.source_layer} -> {v.target_layer}] {v.rule}")
353
+
354
+ return "\n".join(lines)
@@ -0,0 +1,202 @@
1
+ """AST-aware code chunking for semantic search.
2
+
3
+ Produces structural chunks at function/method/class boundaries that include
4
+ actual source code, not just metadata summaries. This significantly improves
5
+ search recall for code-understanding queries (cAST paper: +4.3pts Recall@5).
6
+
7
+ Used by ``SemanticSearchManager`` in two-stage retrieval mode.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import logging
13
+ import os
14
+ from dataclasses import dataclass
15
+ from pathlib import Path
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ @dataclass(slots=True)
21
+ class CodeChunk:
22
+ """A structural code chunk extracted from AST boundaries."""
23
+
24
+ id: str # unique chunk ID (e.g. "func:path:name")
25
+ file_path: str # relative path
26
+ chunk_type: str # "file", "function", "class", "method"
27
+ name: str # symbol name
28
+ text: str # actual code content (for embedding)
29
+ start_line: int
30
+ end_line: int
31
+ metadata: str # additional context (imports, params, bases)
32
+
33
+
34
+ def chunk_file(
35
+ file_path: str,
36
+ relative_path: str,
37
+ content: str | None = None,
38
+ max_chunk_lines: int = 80,
39
+ ) -> list[CodeChunk]:
40
+ """Extract structural code chunks from a file using AST boundaries.
41
+
42
+ Chunks at function, method, and class boundaries. Each chunk includes
43
+ the actual source code (truncated to ``max_chunk_lines``) plus metadata
44
+ (imports, parameters, return types, base classes).
45
+
46
+ Args:
47
+ file_path: Absolute path to the source file.
48
+ relative_path: Relative path for chunk IDs.
49
+ content: Optional file content (read from disk if not provided).
50
+ max_chunk_lines: Maximum lines of code to include per chunk.
51
+
52
+ Returns:
53
+ List of CodeChunk objects.
54
+ """
55
+ if content is None:
56
+ try:
57
+ content = Path(file_path).read_text(encoding="utf-8", errors="replace")
58
+ except OSError:
59
+ return []
60
+
61
+ from attocode_intel._internal.integrations.context.codebase_ast import parse_file
62
+
63
+ try:
64
+ ast = parse_file(file_path, content=content)
65
+ except Exception:
66
+ return []
67
+
68
+ lines = content.splitlines()
69
+ chunks: list[CodeChunk] = []
70
+
71
+ # File-level chunk: imports + top-level summary
72
+ imports = [imp.module for imp in ast.imports[:15]]
73
+ symbols = ast.get_symbols()[:15]
74
+ file_meta_parts = []
75
+ if imports:
76
+ file_meta_parts.append(f"imports: {', '.join(imports)}")
77
+ if symbols:
78
+ file_meta_parts.append(f"defines: {', '.join(symbols)}")
79
+
80
+ # Take first N lines as file overview (docstring + imports area)
81
+ file_preview_end = min(40, len(lines))
82
+ file_text = "\n".join(lines[:file_preview_end])
83
+ if len(lines) > file_preview_end:
84
+ file_text += f"\n... ({len(lines) - file_preview_end} more lines)"
85
+
86
+ chunks.append(CodeChunk(
87
+ id=f"file:{relative_path}",
88
+ file_path=relative_path,
89
+ chunk_type="file",
90
+ name=os.path.basename(relative_path),
91
+ text=file_text,
92
+ start_line=1,
93
+ end_line=len(lines),
94
+ metadata=" | ".join(file_meta_parts),
95
+ ))
96
+
97
+ # Function-level chunks
98
+ for func in ast.functions:
99
+ start = max(0, func.start_line - 1)
100
+ end = min(len(lines), func.end_line)
101
+ code_lines = lines[start:end]
102
+ if len(code_lines) > max_chunk_lines:
103
+ code_lines = code_lines[:max_chunk_lines]
104
+ code_lines.append(f" # ... ({func.end_line - func.start_line - max_chunk_lines} more lines)")
105
+
106
+ params = ", ".join(p.name for p in func.parameters[:8])
107
+ meta_parts = [f"function {func.name}({params})"]
108
+ if func.return_type:
109
+ meta_parts.append(f"-> {func.return_type}")
110
+ if func.decorators:
111
+ meta_parts.append(f"decorators: {', '.join(func.decorators[:3])}")
112
+
113
+ chunks.append(CodeChunk(
114
+ id=f"func:{relative_path}:{func.name}",
115
+ file_path=relative_path,
116
+ chunk_type="function",
117
+ name=func.name,
118
+ text="\n".join(code_lines),
119
+ start_line=func.start_line,
120
+ end_line=func.end_line,
121
+ metadata=" | ".join(meta_parts),
122
+ ))
123
+
124
+ # Class-level chunks
125
+ for cls in ast.classes:
126
+ start = max(0, cls.start_line - 1)
127
+ end = min(len(lines), cls.end_line)
128
+ code_lines = lines[start:end]
129
+ if len(code_lines) > max_chunk_lines:
130
+ code_lines = code_lines[:max_chunk_lines]
131
+ code_lines.append(f" # ... ({cls.end_line - cls.start_line - max_chunk_lines} more lines)")
132
+
133
+ meta_parts = [f"class {cls.name}"]
134
+ if cls.bases:
135
+ meta_parts.append(f"extends: {', '.join(cls.bases[:5])}")
136
+ methods = [m.name for m in cls.methods[:10]]
137
+ if methods:
138
+ meta_parts.append(f"methods: {', '.join(methods)}")
139
+
140
+ chunks.append(CodeChunk(
141
+ id=f"cls:{relative_path}:{cls.name}",
142
+ file_path=relative_path,
143
+ chunk_type="class",
144
+ name=cls.name,
145
+ text="\n".join(code_lines),
146
+ start_line=cls.start_line,
147
+ end_line=cls.end_line,
148
+ metadata=" | ".join(meta_parts),
149
+ ))
150
+
151
+ # Method-level chunks for large classes
152
+ for method in cls.methods:
153
+ m_start = max(0, method.start_line - 1)
154
+ m_end = min(len(lines), method.end_line)
155
+ m_lines = lines[m_start:m_end]
156
+ if len(m_lines) > max_chunk_lines:
157
+ m_lines = m_lines[:max_chunk_lines]
158
+ m_lines.append(f" # ... ({method.end_line - method.start_line - max_chunk_lines} more lines)")
159
+
160
+ m_params = ", ".join(p.name for p in method.parameters[:8])
161
+ m_meta_parts = [f"method {cls.name}.{method.name}({m_params})"]
162
+ if method.return_type:
163
+ m_meta_parts.append(f"-> {method.return_type}")
164
+
165
+ chunks.append(CodeChunk(
166
+ id=f"method:{relative_path}:{cls.name}.{method.name}",
167
+ file_path=relative_path,
168
+ chunk_type="method",
169
+ name=f"{cls.name}.{method.name}",
170
+ text="\n".join(m_lines),
171
+ start_line=method.start_line,
172
+ end_line=method.end_line,
173
+ metadata=" | ".join(m_meta_parts),
174
+ ))
175
+
176
+ return chunks
177
+
178
+
179
+ def reciprocal_rank_fusion(
180
+ *result_lists: list[tuple[str, float]],
181
+ k: int = 60,
182
+ ) -> list[tuple[str, float]]:
183
+ """Merge multiple ranked result lists using Reciprocal Rank Fusion.
184
+
185
+ Each input is a list of (id, score) tuples sorted by score descending.
186
+ Returns a merged list sorted by fused score descending.
187
+
188
+ Args:
189
+ *result_lists: Variable number of ranked result lists.
190
+ k: RRF constant (default 60, standard from literature).
191
+
192
+ Returns:
193
+ Merged list of (id, fused_score) sorted by score descending.
194
+ """
195
+ fused: dict[str, float] = {}
196
+
197
+ for results in result_lists:
198
+ for rank, (item_id, _score) in enumerate(results):
199
+ fused[item_id] = fused.get(item_id, 0.0) + 1.0 / (k + rank + 1)
200
+
201
+ sorted_results = sorted(fused.items(), key=lambda x: x[1], reverse=True)
202
+ return sorted_results