batho 1.2.0__tar.gz → 1.3.0__tar.gz

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 (169) hide show
  1. batho-1.3.0/.zenodo.json +32 -0
  2. batho-1.3.0/CITATION.cff +34 -0
  3. {batho-1.2.0 → batho-1.3.0}/PKG-INFO +21 -1
  4. {batho-1.2.0 → batho-1.3.0}/README.md +20 -0
  5. {batho-1.2.0 → batho-1.3.0}/batho/__init__.py +5 -1
  6. {batho-1.2.0 → batho-1.3.0}/batho/cli/build.py +12 -0
  7. {batho-1.2.0 → batho-1.3.0}/batho/cli/patch.py +13 -1
  8. {batho-1.2.0 → batho-1.3.0}/batho/core/config/loader.py +98 -7
  9. {batho-1.2.0 → batho-1.3.0}/batho/core/config/models.py +33 -0
  10. {batho-1.2.0 → batho-1.3.0}/batho/core/schemas.py +1 -1
  11. {batho-1.2.0 → batho-1.3.0}/batho/mcp/graph_builder.py +5 -2
  12. {batho-1.2.0 → batho-1.3.0}/batho/mcp/registry.py +34 -18
  13. {batho-1.2.0 → batho-1.3.0}/batho/mcp/resources.py +3 -10
  14. {batho-1.2.0 → batho-1.3.0}/batho/mcp/server.py +1 -2
  15. {batho-1.2.0 → batho-1.3.0}/batho/mcp/tools.py +28 -8
  16. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/bsg_map/__init__.py +3 -17
  17. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/rules.py +41 -30
  18. {batho-1.2.0 → batho-1.3.0}/batho/modules/dependency/indexer.py +0 -1
  19. {batho-1.2.0 → batho-1.3.0}/batho/modules/dependency/introspector.py +4 -0
  20. {batho-1.2.0 → batho-1.3.0}/batho/modules/dependency/resolution_cache.py +24 -5
  21. {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/pipeline.py +26 -7
  22. {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/scope_manager.py +12 -0
  23. {batho-1.2.0 → batho-1.3.0}/batho/modules/graph/__init__.py +10 -0
  24. batho-1.3.0/batho/modules/graph/builder/arrow_graph.py +1208 -0
  25. {batho-1.2.0 → batho-1.3.0}/batho/modules/graph/builder/codegraph.py +239 -66
  26. batho-1.3.0/batho/modules/graph/builder/factory.py +90 -0
  27. batho-1.3.0/batho/modules/graph/builder/protocol.py +87 -0
  28. {batho-1.2.0 → batho-1.3.0}/batho/modules/graph/community.py +96 -6
  29. {batho-1.2.0 → batho-1.3.0}/batho/modules/graph/incremental.py +1 -0
  30. {batho-1.2.0 → batho-1.3.0}/batho/modules/integrity/checkers/bundle_checker.py +1 -1
  31. {batho-1.2.0 → batho-1.3.0}/batho/modules/integrity/engine.py +1 -1
  32. {batho-1.2.0 → batho-1.3.0}/batho/modules/storage/arrow_bundle/__init__.py +1 -1
  33. {batho-1.2.0 → batho-1.3.0}/batho/modules/storage/arrow_bundle/schemas.py +1 -1
  34. {batho-1.2.0 → batho-1.3.0}/batho/modules/storage/arrow_store/__init__.py +1 -1
  35. {batho-1.2.0 → batho-1.3.0}/batho/modules/storage/arrow_store/metrics.py +1 -1
  36. {batho-1.2.0 → batho-1.3.0}/batho/modules/storage/arrow_store/store.py +4 -4
  37. {batho-1.2.0 → batho-1.3.0}/batho/orchestrator/build.py +119 -30
  38. {batho-1.2.0 → batho-1.3.0}/batho/orchestrator/patch.py +62 -3
  39. {batho-1.2.0 → batho-1.3.0}/batho/utils/memory_monitor.py +44 -3
  40. {batho-1.2.0 → batho-1.3.0}/batho_cli.py +8 -1
  41. batho-1.3.0/codemeta.json +31 -0
  42. {batho-1.2.0 → batho-1.3.0}/pyproject.toml +1 -1
  43. {batho-1.2.0 → batho-1.3.0}/.gitignore +0 -0
  44. {batho-1.2.0 → batho-1.3.0}/LICENSE +0 -0
  45. {batho-1.2.0 → batho-1.3.0}/batho/cli/_utils.py +0 -0
  46. {batho-1.2.0 → batho-1.3.0}/batho/cli/diff.py +0 -0
  47. {batho-1.2.0 → batho-1.3.0}/batho/cli/export.py +0 -0
  48. {batho-1.2.0 → batho-1.3.0}/batho/cli/fix.py +0 -0
  49. {batho-1.2.0 → batho-1.3.0}/batho/cli/gc.py +0 -0
  50. {batho-1.2.0 → batho-1.3.0}/batho/cli/load.py +0 -0
  51. {batho-1.2.0 → batho-1.3.0}/batho/cli/mcp.py +0 -0
  52. {batho-1.2.0 → batho-1.3.0}/batho/core/__init__.py +0 -0
  53. {batho-1.2.0 → batho-1.3.0}/batho/core/batho_data/popular-packages.yaml +0 -0
  54. {batho-1.2.0 → batho-1.3.0}/batho/core/config/__init__.py +0 -0
  55. {batho-1.2.0 → batho-1.3.0}/batho/core/config/default-ignore-patterns.yaml +0 -0
  56. {batho-1.2.0 → batho-1.3.0}/batho/mcp/__init__.py +0 -0
  57. {batho-1.2.0 → batho-1.3.0}/batho/mcp/__main__.py +0 -0
  58. {batho-1.2.0 → batho-1.3.0}/batho/mcp/community_summaries.py +0 -0
  59. {batho-1.2.0 → batho-1.3.0}/batho/mcp/delta_reader.py +0 -0
  60. {batho-1.2.0 → batho-1.3.0}/batho/mcp/errors.py +0 -0
  61. {batho-1.2.0 → batho-1.3.0}/batho/mcp/instructions.py +0 -0
  62. {batho-1.2.0 → batho-1.3.0}/batho/mcp/prompts.py +0 -0
  63. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/__init__.py +0 -0
  64. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/bsg.py +0 -0
  65. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/bsg_map/constants.py +0 -0
  66. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/bsg_map/relativizer.py +0 -0
  67. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/bsg_map/render_agent.py +0 -0
  68. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/bsg_map/render_storage.py +0 -0
  69. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/core_engine/__init__.py +0 -0
  70. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_bidirectional_foundation.yaml +0 -0
  71. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_detection_cicd.yaml +0 -0
  72. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_detection_cloud_providers.yaml +0 -0
  73. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_detection_cpp.yaml +0 -0
  74. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_detection_csharp.yaml +0 -0
  75. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_detection_dart.yaml +0 -0
  76. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_detection_elixir.yaml +0 -0
  77. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_detection_foundation.yaml +0 -0
  78. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_detection_kotlin.yaml +0 -0
  79. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_detection_php.yaml +0 -0
  80. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_detection_ruby.yaml +0 -0
  81. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_detection_scala.yaml +0 -0
  82. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_detection_swift.yaml +0 -0
  83. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_detection_test_frameworks.yaml +0 -0
  84. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_file_categorization.yaml +0 -0
  85. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_framework_angular.yaml +0 -0
  86. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_framework_django.yaml +0 -0
  87. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_framework_flask.yaml +0 -0
  88. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_framework_nodejs.yaml +0 -0
  89. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_framework_other.yaml +0 -0
  90. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_framework_python.yaml +0 -0
  91. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_framework_react.yaml +0 -0
  92. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_framework_vue.yaml +0 -0
  93. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_graph_foundation.yaml +0 -0
  94. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_token_optimization.yaml +0 -0
  95. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/test_bidirectional_gap_coverage.yaml +0 -0
  96. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/test_bidirectional_integrity.yaml +0 -0
  97. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/test_bidirectional_reconstruction.yaml +0 -0
  98. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/interceptors/bsg_api_contract_guardian.yaml +0 -0
  99. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/interceptors/bsg_auth_boundary_shield.yaml +0 -0
  100. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/interceptors/bsg_dependency_blast_radius.yaml +0 -0
  101. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/interceptors/bsg_hardcoded_secret_catcher.yaml +0 -0
  102. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/interceptors/bsg_iac_drift_sentinel.yaml +0 -0
  103. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/interceptors/bsg_nplus1_query_catcher.yaml +0 -0
  104. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/interceptors/bsg_reconstruction_interceptors.yaml +0 -0
  105. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/interceptors/bsg_resource_leak_preventer.yaml +0 -0
  106. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/interceptors/bsg_schema_migration_enforcer.yaml +0 -0
  107. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/interceptors/bsg_silent_failure_catcher.yaml +0 -0
  108. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/schemas/bsg-plugin-schema-v1.json +0 -0
  109. {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/schemas/bsg-plugin-schema-v2.json +0 -0
  110. {batho-1.2.0 → batho-1.3.0}/batho/modules/dependency/__init__.py +0 -0
  111. {batho-1.2.0 → batho-1.3.0}/batho/modules/dependency/manifest_parser.py +0 -0
  112. {batho-1.2.0 → batho-1.3.0}/batho/modules/dependency/popular_packages.py +0 -0
  113. {batho-1.2.0 → batho-1.3.0}/batho/modules/dependency/stdlib_tables.py +0 -0
  114. {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/__init__.py +0 -0
  115. {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/ast_cache.py +0 -0
  116. {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/extraction_result.py +0 -0
  117. {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/extractor.py +0 -0
  118. {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/fallback_parser.py +0 -0
  119. {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/submodules/languages/css.py +0 -0
  120. {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/submodules/languages/hcl.py +0 -0
  121. {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/submodules/languages/html.py +0 -0
  122. {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/submodules/languages/json.py +0 -0
  123. {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/submodules/languages/markdown.py +0 -0
  124. {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/submodules/languages/toml.py +0 -0
  125. {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/submodules/languages/yaml.py +0 -0
  126. {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/submodules/parser_factory/__init__.py +0 -0
  127. {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/submodules/parser_factory/_common.py +0 -0
  128. {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/submodules/parser_factory/_queries.py +0 -0
  129. {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/submodules/parser_factory/detector.py +0 -0
  130. {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/submodules/parser_factory/factory.py +0 -0
  131. {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/submodules/parser_factory/registry.py +0 -0
  132. {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/symbol_table.py +0 -0
  133. {batho-1.2.0 → batho-1.3.0}/batho/modules/graph/diff_engine/node_diff.py +0 -0
  134. {batho-1.2.0 → batho-1.3.0}/batho/modules/graph/reconstructor/reconstructor.py +0 -0
  135. {batho-1.2.0 → batho-1.3.0}/batho/modules/integrity/__init__.py +0 -0
  136. {batho-1.2.0 → batho-1.3.0}/batho/modules/integrity/checkers/__init__.py +0 -0
  137. {batho-1.2.0 → batho-1.3.0}/batho/modules/integrity/checkers/blob_checker.py +0 -0
  138. {batho-1.2.0 → batho-1.3.0}/batho/modules/integrity/checkers/graph_checker.py +0 -0
  139. {batho-1.2.0 → batho-1.3.0}/batho/modules/integrity/checkers/state_checker.py +0 -0
  140. {batho-1.2.0 → batho-1.3.0}/batho/modules/integrity/checks/__init__.py +0 -0
  141. {batho-1.2.0 → batho-1.3.0}/batho/modules/integrity/cli.py +0 -0
  142. {batho-1.2.0 → batho-1.3.0}/batho/modules/integrity/models.py +0 -0
  143. {batho-1.2.0 → batho-1.3.0}/batho/modules/integrity/repairers/__init__.py +0 -0
  144. {batho-1.2.0 → batho-1.3.0}/batho/modules/integrity/repairers/blob_repairer.py +0 -0
  145. {batho-1.2.0 → batho-1.3.0}/batho/modules/integrity/repairers/graph_repairer.py +0 -0
  146. {batho-1.2.0 → batho-1.3.0}/batho/modules/integrity/repairers/state_repairer.py +0 -0
  147. {batho-1.2.0 → batho-1.3.0}/batho/modules/integrity/report.py +0 -0
  148. {batho-1.2.0 → batho-1.3.0}/batho/modules/storage/__init__.py +0 -0
  149. {batho-1.2.0 → batho-1.3.0}/batho/modules/storage/arrow_bundle/bundle.py +0 -0
  150. {batho-1.2.0 → batho-1.3.0}/batho/modules/storage/arrow_bundle/helpers.py +0 -0
  151. {batho-1.2.0 → batho-1.3.0}/batho/modules/storage/arrow_bundle/incremental.py +0 -0
  152. {batho-1.2.0 → batho-1.3.0}/batho/modules/storage/arrow_bundle/manager.py +0 -0
  153. {batho-1.2.0 → batho-1.3.0}/batho/modules/storage/arrow_bundle/reader.py +0 -0
  154. {batho-1.2.0 → batho-1.3.0}/batho/modules/storage/arrow_bundle/writer.py +0 -0
  155. {batho-1.2.0 → batho-1.3.0}/batho/modules/storage/arrow_store/compaction.py +0 -0
  156. {batho-1.2.0 → batho-1.3.0}/batho/modules/storage/arrow_store/schemas.py +0 -0
  157. {batho-1.2.0 → batho-1.3.0}/batho/modules/storage/cache/unified_cache.py +0 -0
  158. {batho-1.2.0 → batho-1.3.0}/batho/orchestrator/__init__.py +0 -0
  159. {batho-1.2.0 → batho-1.3.0}/batho/orchestrator/export.py +0 -0
  160. {batho-1.2.0 → batho-1.3.0}/batho/orchestrator/gc.py +0 -0
  161. {batho-1.2.0 → batho-1.3.0}/batho/orchestrator/load.py +0 -0
  162. {batho-1.2.0 → batho-1.3.0}/batho/utils/__init__.py +0 -0
  163. {batho-1.2.0 → batho-1.3.0}/batho/utils/cli_output.py +0 -0
  164. {batho-1.2.0 → batho-1.3.0}/batho/utils/encoding.py +0 -0
  165. {batho-1.2.0 → batho-1.3.0}/batho/utils/file_io.py +0 -0
  166. {batho-1.2.0 → batho-1.3.0}/batho/utils/hash.py +0 -0
  167. {batho-1.2.0 → batho-1.3.0}/batho/utils/ignore.py +0 -0
  168. {batho-1.2.0 → batho-1.3.0}/batho/utils/logging.py +0 -0
  169. {batho-1.2.0 → batho-1.3.0}/batho/utils/path_sanitizer.py +0 -0
@@ -0,0 +1,32 @@
1
+ {
2
+ "title": "Batho: Deterministic Code Intelligence Engine",
3
+ "description": "Enterprise-grade multi-language source code indexer and semantic code intelligence engine powered by tree-sitter AST parsing, BSG compression, and relational code graph snapshots.",
4
+ "creators": [
5
+ {
6
+ "name": "Sharma, Rishiraj",
7
+ "orcid": "0009-0001-8817-7606"
8
+ }
9
+ ],
10
+ "keywords": [
11
+ "code intelligence",
12
+ "semantic code graph",
13
+ "tree-sitter",
14
+ "static analysis",
15
+ "LLM context",
16
+ "code indexing",
17
+ "deterministic reconstruction"
18
+ ],
19
+ "license": "Apache-2.0",
20
+ "upload_type": "software",
21
+ "version": "1.3.0",
22
+ "access_right": "open",
23
+ "publication_date": "2026-07-22",
24
+ "related_identifiers": [
25
+ {
26
+ "identifier": "https://github.com/sageoz/batho",
27
+ "relation": "isSupplementTo",
28
+ "resource_type": "software",
29
+ "scheme": "url"
30
+ }
31
+ ]
32
+ }
@@ -0,0 +1,34 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use Batho, please cite it as below."
3
+ title: "Batho: Deterministic Code Intelligence Engine"
4
+ authors:
5
+ - family-names: "Sharma"
6
+ given-names: "Rishiraj"
7
+ orcid: "https://orcid.org/0009-0001-8817-7606"
8
+ version: "1.3.0"
9
+ date-released: "2026-07-22"
10
+ license: "Apache-2.0"
11
+ repository-code: "https://github.com/sageoz/batho"
12
+ url: "https://pypi.org/project/batho/"
13
+ doi: "10.5281/zenodo.21407508"
14
+ keywords:
15
+ - code intelligence
16
+ - semantic code graph
17
+ - tree-sitter
18
+ - abstract syntax tree
19
+ - static analysis
20
+ - LLM context
21
+ - code indexing
22
+ - deterministic reconstruction
23
+ preferred-citation:
24
+ type: software
25
+ title: "Batho: Deterministic Code Intelligence Engine"
26
+ authors:
27
+ - family-names: "Sharma"
28
+ given-names: "Rishiraj"
29
+ orcid: "https://orcid.org/0009-0001-8817-7606"
30
+ version: "1.3.0"
31
+ date-released: "2026-07-22"
32
+ license: "Apache-2.0"
33
+ url: "https://github.com/sageoz/batho"
34
+ doi: "10.5281/zenodo.21407508"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: batho
3
- Version: 1.2.0
3
+ Version: 1.3.0
4
4
  Summary: Enterprise-grade multi-language source code indexer and semantic code intelligence engine powered by tree-sitter AST parsing, BSG compression, and relational code graph snapshots.
5
5
  Project-URL: homepage, https://batho.sageoz.org
6
6
  Project-URL: repository, https://github.com/sageoz/batho
@@ -266,6 +266,7 @@ Description-Content-Type: text/markdown
266
266
  <a href="https://github.com/sageoz/batho/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-blue" alt="License"></a>
267
267
  <a href="https://batho.sageoz.org"><img src="https://img.shields.io/badge/docs-batho.sageoz.org-green" alt="Documentation"></a>
268
268
  <a href="https://github.com/sageoz/batho/stargazers"><img src="https://img.shields.io/github/stars/sageoz/batho?style=flat" alt="Stars"></a>
269
+ <a href="https://doi.org/10.5281/zenodo.21407508"><img src="https://img.shields.io/badge/DOI-10.5281/zenodo.21407508-blue" alt="DOI"></a>
269
270
  </p>
270
271
 
271
272
  <p align="center">
@@ -434,8 +435,27 @@ uv run python batho_cli.py --help
434
435
 
435
436
  Co-authored with [Devin](https://devin.ai) — autonomous AI software engineer by Cognition.
436
437
 
438
+ ---
439
+ Citation
440
+
441
+ If you use Batho in your research, please cite it as:
442
+
443
+ ```bibtex
444
+ @misc{Sharma_Batho_2026,
445
+ author = {Sharma, Rishiraj},
446
+ doi = {10.5281/zenodo.21407508},
447
+ month = {7},
448
+ title = {Batho: Deterministic Code Intelligence Engine},
449
+ url = {https://pypi.org/project/batho/},
450
+ year = {2026}
451
+ }
452
+ ```
453
+
454
+ You can also find the citation metadata in [`CITATION.cff`](CITATION.cff) or use GitHub's **"Cite this repository"** button.
455
+
437
456
  ---
438
457
 
458
+ ##
439
459
  ## License
440
460
 
441
461
  Apache License 2.0 — see [LICENSE](LICENSE) for details.
@@ -14,6 +14,7 @@
14
14
  <a href="https://github.com/sageoz/batho/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-blue" alt="License"></a>
15
15
  <a href="https://batho.sageoz.org"><img src="https://img.shields.io/badge/docs-batho.sageoz.org-green" alt="Documentation"></a>
16
16
  <a href="https://github.com/sageoz/batho/stargazers"><img src="https://img.shields.io/github/stars/sageoz/batho?style=flat" alt="Stars"></a>
17
+ <a href="https://doi.org/10.5281/zenodo.21407508"><img src="https://img.shields.io/badge/DOI-10.5281/zenodo.21407508-blue" alt="DOI"></a>
17
18
  </p>
18
19
 
19
20
  <p align="center">
@@ -182,8 +183,27 @@ uv run python batho_cli.py --help
182
183
 
183
184
  Co-authored with [Devin](https://devin.ai) — autonomous AI software engineer by Cognition.
184
185
 
186
+ ---
187
+ Citation
188
+
189
+ If you use Batho in your research, please cite it as:
190
+
191
+ ```bibtex
192
+ @misc{Sharma_Batho_2026,
193
+ author = {Sharma, Rishiraj},
194
+ doi = {10.5281/zenodo.21407508},
195
+ month = {7},
196
+ title = {Batho: Deterministic Code Intelligence Engine},
197
+ url = {https://pypi.org/project/batho/},
198
+ year = {2026}
199
+ }
200
+ ```
201
+
202
+ You can also find the citation metadata in [`CITATION.cff`](CITATION.cff) or use GitHub's **"Cite this repository"** button.
203
+
185
204
  ---
186
205
 
206
+ ##
187
207
  ## License
188
208
 
189
209
  Apache License 2.0 — see [LICENSE](LICENSE) for details.
@@ -5,13 +5,15 @@ This package provides the core functionality for analyzing code repositories,
5
5
  building dependency graphs, and generating contextual information for LLMs.
6
6
  """
7
7
 
8
- __version__ = "1.2.0"
8
+ __version__ = "1.3.0"
9
9
 
10
10
  from batho.core.config import get_config_cached, reload_config
11
11
  from batho.modules.compression.bsg_map import BSGMap
12
12
 
13
13
  # Import and re-export public APIs from submodules
14
14
  from batho.modules.graph.builder.codegraph import CodeGraphIndexer, InMemoryGraph
15
+ from batho.modules.graph.builder.arrow_graph import ArrowGraph
16
+ from batho.modules.graph.builder.factory import create_graph
15
17
 
16
18
  from batho.utils.logging import get_logger
17
19
 
@@ -19,6 +21,8 @@ __all__ = [
19
21
  # Core indexing
20
22
  "CodeGraphIndexer",
21
23
  "InMemoryGraph",
24
+ "ArrowGraph",
25
+ "create_graph",
22
26
  # BSG rendering
23
27
  "BSGMap",
24
28
  # Config
@@ -41,6 +41,17 @@ def register_build_parser(subparsers: argparse._SubParsersAction) -> None:
41
41
  default=None,
42
42
  help="Skip files exceeding this size in kilobytes",
43
43
  )
44
+ parser.add_argument(
45
+ "--graph-backend",
46
+ type=str,
47
+ choices=["auto", "in-memory", "arrow"],
48
+ default=None,
49
+ help=(
50
+ "Graph storage backend: 'auto' (default, threshold-based), "
51
+ "'in-memory', or 'arrow' (columnar, memory-mapped). "
52
+ "Overrides graph.backend.backend in batho.yaml."
53
+ ),
54
+ )
44
55
  parser.set_defaults(func=cmd_build)
45
56
 
46
57
 
@@ -54,6 +65,7 @@ def cmd_build(args: argparse.Namespace) -> int:
54
65
  verbose=args.verbose,
55
66
  max_workers=args.max_workers,
56
67
  max_file_size_kb=args.max_file_size_kb,
68
+ graph_backend=args.graph_backend,
57
69
  )
58
70
 
59
71
  result = run_build(options)
@@ -18,7 +18,7 @@ def register_patch_parser(subparsers: argparse._SubParsersAction) -> None:
18
18
  parents=[create_base_parser()],
19
19
  help="Incremental patch of an existing artifact database",
20
20
  description=(
21
- "Detects changes natively using content hashing against the SQLite "
21
+ "Detects changes natively using content hashing against the Arrow Bundle "
22
22
  "file_tracking table. Unlike previous versions, this does not use Git "
23
23
  "status for change detection, eliminating false positives from "
24
24
  "uncommitted files."
@@ -30,6 +30,17 @@ def register_patch_parser(subparsers: argparse._SubParsersAction) -> None:
30
30
  default=None,
31
31
  help="Skip files exceeding this size in kilobytes during hash scan",
32
32
  )
33
+ parser.add_argument(
34
+ "--graph-backend",
35
+ type=str,
36
+ choices=["auto", "in-memory", "arrow"],
37
+ default=None,
38
+ help=(
39
+ "Accepted for API symmetry with `build`. Patch always uses the "
40
+ "in-memory graph backend internally; a warning is logged if a "
41
+ "non-default backend is requested."
42
+ ),
43
+ )
33
44
 
34
45
  parser.set_defaults(func=cmd_patch)
35
46
 
@@ -42,6 +53,7 @@ def cmd_patch(args: argparse.Namespace) -> int:
42
53
  root=args.root,
43
54
  verbose=args.verbose,
44
55
  max_file_size_kb=args.max_file_size_kb,
56
+ graph_backend=args.graph_backend,
45
57
  )
46
58
 
47
59
  result = run_patch(options)
@@ -50,6 +50,13 @@ def _env_int(name: str, default: int) -> int:
50
50
  return default
51
51
 
52
52
 
53
+ def _env_float(name: str, default: float) -> float:
54
+ try:
55
+ return float(os.getenv(name, str(default)))
56
+ except (ValueError, TypeError):
57
+ return default
58
+
59
+
53
60
  def _env_bool(name: str, default: bool) -> bool:
54
61
  raw = os.getenv(name)
55
62
  if raw is None:
@@ -96,16 +103,18 @@ def _safe_set_nested(d: dict[str, Any], keys: list[str], val: Any) -> None:
96
103
  curr[keys[-1]] = val
97
104
 
98
105
 
99
- def get_config_with_root(root_dir: Path) -> dict[str, Any]:
106
+ def get_config_with_root(root_dir: Path, auto_create: bool = False) -> dict[str, Any]:
100
107
  """Return validated config as a plain dict, loading batho.yaml from root_dir.
101
108
 
102
- If batho.yaml does not exist, it is created with default configuration options.
109
+ If batho.yaml does not exist and auto_create is True, it is created with
110
+ default configuration options. Otherwise, the default config is returned
111
+ without writing to disk.
103
112
  """
104
113
  base_cfg: dict[str, Any] = Config().model_dump()
105
114
 
106
115
  cfg_path = root_dir / "batho.yaml"
107
116
 
108
- if not cfg_path.exists():
117
+ if not cfg_path.exists() and auto_create:
109
118
  try:
110
119
  cfg_path.write_text(
111
120
  yaml.safe_dump(base_cfg, default_flow_style=False, sort_keys=False),
@@ -223,6 +232,49 @@ def get_config_with_root(root_dir: Path) -> dict[str, Any]:
223
232
  _env_bool("BATHO_GRAPH_ORPHAN_PRUNING_KEEP_EXPORTS", _safe_get_nested(base_cfg, ["graph", "orphan_pruning", "keep_exports"], True)),
224
233
  )
225
234
 
235
+ # Graph backend overrides
236
+ env_graph_backend = _env("BATHO_GRAPH_BACKEND")
237
+ if env_graph_backend is not None:
238
+ if env_graph_backend in ("auto", "in-memory", "arrow"):
239
+ _safe_set_nested(base_cfg, ["graph", "backend", "backend"], env_graph_backend)
240
+ else:
241
+ # An invalid env value must NOT reach Pydantic validation: a
242
+ # ValidationError here triggers full batho.yaml regeneration,
243
+ # discarding all user settings over a one-time typo.
244
+ _get_logger().warning(
245
+ "invalid_graph_backend_env_ignored",
246
+ value=env_graph_backend,
247
+ valid=["auto", "in-memory", "arrow"],
248
+ )
249
+ _safe_set_nested(
250
+ base_cfg,
251
+ ["graph", "backend", "auto_threshold_files"],
252
+ _env_int("BATHO_GRAPH_AUTO_THRESHOLD_FILES", _safe_get_nested(base_cfg, ["graph", "backend", "auto_threshold_files"], 500)),
253
+ )
254
+ _safe_set_nested(
255
+ base_cfg,
256
+ ["graph", "backend", "auto_threshold_entities"],
257
+ _env_int("BATHO_GRAPH_AUTO_THRESHOLD_ENTITIES", _safe_get_nested(base_cfg, ["graph", "backend", "auto_threshold_entities"], 30_000)),
258
+ )
259
+ env_arrow_staging_dir = _env("BATHO_GRAPH_ARROW_STAGING_DIR")
260
+ if env_arrow_staging_dir is not None:
261
+ _safe_set_nested(base_cfg, ["graph", "backend", "arrow_staging_dir"], env_arrow_staging_dir)
262
+ _safe_set_nested(
263
+ base_cfg,
264
+ ["graph", "backend", "arrow_flush_rows"],
265
+ _env_int("BATHO_GRAPH_ARROW_FLUSH_ROWS", _safe_get_nested(base_cfg, ["graph", "backend", "arrow_flush_rows"], 5000)),
266
+ )
267
+ _safe_set_nested(
268
+ base_cfg,
269
+ ["graph", "backend", "arrow_flush_bytes_mb"],
270
+ _env_float("BATHO_GRAPH_ARROW_FLUSH_BYTES_MB", _safe_get_nested(base_cfg, ["graph", "backend", "arrow_flush_bytes_mb"], 1.0)),
271
+ )
272
+ _safe_set_nested(
273
+ base_cfg,
274
+ ["graph", "backend", "arrow_recompact_delta_ratio"],
275
+ _env_float("BATHO_GRAPH_ARROW_RECOMPACT_DELTA_RATIO", _safe_get_nested(base_cfg, ["graph", "backend", "arrow_recompact_delta_ratio"], 0.10)),
276
+ )
277
+
226
278
  # Flags overrides
227
279
  env_fail_on_warning = os.getenv("BATHO_FAIL_ON_WARNING")
228
280
  env_strict = os.getenv("BATHO_STRICT")
@@ -318,6 +370,45 @@ def get_config_with_root(root_dir: Path) -> dict[str, Any]:
318
370
  _env_bool("BATHO_ARTIFACT_BLOBS_DELTA_STATS", _safe_get_nested(base_cfg, ["artifact_blobs", "run_artifacts", "delta_stats"], True)),
319
371
  )
320
372
 
373
+ # Memory overrides
374
+ _safe_set_nested(
375
+ base_cfg,
376
+ ["memory", "warning_threshold_mb"],
377
+ _env_float("BATHO_MEMORY_WARNING_THRESHOLD_MB", _safe_get_nested(base_cfg, ["memory", "warning_threshold_mb"], 800.0)),
378
+ )
379
+ _safe_set_nested(
380
+ base_cfg,
381
+ ["memory", "critical_threshold_mb"],
382
+ _env_float("BATHO_MEMORY_CRITICAL_THRESHOLD_MB", _safe_get_nested(base_cfg, ["memory", "critical_threshold_mb"], 1500.0)),
383
+ )
384
+ _safe_set_nested(
385
+ base_cfg,
386
+ ["memory", "rss_flush_threshold_mb"],
387
+ _env_float("BATHO_MEMORY_RSS_FLUSH_THRESHOLD_MB", _safe_get_nested(base_cfg, ["memory", "rss_flush_threshold_mb"], 1000.0)),
388
+ )
389
+ _safe_set_nested(
390
+ base_cfg,
391
+ ["memory", "max_per_worker_mb"],
392
+ _env_float("BATHO_MEMORY_MAX_PER_WORKER_MB", _safe_get_nested(base_cfg, ["memory", "max_per_worker_mb"], 150.0)),
393
+ )
394
+
395
+ # Community detection overrides
396
+ _safe_set_nested(
397
+ base_cfg,
398
+ ["community_detection", "enabled"],
399
+ _env_bool("BATHO_COMMUNITY_DETECTION_ENABLED", _safe_get_nested(base_cfg, ["community_detection", "enabled"], True)),
400
+ )
401
+ _safe_set_nested(
402
+ base_cfg,
403
+ ["community_detection", "skip_threshold"],
404
+ _env_int("BATHO_COMMUNITY_DETECTION_SKIP_THRESHOLD", _safe_get_nested(base_cfg, ["community_detection", "skip_threshold"], 200_000)),
405
+ )
406
+ _safe_set_nested(
407
+ base_cfg,
408
+ ["community_detection", "sample_threshold"],
409
+ _env_int("BATHO_COMMUNITY_DETECTION_SAMPLE_THRESHOLD", _safe_get_nested(base_cfg, ["community_detection", "sample_threshold"], 100_000)),
410
+ )
411
+
321
412
  # BSG overrides
322
413
  _safe_set_nested(
323
414
  base_cfg,
@@ -454,12 +545,12 @@ def get_config_with_root(root_dir: Path) -> dict[str, Any]:
454
545
 
455
546
 
456
547
  @lru_cache(maxsize=None)
457
- def _get_config_cached_for_root(root_dir: Path) -> dict[str, Any]:
458
- return get_config_with_root(root_dir)
548
+ def _get_config_cached_for_root(root_dir: Path, auto_create: bool = False) -> dict[str, Any]:
549
+ return get_config_with_root(root_dir, auto_create=auto_create)
459
550
 
460
551
 
461
- def get_config_cached() -> dict[str, Any]:
462
- return _get_config_cached_for_root(get_active_root())
552
+ def get_config_cached(auto_create: bool = False) -> dict[str, Any]:
553
+ return _get_config_cached_for_root(get_active_root(), auto_create)
463
554
 
464
555
 
465
556
  def reload_config() -> dict[str, Any]:
@@ -96,7 +96,30 @@ class GraphOrphanPruningConfig(BaseModel):
96
96
  keep_exports: bool = Field(default=True)
97
97
 
98
98
 
99
+ class GraphBackendConfig(BaseModel):
100
+ """Graph storage backend selection and Arrow-specific tuning."""
101
+
102
+ backend: str = Field(default="auto") # "auto" | "in-memory" | "arrow"
103
+ auto_threshold_files: int = Field(default=500, ge=1)
104
+ auto_threshold_entities: int = Field(default=30_000, ge=1)
105
+ arrow_staging_dir: str = Field(default=".batho/graph_staging")
106
+ arrow_flush_rows: int = Field(default=5000, ge=100)
107
+ arrow_flush_bytes_mb: float = Field(default=1.0, ge=0.1)
108
+ # Reserved for future delta-overlay support (patch on Arrow); unused in Phase 1.
109
+ arrow_recompact_delta_ratio: float = Field(default=0.10, ge=0.01, le=1.0)
110
+
111
+ @field_validator("backend")
112
+ @classmethod
113
+ def _validate_backend(cls, v: str) -> str:
114
+ if v not in ("auto", "in-memory", "arrow"):
115
+ raise ValueError(
116
+ "graph.backend.backend must be 'auto', 'in-memory', or 'arrow'"
117
+ )
118
+ return v
119
+
120
+
99
121
  class GraphConfig(BaseModel):
122
+ backend: GraphBackendConfig = Field(default_factory=GraphBackendConfig)
100
123
  cycle_detection: GraphCycleDetectionConfig = Field(
101
124
  default_factory=GraphCycleDetectionConfig
102
125
  )
@@ -107,6 +130,15 @@ class GraphConfig(BaseModel):
107
130
 
108
131
  class CommunityDetectionConfig(BaseModel):
109
132
  enabled: bool = Field(default=True)
133
+ skip_threshold: int = Field(default=200_000, ge=1)
134
+ sample_threshold: int = Field(default=100_000, ge=1)
135
+
136
+
137
+ class MemoryConfig(BaseModel):
138
+ warning_threshold_mb: float = Field(default=800.0, ge=1.0)
139
+ critical_threshold_mb: float = Field(default=1500.0, ge=1.0)
140
+ rss_flush_threshold_mb: float = Field(default=1000.0, ge=1.0)
141
+ max_per_worker_mb: float = Field(default=150.0, ge=1.0)
110
142
 
111
143
 
112
144
  class FlagsConfig(BaseModel):
@@ -255,6 +287,7 @@ class Config(BaseModel):
255
287
  plugins: PluginsConfig = Field(default_factory=PluginsConfig)
256
288
  artifact_blobs: ArtifactBlobsConfig = Field(default_factory=ArtifactBlobsConfig)
257
289
  persistence: PersistenceConfig = Field(default_factory=PersistenceConfig)
290
+ memory: MemoryConfig = Field(default_factory=MemoryConfig)
258
291
  bsg: BsgConfig = Field(default_factory=BsgConfig)
259
292
  dependency: DependencyConfig = Field(default_factory=DependencyConfig)
260
293
  extraction: ExtractionConfig = Field(default_factory=ExtractionConfig)
@@ -590,7 +590,7 @@ class Entity(BaseModel):
590
590
  "enclosing_start_byte": self.enclosing_start_byte,
591
591
  "enclosing_end_byte": self.enclosing_end_byte,
592
592
  "is_documentation": self.is_documentation,
593
- # Persist children_order in agent view so it survives the SQLite cache.
593
+ # Persist children_order in agent view so it survives the AST cache.
594
594
  # Raw content is intentionally excluded from agent view to keep the
595
595
  # cache lightweight — it is dynamically regenerated on cache hits.
596
596
  "children_order": list(self.children_order),
@@ -153,8 +153,11 @@ def format_concise(
153
153
  prefix += " → " + ", ".join(parts)
154
154
  incoming = rels_by_target.get(eid, [])
155
155
  if incoming:
156
- sources = [name_by_id.get(r.get("source_id", ""), r.get("source_id", "")) for r in incoming]
157
- prefix += f" ← called by: {', '.join(sources)}"
156
+ in_parts = [
157
+ f"{_rel_arrow(r.get('relation_type', ''))}: {name_by_id.get(r.get('source_id', ''), r.get('source_id', ''))}"
158
+ for r in incoming
159
+ ]
160
+ prefix += " ← " + ", ".join(in_parts)
158
161
  lines.append(prefix)
159
162
  lines.append("")
160
163
 
@@ -7,6 +7,9 @@ The MCP server reads this registry to create one BathoBundleReader per repo.
7
7
  from __future__ import annotations
8
8
 
9
9
  import json
10
+ import os
11
+ import tempfile
12
+ import threading
10
13
  from dataclasses import dataclass, asdict
11
14
  from pathlib import Path
12
15
 
@@ -34,6 +37,7 @@ class RepoRegistry:
34
37
 
35
38
  def __init__(self, config_path: Path | None = None) -> None:
36
39
  self.config_path = Path(config_path) if config_path else DEFAULT_CONFIG_PATH
40
+ self._lock = threading.Lock()
37
41
 
38
42
  def load(self) -> list[RepoEntry]:
39
43
  """Load entries from the JSON config file. Returns empty list if file doesn't exist."""
@@ -48,32 +52,44 @@ class RepoRegistry:
48
52
  return [RepoEntry(name=r["name"], path=r["path"]) for r in repos if "name" in r and "path" in r]
49
53
 
50
54
  def save(self, entries: list[RepoEntry]) -> None:
51
- """Write entries to the JSON config file. Creates parent dirs if needed."""
55
+ """Write entries to the JSON config file atomically. Creates parent dirs if needed."""
52
56
  self.config_path.parent.mkdir(parents=True, exist_ok=True)
53
57
  data = {"repos": [asdict(e) for e in entries]}
54
- self.config_path.write_text(json.dumps(data, indent=2), encoding="utf-8")
58
+ fd, tmp_path = tempfile.mkstemp(dir=self.config_path.parent, prefix="mcp-repos.", suffix=".tmp")
59
+ try:
60
+ with os.fdopen(fd, "w", encoding="utf-8") as f:
61
+ f.write(json.dumps(data, indent=2))
62
+ os.replace(tmp_path, str(self.config_path))
63
+ except Exception:
64
+ try:
65
+ os.unlink(tmp_path)
66
+ except OSError:
67
+ pass
68
+ raise
55
69
 
56
70
  def add(self, name: str, path: str) -> RepoEntry:
57
71
  """Add or update a repo entry. Returns the entry."""
58
- entries = self.load()
59
- resolved = str(Path(path).resolve())
60
- entry = RepoEntry(name=name, path=resolved)
61
- # Upsert: replace if name exists, otherwise append
62
- entries = [e for e in entries if e.name != name]
63
- entries.append(entry)
64
- self.save(entries)
65
- LOGGER.info("registry_add", name=name, path=resolved)
66
- return entry
72
+ with self._lock:
73
+ entries = self.load()
74
+ resolved = str(Path(path).resolve())
75
+ entry = RepoEntry(name=name, path=resolved)
76
+ # Upsert: replace if name exists, otherwise append
77
+ entries = [e for e in entries if e.name != name]
78
+ entries.append(entry)
79
+ self.save(entries)
80
+ LOGGER.info("registry_add", name=name, path=resolved)
81
+ return entry
67
82
 
68
83
  def remove(self, name: str) -> bool:
69
84
  """Remove a repo entry by name. Returns True if removed, False if not found."""
70
- entries = self.load()
71
- filtered = [e for e in entries if e.name != name]
72
- if len(filtered) == len(entries):
73
- return False
74
- self.save(filtered)
75
- LOGGER.info("registry_remove", name=name)
76
- return True
85
+ with self._lock:
86
+ entries = self.load()
87
+ filtered = [e for e in entries if e.name != name]
88
+ if len(filtered) == len(entries):
89
+ return False
90
+ self.save(filtered)
91
+ LOGGER.info("registry_remove", name=name)
92
+ return True
77
93
 
78
94
  def get(self, name: str) -> RepoEntry | None:
79
95
  """Look up a repo entry by name."""
@@ -10,6 +10,7 @@ import json
10
10
 
11
11
  from fastmcp import FastMCP
12
12
 
13
+ from batho.core.schemas import EntityType, RelationshipType
13
14
  from batho.mcp.registry import RepoRegistry
14
15
 
15
16
 
@@ -29,16 +30,8 @@ def register_resources(
29
30
  - Response formats: summary, concise, detailed
30
31
  """
31
32
  schema_data = {
32
- "entity_types": [
33
- "FUNCTION", "CLASS", "METHOD", "MODULE",
34
- "VARIABLE", "INTERFACE", "ENUM", "STRUCT",
35
- "TYPE_ALIAS", "CONSTANT", "PROPERTY", "CONSTRUCTOR",
36
- ],
37
- "relation_types": [
38
- "CALLS", "IMPORTS", "USES", "REFERENCES",
39
- "DEFINES", "INHERITS", "IMPLEMENTS", "OVERRIDES",
40
- "CONTAINS", "DECLARES",
41
- ],
33
+ "entity_types": [e.name for e in EntityType],
34
+ "relation_types": [r.name for r in RelationshipType],
42
35
  "response_formats": {
43
36
  "summary": "~200-500 tokens, high-level overview only",
44
37
  "concise": "~50 tokens per entity, minimal detail",
@@ -11,6 +11,7 @@ from pathlib import Path
11
11
 
12
12
  from fastmcp import FastMCP
13
13
 
14
+ from batho import __version__ as BATHO_MCP_VERSION
14
15
  from batho.mcp.instructions import INSTRUCTIONS
15
16
  from batho.mcp.tools import register_tools
16
17
  from batho.mcp.prompts import register_prompts
@@ -21,8 +22,6 @@ import structlog
21
22
 
22
23
  LOGGER = structlog.get_logger(__name__)
23
24
 
24
- BATHO_MCP_VERSION = "1.2.0"
25
-
26
25
 
27
26
  def create_app(
28
27
  root: str | None = None,