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,21 @@
1
+ """Validate remotely supplied repository locations before storing them."""
2
+ from __future__ import annotations
3
+
4
+ import re
5
+ from urllib.parse import urlsplit
6
+
7
+ from fastapi import HTTPException
8
+
9
+
10
+ def validate_remote_source(clone_url=None, local_path=None):
11
+ from attocode_intel.api.deps import get_config
12
+
13
+ if not get_config().is_service_mode:
14
+ return
15
+ if local_path:
16
+ raise HTTPException(422, "Remote clients cannot select server filesystem paths. Connect a Git URL or use local MCP.")
17
+ if clone_url:
18
+ parsed = urlsplit(clone_url)
19
+ scp_url = re.fullmatch(r"[\w.-]+@[\w.-]+:[^\s]+", clone_url)
20
+ if not scp_url and not (parsed.scheme in {"https", "http", "ssh"} and parsed.hostname):
21
+ raise HTTPException(422, "Repository URL must use HTTP(S) or SSH; local paths and file URLs are not accepted.")
@@ -0,0 +1 @@
1
+ """API route modules."""
@@ -0,0 +1,158 @@
1
+ """Server-side retrieval pin computation.
2
+
3
+ HTTP search routes return ``pin_id`` + ``manifest_hash`` so clients can
4
+ track the state they queried against (matching the local stdio MCP
5
+ ``_stamp_pin``). Per-store manifest hashes are computed via cheap
6
+ ``SELECT COUNT + MAX(updated_at)`` probes; pins are ephemeral and not
7
+ persisted in a pins table.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import logging
13
+ import uuid
14
+ from typing import TYPE_CHECKING
15
+
16
+ from attocode_intel.artifacts import RetrievalPin, compute_store_hash
17
+
18
+ if TYPE_CHECKING:
19
+ from sqlalchemy.ext.asyncio import AsyncSession
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ async def compute_server_manifest_hashes(
25
+ session: AsyncSession,
26
+ *,
27
+ repo_id: uuid.UUID | None = None,
28
+ branch_id: uuid.UUID | None = None,
29
+ ) -> dict[str, str]:
30
+ """Compute cheap per-store content hashes for the server-side stores.
31
+
32
+ Returns a ``{store_name: sha256hex}`` dict that matches the shape
33
+ of :func:`attocode_intel.tools.pin_tools._compute_current_manifest_hashes`
34
+ (the local-side equivalent). A store's hash changes whenever its
35
+ row count or latest ``created_at`` / ``updated_at`` timestamp moves.
36
+
37
+ ``repo_id`` / ``branch_id`` may scope the hash; the current
38
+ implementation uses a global view — the returned hash is
39
+ informational, not a cryptographic commitment to the repo's exact
40
+ state.
41
+ """
42
+ from sqlalchemy import text
43
+
44
+ results: dict[str, str] = {}
45
+
46
+ # Each store is probed via a single cheap aggregate query. Failures
47
+ # on any individual store fall through to ``absent`` so a missing
48
+ # table (e.g. on an older schema) doesn't break the whole response.
49
+ async def _probe(name: str, sql: str, params: dict) -> None:
50
+ try:
51
+ row = (await session.execute(text(sql), params)).fetchone()
52
+ except Exception as exc:
53
+ logger.debug("server pin: %s probe failed: %s", name, exc)
54
+ results[name] = "absent"
55
+ return
56
+ if row is None:
57
+ results[name] = "absent"
58
+ return
59
+ row_count = int(row[0] or 0)
60
+ # Postgres MAX() on timestamptz returns a datetime; coerce to
61
+ # float epoch for the hash so it matches the local format.
62
+ ts = row[1]
63
+ max_updated_at: float | None = None
64
+ if ts is not None:
65
+ try:
66
+ max_updated_at = ts.timestamp()
67
+ except AttributeError:
68
+ max_updated_at = float(ts)
69
+ results[name] = compute_store_hash(
70
+ schema_version="1",
71
+ row_count=row_count,
72
+ max_updated_at=max_updated_at,
73
+ )
74
+
75
+ repo_clause = "" if repo_id is None else " (scoped)"
76
+ branch_params: dict = {}
77
+ if branch_id is not None:
78
+ branch_params["branch_id"] = str(branch_id)
79
+
80
+ # file_contents — global; repo scoping would require a join via
81
+ # branch_files which is overkill here.
82
+ await _probe(
83
+ "file_contents",
84
+ "SELECT COUNT(*), MAX(created_at) FROM file_contents",
85
+ {},
86
+ )
87
+ await _probe(
88
+ "symbols",
89
+ "SELECT COUNT(*), MAX(created_at) FROM symbols",
90
+ {},
91
+ )
92
+ await _probe(
93
+ "embeddings",
94
+ "SELECT COUNT(*), MAX(created_at) FROM embeddings",
95
+ {},
96
+ )
97
+ await _probe(
98
+ "dependencies",
99
+ "SELECT COUNT(*), NULL::timestamptz FROM dependencies",
100
+ {},
101
+ )
102
+ # branch_files scoped per branch if we have one, else global row count.
103
+ if branch_id is not None:
104
+ await _probe(
105
+ "branch_files",
106
+ "SELECT COUNT(*), NULL::timestamptz FROM branch_files "
107
+ "WHERE branch_id = CAST(:branch_id AS uuid)",
108
+ branch_params,
109
+ )
110
+ else:
111
+ await _probe(
112
+ "branch_files",
113
+ "SELECT COUNT(*), NULL::timestamptz FROM branch_files",
114
+ {},
115
+ )
116
+
117
+ logger.debug(
118
+ "server pin%s: computed %d store hashes",
119
+ repo_clause, len(results),
120
+ )
121
+ return results
122
+
123
+
124
+ async def build_retrieval_pin(
125
+ session: AsyncSession,
126
+ *,
127
+ repo_id: uuid.UUID | None = None,
128
+ branch_id: uuid.UUID | None = None,
129
+ ) -> RetrievalPin:
130
+ """Build a fresh :class:`RetrievalPin` for the current server state.
131
+
132
+ Server pins are ephemeral (not persisted in a pins table). The
133
+ ``pin_id`` is deterministic off the manifest hash so repeat calls
134
+ on an unchanged state return an identical id, letting clients
135
+ cheap-compare across requests.
136
+ """
137
+ hashes = await compute_server_manifest_hashes(
138
+ session, repo_id=repo_id, branch_id=branch_id,
139
+ )
140
+ pin = RetrievalPin.create(
141
+ manifest_hashes=hashes,
142
+ branch_id=str(branch_id) if branch_id else None,
143
+ ttl_seconds=0,
144
+ )
145
+ # Deterministic id — matches the local ``_stamp_pin`` format
146
+ # (``pin_<hex20>``) so clients don't need to distinguish server
147
+ # vs. local pins by format.
148
+ deterministic_id = f"pin_{pin.manifest_hash[:20]}"
149
+ return RetrievalPin(
150
+ pin_id=deterministic_id,
151
+ schema_version=pin.schema_version,
152
+ manifest_hashes=pin.manifest_hashes,
153
+ manifest_hash=pin.manifest_hash,
154
+ overlay_id=pin.overlay_id,
155
+ branch_id=pin.branch_id,
156
+ created_at=pin.created_at,
157
+ expires_at=pin.expires_at,
158
+ )
@@ -0,0 +1,148 @@
1
+ """Activity feed / audit log endpoints (service mode only)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import uuid
6
+ from datetime import datetime
7
+
8
+ from fastapi import APIRouter, Depends, HTTPException, Query
9
+ from pydantic import BaseModel
10
+ from sqlalchemy import func, select
11
+ from sqlalchemy.ext.asyncio import AsyncSession
12
+
13
+ from attocode_intel.api.auth import resolve_auth
14
+ from attocode_intel.api.auth.context import AuthContext
15
+ from attocode_intel.api.deps import get_db_session
16
+
17
+ router = APIRouter(tags=["activity"])
18
+
19
+
20
+ # --- Response models ---
21
+
22
+
23
+ class ActivityEventResponse(BaseModel):
24
+ id: str
25
+ event_type: str
26
+ org_id: str
27
+ repo_id: str | None = None
28
+ user_id: str | None = None
29
+ detail: dict = {}
30
+ created_at: str
31
+
32
+
33
+ class ActivityFeedResponse(BaseModel):
34
+ events: list[ActivityEventResponse]
35
+ total: int
36
+ limit: int
37
+ offset: int
38
+ has_more: bool
39
+
40
+
41
+ # --- Endpoints ---
42
+
43
+
44
+ @router.get("/api/v2/orgs/{org_id}/activity", response_model=ActivityFeedResponse)
45
+ async def get_org_activity(
46
+ org_id: uuid.UUID,
47
+ limit: int = Query(50, ge=1, le=100),
48
+ offset: int = Query(0, ge=0),
49
+ event_type: str = Query("", description="Filter by event type"),
50
+ since: str = Query("", description="ISO datetime to filter events after"),
51
+ auth: AuthContext = Depends(resolve_auth),
52
+ session: AsyncSession = Depends(get_db_session),
53
+ ) -> ActivityFeedResponse:
54
+ """Get organization activity feed (paginated)."""
55
+ from attocode_intel.db.models import AuditEvent
56
+
57
+ # Build query
58
+ query = select(AuditEvent).where(AuditEvent.org_id == org_id)
59
+
60
+ if event_type:
61
+ query = query.where(AuditEvent.event_type == event_type)
62
+ if since:
63
+ try:
64
+ since_dt = datetime.fromisoformat(since)
65
+ query = query.where(AuditEvent.created_at >= since_dt)
66
+ except ValueError:
67
+ raise HTTPException(status_code=400, detail="Invalid 'since' datetime format")
68
+
69
+ # Count total
70
+ count_result = await session.execute(
71
+ select(func.count()).select_from(query.subquery())
72
+ )
73
+ total = count_result.scalar() or 0
74
+
75
+ # Fetch page
76
+ result = await session.execute(
77
+ query.order_by(AuditEvent.created_at.desc())
78
+ .offset(offset).limit(limit)
79
+ )
80
+
81
+ events = []
82
+ for event in result.scalars():
83
+ events.append(ActivityEventResponse(
84
+ id=str(event.id),
85
+ event_type=event.event_type,
86
+ org_id=str(event.org_id),
87
+ repo_id=str(event.repo_id) if event.repo_id else None,
88
+ user_id=str(event.user_id) if event.user_id else None,
89
+ detail=event.detail or {},
90
+ created_at=event.created_at.isoformat(),
91
+ ))
92
+
93
+ return ActivityFeedResponse(
94
+ events=events, total=total, limit=limit, offset=offset,
95
+ has_more=(offset + limit < total),
96
+ )
97
+
98
+
99
+ @router.get("/api/v2/repos/{repo_id}/activity", response_model=ActivityFeedResponse)
100
+ async def get_repo_activity(
101
+ repo_id: uuid.UUID,
102
+ limit: int = Query(50, ge=1, le=100),
103
+ offset: int = Query(0, ge=0),
104
+ event_type: str = Query("", description="Filter by event type"),
105
+ since: str = Query("", description="ISO datetime to filter events after"),
106
+ auth: AuthContext = Depends(resolve_auth),
107
+ session: AsyncSession = Depends(get_db_session),
108
+ ) -> ActivityFeedResponse:
109
+ """Get repository activity feed (paginated)."""
110
+ from attocode_intel.db.models import AuditEvent
111
+
112
+ query = select(AuditEvent).where(AuditEvent.repo_id == repo_id)
113
+
114
+ if event_type:
115
+ query = query.where(AuditEvent.event_type == event_type)
116
+ if since:
117
+ try:
118
+ since_dt = datetime.fromisoformat(since)
119
+ query = query.where(AuditEvent.created_at >= since_dt)
120
+ except ValueError:
121
+ raise HTTPException(status_code=400, detail="Invalid 'since' datetime format")
122
+
123
+ count_result = await session.execute(
124
+ select(func.count()).select_from(query.subquery())
125
+ )
126
+ total = count_result.scalar() or 0
127
+
128
+ result = await session.execute(
129
+ query.order_by(AuditEvent.created_at.desc())
130
+ .offset(offset).limit(limit)
131
+ )
132
+
133
+ events = []
134
+ for event in result.scalars():
135
+ events.append(ActivityEventResponse(
136
+ id=str(event.id),
137
+ event_type=event.event_type,
138
+ org_id=str(event.org_id),
139
+ repo_id=str(event.repo_id) if event.repo_id else None,
140
+ user_id=str(event.user_id) if event.user_id else None,
141
+ detail=event.detail or {},
142
+ created_at=event.created_at.isoformat(),
143
+ ))
144
+
145
+ return ActivityFeedResponse(
146
+ events=events, total=total, limit=limit, offset=offset,
147
+ has_more=(offset + limit < total),
148
+ )
@@ -0,0 +1,102 @@
1
+ """ADR endpoints — text-oriented parity for MCP architecture-decision tools."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from fastapi import APIRouter, Depends, Query, Request
6
+
7
+ from attocode_intel.api.auth import verify_auth
8
+ from attocode_intel.api.knowledge_bridge import shared_operation
9
+ from attocode_intel.api.deps import BranchParam, ensure_branch_supported, get_service_or_404
10
+ from attocode_intel.api.models import RecordADRRequest, TextResult, UpdateADRStatusRequest
11
+
12
+ router_v1 = APIRouter(
13
+ prefix="/api/v1/projects/{project_id}/adrs",
14
+ tags=["adrs"],
15
+ dependencies=[Depends(verify_auth)],
16
+ )
17
+
18
+
19
+ @router_v1.post("", response_model=TextResult)
20
+ async def record_adr_v1(
21
+ request: Request,
22
+ project_id: str,
23
+ req: RecordADRRequest,
24
+ branch: BranchParam = "",
25
+ ) -> TextResult:
26
+ """Record a new architecture decision."""
27
+ shared = await shared_operation(request, project_id, "record_adr", req.model_dump())
28
+ if shared is not None:
29
+ return TextResult(result=shared["result"])
30
+ ensure_branch_supported(branch)
31
+ svc = await get_service_or_404(project_id)
32
+ return TextResult(
33
+ result=svc.record_adr(
34
+ title=req.title,
35
+ context=req.context,
36
+ decision=req.decision,
37
+ consequences=req.consequences,
38
+ related_files=req.related_files,
39
+ tags=req.tags,
40
+ )
41
+ )
42
+
43
+
44
+ @router_v1.get("", response_model=TextResult)
45
+ async def list_adrs_v1(
46
+ request: Request,
47
+ project_id: str,
48
+ branch: BranchParam = "",
49
+ status: str = "",
50
+ tag: str = "",
51
+ search: str = Query("", description="Search title, context, and decision"),
52
+ ) -> TextResult:
53
+ """List ADRs with optional filtering."""
54
+ shared = await shared_operation(
55
+ request, project_id, "list_adrs", {"status": status, "tag": tag, "search": search}
56
+ )
57
+ if shared is not None:
58
+ return TextResult(result=shared["result"])
59
+ ensure_branch_supported(branch)
60
+ svc = await get_service_or_404(project_id)
61
+ return TextResult(result=svc.list_adrs(status=status, tag=tag, search=search))
62
+
63
+
64
+ @router_v1.get("/{number}", response_model=TextResult)
65
+ async def get_adr_v1(
66
+ request: Request,
67
+ project_id: str,
68
+ number: int,
69
+ branch: BranchParam = "",
70
+ ) -> TextResult:
71
+ """Fetch a single ADR."""
72
+ shared = await shared_operation(request, project_id, "get_adr", {"number": number})
73
+ if shared is not None:
74
+ return TextResult(result=shared["result"])
75
+ ensure_branch_supported(branch)
76
+ svc = await get_service_or_404(project_id)
77
+ return TextResult(result=svc.get_adr(number))
78
+
79
+
80
+ @router_v1.post("/{number}/status", response_model=TextResult)
81
+ async def update_adr_status_v1(
82
+ request: Request,
83
+ project_id: str,
84
+ number: int,
85
+ req: UpdateADRStatusRequest,
86
+ branch: BranchParam = "",
87
+ ) -> TextResult:
88
+ """Update ADR lifecycle state."""
89
+ shared = await shared_operation(
90
+ request, project_id, "update_adr_status", {"number": number, **req.model_dump()}
91
+ )
92
+ if shared is not None:
93
+ return TextResult(result=shared["result"])
94
+ ensure_branch_supported(branch)
95
+ svc = await get_service_or_404(project_id)
96
+ return TextResult(
97
+ result=svc.update_adr_status(
98
+ number=number,
99
+ status=req.status,
100
+ superseded_by=req.superseded_by,
101
+ )
102
+ )