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,269 @@
1
+ """Repository-scoped durable knowledge shared by MCP and HTTP."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+ import uuid
8
+ from datetime import UTC, datetime
9
+ from pathlib import Path
10
+
11
+ from sqlalchemy import JSON, DateTime, ForeignKey, Integer, String, Text, Uuid, cast, or_, select
12
+ from sqlalchemy.orm import Mapped, mapped_column
13
+
14
+ from attocode_intel.db.base import Base
15
+
16
+ KNOWLEDGE_TOOLS = frozenset(
17
+ {
18
+ "recall",
19
+ "record_learning",
20
+ "list_learnings",
21
+ "learning_feedback",
22
+ "record_adr",
23
+ "list_adrs",
24
+ "get_adr",
25
+ "update_adr_status",
26
+ "update_learning",
27
+ }
28
+ )
29
+
30
+
31
+ class KnowledgeEntry(Base):
32
+ __tablename__ = "knowledge_entries"
33
+ id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
34
+ repo_id: Mapped[uuid.UUID] = mapped_column(
35
+ Uuid, ForeignKey("repositories.id", ondelete="CASCADE"), index=True
36
+ )
37
+ author_id: Mapped[str] = mapped_column(String(64), default="")
38
+ kind: Mapped[str] = mapped_column(String(16), default="learning")
39
+ status: Mapped[str] = mapped_column(String(16), default="active")
40
+ scope: Mapped[str] = mapped_column(Text, default="")
41
+ payload: Mapped[dict] = mapped_column(JSON, default=dict)
42
+ revision: Mapped[str] = mapped_column(String(64), default="")
43
+ anchor_hash: Mapped[str] = mapped_column(String(64), default="")
44
+ created_at: Mapped[datetime] = mapped_column(
45
+ DateTime(timezone=True), default=lambda: datetime.now(UTC)
46
+ )
47
+ updated_at: Mapped[datetime] = mapped_column(
48
+ DateTime(timezone=True), default=lambda: datetime.now(UTC)
49
+ )
50
+
51
+
52
+ def anchor_hash(root: str, scope: str) -> str:
53
+ if not scope:
54
+ return ""
55
+ base = Path(root).resolve()
56
+ path = (base / scope).resolve()
57
+ if not path.is_relative_to(base):
58
+ raise ValueError("Knowledge scope must remain inside the selected repository")
59
+ if not path.is_file():
60
+ return ""
61
+ return hashlib.sha256(path.read_bytes()).hexdigest()
62
+
63
+
64
+ class PostgresKnowledge:
65
+ """Session-injected store; every lookup includes the authorized repository ID."""
66
+
67
+ def __init__(self, session, context):
68
+ self.session = session
69
+ self.context = context
70
+ self.repo_id = uuid.UUID(context.workspace)
71
+
72
+ def statement(self):
73
+ return select(KnowledgeEntry).where(KnowledgeEntry.repo_id == self.repo_id)
74
+
75
+ def serialize(self, entry):
76
+ current_hash = anchor_hash(self.context.project_dir, entry.scope)
77
+ anchors = entry.payload.get("file_anchors", {})
78
+ stale = bool(entry.anchor_hash and current_hash != entry.anchor_hash) or any(
79
+ anchor_hash(self.context.project_dir, path) != digest
80
+ for path, digest in anchors.items()
81
+ )
82
+ origin_anchor = entry.payload.get("anchor_blob_oid", "")
83
+ if origin_anchor and entry.scope:
84
+ target = Path(self.context.project_dir) / entry.scope
85
+ if not target.is_file():
86
+ stale = True
87
+ elif origin_anchor.startswith("sha256:"):
88
+ stale = stale or origin_anchor != "sha256:" + current_hash
89
+ elif origin_anchor.startswith("git:"):
90
+ data = target.read_bytes()
91
+ stale = (
92
+ stale
93
+ or origin_anchor
94
+ != "git:"
95
+ + hashlib.sha1(b"blob " + str(len(data)).encode() + b"\0" + data).hexdigest()
96
+ )
97
+ return {
98
+ **entry.payload,
99
+ "id": entry.id,
100
+ "number": entry.id,
101
+ "kind": entry.kind,
102
+ "status": entry.status,
103
+ "scope": entry.scope,
104
+ "author_id": entry.author_id,
105
+ "created_at": entry.created_at.isoformat(),
106
+ "updated_at": entry.updated_at.isoformat(),
107
+ "revision": entry.revision,
108
+ "stale": stale,
109
+ }
110
+
111
+ async def execute(self, operation: str, arguments: dict):
112
+ args = dict(arguments)
113
+ if operation in {"record_learning", "record_adr"}:
114
+ kind = "adr" if operation == "record_adr" else "learning"
115
+ description = args.get("description") if kind == "learning" else args.get("title")
116
+ if not description or not description.strip():
117
+ raise ValueError("A non-empty description or title is required")
118
+ if kind == "learning":
119
+ from attocode_intel._internal.integrations.context.memory_store import VALID_TYPES
120
+
121
+ if args.get("type") not in VALID_TYPES:
122
+ raise ValueError(f"type must be one of {sorted(VALID_TYPES)}")
123
+ confidence = float(args.get("confidence", 0.7))
124
+ if not 0 <= confidence <= 1:
125
+ raise ValueError("confidence must be between zero and one")
126
+ scope = args.pop("scope", "")
127
+ if kind == "adr":
128
+ args["file_anchors"] = {
129
+ path: anchor_hash(self.context.project_dir, path)
130
+ for path in args.get("related_files") or []
131
+ }
132
+ entry = KnowledgeEntry(
133
+ repo_id=self.repo_id,
134
+ kind=kind,
135
+ payload=args,
136
+ scope=scope,
137
+ author_id=str(self.context.identity.user_id or "service"),
138
+ status="proposed" if kind == "adr" else "active",
139
+ revision=self.context.revision,
140
+ anchor_hash=anchor_hash(self.context.project_dir, scope),
141
+ )
142
+ self.session.add(entry)
143
+ await self.session.flush()
144
+ await self.session.refresh(entry)
145
+ return self.serialize(entry)
146
+ if operation in {"learning_feedback", "update_adr_status", "get_adr", "update_learning"}:
147
+ number = args.get("learning_id", args.get("number", args.get("adr_number")))
148
+ stmt = self.statement().where(KnowledgeEntry.id == number)
149
+ if operation != "get_adr":
150
+ stmt = stmt.with_for_update()
151
+ entry = (await self.session.execute(stmt)).scalar_one_or_none()
152
+ if entry is None:
153
+ raise ValueError("Knowledge entry not found in this repository")
154
+ expected = "adr" if operation in {"get_adr", "update_adr_status"} else "learning"
155
+ if entry.kind != expected:
156
+ raise ValueError(f"Entry is not a {expected}")
157
+ if operation == "learning_feedback":
158
+ payload = dict(entry.payload)
159
+ key = "helpful_count" if args.get("helpful") else "unhelpful_count"
160
+ payload[key] = payload.get(key, 0) + 1
161
+ payload["confidence"] = max(
162
+ 0.1,
163
+ min(
164
+ 1.0,
165
+ payload.get("confidence", 0.7) + (0.05 if args.get("helpful") else -0.1),
166
+ ),
167
+ )
168
+ if payload.get("unhelpful_count", 0) >= 5 and payload["confidence"] < 0.15:
169
+ entry.status = "archived"
170
+ entry.payload = payload
171
+ elif operation == "update_learning":
172
+ if entry.kind != "learning":
173
+ raise ValueError("Entry is not a learning")
174
+ payload = dict(entry.payload)
175
+ for key in ("description", "details", "confidence"):
176
+ if key in args:
177
+ payload[key] = args[key]
178
+ if (
179
+ not payload.get("description", "").strip()
180
+ or not 0 <= payload.get("confidence", 0.7) <= 1
181
+ ):
182
+ raise ValueError(
183
+ "A non-empty description and confidence between zero and one are required"
184
+ )
185
+ status = args.get("status", entry.status)
186
+ if status not in {"active", "archived"}:
187
+ raise ValueError("Learning status must be active or archived")
188
+ entry.status, entry.payload = status, payload
189
+ elif operation == "update_adr_status":
190
+ from attocode_intel.tools.adr_tools import _STATUS_TRANSITIONS
191
+
192
+ status = args.get("new_status", args.get("status"))
193
+ if status not in _STATUS_TRANSITIONS.get(entry.status, set()):
194
+ raise ValueError("Invalid ADR status transition")
195
+ replacement = args.get("superseded_by")
196
+ if status == "superseded" and replacement is None:
197
+ raise ValueError("superseded_by is required")
198
+ if replacement is not None:
199
+ target = (
200
+ await self.session.execute(
201
+ self.statement().where(
202
+ KnowledgeEntry.id == replacement, KnowledgeEntry.kind == "adr"
203
+ )
204
+ )
205
+ ).scalar_one_or_none()
206
+ if target is None or target.id == entry.id:
207
+ raise ValueError(
208
+ "Superseding ADR must exist in this repository and differ from this ADR"
209
+ )
210
+ entry.status = status
211
+ entry.payload = {**entry.payload, "superseded_by": args.get("superseded_by")}
212
+ if operation != "get_adr":
213
+ entry.updated_at = datetime.now(UTC)
214
+ await self.session.flush()
215
+ return self.serialize(entry)
216
+ kind = "adr" if operation == "list_adrs" else "learning"
217
+ stmt = self.statement().where(KnowledgeEntry.kind == kind)
218
+ status = args.get("status", "active" if kind == "learning" else "")
219
+ if status:
220
+ stmt = stmt.where(KnowledgeEntry.status == status)
221
+ query = args.get("query", args.get("search", "")).lower().split()
222
+ if query:
223
+ stmt = stmt.where(
224
+ or_(
225
+ *(
226
+ cast(KnowledgeEntry.payload, Text).icontains(term, autoescape=True)
227
+ for term in query[:20]
228
+ )
229
+ )
230
+ )
231
+ entries = (
232
+ (
233
+ await self.session.execute(
234
+ stmt.order_by(KnowledgeEntry.updated_at.desc()).limit(1000)
235
+ )
236
+ )
237
+ .scalars()
238
+ .all()
239
+ )
240
+ scope = args.get("scope", "").rstrip("/")
241
+ rows = [
242
+ self.serialize(e)
243
+ for e in entries
244
+ if not scope or not e.scope or e.scope == scope or e.scope.startswith(scope + "/")
245
+ ]
246
+ if args.get("type"):
247
+ rows = [r for r in rows if r.get("type") == args["type"]]
248
+ if args.get("tag"):
249
+ rows = [r for r in rows if args["tag"] in r.get("tags", [])]
250
+ if query:
251
+ rows.sort(
252
+ key=lambda r: (
253
+ sum(term in json.dumps(r).lower() for term in query),
254
+ not r["stale"],
255
+ r.get("confidence", 0.7),
256
+ ),
257
+ reverse=True,
258
+ )
259
+ return rows[: min(int(args.get("max_results", 100)), 1000)]
260
+
261
+
262
+ async def execute_knowledge(context, operation, arguments):
263
+ from attocode_intel.db.engine import get_session
264
+
265
+ async for session in get_session():
266
+ result = await PostgresKnowledge(session, context).execute(operation, arguments)
267
+ await session.commit()
268
+ return result
269
+ raise RuntimeError("Knowledge database unavailable")
@@ -0,0 +1,77 @@
1
+ """Structured adapter over the existing local SQLite stores."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ from pathlib import Path
7
+
8
+ LEARNING_TOOLS = {
9
+ "recall",
10
+ "record_learning",
11
+ "list_learnings",
12
+ "learning_feedback",
13
+ "update_learning",
14
+ }
15
+
16
+
17
+ def execute_local_knowledge(context, operation, arguments):
18
+ args = dict(arguments)
19
+ store = context.service._get_memory_store()
20
+
21
+ def rows(status="active", kind=None):
22
+ result = store.list_all(status=status, type=kind)
23
+ for row in result:
24
+ row.update(status=status, author_id="local", revision="working-tree", stale=False)
25
+ anchor, scope = row.get("anchor_blob_oid", ""), row.get("scope", "")
26
+ if anchor and scope:
27
+ path = Path(context.project_dir) / scope
28
+ if not path.is_file():
29
+ row["stale"] = True
30
+ else:
31
+ data = path.read_bytes()
32
+ current = (
33
+ "git:"
34
+ + hashlib.sha1(
35
+ b"blob " + str(len(data)).encode() + b"\0" + data
36
+ ).hexdigest()
37
+ if anchor.startswith("git:")
38
+ else "sha256:" + hashlib.sha256(data).hexdigest()
39
+ )
40
+ row["stale"] = current != anchor
41
+ return result
42
+
43
+ if operation == "record_learning":
44
+ if not args["description"].strip():
45
+ raise ValueError("A non-empty description is required")
46
+ if not 0 <= args.get("confidence", 0.7) <= 1:
47
+ raise ValueError("confidence must be between zero and one")
48
+ path = (Path(context.project_dir) / args.get("scope", "")).resolve()
49
+ if not path.is_relative_to(Path(context.project_dir)):
50
+ raise ValueError("Knowledge scope must remain inside the selected repository")
51
+ if path.is_file() and not args.get("anchor_blob_oid"):
52
+ args["anchor_blob_oid"] = "sha256:" + hashlib.sha256(path.read_bytes()).hexdigest()
53
+ number = store.add(**args)
54
+ return next(row for row in rows() if row["id"] == number)
55
+ if operation in {"update_learning", "learning_feedback"}:
56
+ number = args.pop("learning_id")
57
+ existing = next((r for r in rows() + rows("archived") if r["id"] == number), None)
58
+ if existing is None:
59
+ raise ValueError("Learning not found")
60
+ if operation == "update_learning":
61
+ if "description" in args and not args["description"].strip():
62
+ raise ValueError("A non-empty description is required")
63
+ store.update(number, **args)
64
+ else:
65
+ store.record_feedback(number, args["helpful"])
66
+ return next(r for r in rows() + rows("archived") if r["id"] == number)
67
+ if operation == "recall":
68
+ matched = store.recall(**args)
69
+ by_id = {row["id"]: row for row in rows()}
70
+ return [by_id[r["id"]] for r in matched if r["id"] in by_id]
71
+ result = rows(args.get("status", "active"), args.get("type") or None)
72
+ scope = args.get("scope", "").rstrip("/")
73
+ return [
74
+ r
75
+ for r in result
76
+ if not scope or not r["scope"] or r["scope"] == scope or r["scope"].startswith(scope + "/")
77
+ ]
@@ -0,0 +1,36 @@
1
+ [alembic]
2
+ script_location = %(here)s
3
+ sqlalchemy.url = postgresql+asyncpg://localhost/code_intel
4
+
5
+ [loggers]
6
+ keys = root,sqlalchemy,alembic
7
+
8
+ [handlers]
9
+ keys = console
10
+
11
+ [formatters]
12
+ keys = generic
13
+
14
+ [logger_root]
15
+ level = WARN
16
+ handlers = console
17
+
18
+ [logger_sqlalchemy]
19
+ level = WARN
20
+ handlers =
21
+ qualname = sqlalchemy.engine
22
+
23
+ [logger_alembic]
24
+ level = INFO
25
+ handlers =
26
+ qualname = alembic
27
+
28
+ [handler_console]
29
+ class = StreamHandler
30
+ args = (sys.stderr,)
31
+ level = NOTSET
32
+ formatter = generic
33
+
34
+ [formatter_generic]
35
+ format = %(levelname)-5.5s [%(name)s] %(message)s
36
+ datefmt = %H:%M:%S
@@ -0,0 +1,71 @@
1
+ """Alembic async migration environment."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import os
7
+ from logging.config import fileConfig
8
+
9
+ from alembic import context
10
+ from sqlalchemy import pool
11
+ from sqlalchemy.ext.asyncio import async_engine_from_config
12
+
13
+ from attocode_intel.db.base import Base
14
+
15
+ # Import all models so metadata is populated
16
+ import attocode_intel.db.models # noqa: F401
17
+ import attocode_intel.knowledge # noqa: F401
18
+
19
+ config = context.config
20
+
21
+ if config.config_file_name is not None:
22
+ fileConfig(config.config_file_name)
23
+
24
+ target_metadata = Base.metadata
25
+
26
+ # Allow DATABASE_URL env override
27
+ database_url = os.environ.get("DATABASE_URL", "")
28
+ if database_url:
29
+ config.set_main_option("sqlalchemy.url", database_url)
30
+
31
+
32
+ def run_migrations_offline() -> None:
33
+ """Run migrations in 'offline' mode — generates SQL script."""
34
+ url = config.get_main_option("sqlalchemy.url")
35
+ context.configure(
36
+ url=url,
37
+ target_metadata=target_metadata,
38
+ literal_binds=True,
39
+ dialect_opts={"paramstyle": "named"},
40
+ )
41
+ with context.begin_transaction():
42
+ context.run_migrations()
43
+
44
+
45
+ def do_run_migrations(connection) -> None:
46
+ context.configure(connection=connection, target_metadata=target_metadata)
47
+ with context.begin_transaction():
48
+ context.run_migrations()
49
+
50
+
51
+ async def run_async_migrations() -> None:
52
+ """Run migrations in 'online' mode with async engine."""
53
+ connectable = async_engine_from_config(
54
+ config.get_section(config.config_ini_section, {}),
55
+ prefix="sqlalchemy.",
56
+ poolclass=pool.NullPool,
57
+ )
58
+ async with connectable.connect() as connection:
59
+ await connection.run_sync(do_run_migrations)
60
+ await connectable.dispose()
61
+
62
+
63
+ def run_migrations_online() -> None:
64
+ """Entry point for online migrations."""
65
+ asyncio.run(run_async_migrations())
66
+
67
+
68
+ if context.is_offline_mode():
69
+ run_migrations_offline()
70
+ else:
71
+ run_migrations_online()
@@ -0,0 +1,27 @@
1
+ """${message}
2
+
3
+ Revision ID: ${up_revision}
4
+ Revises: ${down_revision | comma,n}
5
+ Create Date: ${create_date}
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from typing import Sequence, Union
10
+
11
+ from alembic import op
12
+ import sqlalchemy as sa
13
+ ${imports if imports else ""}
14
+
15
+ # revision identifiers, used by Alembic.
16
+ revision: str = ${repr(up_revision)}
17
+ down_revision: Union[str, None] = ${repr(down_revision)}
18
+ branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
19
+ depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
20
+
21
+
22
+ def upgrade() -> None:
23
+ ${upgrades if upgrades else "pass"}
24
+
25
+
26
+ def downgrade() -> None:
27
+ ${downgrades if downgrades else "pass"}