java-codebase-rag 0.6.2__py3-none-any.whl → 0.6.4__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.
@@ -9,10 +9,11 @@ import sys
9
9
  import threading
10
10
  import time
11
11
  from pathlib import Path
12
+ from typing import Callable
12
13
 
13
- from java_codebase_rag.cli_format import Spinner, is_noise_line, stderr_is_tty
14
- from java_codebase_rag.cli_progress import emit_vectors_finish, emit_vectors_start
14
+ from java_codebase_rag.cli_format import is_noise_line
15
15
  from java_codebase_rag.config import cocoindex_subprocess_env_defaults
16
+ from java_codebase_rag.progress import ProgressEvent, ProgressRelay, make_relay
16
17
 
17
18
  COCOINDEX_TARGET = "java_index_flow_lancedb.py:JavaCodeIndexLance"
18
19
 
@@ -66,11 +67,26 @@ def _popen_capturing_stderr(
66
67
  proc: subprocess.Popen[bytes],
67
68
  *,
68
69
  verbose: bool = True,
70
+ on_progress: Callable[[ProgressEvent], None] | None = None,
71
+ on_progress_console: object | None = None,
69
72
  ) -> tuple[str, str, int]:
70
- """Capture stdout/stderr; relay stderr through noise filter (or verbatim in verbose mode)."""
73
+ """Capture stdout/stderr; relay stderr through noise filter (or verbatim in verbose mode).
74
+
75
+ When ``on_progress`` is set, stderr is drained through a :class:`ProgressRelay`
76
+ instead of the bare ``_LineFilter``: progress lines are parsed first, routed to
77
+ ``on_progress``, and suppressed from the relay; non-progress lines follow the
78
+ relay's routing (``console.print`` while a Live region is up via
79
+ ``on_progress_console``, raw ``buffer.write`` in verbose mode).
80
+ """
71
81
  out_buf = bytearray()
72
82
  err_buf = bytearray()
73
- filt = _LineFilter() if not verbose else None
83
+ if on_progress is not None:
84
+ relay = make_relay(
85
+ on_progress, console=on_progress_console, verbose=verbose
86
+ )
87
+ filt: _LineFilter | ProgressRelay | None = relay
88
+ else:
89
+ filt = _LineFilter() if not verbose else None
74
90
 
75
91
  def drain_out() -> None:
76
92
  assert proc.stdout is not None
@@ -112,6 +128,8 @@ def run_cocoindex_update(
112
128
  quiet: bool,
113
129
  verbose: bool = True,
114
130
  lance_project_root: Path | None = None,
131
+ on_progress: Callable[[ProgressEvent], None] | None = None,
132
+ on_progress_console: object | None = None,
115
133
  ) -> subprocess.CompletedProcess[str]:
