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.
- batho-1.3.0/.zenodo.json +32 -0
- batho-1.3.0/CITATION.cff +34 -0
- {batho-1.2.0 → batho-1.3.0}/PKG-INFO +21 -1
- {batho-1.2.0 → batho-1.3.0}/README.md +20 -0
- {batho-1.2.0 → batho-1.3.0}/batho/__init__.py +5 -1
- {batho-1.2.0 → batho-1.3.0}/batho/cli/build.py +12 -0
- {batho-1.2.0 → batho-1.3.0}/batho/cli/patch.py +13 -1
- {batho-1.2.0 → batho-1.3.0}/batho/core/config/loader.py +98 -7
- {batho-1.2.0 → batho-1.3.0}/batho/core/config/models.py +33 -0
- {batho-1.2.0 → batho-1.3.0}/batho/core/schemas.py +1 -1
- {batho-1.2.0 → batho-1.3.0}/batho/mcp/graph_builder.py +5 -2
- {batho-1.2.0 → batho-1.3.0}/batho/mcp/registry.py +34 -18
- {batho-1.2.0 → batho-1.3.0}/batho/mcp/resources.py +3 -10
- {batho-1.2.0 → batho-1.3.0}/batho/mcp/server.py +1 -2
- {batho-1.2.0 → batho-1.3.0}/batho/mcp/tools.py +28 -8
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/bsg_map/__init__.py +3 -17
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/rules.py +41 -30
- {batho-1.2.0 → batho-1.3.0}/batho/modules/dependency/indexer.py +0 -1
- {batho-1.2.0 → batho-1.3.0}/batho/modules/dependency/introspector.py +4 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/dependency/resolution_cache.py +24 -5
- {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/pipeline.py +26 -7
- {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/scope_manager.py +12 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/graph/__init__.py +10 -0
- batho-1.3.0/batho/modules/graph/builder/arrow_graph.py +1208 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/graph/builder/codegraph.py +239 -66
- batho-1.3.0/batho/modules/graph/builder/factory.py +90 -0
- batho-1.3.0/batho/modules/graph/builder/protocol.py +87 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/graph/community.py +96 -6
- {batho-1.2.0 → batho-1.3.0}/batho/modules/graph/incremental.py +1 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/integrity/checkers/bundle_checker.py +1 -1
- {batho-1.2.0 → batho-1.3.0}/batho/modules/integrity/engine.py +1 -1
- {batho-1.2.0 → batho-1.3.0}/batho/modules/storage/arrow_bundle/__init__.py +1 -1
- {batho-1.2.0 → batho-1.3.0}/batho/modules/storage/arrow_bundle/schemas.py +1 -1
- {batho-1.2.0 → batho-1.3.0}/batho/modules/storage/arrow_store/__init__.py +1 -1
- {batho-1.2.0 → batho-1.3.0}/batho/modules/storage/arrow_store/metrics.py +1 -1
- {batho-1.2.0 → batho-1.3.0}/batho/modules/storage/arrow_store/store.py +4 -4
- {batho-1.2.0 → batho-1.3.0}/batho/orchestrator/build.py +119 -30
- {batho-1.2.0 → batho-1.3.0}/batho/orchestrator/patch.py +62 -3
- {batho-1.2.0 → batho-1.3.0}/batho/utils/memory_monitor.py +44 -3
- {batho-1.2.0 → batho-1.3.0}/batho_cli.py +8 -1
- batho-1.3.0/codemeta.json +31 -0
- {batho-1.2.0 → batho-1.3.0}/pyproject.toml +1 -1
- {batho-1.2.0 → batho-1.3.0}/.gitignore +0 -0
- {batho-1.2.0 → batho-1.3.0}/LICENSE +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/cli/_utils.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/cli/diff.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/cli/export.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/cli/fix.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/cli/gc.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/cli/load.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/cli/mcp.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/core/__init__.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/core/batho_data/popular-packages.yaml +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/core/config/__init__.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/core/config/default-ignore-patterns.yaml +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/mcp/__init__.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/mcp/__main__.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/mcp/community_summaries.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/mcp/delta_reader.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/mcp/errors.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/mcp/instructions.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/mcp/prompts.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/__init__.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/bsg.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/bsg_map/constants.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/bsg_map/relativizer.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/bsg_map/render_agent.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/bsg_map/render_storage.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/core_engine/__init__.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_bidirectional_foundation.yaml +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_detection_cicd.yaml +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_detection_cloud_providers.yaml +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_detection_cpp.yaml +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_detection_csharp.yaml +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_detection_dart.yaml +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_detection_elixir.yaml +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_detection_foundation.yaml +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_detection_kotlin.yaml +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_detection_php.yaml +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_detection_ruby.yaml +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_detection_scala.yaml +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_detection_swift.yaml +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_detection_test_frameworks.yaml +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_file_categorization.yaml +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_framework_angular.yaml +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_framework_django.yaml +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_framework_flask.yaml +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_framework_nodejs.yaml +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_framework_other.yaml +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_framework_python.yaml +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_framework_react.yaml +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_framework_vue.yaml +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_graph_foundation.yaml +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/bsg_token_optimization.yaml +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/test_bidirectional_gap_coverage.yaml +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/test_bidirectional_integrity.yaml +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/foundation/test_bidirectional_reconstruction.yaml +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/interceptors/bsg_api_contract_guardian.yaml +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/interceptors/bsg_auth_boundary_shield.yaml +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/interceptors/bsg_dependency_blast_radius.yaml +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/interceptors/bsg_hardcoded_secret_catcher.yaml +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/interceptors/bsg_iac_drift_sentinel.yaml +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/interceptors/bsg_nplus1_query_catcher.yaml +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/interceptors/bsg_reconstruction_interceptors.yaml +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/interceptors/bsg_resource_leak_preventer.yaml +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/interceptors/bsg_schema_migration_enforcer.yaml +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/plugins/interceptors/bsg_silent_failure_catcher.yaml +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/schemas/bsg-plugin-schema-v1.json +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/compression/schemas/bsg-plugin-schema-v2.json +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/dependency/__init__.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/dependency/manifest_parser.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/dependency/popular_packages.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/dependency/stdlib_tables.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/__init__.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/ast_cache.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/extraction_result.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/extractor.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/fallback_parser.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/submodules/languages/css.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/submodules/languages/hcl.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/submodules/languages/html.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/submodules/languages/json.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/submodules/languages/markdown.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/submodules/languages/toml.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/submodules/languages/yaml.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/submodules/parser_factory/__init__.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/submodules/parser_factory/_common.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/submodules/parser_factory/_queries.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/submodules/parser_factory/detector.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/submodules/parser_factory/factory.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/submodules/parser_factory/registry.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/extraction/symbol_table.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/graph/diff_engine/node_diff.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/graph/reconstructor/reconstructor.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/integrity/__init__.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/integrity/checkers/__init__.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/integrity/checkers/blob_checker.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/integrity/checkers/graph_checker.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/integrity/checkers/state_checker.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/integrity/checks/__init__.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/integrity/cli.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/integrity/models.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/integrity/repairers/__init__.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/integrity/repairers/blob_repairer.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/integrity/repairers/graph_repairer.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/integrity/repairers/state_repairer.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/integrity/report.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/storage/__init__.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/storage/arrow_bundle/bundle.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/storage/arrow_bundle/helpers.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/storage/arrow_bundle/incremental.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/storage/arrow_bundle/manager.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/storage/arrow_bundle/reader.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/storage/arrow_bundle/writer.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/storage/arrow_store/compaction.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/storage/arrow_store/schemas.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/modules/storage/cache/unified_cache.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/orchestrator/__init__.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/orchestrator/export.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/orchestrator/gc.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/orchestrator/load.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/utils/__init__.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/utils/cli_output.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/utils/encoding.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/utils/file_io.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/utils/hash.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/utils/ignore.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/utils/logging.py +0 -0
- {batho-1.2.0 → batho-1.3.0}/batho/utils/path_sanitizer.py +0 -0
batho-1.3.0/.zenodo.json
ADDED
|
@@ -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
|
+
}
|
batho-1.3.0/CITATION.cff
ADDED
|
@@ -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.
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
157
|
-
|
|
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.
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
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,
|