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,411 @@
1
+ """Temporal coupling analysis — git-history-based code intelligence.
2
+
3
+ Builds a co-change matrix from git history to identify:
4
+ - Files that frequently change together (change coupling)
5
+ - High-churn hotspots (most-modified files)
6
+ - Merge risk prediction (temporal + structural coupling)
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import logging
12
+ import subprocess
13
+ import time
14
+ from collections import defaultdict
15
+ from dataclasses import dataclass, field
16
+ from itertools import combinations
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+ # Minimum commits to consider a coupling signal meaningful
21
+ _MIN_COCHANGES = 2
22
+ # Cache staleness threshold (seconds)
23
+ _CACHE_TTL = 3600 # 1 hour
24
+
25
+
26
+ @dataclass(slots=True)
27
+ class CoChangeEntry:
28
+ """A file that co-changes with a target file."""
29
+
30
+ path: str
31
+ coupling_score: float # co_changes / min(changes_a, changes_b)
32
+ co_changes: int # times changed in same commit
33
+ individual_changes: int # total changes to this file
34
+
35
+
36
+ @dataclass(slots=True)
37
+ class ChurnEntry:
38
+ """A file's churn metrics over a time window."""
39
+
40
+ path: str
41
+ commits: int
42
+ authors: list[str]
43
+ lines_added: int
44
+ lines_removed: int
45
+ churn_score: float # normalized change intensity
46
+
47
+
48
+ @dataclass(slots=True)
49
+ class MergeRiskEntry:
50
+ """A predicted file change from merge risk analysis."""
51
+
52
+ path: str
53
+ reason: str # "temporal" | "structural" | "both"
54
+ confidence: float
55
+ coupling_score: float | None
56
+ structural_distance: int | None
57
+
58
+
59
+ @dataclass
60
+ class TemporalCouplingAnalyzer:
61
+ """Analyzes git history to find temporal coupling patterns.
62
+
63
+ Usage::
64
+
65
+ analyzer = TemporalCouplingAnalyzer(project_dir="/path/to/repo")
66
+ coupling = analyzer.get_change_coupling("src/main.py", days=90)
67
+ hotspots = analyzer.get_churn_hotspots(days=90)
68
+ risk = analyzer.get_merge_risk(["src/auth.py"], dep_graph=graph)
69
+ """
70
+
71
+ project_dir: str
72
+
73
+ # Cached analysis results
74
+ _commit_files: list[dict] = field(default_factory=list, repr=False)
75
+ _file_commits: dict[str, int] = field(default_factory=dict, repr=False)
76
+ _co_change_matrix: dict[tuple[str, str], int] = field(default_factory=dict, repr=False)
77
+ _file_churn: dict[str, dict] = field(default_factory=dict, repr=False)
78
+ _cache_window_days: int = field(default=0, repr=False)
79
+ _cache_time: float = field(default=0.0, repr=False)
80
+
81
+ def _run_git(self, args: list[str]) -> str:
82
+ """Run a git command and return stdout."""
83
+ try:
84
+ result = subprocess.run(
85
+ ["git"] + args,
86
+ cwd=self.project_dir,
87
+ capture_output=True,
88
+ text=True,
89
+ timeout=60,
90
+ )
91
+ if result.returncode != 0:
92
+ logger.debug("git %s failed: %s", " ".join(args), result.stderr.strip())
93
+ return ""
94
+ return result.stdout
95
+ except (FileNotFoundError, subprocess.TimeoutExpired) as exc:
96
+ logger.debug("git command error: %s", exc)
97
+ return ""
98
+
99
+ def _ensure_cache(self, days: int) -> None:
100
+ """Populate the co-change matrix cache if stale or wrong window."""
101
+ now = time.time()
102
+ if (
103
+ self._cache_window_days == days
104
+ and self._cache_time > 0
105
+ and (now - self._cache_time) < _CACHE_TTL
106
+ ):
107
+ return
108
+
109
+ self._build_cache(days)
110
+ self._cache_window_days = days
111
+ self._cache_time = now
112
+
113
+ def _build_cache(self, days: int) -> None:
114
+ """Parse git log and build the co-change matrix."""
115
+ raw = self._run_git([
116
+ "log",
117
+ f"--since={days} days ago",
118
+ "--numstat",
119
+ "--format=%H|%an|%aI",
120
+ ])
121
+ if not raw.strip():
122
+ self._commit_files = []
123
+ self._file_commits = {}
124
+ self._co_change_matrix = {}
125
+ self._file_churn = {}
126
+ return
127
+
128
+ # Parse commits
129
+ commits: list[dict] = []
130
+ current: dict | None = None
131
+
132
+ for line in raw.splitlines():
133
+ line = line.strip()
134
+ if not line:
135
+ continue
136
+
137
+ # Header: sha|author|date
138
+ if "|" in line and line.count("|") >= 2:
139
+ parts = line.split("|", 2)
140
+ if len(parts) == 3 and len(parts[0]) >= 7:
141
+ if current is not None:
142
+ commits.append(current)
143
+ current = {
144
+ "sha": parts[0],
145
+ "author": parts[1],
146
+ "date": parts[2][:10] if len(parts[2]) >= 10 else parts[2],
147
+ "files": [],
148
+ }
149
+ continue
150
+
151
+ # Numstat: added\tremoved\tpath
152
+ if current is not None and "\t" in line:
153
+ parts = line.split("\t")
154
+ if len(parts) >= 3:
155
+ try:
156
+ added = int(parts[0]) if parts[0] != "-" else 0
157
+ removed = int(parts[1]) if parts[1] != "-" else 0
158
+ except ValueError:
159
+ continue
160
+ file_path = parts[2]
161
+ if " => " in file_path:
162
+ file_path = file_path.split(" => ")[-1].rstrip("}")
163
+ if "{" in file_path:
164
+ file_path = file_path.replace("{", "").replace("}", "")
165
+ current["files"].append({
166
+ "path": file_path,
167
+ "added": added,
168
+ "removed": removed,
169
+ })
170
+
171
+ if current is not None:
172
+ commits.append(current)
173
+
174
+ self._commit_files = commits
175
+
176
+ # Build file commit counts
177
+ file_commits: dict[str, int] = defaultdict(int)
178
+ file_churn: dict[str, dict] = defaultdict(
179
+ lambda: {"commits": 0, "authors": set(), "added": 0, "removed": 0}
180
+ )
181
+
182
+ for commit in commits:
183
+ file_paths = [f["path"] for f in commit["files"]]
184
+ for f in commit["files"]:
185
+ fp = f["path"]
186
+ file_commits[fp] += 1
187
+ churn = file_churn[fp]
188
+ churn["commits"] += 1
189
+ churn["authors"].add(commit["author"])
190
+ churn["added"] += f["added"]
191
+ churn["removed"] += f["removed"]
192
+
193
+ self._file_commits = dict(file_commits)
194
+
195
+ # Build co-change matrix
196
+ co_changes: dict[tuple[str, str], int] = defaultdict(int)
197
+ for commit in commits:
198
+ file_paths = sorted(set(f["path"] for f in commit["files"]))
199
+ # Only count pairs (skip mega-commits with > 50 files — likely merges/renames)
200
+ if len(file_paths) > 50:
201
+ continue
202
+ for a, b in combinations(file_paths, 2):
203
+ co_changes[(a, b)] += 1
204
+
205
+ self._co_change_matrix = dict(co_changes)
206
+
207
+ # Finalize churn (convert author sets to sorted lists)
208
+ self._file_churn = {
209
+ fp: {
210
+ "commits": c["commits"],
211
+ "authors": sorted(c["authors"]),
212
+ "added": c["added"],
213
+ "removed": c["removed"],
214
+ }
215
+ for fp, c in file_churn.items()
216
+ }
217
+
218
+ def get_change_coupling(
219
+ self,
220
+ file_path: str,
221
+ *,
222
+ days: int = 90,
223
+ min_coupling: float = 0.3,
224
+ top_k: int = 20,
225
+ ) -> list[CoChangeEntry]:
226
+ """Find files that frequently co-change with *file_path*.
227
+
228
+ Args:
229
+ file_path: Target file (relative to project root).
230
+ days: Time window in days.
231
+ min_coupling: Minimum coupling score to include (0.0-1.0).
232
+ top_k: Maximum results to return.
233
+ """
234
+ self._ensure_cache(days)
235
+
236
+ target_changes = self._file_commits.get(file_path, 0)
237
+ if target_changes == 0:
238
+ return []
239
+
240
+ results: list[CoChangeEntry] = []
241
+ for (a, b), count in self._co_change_matrix.items():
242
+ other = None
243
+ if a == file_path:
244
+ other = b
245
+ elif b == file_path:
246
+ other = a
247
+ if other is None:
248
+ continue
249
+
250
+ if count < _MIN_COCHANGES:
251
+ continue
252
+
253
+ other_changes = self._file_commits.get(other, 1)
254
+ score = count / min(target_changes, other_changes)
255
+
256
+ if score >= min_coupling:
257
+ results.append(CoChangeEntry(
258
+ path=other,
259
+ coupling_score=round(score, 3),
260
+ co_changes=count,
261
+ individual_changes=other_changes,
262
+ ))
263
+
264
+ results.sort(key=lambda e: -e.coupling_score)
265
+ return results[:top_k]
266
+
267
+ def get_churn_hotspots(
268
+ self,
269
+ *,
270
+ days: int = 90,
271
+ top_n: int = 20,
272
+ ) -> list[ChurnEntry]:
273
+ """Rank files by change frequency.
274
+
275
+ Args:
276
+ days: Time window in days.
277
+ top_n: Number of top files to return.
278
+ """
279
+ self._ensure_cache(days)
280
+
281
+ if not self._file_churn:
282
+ return []
283
+
284
+ # Compute churn score: commits × (1 + log(added + removed + 1))
285
+ import math
286
+ entries: list[ChurnEntry] = []
287
+ max_commits = max(c["commits"] for c in self._file_churn.values())
288
+
289
+ for fp, churn in self._file_churn.items():
290
+ total_lines = churn["added"] + churn["removed"]
291
+ raw_score = churn["commits"] * (1 + math.log1p(total_lines))
292
+ # Normalize to 0-1 range
293
+ normalized = raw_score / (max_commits * (1 + math.log1p(
294
+ max(c["added"] + c["removed"] for c in self._file_churn.values())
295
+ ))) if max_commits > 0 else 0.0
296
+
297
+ entries.append(ChurnEntry(
298
+ path=fp,
299
+ commits=churn["commits"],
300
+ authors=churn["authors"],
301
+ lines_added=churn["added"],
302
+ lines_removed=churn["removed"],
303
+ churn_score=round(normalized, 4),
304
+ ))
305
+
306
+ entries.sort(key=lambda e: -e.churn_score)
307
+ return entries[:top_n]
308
+
309
+ def get_churn_score(self, file_path: str, *, days: int = 90) -> float:
310
+ """Return normalized churn score for a single file (0.0-1.0).
311
+
312
+ Used by hotspot integration in helpers.py.
313
+ """
314
+ self._ensure_cache(days)
315
+ churn = self._file_churn.get(file_path)
316
+ if not churn or not self._file_churn:
317
+ return 0.0
318
+
319
+ import math
320
+ total_lines = churn["added"] + churn["removed"]
321
+ raw_score = churn["commits"] * (1 + math.log1p(total_lines))
322
+ max_raw = max(
323
+ c["commits"] * (1 + math.log1p(c["added"] + c["removed"]))
324
+ for c in self._file_churn.values()
325
+ )
326
+ return round(raw_score / max_raw, 4) if max_raw > 0 else 0.0
327
+
328
+ def get_merge_risk(
329
+ self,
330
+ files: list[str],
331
+ *,
332
+ days: int = 90,
333
+ dep_graph_forward: dict[str, set[str]] | None = None,
334
+ dep_graph_reverse: dict[str, set[str]] | None = None,
335
+ min_confidence: float = 0.3,
336
+ top_k: int = 20,
337
+ ) -> list[MergeRiskEntry]:
338
+ """Predict which files will likely need changes alongside *files*.
339
+
340
+ Combines temporal coupling (co-change matrix) with structural
341
+ coupling (dependency graph) for a unified risk assessment.
342
+
343
+ Args:
344
+ files: Files being modified.
345
+ days: Time window for temporal coupling.
346
+ dep_graph_forward: file -> set of files it imports (optional).
347
+ dep_graph_reverse: file -> set of files that import it (optional).
348
+ min_confidence: Minimum confidence to include.
349
+ top_k: Maximum results.
350
+ """
351
+ self._ensure_cache(days)
352
+ file_set = set(files)
353
+ predictions: dict[str, MergeRiskEntry] = {}
354
+
355
+ # Temporal predictions
356
+ for fp in files:
357
+ coupling = self.get_change_coupling(
358
+ fp, days=days, min_coupling=0.2, top_k=50,
359
+ )
360
+ for entry in coupling:
361
+ if entry.path in file_set:
362
+ continue
363
+ confidence = entry.coupling_score * 0.8 # temporal weight
364
+ if entry.path in predictions:
365
+ existing = predictions[entry.path]
366
+ if confidence > existing.confidence:
367
+ existing.confidence = round(confidence, 3)
368
+ if existing.reason == "structural":
369
+ existing.reason = "both"
370
+ existing.coupling_score = entry.coupling_score
371
+ else:
372
+ predictions[entry.path] = MergeRiskEntry(
373
+ path=entry.path,
374
+ reason="temporal",
375
+ confidence=round(confidence, 3),
376
+ coupling_score=entry.coupling_score,
377
+ structural_distance=None,
378
+ )
379
+
380
+ # Structural predictions (1-hop)
381
+ if dep_graph_forward or dep_graph_reverse:
382
+ fwd = dep_graph_forward or {}
383
+ rev = dep_graph_reverse or {}
384
+ for fp in files:
385
+ neighbors: set[str] = set()
386
+ neighbors.update(fwd.get(fp, set()))
387
+ neighbors.update(rev.get(fp, set()))
388
+ for neighbor in neighbors:
389
+ if neighbor in file_set:
390
+ continue
391
+ confidence = 0.5 # structural base confidence
392
+ if neighbor in predictions:
393
+ existing = predictions[neighbor]
394
+ if existing.reason == "temporal":
395
+ existing.reason = "both"
396
+ existing.confidence = round(
397
+ min(1.0, existing.confidence + confidence * 0.3), 3,
398
+ )
399
+ existing.structural_distance = 1
400
+ else:
401
+ predictions[neighbor] = MergeRiskEntry(
402
+ path=neighbor,
403
+ reason="structural",
404
+ confidence=round(confidence, 3),
405
+ coupling_score=None,
406
+ structural_distance=1,
407
+ )
408
+
409
+ results = [e for e in predictions.values() if e.confidence >= min_confidence]
410
+ results.sort(key=lambda e: -e.confidence)
411
+ return results[:top_k]