java-codebase-rag 0.6.1__tar.gz → 0.6.3__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.1/java_codebase_rag.egg-info → java_codebase_rag-0.6.3}/PKG-INFO +4 -1
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/README.md +2 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/build_ast_graph.py +95 -7
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/java_codebase_rag/cli.py +105 -15
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/java_codebase_rag/cli_format.py +8 -35
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/java_codebase_rag/cli_progress.py +17 -21
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/java_codebase_rag/config.py +54 -5
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/java_codebase_rag/installer.py +192 -45
- java_codebase_rag-0.6.3/java_codebase_rag/lance_optimize.py +222 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/java_codebase_rag/pipeline.py +71 -23
- java_codebase_rag-0.6.3/java_codebase_rag/progress.py +570 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3/java_codebase_rag.egg-info}/PKG-INFO +4 -1
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/java_codebase_rag.egg-info/SOURCES.txt +4 -1
- java_codebase_rag-0.6.3/java_codebase_rag.egg-info/entry_points.txt +3 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/java_codebase_rag.egg-info/requires.txt +1 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/java_index_flow_lancedb.py +155 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/pyproject.toml +3 -2
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/server.py +75 -9
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_ast_graph_build.py +136 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_cli_progress_stdout_invariant.py +3 -3
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_config.py +149 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_installer.py +427 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_java_codebase_rag_cli.py +282 -2
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_microservice_scope.py +118 -0
- java_codebase_rag-0.6.3/tests/test_progress.py +463 -0
- java_codebase_rag-0.6.3/tests/test_vectors_progress.py +349 -0
- java_codebase_rag-0.6.1/java_codebase_rag/lance_optimize.py +0 -148
- java_codebase_rag-0.6.1/java_codebase_rag.egg-info/entry_points.txt +0 -3
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/LICENSE +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/ast_java.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/brownfield_events.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/chunk_heuristics.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/graph_enrich.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/index_common.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/java_codebase_rag/__init__.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/java_codebase_rag/_fdlimit.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/java_codebase_rag/install_data/agents/explorer-rag-enhanced.md +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/java_codebase_rag/install_data/skills/explore-codebase/SKILL.md +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/java_codebase_rag.egg-info/dependency_links.txt +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/java_codebase_rag.egg-info/top_level.txt +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/java_index_v1_common.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/java_ontology.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/ladybug_queries.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/mcp_hints.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/mcp_v2.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/path_filtering.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/pr_analysis.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/search_lancedb.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/setup.cfg +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_agent_skills_static.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_assign_endpoint_client_extraction.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_ast_java_calls.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_ast_java_capabilities.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_bank_chat_brownfield_integration.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_brownfield_clients.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_brownfield_events.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_brownfield_overrides.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_brownfield_routes.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_call_edge_matching.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_call_edges_e2e.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_call_graph_receiver_resolution.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_call_graph_smoke_roundtrip.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_call_invariant.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_cli_quiet_parity.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_client_hint_recovery.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_client_node_extraction.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_client_role_rename.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_cross_service_resolution_flag.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_edge_navigation_doc.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_fd_limit.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_feign_not_exposer.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_graph_enrich.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_incremental_graph.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_installer_integration.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_ladybug_queries.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_lance_optimize.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_lancedb_e2e.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_mcp_hints.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_mcp_server_project_root.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_mcp_tools.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_mcp_v2.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_mcp_v2_compose.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_meta_chain_core.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_outgoing_call_extraction.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_packaging_metadata.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_path_filtering.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_pr_analysis.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_resolve_routes_messaging_layer_c.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_route_extraction.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_schema_consistency.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_search_lancedb.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/tests/test_search_lancedb_capability.py +0 -0
- {java_codebase_rag-0.6.1 → java_codebase_rag-0.6.3}/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.3
|
|
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
|
|
@@ -106,6 +107,8 @@ 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.
|
|
@@ -65,6 +65,8 @@ 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.
|
|
@@ -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)
|
|
@@ -1972,7 +2058,7 @@ def pass4_routes(
|
|
|
1972
2058
|
meta_chain = collect_annotation_meta_chain(prs)
|
|
1973
2059
|
if verbose:
|
|
1974
2060
|
_verbose_stderr_line(_PASS4_START)
|
|
1975
|
-
with _VerbosePassHeartbeats("[graph] pass 4", verbose=verbose):
|
|
2061
|
+
with _graph_pass_progress("4/6", verbose=verbose), _VerbosePassHeartbeats("[graph] pass 4", verbose=verbose):
|
|
1976
2062
|
|
|
1977
2063
|
for ast in asts.values():
|
|
1978
2064
|
stats.routes_skipped_unresolved += ast.routes_skipped_unresolved
|
|
@@ -2149,7 +2235,7 @@ def pass5_imperative_edges(
|
|
|
2149
2235
|
|
|
2150
2236
|
if verbose:
|
|
2151
2237
|
_verbose_stderr_line(_PASS5_START)
|
|
2152
|
-
with _VerbosePassHeartbeats("[graph] pass 5", verbose=verbose):
|
|
2238
|
+
with _graph_pass_progress("5/6", verbose=verbose), _VerbosePassHeartbeats("[graph] pass 5", verbose=verbose):
|
|
2153
2239
|
for member in sorted(tables.members, key=lambda x: x.node_id):
|
|
2154
2240
|
if member.decl.is_constructor:
|
|
2155
2241
|
continue
|
|
@@ -2551,7 +2637,7 @@ def pass6_match_edges(
|
|
|
2551
2637
|
|
|
2552
2638
|
if verbose:
|
|
2553
2639
|
_verbose_stderr_line(_PASS6_START)
|
|
2554
|
-
with _VerbosePassHeartbeats("[graph] pass 6", verbose=verbose):
|
|
2640
|
+
with _graph_pass_progress("6/6", verbose=verbose), _VerbosePassHeartbeats("[graph] pass 6", verbose=verbose):
|
|
2555
2641
|
for row in tables.http_call_rows:
|
|
2556
2642
|
if row.match != "unresolved":
|
|
2557
2643
|
continue
|
|
@@ -3586,7 +3672,9 @@ def incremental_rebuild(
|
|
|
3586
3672
|
_verbose_stderr_line("[increment] rebuilding scoped files (passes 1-4)")
|
|
3587
3673
|
|
|
3588
3674
|
tables = GraphTables()
|
|
3589
|
-
asts = pass1_parse(
|
|
3675
|
+
asts = pass1_parse(
|
|
3676
|
+
source_root, tables, verbose=verbose, scope_files=scope_files, removed_files=removed
|
|
3677
|
+
)
|
|
3590
3678
|
|
|
3591
3679
|
# Load existing types and members for cross-file resolution (only from unchanged files)
|
|
3592
3680
|
_load_existing_types(conn, tables, exclude_files=scope_files)
|
|
@@ -6,6 +6,7 @@ from __future__ import annotations
|
|
|
6
6
|
import argparse
|
|
7
7
|
import asyncio
|
|
8
8
|
import json
|
|
9
|
+
import os
|
|
9
10
|
import pprint
|
|
10
11
|
import shutil
|
|
11
12
|
import sys
|
|
@@ -130,15 +131,41 @@ def _run_with_pipeline_progress(
|
|
|
130
131
|
cfg: ResolvedOperatorConfig,
|
|
131
132
|
*,
|
|
132
133
|
quiet: bool,
|
|
133
|
-
|
|
134
|
+
verbose: bool = False,
|
|
135
|
+
work: Callable[["PipelineProgress | None"], int],
|
|
134
136
|
) -> int:
|
|
135
|
-
|
|
136
|
-
|
|
137
|
+
"""Run ``work`` under the unified progress renderer (default TTY mode only).
|
|
138
|
+
|
|
139
|
+
``work`` receives a :class:`PipelineProgress` whose ``on_progress`` callback
|
|
140
|
+
should be forwarded to the graph/vectors pipeline helpers so their
|
|
141
|
+
``JCIRAG_PROGRESS`` events feed the renderer. In ``--quiet`` or ``--verbose``
|
|
142
|
+
mode the context is ``None`` (no Live region: quiet is silent, verbose
|
|
143
|
+
raw-relays subprocess output).
|
|
144
|
+
"""
|
|
145
|
+
if quiet or verbose:
|
|
146
|
+
return int(work(None))
|
|
147
|
+
from java_codebase_rag.progress import build_index_progress_context
|
|
148
|
+
|
|
149
|
+
# PR-3 owns all three tasks in order: Vectors → Optimize → Graph. The vectors
|
|
150
|
+
# task is fed by the cocoindex child's per-file ticks + approximate total
|
|
151
|
+
# (subprocess transport, parsed by ProgressRelay); the optimize task is fed
|
|
152
|
+
# in-process by lance_optimize; the graph task is fed by the build_ast_graph
|
|
153
|
+
# child (subprocess transport). A task only becomes visible/running once its
|
|
154
|
+
# first event arrives.
|
|
155
|
+
renderer, on_progress, console = build_index_progress_context()
|
|
156
|
+
progress = PipelineProgress(renderer=renderer)
|
|
157
|
+
progress.on_progress = on_progress
|
|
158
|
+
progress.console = console
|
|
159
|
+
|
|
137
160
|
_pipeline_header(subcommand, cfg)
|
|
138
161
|
t0 = time.perf_counter()
|
|
139
162
|
code = 0
|
|
163
|
+
# start() always flips _started (the non-TTY fallback is a no-op for Live but
|
|
164
|
+
# still needs the flag so apply() routes to the concise-line printer). The
|
|
165
|
+
# TTY Live region is entered inside start() only when the console is a TTY.
|
|
166
|
+
renderer.start()
|
|
140
167
|
try:
|
|
141
|
-
code = int(work())
|
|
168
|
+
code = int(work(progress))
|
|
142
169
|
return code
|
|
143
170
|
except BaseException as exc:
|
|
144
171
|
# Keep footer aligned with process outcome (main maps unhandled Exception -> exit 2).
|
|
@@ -154,9 +181,26 @@ def _run_with_pipeline_progress(
|
|
|
154
181
|
code = 2
|
|
155
182
|
raise
|
|
156
183
|
finally:
|
|
184
|
+
renderer.stop()
|
|
157
185
|
_pipeline_footer(subcommand, t0, code)
|
|
158
186
|
|
|
159
187
|
|
|
188
|
+
class PipelineProgress:
|
|
189
|
+
"""Progress context handed to ``work``: the renderer + a ready ``on_progress``.
|
|
190
|
+
|
|
191
|
+
``on_progress``/``console`` are wired by :func:`_run_with_pipeline_progress`
|
|
192
|
+
and should be forwarded to the pipeline helpers' ``on_progress`` /
|
|
193
|
+
``on_progress_console`` parameters. ``console`` is the renderer's stderr
|
|
194
|
+
``rich.Console`` so the subprocess drain routes non-progress lines through
|
|
195
|
+
``console.print`` while the Live region is up (single-writer invariant).
|
|
196
|
+
"""
|
|
197
|
+
|
|
198
|
+
def __init__(self, *, renderer: "object | None") -> None:
|
|
199
|
+
self.renderer = renderer
|
|
200
|
+
self.on_progress: "Callable | None" = None
|
|
201
|
+
self.console: "object | None" = None
|
|
202
|
+
|
|
203
|
+
|
|
160
204
|
def _jsonable(value: Any) -> Any:
|
|
161
205
|
if hasattr(value, "model_dump"):
|
|
162
206
|
return value.model_dump()
|
|
@@ -265,7 +309,7 @@ def _cmd_init(args: argparse.Namespace) -> int:
|
|
|
265
309
|
return 2
|
|
266
310
|
cfg.index_dir.mkdir(parents=True, exist_ok=True)
|
|
267
311
|
|
|
268
|
-
def work() -> int:
|
|
312
|
+
def work(progress: "PipelineProgress | None") -> int:
|
|
269
313
|
env = cfg.subprocess_env()
|
|
270
314
|
verbose = bool(args.verbose)
|
|
271
315
|
coco = run_cocoindex_update(
|
|
@@ -274,6 +318,8 @@ def _cmd_init(args: argparse.Namespace) -> int:
|
|
|
274
318
|
quiet=bool(args.quiet),
|
|
275
319
|
verbose=verbose,
|
|
276
320
|
lance_project_root=None if args.quiet else cfg.source_root,
|
|
321
|
+
on_progress=progress.on_progress if progress is not None else None,
|
|
322
|
+
on_progress_console=progress.console if progress is not None else None,
|
|
277
323
|
)
|
|
278
324
|
if coco.returncode != 0:
|
|
279
325
|
_emit(
|
|
@@ -294,6 +340,8 @@ def _cmd_init(args: argparse.Namespace) -> int:
|
|
|
294
340
|
verbose=verbose,
|
|
295
341
|
quiet=bool(args.quiet),
|
|
296
342
|
env=env,
|
|
343
|
+
on_progress=progress.on_progress if progress is not None else None,
|
|
344
|
+
on_progress_console=progress.console if progress is not None else None,
|
|
297
345
|
)
|
|
298
346
|
if g.returncode != 0:
|
|
299
347
|
_emit(
|
|
@@ -309,7 +357,9 @@ def _cmd_init(args: argparse.Namespace) -> int:
|
|
|
309
357
|
_emit({"success": True, "message": "init completed"})
|
|
310
358
|
return 0
|
|
311
359
|
|
|
312
|
-
return _run_with_pipeline_progress(
|
|
360
|
+
return _run_with_pipeline_progress(
|
|
361
|
+
"init", cfg, quiet=bool(args.quiet), verbose=bool(args.verbose), work=work
|
|
362
|
+
)
|
|
313
363
|
|
|
314
364
|
|
|
315
365
|
def _cmd_increment(args: argparse.Namespace) -> int:
|
|
@@ -322,7 +372,7 @@ def _cmd_increment(args: argparse.Namespace) -> int:
|
|
|
322
372
|
if vectors_only:
|
|
323
373
|
_emit_increment_ladybug_warning()
|
|
324
374
|
|
|
325
|
-
def work() -> int:
|
|
375
|
+
def work(progress: "PipelineProgress | None") -> int:
|
|
326
376
|
env = cfg.subprocess_env()
|
|
327
377
|
coco = run_cocoindex_update(
|
|
328
378
|
env,
|
|
@@ -330,6 +380,8 @@ def _cmd_increment(args: argparse.Namespace) -> int:
|
|
|
330
380
|
quiet=bool(args.quiet),
|
|
331
381
|
verbose=bool(args.verbose),
|
|
332
382
|
lance_project_root=None if args.quiet else cfg.source_root,
|
|
383
|
+
on_progress=progress.on_progress if progress is not None else None,
|
|
384
|
+
on_progress_console=progress.console if progress is not None else None,
|
|
333
385
|
)
|
|
334
386
|
if coco.returncode != 0:
|
|
335
387
|
_emit(
|
|
@@ -355,6 +407,8 @@ def _cmd_increment(args: argparse.Namespace) -> int:
|
|
|
355
407
|
verbose=bool(args.verbose),
|
|
356
408
|
quiet=bool(args.quiet),
|
|
357
409
|
env=env,
|
|
410
|
+
on_progress=progress.on_progress if progress is not None else None,
|
|
411
|
+
on_progress_console=progress.console if progress is not None else None,
|
|
358
412
|
)
|
|
359
413
|
|
|
360
414
|
# Check if incremental fell back to full rebuild
|
|
@@ -388,7 +442,9 @@ def _cmd_increment(args: argparse.Namespace) -> int:
|
|
|
388
442
|
_emit({"success": True, "message": "increment completed (Lance + graph updated)"})
|
|
389
443
|
return 0
|
|
390
444
|
|
|
391
|
-
return _run_with_pipeline_progress(
|
|
445
|
+
return _run_with_pipeline_progress(
|
|
446
|
+
"increment", cfg, quiet=bool(args.quiet), verbose=bool(args.verbose), work=work
|
|
447
|
+
)
|
|
392
448
|
|
|
393
449
|
|
|
394
450
|
def _cmd_reprocess(args: argparse.Namespace) -> int:
|
|
@@ -396,14 +452,18 @@ def _cmd_reprocess(args: argparse.Namespace) -> int:
|
|
|
396
452
|
_startup_hints(cfg)
|
|
397
453
|
cfg.apply_to_os_environ()
|
|
398
454
|
|
|
399
|
-
def work() -> int:
|
|
455
|
+
def work(progress: "PipelineProgress | None") -> int:
|
|
400
456
|
env = cfg.subprocess_env()
|
|
401
457
|
verbose = bool(args.verbose)
|
|
402
458
|
vectors_only = bool(getattr(args, "vectors_only", False))
|
|
403
459
|
graph_only = bool(getattr(args, "graph_only", False))
|
|
404
460
|
|
|
405
461
|
if vectors_only:
|
|
406
|
-
coco = run_cocoindex_update(
|
|
462
|
+
coco = run_cocoindex_update(
|
|
463
|
+
env, full_reprocess=True, quiet=bool(args.quiet), verbose=verbose,
|
|
464
|
+
on_progress=progress.on_progress if progress is not None else None,
|
|
465
|
+
on_progress_console=progress.console if progress is not None else None,
|
|
466
|
+
)
|
|
407
467
|
if _is_cocoindex_preflight_blocker(coco):
|
|
408
468
|
payload: dict[str, Any] = {
|
|
409
469
|
"success": False,
|
|
@@ -442,6 +502,8 @@ def _cmd_reprocess(args: argparse.Namespace) -> int:
|
|
|
442
502
|
verbose=verbose,
|
|
443
503
|
quiet=bool(args.quiet),
|
|
444
504
|
env=env,
|
|
505
|
+
on_progress=progress.on_progress if progress is not None else None,
|
|
506
|
+
on_progress_console=progress.console if progress is not None else None,
|
|
445
507
|
)
|
|
446
508
|
if _is_graph_preflight_blocker(g):
|
|
447
509
|
payload = {
|
|
@@ -476,12 +538,21 @@ def _cmd_reprocess(args: argparse.Namespace) -> int:
|
|
|
476
538
|
|
|
477
539
|
import server # lazy: pulls sentence_transformers/torch/lancedb/kuzu
|
|
478
540
|
|
|
479
|
-
result = asyncio.run(
|
|
541
|
+
result = asyncio.run(
|
|
542
|
+
server.run_refresh_pipeline(
|
|
543
|
+
quiet=bool(args.quiet),
|
|
544
|
+
verbose=verbose,
|
|
545
|
+
on_progress=progress.on_progress if progress is not None else None,
|
|
546
|
+
on_progress_console=progress.console if progress is not None else None,
|
|
547
|
+
)
|
|
548
|
+
)
|
|
480
549
|
payload = result.model_dump()
|
|
481
550
|
_emit_reprocess_outcome(payload)
|
|
482
551
|
return _reprocess_exit_code(payload)
|
|
483
552
|
|
|
484
|
-
return _run_with_pipeline_progress(
|
|
553
|
+
return _run_with_pipeline_progress(
|
|
554
|
+
"reprocess", cfg, quiet=bool(args.quiet), verbose=bool(args.verbose), work=work
|
|
555
|
+
)
|
|
485
556
|
|
|
486
557
|
|
|
487
558
|
def _cmd_install(args: argparse.Namespace) -> int:
|
|
@@ -494,6 +565,7 @@ def _cmd_install(args: argparse.Namespace) -> int:
|
|
|
494
565
|
model=args.model,
|
|
495
566
|
source_root=None, # None means cwd; installer confirms interactively
|
|
496
567
|
quiet=bool(args.quiet),
|
|
568
|
+
verbose=bool(args.verbose),
|
|
497
569
|
)
|
|
498
570
|
|
|
499
571
|
|
|
@@ -503,6 +575,8 @@ def _cmd_update(args: argparse.Namespace) -> int:
|
|
|
503
575
|
return run_update(
|
|
504
576
|
force=bool(args.force),
|
|
505
577
|
dry_run=bool(args.dry_run),
|
|
578
|
+
quiet=bool(args.quiet),
|
|
579
|
+
verbose=bool(args.verbose),
|
|
506
580
|
)
|
|
507
581
|
|
|
508
582
|
|
|
@@ -536,7 +610,7 @@ def _cmd_erase(args: argparse.Namespace) -> int:
|
|
|
536
610
|
print("Aborted.", file=sys.stderr)
|
|
537
611
|
return 2
|
|
538
612
|
|
|
539
|
-
def work() -> int:
|
|
613
|
+
def work(progress: "PipelineProgress | None") -> int:
|
|
540
614
|
env = cfg.subprocess_env()
|
|
541
615
|
drop = run_cocoindex_drop(env, quiet=bool(args.quiet))
|
|
542
616
|
if drop.returncode == 127:
|
|
@@ -569,7 +643,7 @@ def _cmd_erase(args: argparse.Namespace) -> int:
|
|
|
569
643
|
_emit({"success": True, "message": "erase completed"})
|
|
570
644
|
return 0
|
|
571
645
|
|
|
572
|
-
return _run_with_pipeline_progress("erase", cfg, quiet=bool(args.quiet), work=work)
|
|
646
|
+
return _run_with_pipeline_progress("erase", cfg, quiet=bool(args.quiet), verbose=bool(getattr(args, "verbose", False)), work=work)
|
|
573
647
|
|
|
574
648
|
|
|
575
649
|
def _cmd_meta(args: argparse.Namespace) -> int:
|
|
@@ -930,5 +1004,21 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
930
1004
|
return 2
|
|
931
1005
|
|
|
932
1006
|
|
|
1007
|
+
def _console_script_main() -> None:
|
|
1008
|
+
"""Real CLI entry: terminate without interpreter finalization.
|
|
1009
|
+
|
|
1010
|
+
A pyarrow/lance worker thread (loaded via lancedb in lifecycle commands) can
|
|
1011
|
+
outlive CPython finalization in a one-shot CLI subprocess and trip
|
|
1012
|
+
``PyGILState_Release`` (SIGABRT, exit -6). Flushing + ``os._exit`` skips that
|
|
1013
|
+
racy teardown — the command has already done its work and emitted its result.
|
|
1014
|
+
``main()`` stays return-based so in-process test callers (``cli.main(...)``)
|
|
1015
|
+
keep working.
|
|
1016
|
+
"""
|
|
1017
|
+
rc = main()
|
|
1018
|
+
sys.stdout.flush()
|
|
1019
|
+
sys.stderr.flush()
|
|
1020
|
+
os._exit(rc)
|
|
1021
|
+
|
|
1022
|
+
|
|
933
1023
|
if __name__ == "__main__":
|
|
934
|
-
|
|
1024
|
+
_console_script_main()
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
"""TTY-aware ANSI formatting for CLI stderr progress."""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
-
import itertools
|
|
5
4
|
import sys
|
|
6
|
-
import threading
|
|
7
|
-
import time
|
|
8
5
|
|
|
9
6
|
_RESET = "\033[0m"
|
|
10
7
|
_BOLD = "\033[1m"
|
|
@@ -16,8 +13,6 @@ _CYAN = "\033[36m"
|
|
|
16
13
|
CHECK = "✓"
|
|
17
14
|
CROSS = "✗"
|
|
18
15
|
|
|
19
|
-
_SPINNER_FRAMES = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"
|
|
20
|
-
|
|
21
16
|
_NOISE_CONTAINS: tuple[bytes, ...] = (
|
|
22
17
|
b"lance::",
|
|
23
18
|
b"FutureWarning",
|
|
@@ -25,6 +20,14 @@ _NOISE_CONTAINS: tuple[bytes, ...] = (
|
|
|
25
20
|
b'"event": "brownfield-',
|
|
26
21
|
b"unknown producer source strategy",
|
|
27
22
|
b"unknown client source strategy",
|
|
23
|
+
# Builder verbose heartbeats / pass banners: in default mode the renderer's
|
|
24
|
+
# bar subsumes these, so they must NOT also appear as raw lines above the
|
|
25
|
+
# Live region. --verbose raw-relay bypasses this filter and still shows them.
|
|
26
|
+
b"[graph] pass ",
|
|
27
|
+
b"[graph] scoped write ",
|
|
28
|
+
b"[graph] writing ",
|
|
29
|
+
b"[graph] done ",
|
|
30
|
+
b"[increment] ",
|
|
28
31
|
)
|
|
29
32
|
|
|
30
33
|
|
|
@@ -80,33 +83,3 @@ def styled_check() -> str:
|
|
|
80
83
|
|
|
81
84
|
def styled_cross() -> str:
|
|
82
85
|
return red(CROSS) if stderr_is_tty() else CROSS
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
class Spinner:
|
|
86
|
-
"""Braille spinner that overwrites the current stderr line until stopped."""
|
|
87
|
-
|
|
88
|
-
def __init__(self, label: str) -> None:
|
|
89
|
-
self._label = label
|
|
90
|
-
self._stop = threading.Event()
|
|
91
|
-
self._thread: threading.Thread | None = None
|
|
92
|
-
|
|
93
|
-
def start(self) -> None:
|
|
94
|
-
self._thread = threading.Thread(target=self._run, name="spinner", daemon=True)
|
|
95
|
-
self._thread.start()
|
|
96
|
-
|
|
97
|
-
def stop(self) -> None:
|
|
98
|
-
self._stop.set()
|
|
99
|
-
if self._thread is not None:
|
|
100
|
-
self._thread.join(timeout=2.0)
|
|
101
|
-
sys.stderr.buffer.write(b"\r\x1b[2K")
|
|
102
|
-
sys.stderr.buffer.flush()
|
|
103
|
-
|
|
104
|
-
def _run(self) -> None:
|
|
105
|
-
frames = itertools.cycle(_SPINNER_FRAMES)
|
|
106
|
-
t0 = time.monotonic()
|
|
107
|
-
while not self._stop.wait(0.3):
|
|
108
|
-
elapsed = time.monotonic() - t0
|
|
109
|
-
frame = next(frames)
|
|
110
|
-
line = f"\r{frame} {self._label} · {elapsed:.0f}s"
|
|
111
|
-
sys.stderr.buffer.write(line.encode())
|
|
112
|
-
sys.stderr.buffer.flush()
|
|
@@ -3,26 +3,10 @@ from __future__ import annotations
|
|
|
3
3
|
|
|
4
4
|
import asyncio
|
|
5
5
|
import sys
|
|
6
|
+
from typing import Callable
|
|
6
7
|
|
|
7
|
-
from java_codebase_rag.cli_format import
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def emit_vectors_start() -> None:
|
|
11
|
-
print(
|
|
12
|
-
bold_cyan("[vectors]") + " running · cocoindex update",
|
|
13
|
-
file=sys.stderr,
|
|
14
|
-
flush=True,
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def emit_vectors_finish(*, elapsed_s: float, exit_code: int) -> None:
|
|
19
|
-
marker = styled_check() if exit_code == 0 else styled_cross()
|
|
20
|
-
print(
|
|
21
|
-
f"{marker} {bold_cyan('[vectors]')} finished · {elapsed_s:.2f}s"
|
|
22
|
-
+ (f" (exit={exit_code})" if exit_code != 0 else ""),
|
|
23
|
-
file=sys.stderr,
|
|
24
|
-
flush=True,
|
|
25
|
-
)
|
|
8
|
+
from java_codebase_rag.cli_format import is_noise_line
|
|
9
|
+
from java_codebase_rag.progress import ProgressEvent, make_relay
|
|
26
10
|
|
|
27
11
|
|
|
28
12
|
class _AsyncLineFilter:
|
|
@@ -61,8 +45,15 @@ async def accumulate_and_relay_subprocess_streams(
|
|
|
61
45
|
*,
|
|
62
46
|
relay: bool,
|
|
63
47
|
verbose: bool = True,
|
|
48
|
+
on_progress: Callable[[ProgressEvent], None] | None = None,
|
|
49
|
+
on_progress_console: object | None = None,
|
|
64
50
|
) -> tuple[bytes, bytes]:
|
|
65
|
-
"""Read stdout and stderr until EOF; optionally copy non-noise stderr chunks to stderr.
|
|
51
|
+
"""Read stdout and stderr until EOF; optionally copy non-noise stderr chunks to stderr.
|
|
52
|
+
|
|
53
|
+
When ``on_progress`` is set, stderr is drained through a :class:`ProgressRelay`
|
|
54
|
+
so ``JCIRAG_PROGRESS`` lines are parsed and routed to ``on_progress`` (and
|
|
55
|
+
suppressed from the relay), matching the sync ``pipeline._popen_capturing_stderr``.
|
|
56
|
+
"""
|
|
66
57
|
stdout = proc.stdout
|
|
67
58
|
stderr = proc.stderr
|
|
68
59
|
if stdout is None or stderr is None:
|
|
@@ -70,7 +61,12 @@ async def accumulate_and_relay_subprocess_streams(
|
|
|
70
61
|
|
|
71
62
|
out_buf = bytearray()
|
|
72
63
|
err_buf = bytearray()
|
|
73
|
-
|
|
64
|
+
if on_progress is not None:
|
|
65
|
+
filt = make_relay(on_progress, console=on_progress_console, verbose=verbose)
|
|
66
|
+
elif relay and not verbose:
|
|
67
|
+
filt = _AsyncLineFilter()
|
|
68
|
+
else:
|
|
69
|
+
filt = None
|
|
74
70
|
|
|
75
71
|
async def drain_stdout(reader: asyncio.StreamReader, target: bytearray) -> None:
|
|
76
72
|
while True:
|