116
134
  result = _run_cocoindex_update_impl(
117
135
  env,
@@ -119,19 +137,25 @@ def run_cocoindex_update(
119
137
  quiet=quiet,
120
138
  verbose=verbose,
121
139
  lance_project_root=lance_project_root,
140
+ on_progress=on_progress,
141
+ on_progress_console=on_progress_console,
122
142
  )
123
143
  # After cocoindex returns exit 0 there are no concurrent writers, so this
124
144
  # is the safe window to compact the Lance tables. The flow disabled its
125
145
  # in-flight background optimize (see java_index_flow_lancedb.py), making
126
146
  # this serialized pass the sole optimizer. Optimize failure does not flip
127
147
  # the cocoindex CompletedProcess (a successful index is still usable, just
128
- # not compacted); the outcome is logged to stderr only.
148
+ # not compacted); the outcome is logged to stderr only. Thread the
149
+ # in-process on_progress so the optimize phase renders via the same
150
+ # renderer (the flow cannot emit it — it runs in the child).
129
151
  if result.returncode == 0:
130
- _maybe_run_serialized_optimize(env, quiet=quiet)
152
+ _maybe_run_serialized_optimize(env, quiet=quiet, on_progress=on_progress)
131
153
  return result
132
154
 
133
155
 
134
- def _maybe_run_serialized_optimize(env: dict[str, str], *, quiet: bool) -> None:
156
+ def _maybe_run_serialized_optimize(
157
+ env: dict[str, str], *, quiet: bool, on_progress: Callable | None = None
158
+ ) -> None:
135
159
  """Resolve the index dir from *env* and run the serialized Lance optimize.
136
160
 
137
161
  The flow's lifespan reads ``JAVA_CODEBASE_RAG_INDEX_DIR`` (set by the CLI /
@@ -150,7 +174,7 @@ def _maybe_run_serialized_optimize(env: dict[str, str], *, quiet: bool) -> None:
150
174
  try:
151
175
  from java_codebase_rag.lance_optimize import optimize_lance_tables
152
176
 
153
- asyncio.run(optimize_lance_tables(Path(idx_raw), quiet=quiet))
177
+ asyncio.run(optimize_lance_tables(Path(idx_raw), quiet=quiet, on_progress=on_progress))
154
178
  except Exception as exc:
155
179
  # Never crash the CLI on an optimize failure — surface on stderr only.
156
180
  print(f"java-codebase-rag: optimize failed: {exc}", file=sys.stderr)
@@ -163,9 +187,15 @@ def _run_cocoindex_update_impl(
163
187
  quiet: bool,
164
188
  verbose: bool = True,
165
189
  lance_project_root: Path | None = None,
190
+ on_progress: Callable[[ProgressEvent], None] | None = None,
191
+ on_progress_console: object | None = None,
166
192
  ) -> subprocess.CompletedProcess[str]:
167
193
  exe = cocoindex_bin()
168
194
  if not exe.is_file():
195
+ # 127 pre-spawn stub: never mark the vectors task running — emit a
196
+ # terminal failed event so the renderer doesn't leave it hung.
197
+ if on_progress is not None:
198
+ on_progress(ProgressEvent(kind="vectors", phase=None, pass_=None, done=None, total=None, status="failed", elapsed_s=None))
169
199
  return subprocess.CompletedProcess(
170
200
  args=[str(exe)],
171
201
  returncode=127,
@@ -175,6 +205,8 @@ def _run_cocoindex_update_impl(
175
205
  bd = bundle_dir()
176
206
  flow = bd / "java_index_flow_lancedb.py"
177
207
  if not flow.is_file():
208
+ if on_progress is not None:
209
+ on_progress(ProgressEvent(kind="vectors", phase=None, pass_=None, done=None, total=None, status="failed", elapsed_s=None))
178
210
  return subprocess.CompletedProcess(
179
211
  args=[],
180
212
  returncode=126,
@@ -201,17 +233,10 @@ def _run_cocoindex_update_impl(
201
233
  text=True,
202
234
  )
203
235
 
204
- emit_progress = lance_project_root is not None
205
- use_spinner = emit_progress and stderr_is_tty()
206
- if emit_progress and not use_spinner:
207
- emit_vectors_start()
208
- spinner: Spinner | None = None
209
- if use_spinner:
210
- spinner = Spinner("[vectors] running · cocoindex update")
211
- spinner.start()
212
236
  t0 = time.perf_counter()
213
237
  code = -1
214
238
  out_s, err_s = "", ""
239
+ proc: subprocess.Popen[bytes] | None = None
215
240
  try:
216
241
  proc = subprocess.Popen(
217
242
  cmd,
@@ -221,12 +246,27 @@ def _run_cocoindex_update_impl(
221
246
  stderr=subprocess.PIPE,
222
247
  bufsize=0,
223
248
  )
224
- out_s, err_s, code = _popen_capturing_stderr(proc, verbose=verbose)
249
+ # Vectors task is marked running only AFTER Popen succeeds — the flow's
250
+ # per-file ticks + approximate total stream in from the child via the
251
+ # relay (parsed by ProgressRelay, routed to on_progress).
252
+ out_s, err_s, code = _popen_capturing_stderr(
253
+ proc, verbose=verbose, on_progress=on_progress, on_progress_console=on_progress_console
254
+ )
225
255
  finally:
226
- if spinner is not None:
227
- spinner.stop()
228
- if emit_progress:
229
- emit_vectors_finish(elapsed_s=time.perf_counter() - t0, exit_code=code)
256
+ # The flow cannot emit the terminal vectors event (no "all files done"
257
+ # hook in cocoindex flows), so the PARENT emits it here based on the
258
+ # cocoindex exit code. This drives clamp-on-completion + the phase
259
+ # transition to Optimize. Emitted even on a spawn failure (code stays
260
+ # -1 → failed) so the renderer's task never hangs at running.
261
+ if on_progress is not None:
262
+ elapsed = time.perf_counter() - t0
263
+ status = "done" if code == 0 else "failed"
264
+ on_progress(
265
+ ProgressEvent(
266
+ kind="vectors", phase=None, pass_=None, done=None, total=None,
267
+ status=status, elapsed_s=elapsed,
268
+ )
269
+ )
230
270
  return subprocess.CompletedProcess(args=cmd, returncode=code, stdout=out_s, stderr=err_s)
231
271
 
232
272
 
@@ -259,6 +299,8 @@ def run_build_ast_graph(
259
299
  verbose: bool,
260
300
  quiet: bool = False,
261
301
  env: dict[str, str] | None = None,
302
+ on_progress: Callable[[ProgressEvent], None] | None = None,
303
+ on_progress_console: object | None = None,
262
304
  ) -> subprocess.CompletedProcess[str]:
263
305
  builder = bundle_dir() / "build_ast_graph.py"
264
306
  if not builder.is_file():
@@ -297,7 +339,9 @@ def run_build_ast_graph(
297
339
  stderr=subprocess.PIPE,
298
340
  bufsize=0,
299
341
  )
300
- out_s, err_s, code = _popen_capturing_stderr(proc, verbose=verbose)
342
+ out_s, err_s, code = _popen_capturing_stderr(
343
+ proc, verbose=verbose, on_progress=on_progress, on_progress_console=on_progress_console
344
+ )
301
345
  if not verbose:
302
346
  from java_codebase_rag.cli_format import bold_cyan, styled_check, styled_cross
303
347
  marker = styled_check() if code == 0 else styled_cross()
@@ -312,6 +356,8 @@ def run_incremental_graph(
312
356
  verbose: bool,
313
357
  quiet: bool = False,
314
358
  env: dict[str, str] | None = None,
359
+ on_progress: Callable[[ProgressEvent], None] | None = None,
360
+ on_progress_console: object | None = None,
315
361
  ) -> subprocess.CompletedProcess[str]:
316
362
  """Run incremental graph rebuild by passing --incremental flag to build_ast_graph.py."""
317
363
  builder = bundle_dir() / "build_ast_graph.py"
@@ -352,7 +398,9 @@ def run_incremental_graph(
352
398
  stderr=subprocess.PIPE,
353
399
  bufsize=0,
354
400
  )
355
- out_s, err_s, code = _popen_capturing_stderr(proc, verbose=verbose)
401
+ out_s, err_s, code = _popen_capturing_stderr(
402
+ proc, verbose=verbose, on_progress=on_progress, on_progress_console=on_progress_console
403
+ )
356
404
  if not verbose:
357
405
  from java_codebase_rag.cli_format import bold_cyan, styled_check, styled_cross
358
406
  marker = styled_check() if code == 0 else styled_cross()