java-codebase-rag 0.6.2__py3-none-any.whl → 0.6.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- build_ast_graph.py +95 -7
- java_codebase_rag/cli.py +87 -14
- java_codebase_rag/cli_format.py +8 -35
- java_codebase_rag/cli_progress.py +17 -21
- java_codebase_rag/installer.py +180 -43
- java_codebase_rag/lance_optimize.py +125 -51
- java_codebase_rag/pipeline.py +71 -23
- java_codebase_rag/progress.py +570 -0
- {java_codebase_rag-0.6.2.dist-info → java_codebase_rag-0.6.3.dist-info}/METADATA +4 -1
- {java_codebase_rag-0.6.2.dist-info → java_codebase_rag-0.6.3.dist-info}/RECORD +16 -15
- java_index_flow_lancedb.py +155 -0
- server.py +75 -9
- {java_codebase_rag-0.6.2.dist-info → java_codebase_rag-0.6.3.dist-info}/WHEEL +0 -0
- {java_codebase_rag-0.6.2.dist-info → java_codebase_rag-0.6.3.dist-info}/entry_points.txt +0 -0
- {java_codebase_rag-0.6.2.dist-info → java_codebase_rag-0.6.3.dist-info}/licenses/LICENSE +0 -0
- {java_codebase_rag-0.6.2.dist-info → java_codebase_rag-0.6.3.dist-info}/top_level.txt +0 -0
build_ast_graph.py
CHANGED
|
@@ -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)
|
java_codebase_rag/cli.py
CHANGED
|
@@ -131,15 +131,41 @@ def _run_with_pipeline_progress(
|
|
|
131
131
|
cfg: ResolvedOperatorConfig,
|
|
132
132
|
*,
|
|
133
133
|
quiet: bool,
|
|
134
|
-
|
|
134
|
+
verbose: bool = False,
|
|
135
|
+
work: Callable[["PipelineProgress | None"], int],
|
|
135
136
|
) -> int:
|
|
136
|
-
|
|
137
|
-
|
|
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
|
+
|
|
138
160
|
_pipeline_header(subcommand, cfg)
|
|
139
161
|
t0 = time.perf_counter()
|
|
140
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()
|
|
141
167
|
try:
|
|
142
|
-
code = int(work())
|
|
168
|
+
code = int(work(progress))
|
|
143
169
|
return code
|
|
144
170
|
except BaseException as exc:
|
|
145
171
|
# Keep footer aligned with process outcome (main maps unhandled Exception -> exit 2).
|
|
@@ -155,9 +181,26 @@ def _run_with_pipeline_progress(
|
|
|
155
181
|
code = 2
|
|
156
182
|
raise
|
|
157
183
|
finally:
|
|
184
|
+
renderer.stop()
|
|
158
185
|
_pipeline_footer(subcommand, t0, code)
|
|
159
186
|
|
|
160
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
|
+
|
|
161
204
|
def _jsonable(value: Any) -> Any:
|
|
162
205
|
if hasattr(value, "model_dump"):
|
|
163
206
|
return value.model_dump()
|
|
@@ -266,7 +309,7 @@ def _cmd_init(args: argparse.Namespace) -> int:
|
|
|
266
309
|
return 2
|
|
267
310
|
cfg.index_dir.mkdir(parents=True, exist_ok=True)
|
|
268
311
|
|
|
269
|
-
def work() -> int:
|
|
312
|
+
def work(progress: "PipelineProgress | None") -> int:
|
|
270
313
|
env = cfg.subprocess_env()
|
|
271
314
|
verbose = bool(args.verbose)
|
|
272
315
|
coco = run_cocoindex_update(
|
|
@@ -275,6 +318,8 @@ def _cmd_init(args: argparse.Namespace) -> int:
|
|
|
275
318
|
quiet=bool(args.quiet),
|
|
276
319
|
verbose=verbose,
|
|
277
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,
|
|
278
323
|
)
|
|
279
324
|
if coco.returncode != 0:
|
|
280
325
|
_emit(
|
|
@@ -295,6 +340,8 @@ def _cmd_init(args: argparse.Namespace) -> int:
|
|
|
295
340
|
verbose=verbose,
|
|
296
341
|
quiet=bool(args.quiet),
|
|
297
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,
|
|
298
345
|
)
|
|
299
346
|
if g.returncode != 0:
|
|
300
347
|
_emit(
|
|
@@ -310,7 +357,9 @@ def _cmd_init(args: argparse.Namespace) -> int:
|
|
|
310
357
|
_emit({"success": True, "message": "init completed"})
|
|
311
358
|
return 0
|
|
312
359
|
|
|
313
|
-
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
|
+
)
|
|
314
363
|
|
|
315
364
|
|
|
316
365
|
def _cmd_increment(args: argparse.Namespace) -> int:
|
|
@@ -323,7 +372,7 @@ def _cmd_increment(args: argparse.Namespace) -> int:
|
|
|
323
372
|
if vectors_only:
|
|
324
373
|
_emit_increment_ladybug_warning()
|
|
325
374
|
|
|
326
|
-
def work() -> int:
|
|
375
|
+
def work(progress: "PipelineProgress | None") -> int:
|
|
327
376
|
env = cfg.subprocess_env()
|
|
328
377
|
coco = run_cocoindex_update(
|
|
329
378
|
env,
|
|
@@ -331,6 +380,8 @@ def _cmd_increment(args: argparse.Namespace) -> int:
|
|
|
331
380
|
quiet=bool(args.quiet),
|
|
332
381
|
verbose=bool(args.verbose),
|
|
333
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,
|
|
334
385
|
)
|
|
335
386
|
if coco.returncode != 0:
|
|
336
387
|
_emit(
|
|
@@ -356,6 +407,8 @@ def _cmd_increment(args: argparse.Namespace) -> int:
|
|
|
356
407
|
verbose=bool(args.verbose),
|
|
357
408
|
quiet=bool(args.quiet),
|
|
358
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,
|
|
359
412
|
)
|
|
360
413
|
|
|
361
414
|
# Check if incremental fell back to full rebuild
|
|
@@ -389,7 +442,9 @@ def _cmd_increment(args: argparse.Namespace) -> int:
|
|
|
389
442
|
_emit({"success": True, "message": "increment completed (Lance + graph updated)"})
|
|
390
443
|
return 0
|
|
391
444
|
|
|
392
|
-
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
|
+
)
|
|
393
448
|
|
|
394
449
|
|
|
395
450
|
def _cmd_reprocess(args: argparse.Namespace) -> int:
|
|
@@ -397,14 +452,18 @@ def _cmd_reprocess(args: argparse.Namespace) -> int:
|
|
|
397
452
|
_startup_hints(cfg)
|
|
398
453
|
cfg.apply_to_os_environ()
|
|
399
454
|
|
|
400
|
-
def work() -> int:
|
|
455
|
+
def work(progress: "PipelineProgress | None") -> int:
|
|
401
456
|
env = cfg.subprocess_env()
|
|
402
457
|
verbose = bool(args.verbose)
|
|
403
458
|
vectors_only = bool(getattr(args, "vectors_only", False))
|
|
404
459
|
graph_only = bool(getattr(args, "graph_only", False))
|
|
405
460
|
|
|
406
461
|
if vectors_only:
|
|
407
|
-
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
|
+
)
|
|
408
467
|
if _is_cocoindex_preflight_blocker(coco):
|
|
409
468
|
payload: dict[str, Any] = {
|
|
410
469
|
"success": False,
|
|
@@ -443,6 +502,8 @@ def _cmd_reprocess(args: argparse.Namespace) -> int:
|
|
|
443
502
|
verbose=verbose,
|
|
444
503
|
quiet=bool(args.quiet),
|
|
445
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,
|
|
446
507
|
)
|
|
447
508
|
if _is_graph_preflight_blocker(g):
|
|
448
509
|
payload = {
|
|
@@ -477,12 +538,21 @@ def _cmd_reprocess(args: argparse.Namespace) -> int:
|
|
|
477
538
|
|
|
478
539
|
import server # lazy: pulls sentence_transformers/torch/lancedb/kuzu
|
|
479
540
|
|
|
480
|
-
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
|
+
)
|
|
481
549
|
payload = result.model_dump()
|
|
482
550
|
_emit_reprocess_outcome(payload)
|
|
483
551
|
return _reprocess_exit_code(payload)
|
|
484
552
|
|
|
485
|
-
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
|
+
)
|
|
486
556
|
|
|
487
557
|
|
|
488
558
|
def _cmd_install(args: argparse.Namespace) -> int:
|
|
@@ -495,6 +565,7 @@ def _cmd_install(args: argparse.Namespace) -> int:
|
|
|
495
565
|
model=args.model,
|
|
496
566
|
source_root=None, # None means cwd; installer confirms interactively
|
|
497
567
|
quiet=bool(args.quiet),
|
|
568
|
+
verbose=bool(args.verbose),
|
|
498
569
|
)
|
|
499
570
|
|
|
500
571
|
|
|
@@ -504,6 +575,8 @@ def _cmd_update(args: argparse.Namespace) -> int:
|
|
|
504
575
|
return run_update(
|
|
505
576
|
force=bool(args.force),
|
|
506
577
|
dry_run=bool(args.dry_run),
|
|
578
|
+
quiet=bool(args.quiet),
|
|
579
|
+
verbose=bool(args.verbose),
|
|
507
580
|
)
|
|
508
581
|
|
|
509
582
|
|
|
@@ -537,7 +610,7 @@ def _cmd_erase(args: argparse.Namespace) -> int:
|
|
|
537
610
|
print("Aborted.", file=sys.stderr)
|
|
538
611
|
return 2
|
|
539
612
|
|
|
540
|
-
def work() -> int:
|
|
613
|
+
def work(progress: "PipelineProgress | None") -> int:
|
|
541
614
|
env = cfg.subprocess_env()
|
|
542
615
|
drop = run_cocoindex_drop(env, quiet=bool(args.quiet))
|
|
543
616
|
if drop.returncode == 127:
|
|
@@ -570,7 +643,7 @@ def _cmd_erase(args: argparse.Namespace) -> int:
|
|
|
570
643
|
_emit({"success": True, "message": "erase completed"})
|
|
571
644
|
return 0
|
|
572
645
|
|
|
573
|
-
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)
|
|
574
647
|
|
|
575
648
|
|
|
576
649
|
def _cmd_meta(args: argparse.Namespace) -> int:
|
java_codebase_rag/cli_format.py
CHANGED
|
@@ -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:
|