java-codebase-rag 0.6.2__tar.gz → 0.6.4__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.
- {java_codebase_rag-0.6.2/java_codebase_rag.egg-info → java_codebase_rag-0.6.4}/PKG-INFO +13 -10
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/README.md +11 -9
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/ast_java.py +28 -57
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/build_ast_graph.py +118 -26
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/graph_enrich.py +13 -35
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/java_codebase_rag/cli.py +97 -24
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/java_codebase_rag/cli_format.py +8 -35
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/java_codebase_rag/cli_progress.py +17 -21
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/java_codebase_rag/install_data/agents/explorer-rag-enhanced.md +3 -3
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/java_codebase_rag/install_data/skills/explore-codebase/SKILL.md +5 -5
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/java_codebase_rag/installer.py +180 -43
- java_codebase_rag-0.6.4/java_codebase_rag/lance_optimize.py +222 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/java_codebase_rag/pipeline.py +71 -23
- java_codebase_rag-0.6.4/java_codebase_rag/progress.py +570 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4/java_codebase_rag.egg-info}/PKG-INFO +13 -10
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/java_codebase_rag.egg-info/SOURCES.txt +5 -1
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/java_codebase_rag.egg-info/requires.txt +1 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/java_index_flow_lancedb.py +238 -18
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/ladybug_queries.py +45 -51
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/mcp_v2.py +9 -17
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/path_filtering.py +14 -17
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/pr_analysis.py +3 -4
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/pyproject.toml +2 -1
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/search_lancedb.py +2 -2
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/server.py +75 -9
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_ast_graph_build.py +136 -0
- java_codebase_rag-0.6.4/tests/test_ast_java_thread_safety.py +66 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_cli_progress_stdout_invariant.py +3 -3
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_installer.py +293 -1
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_java_codebase_rag_cli.py +226 -2
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_microservice_scope.py +118 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_path_filtering.py +14 -14
- java_codebase_rag-0.6.4/tests/test_progress.py +463 -0
- java_codebase_rag-0.6.4/tests/test_vectors_progress.py +349 -0
- java_codebase_rag-0.6.2/java_codebase_rag/lance_optimize.py +0 -148
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/LICENSE +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/brownfield_events.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/chunk_heuristics.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/index_common.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/java_codebase_rag/__init__.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/java_codebase_rag/_fdlimit.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/java_codebase_rag/config.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/java_codebase_rag.egg-info/dependency_links.txt +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/java_codebase_rag.egg-info/entry_points.txt +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/java_codebase_rag.egg-info/top_level.txt +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/java_index_v1_common.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/java_ontology.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/mcp_hints.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/setup.cfg +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_agent_skills_static.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_assign_endpoint_client_extraction.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_ast_java_calls.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_ast_java_capabilities.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_bank_chat_brownfield_integration.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_brownfield_clients.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_brownfield_events.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_brownfield_overrides.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_brownfield_routes.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_call_edge_matching.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_call_edges_e2e.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_call_graph_receiver_resolution.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_call_graph_smoke_roundtrip.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_call_invariant.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_cli_quiet_parity.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_client_hint_recovery.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_client_node_extraction.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_client_role_rename.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_config.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_cross_service_resolution_flag.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_edge_navigation_doc.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_fd_limit.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_feign_not_exposer.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_graph_enrich.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_incremental_graph.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_installer_integration.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_ladybug_queries.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_lance_optimize.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_lancedb_e2e.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_mcp_hints.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_mcp_server_project_root.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_mcp_tools.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_mcp_v2.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_mcp_v2_compose.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_meta_chain_core.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_outgoing_call_extraction.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_packaging_metadata.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_pr_analysis.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_resolve_routes_messaging_layer_c.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_route_extraction.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_schema_consistency.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_search_lancedb.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_search_lancedb_capability.py +0 -0
- {java_codebase_rag-0.6.2 → java_codebase_rag-0.6.4}/tests/test_string_value_atoms.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: java-codebase-rag
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.4
|
|
4
4
|
Summary: MCP server for semantic + structural search over Java codebases
|
|
5
5
|
Author: HumanBean17
|
|
6
6
|
License-Expression: MIT
|
|
@@ -28,6 +28,7 @@ Requires-Dist: pyarrow<24,>=23.0.1
|
|
|
28
28
|
Requires-Dist: pydantic<3,>=2.0
|
|
29
29
|
Requires-Dist: PyYAML<7,>=6.0.3
|
|
30
30
|
Requires-Dist: questionary<3,>=2.0
|
|
31
|
+
Requires-Dist: rich<15,>=14
|
|
31
32
|
Requires-Dist: sentence-transformers<6,>=5.4.0
|
|
32
33
|
Requires-Dist: tree-sitter<0.26,>=0.25.2
|
|
33
34
|
Requires-Dist: tree-sitter-java<0.24,>=0.23.5
|
|
@@ -43,7 +44,7 @@ Dynamic: license-file
|
|
|
43
44
|
|
|
44
45
|
A graph-native code intelligence layer for Java microservice estates, exposed to LLM agents via the **Model Context Protocol (MCP)**.
|
|
45
46
|
|
|
46
|
-
The system extracts a deterministic property graph from Java source (tree-sitter), stores it in **
|
|
47
|
+
The system extracts a deterministic property graph from Java source (tree-sitter), stores it in **LadybugDB** (graph) alongside a **LanceDB** vector index (chunks), and exposes a deliberately small MCP surface — **five tools**: `search`, `find`, `describe`, `neighbors`, `resolve` — that collapse onto three primitive agent operations: **locate**, **inspect**, **walk**.
|
|
47
48
|
|
|
48
49
|
> **What this MCP is:** a **GPS for code navigation**, not a reasoning engine.
|
|
49
50
|
> Agents use a simple loop:
|
|
@@ -62,9 +63,9 @@ For the design rationale, the GPS metaphor, and the full ontology, see [`docs/pa
|
|
|
62
63
|
|
|
63
64
|
Generic code-search tools (grep, ctags, vector-only RAG) hit a ceiling on real Java microservice estates: they find files but lose the structure that makes a Spring/JAX-RS system navigable. This project is built around five choices that target that gap.
|
|
64
65
|
|
|
65
|
-
- **Hybrid RAG + GraphRAG, not either-or.** Semantic recall (LanceDB chunk vectors) and structural navigation (
|
|
66
|
+
- **Hybrid RAG + GraphRAG, not either-or.** Semantic recall (LanceDB chunk vectors) and structural navigation (LadybugDB property graph) are composed in one surface. `search` finds candidate nodes by meaning; `neighbors` walks the exact edge you care about (`CALLS`, `IMPLEMENTS`, `INJECTS`, `EXPOSES`, …). The agent picks the right primitive per step instead of being forced into pure-vector or pure-symbol search.
|
|
66
67
|
|
|
67
|
-
- **A Java-tuned role model.** Symbols are labelled with stereotypes inferred from Spring and JAX-RS conventions — `CONTROLLER`, `SERVICE`, `REPOSITORY`, `
|
|
68
|
+
- **A Java-tuned role model.** Symbols are labelled with stereotypes inferred from Spring and JAX-RS conventions — `CONTROLLER`, `SERVICE`, `REPOSITORY`, `COMPONENT`, `CONFIG`, `ENTITY`, `CLIENT`, `MAPPER`, `DTO`. Agents can ask "list controllers" or "who injects this repository" directly, instead of grep-ing for `@RestController` and hoping for the best. Roles drive both filtering (`find` with a `NodeFilter`) and ranking.
|
|
68
69
|
|
|
69
70
|
- **Ranking specialized for Java codebases.** The composite ranker is aware of role, microservice, and FQN structure — not a generic BM25. A search for `"chat ingress"` surfaces controllers before utility classes; a search scoped to one microservice doesn't drown in matches from the other 19. Defaults are tuned on the bank-chat fixture and exposed in `docs/CONFIGURATION.md` for per-repo overrides.
|
|
70
71
|
|
|
@@ -106,11 +107,13 @@ java-codebase-rag install --non-interactive --agent claude-code
|
|
|
106
107
|
|
|
107
108
|
After `pip install --upgrade java-codebase-rag`, run `java-codebase-rag update` to refresh shipped artifacts and catch up the index (Lance + graph).
|
|
108
109
|
|
|
110
|
+
All indexing lifecycle commands (`init`, `increment`, `reprocess`, `install`, `update`) show a unified `Vectors → Optimize → Graph` progress bar on stderr during the index build (powered by `rich`); pass `--quiet` to suppress it.
|
|
111
|
+
|
|
109
112
|
### Manual registration
|
|
110
113
|
|
|
111
114
|
If you prefer manual configuration, see [`docs/JAVA-CODEBASE-RAG-CLI.md`](./docs/JAVA-CODEBASE-RAG-CLI.md) for the full CLI reference.
|
|
112
115
|
|
|
113
|
-
> **Stability disclaimer.** This package does **not** promise backward compatibility. MCP tool contracts, env vars, Lance/
|
|
116
|
+
> **Stability disclaimer.** This package does **not** promise backward compatibility. MCP tool contracts, env vars, Lance/LadybugDB schemas, config files, and Python APIs may change without a deprecation period. Track `main` and rebuild indexes when ontology or embedding settings change.
|
|
114
117
|
|
|
115
118
|
---
|
|
116
119
|
|
|
@@ -123,7 +126,7 @@ This repo ships a small multi-module Spring fixture under [`tests/bank-chat-syst
|
|
|
123
126
|
git clone https://github.com/HumanBean17/java-codebase-rag
|
|
124
127
|
cd java-codebase-rag
|
|
125
128
|
|
|
126
|
-
# 2. Build the index (Lance vectors +
|
|
129
|
+
# 2. Build the index (Lance vectors + LadybugDB graph). First run downloads the
|
|
127
130
|
# embedding model (~90 MB) and takes ~30-60s on the fixture.
|
|
128
131
|
java-codebase-rag init --source-root tests/bank-chat-system --index-dir /tmp/bank-chat-index
|
|
129
132
|
|
|
@@ -138,7 +141,7 @@ Smoke-test the index with two checks (`search_lancedb` ships with the package):
|
|
|
138
141
|
JAVA_CODEBASE_RAG_INDEX_DIR=/tmp/bank-chat-index \
|
|
139
142
|
python -m search_lancedb "chat ingress controller" --table java --limit 3
|
|
140
143
|
|
|
141
|
-
# Vector + graph expansion — proves
|
|
144
|
+
# Vector + graph expansion — proves LadybugDB is wired in
|
|
142
145
|
JAVA_CODEBASE_RAG_INDEX_DIR=/tmp/bank-chat-index \
|
|
143
146
|
python -m search_lancedb "chat ingress controller" --table java --limit 3 \
|
|
144
147
|
--graph-expand --expand-depth 2
|
|
@@ -238,8 +241,8 @@ Run `java-codebase-rag --help` to list grouped subcommands. Operator playbook wi
|
|
|
238
241
|
| Setup | `install` | Interactive setup wizard: config, MCP registration, skill/agent deployment, indexing. |
|
|
239
242
|
| Setup | `update` | Refresh shipped artifacts (skill, agent, MCP entry) + incremental Lance/graph catch-up after pip upgrade. |
|
|
240
243
|
| Lifecycle | `init` | First-time index. Refuses if artifacts already exist. |
|
|
241
|
-
| Lifecycle | `increment` | CocoIndex catch-up + incremental
|
|
242
|
-
| Lifecycle | `reprocess` | Full Lance +
|
|
244
|
+
| Lifecycle | `increment` | CocoIndex catch-up + incremental LadybugDB update. `--vectors-only` for Lance only. |
|
|
245
|
+
| Lifecycle | `reprocess` | Full Lance + LadybugDB rebuild. `--vectors-only` / `--graph-only` for a single phase. |
|
|
243
246
|
| Lifecycle | `erase` | Delete index artifacts. Requires `--yes` or TTY confirm. |
|
|
244
247
|
| Introspection | `meta`, `tables`, `diagnose-ignore`, `unresolved-calls` | Health, table listing, ignore-layer diagnostics, receiver-failure call sites. |
|
|
245
248
|
| Analysis | `analyze-pr` | Blast-radius / risk from a unified diff. |
|
|
@@ -274,7 +277,7 @@ python3 -m venv .venv
|
|
|
274
277
|
|
|
275
278
|
The `cocoindex` package powers lifecycle commands that run the indexer (`init`, `increment`, `reprocess`, `erase`). Search and MCP navigation do not invoke it directly.
|
|
276
279
|
|
|
277
|
-
The default embedding model is `sentence-transformers/all-MiniLM-L6-v2` (downloaded on first `init`). Override via the `
|
|
280
|
+
The default embedding model is `sentence-transformers/all-MiniLM-L6-v2` (downloaded on first `init`). Override via the `SBERT_MODEL` env var — see [`docs/CONFIGURATION.md` §1](./docs/CONFIGURATION.md#1-environment-variables).
|
|
278
281
|
|
|
279
282
|
---
|
|
280
283
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A graph-native code intelligence layer for Java microservice estates, exposed to LLM agents via the **Model Context Protocol (MCP)**.
|
|
4
4
|
|
|
5
|
-
The system extracts a deterministic property graph from Java source (tree-sitter), stores it in **
|
|
5
|
+
The system extracts a deterministic property graph from Java source (tree-sitter), stores it in **LadybugDB** (graph) alongside a **LanceDB** vector index (chunks), and exposes a deliberately small MCP surface — **five tools**: `search`, `find`, `describe`, `neighbors`, `resolve` — that collapse onto three primitive agent operations: **locate**, **inspect**, **walk**.
|
|
6
6
|
|
|
7
7
|
> **What this MCP is:** a **GPS for code navigation**, not a reasoning engine.
|
|
8
8
|
> Agents use a simple loop:
|
|
@@ -21,9 +21,9 @@ For the design rationale, the GPS metaphor, and the full ontology, see [`docs/pa
|
|
|
21
21
|
|
|
22
22
|
Generic code-search tools (grep, ctags, vector-only RAG) hit a ceiling on real Java microservice estates: they find files but lose the structure that makes a Spring/JAX-RS system navigable. This project is built around five choices that target that gap.
|
|
23
23
|
|
|
24
|
-
- **Hybrid RAG + GraphRAG, not either-or.** Semantic recall (LanceDB chunk vectors) and structural navigation (
|
|
24
|
+
- **Hybrid RAG + GraphRAG, not either-or.** Semantic recall (LanceDB chunk vectors) and structural navigation (LadybugDB property graph) are composed in one surface. `search` finds candidate nodes by meaning; `neighbors` walks the exact edge you care about (`CALLS`, `IMPLEMENTS`, `INJECTS`, `EXPOSES`, …). The agent picks the right primitive per step instead of being forced into pure-vector or pure-symbol search.
|
|
25
25
|
|
|
26
|
-
- **A Java-tuned role model.** Symbols are labelled with stereotypes inferred from Spring and JAX-RS conventions — `CONTROLLER`, `SERVICE`, `REPOSITORY`, `
|
|
26
|
+
- **A Java-tuned role model.** Symbols are labelled with stereotypes inferred from Spring and JAX-RS conventions — `CONTROLLER`, `SERVICE`, `REPOSITORY`, `COMPONENT`, `CONFIG`, `ENTITY`, `CLIENT`, `MAPPER`, `DTO`. Agents can ask "list controllers" or "who injects this repository" directly, instead of grep-ing for `@RestController` and hoping for the best. Roles drive both filtering (`find` with a `NodeFilter`) and ranking.
|
|
27
27
|
|
|
28
28
|
- **Ranking specialized for Java codebases.** The composite ranker is aware of role, microservice, and FQN structure — not a generic BM25. A search for `"chat ingress"` surfaces controllers before utility classes; a search scoped to one microservice doesn't drown in matches from the other 19. Defaults are tuned on the bank-chat fixture and exposed in `docs/CONFIGURATION.md` for per-repo overrides.
|
|
29
29
|
|
|
@@ -65,11 +65,13 @@ java-codebase-rag install --non-interactive --agent claude-code
|
|
|
65
65
|
|
|
66
66
|
After `pip install --upgrade java-codebase-rag`, run `java-codebase-rag update` to refresh shipped artifacts and catch up the index (Lance + graph).
|
|
67
67
|
|
|
68
|
+
All indexing lifecycle commands (`init`, `increment`, `reprocess`, `install`, `update`) show a unified `Vectors → Optimize → Graph` progress bar on stderr during the index build (powered by `rich`); pass `--quiet` to suppress it.
|
|
69
|
+
|
|
68
70
|
### Manual registration
|
|
69
71
|
|
|
70
72
|
If you prefer manual configuration, see [`docs/JAVA-CODEBASE-RAG-CLI.md`](./docs/JAVA-CODEBASE-RAG-CLI.md) for the full CLI reference.
|
|
71
73
|
|
|
72
|
-
> **Stability disclaimer.** This package does **not** promise backward compatibility. MCP tool contracts, env vars, Lance/
|
|
74
|
+
> **Stability disclaimer.** This package does **not** promise backward compatibility. MCP tool contracts, env vars, Lance/LadybugDB schemas, config files, and Python APIs may change without a deprecation period. Track `main` and rebuild indexes when ontology or embedding settings change.
|
|
73
75
|
|
|
74
76
|
---
|
|
75
77
|
|
|
@@ -82,7 +84,7 @@ This repo ships a small multi-module Spring fixture under [`tests/bank-chat-syst
|
|
|
82
84
|
git clone https://github.com/HumanBean17/java-codebase-rag
|
|
83
85
|
cd java-codebase-rag
|
|
84
86
|
|
|
85
|
-
# 2. Build the index (Lance vectors +
|
|
87
|
+
# 2. Build the index (Lance vectors + LadybugDB graph). First run downloads the
|
|
86
88
|
# embedding model (~90 MB) and takes ~30-60s on the fixture.
|
|
87
89
|
java-codebase-rag init --source-root tests/bank-chat-system --index-dir /tmp/bank-chat-index
|
|
88
90
|
|
|
@@ -97,7 +99,7 @@ Smoke-test the index with two checks (`search_lancedb` ships with the package):
|
|
|
97
99
|
JAVA_CODEBASE_RAG_INDEX_DIR=/tmp/bank-chat-index \
|
|
98
100
|
python -m search_lancedb "chat ingress controller" --table java --limit 3
|
|
99
101
|
|
|
100
|
-
# Vector + graph expansion — proves
|
|
102
|
+
# Vector + graph expansion — proves LadybugDB is wired in
|
|
101
103
|
JAVA_CODEBASE_RAG_INDEX_DIR=/tmp/bank-chat-index \
|
|
102
104
|
python -m search_lancedb "chat ingress controller" --table java --limit 3 \
|
|
103
105
|
--graph-expand --expand-depth 2
|
|
@@ -197,8 +199,8 @@ Run `java-codebase-rag --help` to list grouped subcommands. Operator playbook wi
|
|
|
197
199
|
| Setup | `install` | Interactive setup wizard: config, MCP registration, skill/agent deployment, indexing. |
|
|
198
200
|
| Setup | `update` | Refresh shipped artifacts (skill, agent, MCP entry) + incremental Lance/graph catch-up after pip upgrade. |
|
|
199
201
|
| Lifecycle | `init` | First-time index. Refuses if artifacts already exist. |
|
|
200
|
-
| Lifecycle | `increment` | CocoIndex catch-up + incremental
|
|
201
|
-
| Lifecycle | `reprocess` | Full Lance +
|
|
202
|
+
| Lifecycle | `increment` | CocoIndex catch-up + incremental LadybugDB update. `--vectors-only` for Lance only. |
|
|
203
|
+
| Lifecycle | `reprocess` | Full Lance + LadybugDB rebuild. `--vectors-only` / `--graph-only` for a single phase. |
|
|
202
204
|
| Lifecycle | `erase` | Delete index artifacts. Requires `--yes` or TTY confirm. |
|
|
203
205
|
| Introspection | `meta`, `tables`, `diagnose-ignore`, `unresolved-calls` | Health, table listing, ignore-layer diagnostics, receiver-failure call sites. |
|
|
204
206
|
| Analysis | `analyze-pr` | Blast-radius / risk from a unified diff. |
|
|
@@ -233,7 +235,7 @@ python3 -m venv .venv
|
|
|
233
235
|
|
|
234
236
|
The `cocoindex` package powers lifecycle commands that run the indexer (`init`, `increment`, `reprocess`, `erase`). Search and MCP navigation do not invoke it directly.
|
|
235
237
|
|
|
236
|
-
The default embedding model is `sentence-transformers/all-MiniLM-L6-v2` (downloaded on first `init`). Override via the `
|
|
238
|
+
The default embedding model is `sentence-transformers/all-MiniLM-L6-v2` (downloaded on first `init`). Override via the `SBERT_MODEL` env var — see [`docs/CONFIGURATION.md` §1](./docs/CONFIGURATION.md#1-environment-variables).
|
|
237
239
|
|
|
238
240
|
---
|
|
239
241
|
|
|
@@ -14,8 +14,8 @@ from __future__ import annotations
|
|
|
14
14
|
|
|
15
15
|
import posixpath
|
|
16
16
|
import sys
|
|
17
|
+
import threading
|
|
17
18
|
from dataclasses import dataclass, field
|
|
18
|
-
from functools import lru_cache
|
|
19
19
|
from typing import Iterable
|
|
20
20
|
|
|
21
21
|
import tree_sitter_java as _ts_java
|
|
@@ -215,10 +215,23 @@ _ANON_SUPER_AS_INTERFACE: frozenset[str] = frozenset({
|
|
|
215
215
|
})
|
|
216
216
|
|
|
217
217
|
|
|
218
|
-
|
|
218
|
+
# tree-sitter's ``Parser`` mutates internal state during ``parse()`` and is NOT
|
|
219
|
+
# thread-safe, so each OS thread gets its own instance. ``parse_java`` is called
|
|
220
|
+
# concurrently from worker threads when indexing runs with cocoindex's inflight
|
|
221
|
+
# parallelism — both directly (java_index_flow_lancedb.py: process_java_file
|
|
222
|
+
# offloads parse+enrich to asyncio.to_thread) and transitively
|
|
223
|
+
# (graph_enrich.collect_annotation_meta_chain -> _collect_annotation_decl_index
|
|
224
|
+
# -> parse_java, reached from enrich_chunk). The ``Language`` is immutable and
|
|
225
|
+
# shared; per-thread ``Parser`` construction is lazy and cheap (once per thread),
|
|
226
|
+
# which also preserves parse parallelism instead of serializing it.
|
|
227
|
+
_parser_tls = threading.local()
|
|
228
|
+
|
|
229
|
+
|
|
219
230
|
def _parser() -> Parser:
|
|
220
|
-
|
|
221
|
-
|
|
231
|
+
p = getattr(_parser_tls, "parser", None)
|
|
232
|
+
if p is None:
|
|
233
|
+
_parser_tls.parser = p = Parser(Language(_ts_java.language()))
|
|
234
|
+
return p
|
|
222
235
|
|
|
223
236
|
|
|
224
237
|
# ---------- dataclasses ----------
|
|
@@ -1565,62 +1578,20 @@ def _parse_codebase_http_route_inner_annotation(
|
|
|
1565
1578
|
return out
|
|
1566
1579
|
|
|
1567
1580
|
|
|
1568
|
-
def
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
def visit(n: Node) -> None:
|
|
1572
|
-
if n.type == "annotation":
|
|
1573
|
-
name_node = n.child_by_field_name("name")
|
|
1574
|
-
n_simple = _txt(name_node, src).rsplit(".", 1)[-1] if name_node is not None else ""
|
|
1575
|
-
if n_simple == "CodebaseHttpRoute":
|
|
1576
|
-
found.append(n)
|
|
1577
|
-
for c in n.children:
|
|
1578
|
-
visit(c)
|
|
1581
|
+
def _inner_annotation_nodes(container_ann: Node, src: bytes, target_simple: str) -> list[Node]:
|
|
1582
|
+
"""Collect nested ``@<target_simple>`` annotations anywhere under ``container_ann``.
|
|
1579
1583
|
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
def _codebase_async_route_inner_annotation_nodes(container_ann: Node, src: bytes) -> list[Node]:
|
|
1585
|
-
found: list[Node] = []
|
|
1586
|
-
|
|
1587
|
-
def visit(n: Node) -> None:
|
|
1588
|
-
if n.type == "annotation":
|
|
1589
|
-
name_node = n.child_by_field_name("name")
|
|
1590
|
-
n_simple = _txt(name_node, src).rsplit(".", 1)[-1] if name_node is not None else ""
|
|
1591
|
-
if n_simple == "CodebaseAsyncRoute":
|
|
1592
|
-
found.append(n)
|
|
1593
|
-
for c in n.children:
|
|
1594
|
-
visit(c)
|
|
1595
|
-
|
|
1596
|
-
visit(container_ann)
|
|
1597
|
-
return found
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
def _codebase_http_client_inner_annotation_nodes(container_ann: Node, src: bytes) -> list[Node]:
|
|
1601
|
-
found: list[Node] = []
|
|
1602
|
-
|
|
1603
|
-
def visit(n: Node) -> None:
|
|
1604
|
-
if n.type == "annotation":
|
|
1605
|
-
name_node = n.child_by_field_name("name")
|
|
1606
|
-
n_simple = _txt(name_node, src).rsplit(".", 1)[-1] if name_node is not None else ""
|
|
1607
|
-
if n_simple == "CodebaseHttpClient":
|
|
1608
|
-
found.append(n)
|
|
1609
|
-
for c in n.children:
|
|
1610
|
-
visit(c)
|
|
1611
|
-
|
|
1612
|
-
visit(container_ann)
|
|
1613
|
-
return found
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
def _codebase_producer_inner_annotation_nodes(container_ann: Node, src: bytes) -> list[Node]:
|
|
1584
|
+
Shared by the four brownfield container walkers — ``CodebaseHttpRoute``,
|
|
1585
|
+
``CodebaseAsyncRoute``, ``CodebaseHttpClient``, ``CodebaseProducer`` — which
|
|
1586
|
+
differ only by the target annotation simple name.
|
|
1587
|
+
"""
|
|
1617
1588
|
found: list[Node] = []
|
|
1618
1589
|
|
|
1619
1590
|
def visit(n: Node) -> None:
|
|
1620
1591
|
if n.type == "annotation":
|
|
1621
1592
|
name_node = n.child_by_field_name("name")
|
|
1622
1593
|
n_simple = _txt(name_node, src).rsplit(".", 1)[-1] if name_node is not None else ""
|
|
1623
|
-
if n_simple ==
|
|
1594
|
+
if n_simple == target_simple:
|
|
1624
1595
|
found.append(n)
|
|
1625
1596
|
for c in n.children:
|
|
1626
1597
|
visit(c)
|
|
@@ -1842,7 +1813,7 @@ def _outgoing_calls_from_codebase_http_client_producer_annotations(
|
|
|
1842
1813
|
),
|
|
1843
1814
|
)
|
|
1844
1815
|
elif simple == "CodebaseHttpClients":
|
|
1845
|
-
for inner in
|
|
1816
|
+
for inner in _inner_annotation_nodes(ann, src, "CodebaseHttpClient"):
|
|
1846
1817
|
out.append(
|
|
1847
1818
|
_parse_codebase_http_client_annotation(
|
|
1848
1819
|
inner,
|
|
@@ -1869,7 +1840,7 @@ def _outgoing_calls_from_codebase_http_client_producer_annotations(
|
|
|
1869
1840
|
),
|
|
1870
1841
|
)
|
|
1871
1842
|
elif simple == "CodebaseProducers":
|
|
1872
|
-
for inner in
|
|
1843
|
+
for inner in _inner_annotation_nodes(ann, src, "CodebaseProducer"):
|
|
1873
1844
|
out.append(
|
|
1874
1845
|
_parse_codebase_producer_annotation(
|
|
1875
1846
|
inner,
|
|
@@ -2343,7 +2314,7 @@ def _collect_routes(
|
|
|
2343
2314
|
),
|
|
2344
2315
|
)
|
|
2345
2316
|
elif simple == "CodebaseHttpRoutes":
|
|
2346
|
-
for inner in
|
|
2317
|
+
for inner in _inner_annotation_nodes(node, src, "CodebaseHttpRoute"):
|
|
2347
2318
|
routes.extend(
|
|
2348
2319
|
_parse_codebase_http_route_inner_annotation(
|
|
2349
2320
|
inner,
|
|
@@ -2359,7 +2330,7 @@ def _collect_routes(
|
|
|
2359
2330
|
elif simple in ("CodebaseAsyncRoute", "CodebaseAsyncRoutes"):
|
|
2360
2331
|
nodes = [node]
|
|
2361
2332
|
if simple == "CodebaseAsyncRoutes":
|
|
2362
|
-
nodes = list(
|
|
2333
|
+
nodes = list(_inner_annotation_nodes(node, src, "CodebaseAsyncRoute"))
|
|
2363
2334
|
for ann in nodes:
|
|
2364
2335
|
pairs, _ = _annotation_kv_nodes(ann, src)
|
|
2365
2336
|
topic_node = pairs.get("topic")
|
|
@@ -25,6 +25,7 @@ The LadybugDB DB is dropped and rebuilt on every run (Phase 1 is a full rebuild)
|
|
|
25
25
|
from __future__ import annotations
|
|
26
26
|
|
|
27
27
|
import argparse
|
|
28
|
+
import contextlib
|
|
28
29
|
import hashlib
|
|
29
30
|
import json
|
|
30
31
|
import logging
|
|
@@ -84,6 +85,53 @@ def _verbose_stderr_line(content: str) -> None:
|
|
|
84
85
|
print(content, file=sys.stderr, flush=True)
|
|
85
86
|
|
|
86
87
|
|
|
88
|
+
def _emit_graph_progress(parts: dict[str, object], *, verbose: bool) -> None:
|
|
89
|
+
"""Emit one ``JCIRAG_PROGRESS kind=graph …`` line to stderr (gated by verbose).
|
|
90
|
+
|
|
91
|
+
The parent process (``pipeline.run_build_ast_graph`` /
|
|
92
|
+
``run_incremental_graph``) passes ``--verbose`` in default AND verbose modes
|
|
93
|
+
(only suppressed for ``--quiet``), so this structured progress surfaces in
|
|
94
|
+
default mode (where the parent renders it) and verbose mode (raw relay). In
|
|
95
|
+
``--quiet`` the builder is never invoked with ``--verbose`` so nothing is
|
|
96
|
+
emitted. Field order is fixed so the parser and tests can pin substrings.
|
|
97
|
+
"""
|
|
98
|
+
if not verbose:
|
|
99
|
+
return
|
|
100
|
+
fields = ["kind=graph"]
|
|
101
|
+
for key in ("pass", "done", "total", "status", "elapsed_s"):
|
|
102
|
+
if key in parts:
|
|
103
|
+
fields.append(f"{key}={parts[key]}")
|
|
104
|
+
line = "JCIRAG_PROGRESS " + " ".join(fields)
|
|
105
|
+
_verbose_stderr_line(line)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# Pass-1 per-file tick cadence: bound stderr volume on huge trees without making
|
|
109
|
+
# the bar feel stale. A final tick on pass completion carries status=done.
|
|
110
|
+
_PASS1_TICK_EVERY = 25
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@contextlib.contextmanager
|
|
114
|
+
def _graph_pass_progress(pass_label: str, *, verbose: bool):
|
|
115
|
+
"""Emit ``pass=N/6 status=running`` on entry and ``status=done elapsed_s=…``
|
|
116
|
+
on exit for passes 2–6 (each advances the rendered bar by 1/6).
|
|
117
|
+
|
|
118
|
+
Usage: ``with _graph_pass_progress("2/6", verbose=verbose): …``
|
|
119
|
+
"""
|
|
120
|
+
if not verbose:
|
|
121
|
+
yield
|
|
122
|
+
return
|
|
123
|
+
_emit_graph_progress({"pass": pass_label, "status": "running"}, verbose=verbose)
|
|
124
|
+
t0 = time.time()
|
|
125
|
+
try:
|
|
126
|
+
yield
|
|
127
|
+
finally:
|
|
128
|
+
elapsed = time.time() - t0
|
|
129
|
+
_emit_graph_progress(
|
|
130
|
+
{"pass": pass_label, "status": "done", "elapsed_s": f"{elapsed:.2f}"},
|
|
131
|
+
verbose=verbose,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
87
135
|
class _VerbosePassHeartbeats:
|
|
88
136
|
"""Emit ``[tag] running … Ns elapsed`` every 5s on stderr while in scope (verbose only)."""
|
|
89
137
|
|
|
@@ -837,7 +885,14 @@ def _register_type(
|
|
|
837
885
|
return entry
|
|
838
886
|
|
|
839
887
|
|
|
840
|
-
def pass1_parse(
|
|
888
|
+
def pass1_parse(
|
|
889
|
+
root: Path,
|
|
890
|
+
tables: GraphTables,
|
|
891
|
+
*,
|
|
892
|
+
verbose: bool,
|
|
893
|
+
scope_files: set[str] | None = None,
|
|
894
|
+
removed_files: set[str] | None = None,
|
|
895
|
+
) -> dict[str, JavaFileAst]:
|
|
841
896
|
"""Walk files, parse them, populate node indexes. Returns path -> AST.
|
|
842
897
|
|
|
843
898
|
Args:
|
|
@@ -845,6 +900,11 @@ def pass1_parse(root: Path, tables: GraphTables, *, verbose: bool, scope_files:
|
|
|
845
900
|
tables: GraphTables to populate.
|
|
846
901
|
verbose: Whether to emit progress output.
|
|
847
902
|
scope_files: Optional set of relative POSIX paths to parse. If None, parse all files.
|
|
903
|
+
removed_files: Optional set of relative POSIX paths that no longer exist
|
|
904
|
+
on disk (incremental deletions). These are members of ``scope_files``
|
|
905
|
+
(they were deleted, so they participate in scoped deletion) but are
|
|
906
|
+
never visited by the parse walk, so they must be excluded from the
|
|
907
|
+
pass-1 total to keep ``done`` from undercounting then two-way-clamping.
|
|
848
908
|
"""
|
|
849
909
|
asts: dict[str, JavaFileAst] = {}
|
|
850
910
|
ignore = LayeredIgnore(root)
|
|
@@ -852,6 +912,23 @@ def pass1_parse(root: Path, tables: GraphTables, *, verbose: bool, scope_files:
|
|
|
852
912
|
n_files = 0
|
|
853
913
|
if verbose:
|
|
854
914
|
_verbose_stderr_line(_PASS1_START)
|
|
915
|
+
# Count-first: one filtered walk (no parsing) to set the EXACT total before
|
|
916
|
+
# the parse loop ticks. Single-layer ignore → the count is exact, so the
|
|
917
|
+
# rendered bar is determinate. For a scoped (incremental) parse the total is
|
|
918
|
+
# the number of files that will actually be visited: scope minus any removed
|
|
919
|
+
# files (which are members of scope for deletion but gone from disk, so the
|
|
920
|
+
# parse walk never ticks them); for a full rebuild it is the non-ignored
|
|
921
|
+
# .java count.
|
|
922
|
+
if verbose:
|
|
923
|
+
if scope_files is not None:
|
|
924
|
+
removed = removed_files if removed_files is not None else set()
|
|
925
|
+
pass1_total = len(scope_files - removed)
|
|
926
|
+
else:
|
|
927
|
+
pass1_total = sum(1 for _ in iter_java_source_files(root, ignore=ignore))
|
|
928
|
+
_emit_graph_progress(
|
|
929
|
+
{"pass": "1/6", "done": 0, "total": pass1_total, "status": "running"},
|
|
930
|
+
verbose=verbose,
|
|
931
|
+
)
|
|
855
932
|
slow_sec = 0.0
|
|
856
933
|
raw_slow = os.environ.get("JAVA_CODEBASE_RAG_TEST_GRAPH_SLOW_SEC", "").strip()
|
|
857
934
|
if raw_slow:
|
|
@@ -871,6 +948,11 @@ def pass1_parse(root: Path, tables: GraphTables, *, verbose: bool, scope_files:
|
|
|
871
948
|
if scope_files is not None and rel not in scope_files:
|
|
872
949
|
continue
|
|
873
950
|
n_files += 1
|
|
951
|
+
if verbose and (n_files % _PASS1_TICK_EVERY == 0):
|
|
952
|
+
_emit_graph_progress(
|
|
953
|
+
{"pass": "1/6", "done": n_files, "status": "running"},
|
|
954
|
+
verbose=verbose,
|
|
955
|
+
)
|
|
874
956
|
try:
|
|
875
957
|
content = p.read_bytes()
|
|
876
958
|
except OSError:
|
|
@@ -906,6 +988,10 @@ def pass1_parse(root: Path, tables: GraphTables, *, verbose: bool, scope_files:
|
|
|
906
988
|
|
|
907
989
|
if verbose:
|
|
908
990
|
elapsed = time.time() - t0
|
|
991
|
+
_emit_graph_progress(
|
|
992
|
+
{"pass": "1/6", "done": n_files, "status": "done", "elapsed_s": f"{elapsed:.2f}"},
|
|
993
|
+
verbose=verbose,
|
|
994
|
+
)
|
|
909
995
|
_verbose_stderr_line(
|
|
910
996
|
f"[graph] pass 1 · parsed {n_files} files in {elapsed:.2f}s: "
|
|
911
997
|
f"{len(tables.types)} types, {len(tables.members)} members, "
|
|
@@ -1145,7 +1231,7 @@ def pass2_edges(tables: GraphTables, asts: dict[str, JavaFileAst], *, verbose: b
|
|
|
1145
1231
|
seen_inj: set[tuple[str, str, str, str]] = set()
|
|
1146
1232
|
if verbose:
|
|
1147
1233
|
_verbose_stderr_line(_PASS2_START)
|
|
1148
|
-
with _VerbosePassHeartbeats("[graph] pass 2", verbose=verbose):
|
|
1234
|
+
with _graph_pass_progress("2/6", verbose=verbose), _VerbosePassHeartbeats("[graph] pass 2", verbose=verbose):
|
|
1149
1235
|
for fqn, entry in tables.types.items():
|
|
1150
1236
|
ast = asts.get(entry.file_path)
|
|
1151
1237
|
if ast is None:
|
|
@@ -1818,7 +1904,7 @@ def pass3_calls(tables: GraphTables, asts: dict[str, JavaFileAst], *, verbose: b
|
|
|
1818
1904
|
_verbose_stderr_line(_PASS3_START)
|
|
1819
1905
|
_build_member_indexes(tables)
|
|
1820
1906
|
stats = CallResolutionStats()
|
|
1821
|
-
with _VerbosePassHeartbeats("[graph] pass 3", verbose=verbose):
|
|
1907
|
+
with _graph_pass_progress("3/6", verbose=verbose), _VerbosePassHeartbeats("[graph] pass 3", verbose=verbose):
|
|
1822
1908
|
for rel_path, file_ast in asts.items():
|
|
1823
1909
|
try:
|
|
1824
1910
|
_process_file_calls(file_ast, rel_path, tables, stats)
|
|
@@ -1924,8 +2010,21 @@ def _producer_id(
|
|
|
1924
2010
|
return f"p:{hashlib.sha1(key.encode()).hexdigest()[:16]}"
|
|
1925
2011
|
|
|
1926
2012
|
|
|
2013
|
+
# The four brownfield source layers — single source of truth. Consumed by the
|
|
2014
|
+
# client/producer source-layer classifiers, the *_from_brownfield_pct stats
|
|
2015
|
+
# (via brownfield_strategies), and the brownfield_only authoritativeness gate in
|
|
2016
|
+
# _is_brownfield_sourced. codebase_client/codebase_producer are caller-side
|
|
2017
|
+
# declaration strategies, not layers — they extend brownfield_strategies only.
|
|
2018
|
+
_BROWNFIELD_LAYERS = frozenset({
|
|
2019
|
+
"layer_a_meta",
|
|
2020
|
+
"layer_b_ann",
|
|
2021
|
+
"layer_b_fqn",
|
|
2022
|
+
"layer_c_source",
|
|
2023
|
+
})
|
|
2024
|
+
|
|
2025
|
+
|
|
1927
2026
|
def _client_source_layer(strategy: str) -> str:
|
|
1928
|
-
if strategy in
|
|
2027
|
+
if strategy in _BROWNFIELD_LAYERS:
|
|
1929
2028
|
return strategy
|
|
1930
2029
|
# Some caller extraction paths emit client kind as strategy; treat those
|
|
1931
2030
|
# as builtin-source declarations instead of warning on every row.
|
|
@@ -1937,7 +2036,7 @@ def _client_source_layer(strategy: str) -> str:
|
|
|
1937
2036
|
|
|
1938
2037
|
|
|
1939
2038
|
def _producer_source_layer(strategy: str) -> str:
|
|
1940
|
-
if strategy in
|
|
2039
|
+
if strategy in _BROWNFIELD_LAYERS:
|
|
1941
2040
|
return strategy
|
|
1942
2041
|
if strategy in VALID_PRODUCER_KINDS:
|
|
1943
2042
|
return "builtin"
|
|
@@ -1972,7 +2071,7 @@ def pass4_routes(
|
|
|
1972
2071
|
meta_chain = collect_annotation_meta_chain(prs)
|
|
1973
2072
|
if verbose:
|
|
1974
2073
|
_verbose_stderr_line(_PASS4_START)
|
|
1975
|
-
with _VerbosePassHeartbeats("[graph] pass 4", verbose=verbose):
|
|
2074
|
+
with _graph_pass_progress("4/6", verbose=verbose), _VerbosePassHeartbeats("[graph] pass 4", verbose=verbose):
|
|
1976
2075
|
|
|
1977
2076
|
for ast in asts.values():
|
|
1978
2077
|
stats.routes_skipped_unresolved += ast.routes_skipped_unresolved
|
|
@@ -2149,7 +2248,7 @@ def pass5_imperative_edges(
|
|
|
2149
2248
|
|
|
2150
2249
|
if verbose:
|
|
2151
2250
|
_verbose_stderr_line(_PASS5_START)
|
|
2152
|
-
with _VerbosePassHeartbeats("[graph] pass 5", verbose=verbose):
|
|
2251
|
+
with _graph_pass_progress("5/6", verbose=verbose), _VerbosePassHeartbeats("[graph] pass 5", verbose=verbose):
|
|
2153
2252
|
for member in sorted(tables.members, key=lambda x: x.node_id):
|
|
2154
2253
|
if member.decl.is_constructor:
|
|
2155
2254
|
continue
|
|
@@ -2372,15 +2471,14 @@ def pass5_imperative_edges(
|
|
|
2372
2471
|
tables.producer_stats.producers_by_kind = defaultdict(int)
|
|
2373
2472
|
for row in tables.producer_rows:
|
|
2374
2473
|
tables.producer_stats.producers_by_kind[row.producer_kind] += 1
|
|
2375
|
-
brownfield_strategies =
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
),
|
|
2474
|
+
# brownfield_strategies = the four brownfield layers plus the two
|
|
2475
|
+
# caller-side declaration strategies (@CodebaseHttpClient /
|
|
2476
|
+
# @CodebaseProducer). These extend _BROWNFIELD_LAYERS deliberately:
|
|
2477
|
+
# the *_from_brownfield_pct stats count annotation-declared callers as
|
|
2478
|
+
# brownfield-sourced even though they are not "layers" and so do not
|
|
2479
|
+
# gate brownfield_only authoritativeness in _is_brownfield_sourced.
|
|
2480
|
+
brownfield_strategies = _BROWNFIELD_LAYERS | frozenset(
|
|
2481
|
+
{"codebase_client", "codebase_producer"},
|
|
2384
2482
|
)
|
|
2385
2483
|
if tables.call_edge_stats.http_calls_total:
|
|
2386
2484
|
n_http = sum(
|
|
@@ -2482,14 +2580,6 @@ def _match_call_edge(
|
|
|
2482
2580
|
return "cross_service", candidates
|
|
2483
2581
|
|
|
2484
2582
|
|
|
2485
|
-
_BROWNFIELD_LAYERS = frozenset({
|
|
2486
|
-
"layer_c_source",
|
|
2487
|
-
"layer_b_ann",
|
|
2488
|
-
"layer_b_fqn",
|
|
2489
|
-
"layer_a_meta",
|
|
2490
|
-
})
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
2583
|
def _is_brownfield_sourced(
|
|
2494
2584
|
call_strategy: str,
|
|
2495
2585
|
candidates: list[RouteRow],
|
|
@@ -2551,7 +2641,7 @@ def pass6_match_edges(
|
|
|
2551
2641
|
|
|
2552
2642
|
if verbose:
|
|
2553
2643
|
_verbose_stderr_line(_PASS6_START)
|
|
2554
|
-
with _VerbosePassHeartbeats("[graph] pass 6", verbose=verbose):
|
|
2644
|
+
with _graph_pass_progress("6/6", verbose=verbose), _VerbosePassHeartbeats("[graph] pass 6", verbose=verbose):
|
|
2555
2645
|
for row in tables.http_call_rows:
|
|
2556
2646
|
if row.match != "unresolved":
|
|
2557
2647
|
continue
|
|
@@ -3586,7 +3676,9 @@ def incremental_rebuild(
|
|
|
3586
3676
|
_verbose_stderr_line("[increment] rebuilding scoped files (passes 1-4)")
|
|
3587
3677
|
|
|
3588
3678
|
tables = GraphTables()
|
|
3589
|
-
asts = pass1_parse(
|
|
3679
|
+
asts = pass1_parse(
|
|
3680
|
+
source_root, tables, verbose=verbose, scope_files=scope_files, removed_files=removed
|
|
3681
|
+
)
|
|
3590
3682
|
|
|
3591
3683
|
# Load existing types and members for cross-file resolution (only from unchanged files)
|
|
3592
3684
|
_load_existing_types(conn, tables, exclude_files=scope_files)
|
|
@@ -23,7 +23,7 @@ import sys
|
|
|
23
23
|
from dataclasses import dataclass, field, replace
|
|
24
24
|
from functools import lru_cache
|
|
25
25
|
from pathlib import Path
|
|
26
|
-
from typing import Any
|
|
26
|
+
from typing import Any, TypeVar
|
|
27
27
|
from ast_java import (
|
|
28
28
|
AnnotationRef,
|
|
29
29
|
JavaFileAst,
|
|
@@ -820,7 +820,15 @@ def _route_path_atom(raw_value: str, value_kind: str | None) -> tuple[str, str,
|
|
|
820
820
|
return "", "constant_ref", 0.7, False
|
|
821
821
|
|
|
822
822
|
|
|
823
|
-
|
|
823
|
+
_HINT = TypeVar("_HINT")
|
|
824
|
+
|
|
825
|
+
|
|
826
|
+
def _hint_lookup(ann: AnnotationRef, hints: dict[str, _HINT]) -> _HINT | None:
|
|
827
|
+
"""Resolve a brownfield hint by qualified name, then simple name, then suffix.
|
|
828
|
+
|
|
829
|
+
Shared by route / http-client / async-producer hint resolution; the three
|
|
830
|
+
former copies differed only in the hint value type.
|
|
831
|
+
"""
|
|
824
832
|
q = ann.qualified.strip()
|
|
825
833
|
if q in hints:
|
|
826
834
|
return hints[q]
|
|
@@ -1118,7 +1126,7 @@ def resolve_routes_for_method(
|
|
|
1118
1126
|
|
|
1119
1127
|
# ----- Step 2: Layer B — annotation route hints -----
|
|
1120
1128
|
for _is_m, ann in combined_anns:
|
|
1121
|
-
hint =
|
|
1129
|
+
hint = _hint_lookup(ann, overrides.annotation_to_route_hint)
|
|
1122
1130
|
if hint is None:
|
|
1123
1131
|
continue
|
|
1124
1132
|
working.append(
|
|
@@ -1172,36 +1180,6 @@ def resolve_routes_for_method(
|
|
|
1172
1180
|
return working
|
|
1173
1181
|
|
|
1174
1182
|
|
|
1175
|
-
def _client_hint_lookup(
|
|
1176
|
-
ann: AnnotationRef,
|
|
1177
|
-
hints: dict[str, HttpClientHint],
|
|
1178
|
-
) -> HttpClientHint | None:
|
|
1179
|
-
q = ann.qualified.strip()
|
|
1180
|
-
if q in hints:
|
|
1181
|
-
return hints[q]
|
|
1182
|
-
if ann.name in hints:
|
|
1183
|
-
return hints[ann.name]
|
|
1184
|
-
for k, h in sorted(hints.items(), key=lambda kv: kv[0]):
|
|
1185
|
-
if k.endswith("." + ann.name):
|
|
1186
|
-
return h
|
|
1187
|
-
return None
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
def _async_hint_lookup(
|
|
1191
|
-
ann: AnnotationRef,
|
|
1192
|
-
hints: dict[str, AsyncProducerHint],
|
|
1193
|
-
) -> AsyncProducerHint | None:
|
|
1194
|
-
q = ann.qualified.strip()
|
|
1195
|
-
if q in hints:
|
|
1196
|
-
return hints[q]
|
|
1197
|
-
if ann.name in hints:
|
|
1198
|
-
return hints[ann.name]
|
|
1199
|
-
for k, h in sorted(hints.items(), key=lambda kv: kv[0]):
|
|
1200
|
-
if k.endswith("." + ann.name):
|
|
1201
|
-
return h
|
|
1202
|
-
return None
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
1183
|
def _call_from_http_hint(
|
|
1206
1184
|
*,
|
|
1207
1185
|
hint: HttpClientHint,
|
|
@@ -1296,7 +1274,7 @@ def resolve_http_client_for_method(
|
|
|
1296
1274
|
anchor = builtin_http[0] if builtin_http else (layer_c_src[0] if layer_c_src else None)
|
|
1297
1275
|
|
|
1298
1276
|
for _is_m, ann in combined_anns:
|
|
1299
|
-
hint =
|
|
1277
|
+
hint = _hint_lookup(ann, overrides.annotation_to_http_client_hint)
|
|
1300
1278
|
if hint is None:
|
|
1301
1279
|
continue
|
|
1302
1280
|
brownfield_calls.append(
|
|
@@ -1388,7 +1366,7 @@ def resolve_async_producer_for_method(
|
|
|
1388
1366
|
anchor = builtin_async[0] if builtin_async else (layer_c_src[0] if layer_c_src else None)
|
|
1389
1367
|
|
|
1390
1368
|
for _is_m, ann in combined_anns:
|
|
1391
|
-
hint =
|
|
1369
|
+
hint = _hint_lookup(ann, overrides.annotation_to_async_producer_hint)
|
|
1392
1370
|
if hint is None:
|
|
1393
1371
|
continue
|
|
1394
1372
|
brownfield_calls.append(
|