java-codebase-rag 0.12.0__py3-none-any.whl → 0.12.2__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.
- java_codebase_rag-0.12.2.dist-info/METADATA +35 -0
- java_codebase_rag-0.12.2.dist-info/RECORD +4 -0
- {java_codebase_rag-0.12.0.dist-info → java_codebase_rag-0.12.2.dist-info}/WHEEL +1 -1
- java_codebase_rag/_deprecation.py +0 -103
- java_codebase_rag/_fdlimit.py +0 -56
- java_codebase_rag/_stdio.py +0 -32
- java_codebase_rag/_version.py +0 -35
- java_codebase_rag/absence/__init__.py +0 -0
- java_codebase_rag/absence/absence_diagnosis.py +0 -700
- java_codebase_rag/absence/absence_types.py +0 -124
- java_codebase_rag/absence/absence_vocab.py +0 -460
- java_codebase_rag/analysis/__init__.py +0 -0
- java_codebase_rag/analysis/pr_analysis.py +0 -563
- java_codebase_rag/analysis/resolve_service.py +0 -740
- java_codebase_rag/ast/__init__.py +0 -0
- java_codebase_rag/ast/ast_java.py +0 -2847
- java_codebase_rag/ast/ast_kotlin.py +0 -1794
- java_codebase_rag/ast/brownfield_events.py +0 -58
- java_codebase_rag/ast/chunk_heuristics.py +0 -83
- java_codebase_rag/ast/language.py +0 -117
- java_codebase_rag/cli.py +0 -1215
- java_codebase_rag/cli_dispatch.py +0 -251
- java_codebase_rag/cli_format.py +0 -85
- java_codebase_rag/cli_progress.py +0 -94
- java_codebase_rag/config.py +0 -833
- java_codebase_rag/eval/__init__.py +0 -1
- java_codebase_rag/eval/ground_truth.py +0 -100
- java_codebase_rag/eval/metrics.py +0 -107
- java_codebase_rag/eval/runner.py +0 -556
- java_codebase_rag/graph/__init__.py +0 -0
- java_codebase_rag/graph/build_ast_graph.py +0 -4593
- java_codebase_rag/graph/graph_enrich.py +0 -1940
- java_codebase_rag/graph/graph_types.py +0 -224
- java_codebase_rag/graph/java_ontology.py +0 -465
- java_codebase_rag/graph/ladybug_queries.py +0 -2213
- java_codebase_rag/graph/path_filtering.py +0 -509
- java_codebase_rag/index/__init__.py +0 -0
- java_codebase_rag/index/java_index_flow_lancedb.py +0 -879
- java_codebase_rag/index/java_index_v1_common.py +0 -33
- java_codebase_rag/install_data/__init__.py +0 -0
- java_codebase_rag/install_data/agents/explorer-rag-cli.md +0 -110
- java_codebase_rag/install_data/agents/explorer-rag-enhanced.md +0 -152
- java_codebase_rag/install_data/skills/explore-codebase/SKILL.md +0 -165
- java_codebase_rag/install_data/skills/explore-codebase-cli/SKILL.md +0 -107
- java_codebase_rag/installer.py +0 -2188
- java_codebase_rag/jrag.py +0 -4545
- java_codebase_rag/jrag_envelope.py +0 -1107
- java_codebase_rag/jrag_hints.py +0 -204
- java_codebase_rag/jrag_render.py +0 -926
- java_codebase_rag/lance_optimize.py +0 -264
- java_codebase_rag/mcp/__init__.py +0 -0
- java_codebase_rag/mcp/mcp_hints.py +0 -932
- java_codebase_rag/mcp/mcp_v2.py +0 -1916
- java_codebase_rag/mcp/server.py +0 -886
- java_codebase_rag/pipeline.py +0 -531
- java_codebase_rag/progress.py +0 -570
- java_codebase_rag/read_payloads.py +0 -781
- java_codebase_rag/search/__init__.py +0 -0
- java_codebase_rag/search/index_common.py +0 -10
- java_codebase_rag/search/search_lancedb.py +0 -1296
- java_codebase_rag/search/search_lexical.py +0 -449
- java_codebase_rag/search/search_scoring.py +0 -537
- java_codebase_rag/watch/__init__.py +0 -0
- java_codebase_rag/watch/client.py +0 -230
- java_codebase_rag/watch/daemon.py +0 -396
- java_codebase_rag/watch/lock.py +0 -201
- java_codebase_rag/watch/paths.py +0 -76
- java_codebase_rag/watch/protocol.py +0 -122
- java_codebase_rag/watch/server.py +0 -273
- java_codebase_rag/watch/warm.py +0 -105
- java_codebase_rag/watch/watcher.py +0 -394
- java_codebase_rag-0.12.0.dist-info/METADATA +0 -340
- java_codebase_rag-0.12.0.dist-info/RECORD +0 -75
- java_codebase_rag-0.12.0.dist-info/entry_points.txt +0 -5
- java_codebase_rag-0.12.0.dist-info/licenses/LICENSE +0 -21
- java_codebase_rag-0.12.0.dist-info/top_level.txt +0 -1
- /java_codebase_rag/__init__.py → /java_codebase_rag-0.12.2.dist-info/top_level.txt +0 -0
java_codebase_rag/pipeline.py
DELETED
|
@@ -1,531 +0,0 @@
|
|
|
1
|
-
"""Subprocess helpers for cocoindex + graph builder (no heavy ML imports at import time)."""
|
|
2
|
-
from __future__ import annotations
|
|
3
|
-
|
|
4
|
-
import asyncio
|
|
5
|
-
import importlib.util
|
|
6
|
-
import os
|
|
7
|
-
import shutil
|
|
8
|
-
import subprocess
|
|
9
|
-
import sys
|
|
10
|
-
import threading
|
|
11
|
-
import time
|
|
12
|
-
from pathlib import Path
|
|
13
|
-
from typing import Callable
|
|
14
|
-
|
|
15
|
-
from java_codebase_rag.cli_format import is_noise_line
|
|
16
|
-
from java_codebase_rag.config import cocoindex_subprocess_env_defaults
|
|
17
|
-
from java_codebase_rag.progress import ProgressEvent, ProgressRelay, make_relay
|
|
18
|
-
|
|
19
|
-
COCOINDEX_TARGET = "java_index_flow_lancedb.py:JavaCodeIndexLance"
|
|
20
|
-
|
|
21
|
-
# Operator-facing line printed when an indexing command skips the vectors phase
|
|
22
|
-
# because the vector stack is absent (graph-only install, e.g. macOS Intel where
|
|
23
|
-
# cocoindex[lancedb]/lancedb/sentence-transformers are gated off by PEP 508 markers).
|
|
24
|
-
# Single source of truth — imported by cli.py (init/increment) and server.py
|
|
25
|
-
# (reprocess) so the wording can't drift between the two paths.
|
|
26
|
-
VECTORS_SKIPPED_GRAPH_ONLY = (
|
|
27
|
-
"jrag: vectors skipped — vector stack not installed on this platform "
|
|
28
|
-
"(graph-only mode). The graph is built/refreshed; semantic search is unavailable."
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
# Package-internal locations of the cocoindex flow definition and the graph
|
|
33
|
-
# builder. Both are executed *by file path* — cocoindex loads the flow via a
|
|
34
|
-
# ``file:Class`` target (COCOINDEX_TARGET below) and the builder runs as
|
|
35
|
-
# ``python <path>`` — so we must resolve their paths without importing them
|
|
36
|
-
# (this module deliberately stays free of heavy ML imports at import time).
|
|
37
|
-
# Derived from this file's location so they resolve under both editable and
|
|
38
|
-
# wheel installs.
|
|
39
|
-
_PKG_DIR = Path(__file__).resolve().parent
|
|
40
|
-
_FLOW_DIR = _PKG_DIR / "index"
|
|
41
|
-
_FLOW_FILE = _FLOW_DIR / "java_index_flow_lancedb.py"
|
|
42
|
-
_BUILDER_FILE = _PKG_DIR / "graph" / "build_ast_graph.py"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def cocoindex_bin() -> Path:
|
|
46
|
-
candidate = Path(sys.executable).parent / "cocoindex"
|
|
47
|
-
if candidate.is_file():
|
|
48
|
-
return candidate
|
|
49
|
-
found = shutil.which("cocoindex")
|
|
50
|
-
if found:
|
|
51
|
-
return Path(found)
|
|
52
|
-
return candidate
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
class _LineFilter:
|
|
56
|
-
"""Buffer byte chunks and relay only non-noise lines to stderr."""
|
|
57
|
-
|
|
58
|
-
def __init__(self) -> None:
|
|
59
|
-
self._buf = bytearray()
|
|
60
|
-
self._suppress_next = False
|
|
61
|
-
|
|
62
|
-
def feed(self, chunk: bytes) -> None:
|
|
63
|
-
self._buf.extend(chunk)
|
|
64
|
-
while b"\n" in self._buf:
|
|
65
|
-
line, self._buf = self._buf.split(b"\n", 1)
|
|
66
|
-
line += b"\n"
|
|
67
|
-
noise = is_noise_line(line)
|
|
68
|
-
if noise:
|
|
69
|
-
self._suppress_next = True
|
|
70
|
-
continue
|
|
71
|
-
if self._suppress_next and line[:1] in (b" ", b"\t"):
|
|
72
|
-
continue
|
|
73
|
-
self._suppress_next = False
|
|
74
|
-
sys.stderr.buffer.write(line)
|
|
75
|
-
sys.stderr.buffer.flush()
|
|
76
|
-
|
|
77
|
-
def flush(self) -> None:
|
|
78
|
-
if self._buf:
|
|
79
|
-
if not is_noise_line(self._buf):
|
|
80
|
-
sys.stderr.buffer.write(bytes(self._buf))
|
|
81
|
-
sys.stderr.buffer.flush()
|
|
82
|
-
self._buf.clear()
|
|
83
|
-
self._suppress_next = False
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def _popen_capturing_stderr(
|
|
87
|
-
proc: subprocess.Popen[bytes],
|
|
88
|
-
*,
|
|
89
|
-
verbose: bool = True,
|
|
90
|
-
on_progress: Callable[[ProgressEvent], None] | None = None,
|
|
91
|
-
on_progress_console: object | None = None,
|
|
92
|
-
) -> tuple[str, str, int]:
|
|
93
|
-
"""Capture stdout/stderr; relay stderr through noise filter (or verbatim in verbose mode).
|
|
94
|
-
|
|
95
|
-
When ``on_progress`` is set, stderr is drained through a :class:`ProgressRelay`
|
|
96
|
-
instead of the bare ``_LineFilter``: progress lines are parsed first, routed to
|
|
97
|
-
``on_progress``, and suppressed from the relay; non-progress lines follow the
|
|
98
|
-
relay's routing (``console.print`` while a Live region is up via
|
|
99
|
-
``on_progress_console``, raw ``buffer.write`` in verbose mode).
|
|
100
|
-
"""
|
|
101
|
-
out_buf = bytearray()
|
|
102
|
-
err_buf = bytearray()
|
|
103
|
-
if on_progress is not None:
|
|
104
|
-
relay = make_relay(
|
|
105
|
-
on_progress, console=on_progress_console, verbose=verbose
|
|
106
|
-
)
|
|
107
|
-
filt: _LineFilter | ProgressRelay | None = relay
|
|
108
|
-
else:
|
|
109
|
-
filt = _LineFilter() if not verbose else None
|
|
110
|
-
|
|
111
|
-
def drain_out() -> None:
|
|
112
|
-
assert proc.stdout is not None
|
|
113
|
-
while True:
|
|
114
|
-
chunk = proc.stdout.read(65536)
|
|
115
|
-
if not chunk:
|
|
116
|
-
break
|
|
117
|
-
out_buf.extend(chunk)
|
|
118
|
-
|
|
119
|
-
def drain_err() -> None:
|
|
120
|
-
assert proc.stderr is not None
|
|
121
|
-
while True:
|
|
122
|
-
chunk = proc.stderr.read(65536)
|
|
123
|
-
if not chunk:
|
|
124
|
-
break
|
|
125
|
-
err_buf.extend(chunk)
|
|
126
|
-
if filt is not None:
|
|
127
|
-
filt.feed(chunk)
|
|
128
|
-
else:
|
|
129
|
-
sys.stderr.buffer.write(chunk)
|
|
130
|
-
sys.stderr.buffer.flush()
|
|
131
|
-
|
|
132
|
-
t_out = threading.Thread(target=drain_out, name="stream-stdout", daemon=True)
|
|
133
|
-
t_err = threading.Thread(target=drain_err, name="stream-stderr", daemon=True)
|
|
134
|
-
t_out.start()
|
|
135
|
-
t_err.start()
|
|
136
|
-
# Wait on the CHILD before joining the drain threads. ``Popen.wait()`` is
|
|
137
|
-
# interruptible by Ctrl+C — the underlying ``os.waitpid`` returns EINTR and
|
|
138
|
-
# CPython raises ``KeyboardInterrupt`` — whereas ``Thread.join()`` blocks on
|
|
139
|
-
# an internal lock whose infinite-timeout acquire CPython never polls for
|
|
140
|
-
# signals. Joining *first* therefore made the whole indexing step ignore
|
|
141
|
-
# Ctrl+C until the child happened to close its pipes; cocoindex's teardown
|
|
142
|
-
# (and any flow-server grandchild it spawned) can hold them open for a long
|
|
143
|
-
# time, so an install/reprocess could not be aborted. The daemon drain
|
|
144
|
-
# threads keep the child's stdout/stderr pipes empty while we wait, so the
|
|
145
|
-
# child never blocks on a full pipe.
|
|
146
|
-
try:
|
|
147
|
-
code = proc.wait()
|
|
148
|
-
except BaseException:
|
|
149
|
-
# Ctrl+C or any other abort: best-effort, NON-BLOCKING child teardown
|
|
150
|
-
# so a process we spawned does not outlive us, then re-raise WITHOUT
|
|
151
|
-
# joining the drain threads. They may be blocked on a pipe the child
|
|
152
|
-
# still owns, and a join here would re-introduce the very hang this
|
|
153
|
-
# reordering fixes. The threads are daemons, so they vanish at exit.
|
|
154
|
-
_abort_child(proc)
|
|
155
|
-
raise
|
|
156
|
-
# Normal exit: the child is gone, its pipes hit EOF, and the drain threads
|
|
157
|
-
# return promptly — safe to join here. (If a pipe-inheriting grandchild
|
|
158
|
-
# lingered past the child's exit, these joins would block until it too
|
|
159
|
-
# closed its write ends — on Ctrl+C the shared-process-group SIGINT reaches
|
|
160
|
-
# it and closes them. This window is the short teardown-after-indexing phase,
|
|
161
|
-
# not the long indexing phase the wait-first reorder already made
|
|
162
|
-
# interruptible.)
|
|
163
|
-
t_out.join()
|
|
164
|
-
t_err.join()
|
|
165
|
-
if filt is not None:
|
|
166
|
-
filt.flush()
|
|
167
|
-
return out_buf.decode(errors="replace"), err_buf.decode(errors="replace"), code
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
def _abort_child(proc: subprocess.Popen[bytes]) -> None:
|
|
171
|
-
"""Best-effort, NON-BLOCKING teardown of a spawned child on an abort path.
|
|
172
|
-
|
|
173
|
-
Runs when ``proc.wait()`` raised (Ctrl+C, or any other exception). Sends
|
|
174
|
-
SIGTERM and returns immediately — this path exists to let the operator exit
|
|
175
|
-
*promptly*, and waiting for the child would defeat that. On Ctrl+C the child
|
|
176
|
-
already received SIGINT (same process group); this just guarantees teardown
|
|
177
|
-
for non-signal aborts, after which the child finishes shutting down on its
|
|
178
|
-
own. ``OSError`` (incl. ``ProcessLookupError`` — already-dead / zombie /
|
|
179
|
-
reaped) is swallowed — the only caller re-raises regardless.
|
|
180
|
-
"""
|
|
181
|
-
fn = getattr(proc, "terminate", None)
|
|
182
|
-
if fn is None:
|
|
183
|
-
return
|
|
184
|
-
try:
|
|
185
|
-
fn()
|
|
186
|
-
except OSError:
|
|
187
|
-
pass
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
def run_cocoindex_update(
|
|
191
|
-
env: dict[str, str],
|
|
192
|
-
*,
|
|
193
|
-
full_reprocess: bool,
|
|
194
|
-
quiet: bool,
|
|
195
|
-
verbose: bool = True,
|
|
196
|
-
lance_project_root: Path | None = None,
|
|
197
|
-
on_progress: Callable[[ProgressEvent], None] | None = None,
|
|
198
|
-
on_progress_console: object | None = None,
|
|
199
|
-
) -> subprocess.CompletedProcess[str]:
|
|
200
|
-
if full_reprocess:
|
|
201
|
-
# A full reprocess rebuilds every row, so DROP the Lance target tables
|
|
202
|
-
# first and let cocoindex recreate them via the fast INSERT path. The
|
|
203
|
-
# in-place alternative (cocoindex's bulk-update merge_insert) emits
|
|
204
|
-
# ~one deletion-vector + version commit PER matched row — O(rows) of
|
|
205
|
-
# tiny file IO that scales to multi-minute hangs on large repos
|
|
206
|
-
# (measured ~83s sys time / 3474 deletion files for 3475 chunks on
|
|
207
|
-
# Shopizer; drop+recreate is ~3.6s sys / 0 deletions, ~3.7x faster and
|
|
208
|
-
# hang-free). Output is identical either way (full recompute); only the
|
|
209
|
-
# write path differs. Drop failure is non-fatal — if it somehow fails,
|
|
210
|
-
# the update falls back to the slow in-place path. The same fix is
|
|
211
|
-
# applied on the async server path (``server.run_refresh_pipeline``).
|
|
212
|
-
drop = run_cocoindex_drop(env, quiet=quiet)
|
|
213
|
-
if drop.returncode != 0 and not is_cocoindex_preflight_blocker(drop):
|
|
214
|
-
print(
|
|
215
|
-
"jrag: drop-before-reprocess failed "
|
|
216
|
-
f"(exit {drop.returncode}); falling back to in-place update: "
|
|
217
|
-
f"{(drop.stderr or '').strip()[:200]}",
|
|
218
|
-
file=sys.stderr,
|
|
219
|
-
)
|
|
220
|
-
result = _run_cocoindex_update_impl(
|
|
221
|
-
env,
|
|
222
|
-
full_reprocess=full_reprocess,
|
|
223
|
-
quiet=quiet,
|
|
224
|
-
verbose=verbose,
|
|
225
|
-
lance_project_root=lance_project_root,
|
|
226
|
-
on_progress=on_progress,
|
|
227
|
-
on_progress_console=on_progress_console,
|
|
228
|
-
)
|
|
229
|
-
# After cocoindex returns exit 0 there are no concurrent writers, so this
|
|
230
|
-
# is the safe window to compact the Lance tables. The flow disabled its
|
|
231
|
-
# in-flight background optimize (see java_index_flow_lancedb.py), making
|
|
232
|
-
# this serialized pass the sole optimizer. Optimize failure does not flip
|
|
233
|
-
# the cocoindex CompletedProcess (a successful index is still usable, just
|
|
234
|
-
# not compacted); the outcome is logged to stderr only. Thread the
|
|
235
|
-
# in-process on_progress so the optimize phase renders via the same
|
|
236
|
-
# renderer (the flow cannot emit it — it runs in the child).
|
|
237
|
-
if result.returncode == 0:
|
|
238
|
-
_maybe_run_serialized_optimize(env, quiet=quiet, on_progress=on_progress)
|
|
239
|
-
return result
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
def _maybe_run_serialized_optimize(
|
|
243
|
-
env: dict[str, str], *, quiet: bool, on_progress: Callable | None = None
|
|
244
|
-
) -> None:
|
|
245
|
-
"""Resolve the index dir from *env* and run the serialized Lance optimize.
|
|
246
|
-
|
|
247
|
-
The flow's lifespan reads ``JAVA_CODEBASE_RAG_INDEX_DIR`` (set by the CLI /
|
|
248
|
-
config.subprocess_env), so it is guaranteed present when cocoindex ran.
|
|
249
|
-
If it is somehow absent we skip optimize with a stderr warning rather than
|
|
250
|
-
crash — a successful index is still searchable un-compacted.
|
|
251
|
-
"""
|
|
252
|
-
idx_raw = env.get("JAVA_CODEBASE_RAG_INDEX_DIR", "").strip()
|
|
253
|
-
if not idx_raw:
|
|
254
|
-
print(
|
|
255
|
-
"jrag: optimize skipped — JAVA_CODEBASE_RAG_INDEX_DIR "
|
|
256
|
-
"not set in subprocess env",
|
|
257
|
-
file=sys.stderr,
|
|
258
|
-
)
|
|
259
|
-
return
|
|
260
|
-
try:
|
|
261
|
-
from java_codebase_rag.lance_optimize import optimize_lance_tables
|
|
262
|
-
|
|
263
|
-
asyncio.run(optimize_lance_tables(Path(idx_raw), quiet=quiet, on_progress=on_progress))
|
|
264
|
-
except Exception as exc:
|
|
265
|
-
# Never crash the CLI on an optimize failure — surface on stderr only.
|
|
266
|
-
print(f"jrag: optimize failed: {exc}", file=sys.stderr)
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
def is_cocoindex_preflight_blocker(proc: subprocess.CompletedProcess[str]) -> bool:
|
|
270
|
-
"""True when ``run_cocoindex_update`` returned a pre-spawn stub, not a real cocoindex run.
|
|
271
|
-
|
|
272
|
-
The stubs are emitted by ``_run_cocoindex_update_impl`` just below:
|
|
273
|
-
* returncode 127, args=[exe] -> cocoindex binary not installed (graph-only install,
|
|
274
|
-
e.g. macOS Intel where the vector extra is gated off).
|
|
275
|
-
* returncode 126, args=[] -> ``java_index_flow_lancedb.py`` missing from the bundle.
|
|
276
|
-
A real cocoindex run has ``args`` = the full command list (length > 1), so the
|
|
277
|
-
``len(args) <= 1`` guard distinguishes a stub from a genuine non-zero exit. This is the
|
|
278
|
-
single authoritative detector — ``cli.py`` and ``installer.py`` both call it so the
|
|
279
|
-
"treat as skip, not failure" decision stays aligned with the stub shapes here.
|
|
280
|
-
"""
|
|
281
|
-
return bool(proc.returncode in (126, 127) and len(getattr(proc, "args", ()) or ()) <= 1)
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
def is_graph_preflight_blocker(proc: subprocess.CompletedProcess[str]) -> bool:
|
|
285
|
-
"""True when ``run_build_ast_graph`` returned a pre-spawn stub (builder missing)."""
|
|
286
|
-
return bool(proc.returncode in (126, 127) and len(getattr(proc, "args", ()) or ()) <= 1)
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
def vector_stack_installed() -> bool:
|
|
290
|
-
"""True when the optional vector stack (cocoindex/lancedb/sentence-transformers) is importable.
|
|
291
|
-
|
|
292
|
-
False on graph-only installs (macOS Intel), where PEP 508 markers exclude the trio.
|
|
293
|
-
Used to skip vector-only wizard steps (e.g. embedding-model selection) and to preflight
|
|
294
|
-
branching without spawning cocoindex. Probes all three since they are gated together.
|
|
295
|
-
"""
|
|
296
|
-
return all(
|
|
297
|
-
importlib.util.find_spec(m) is not None
|
|
298
|
-
for m in ("cocoindex", "lancedb", "sentence_transformers")
|
|
299
|
-
)
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
def _run_cocoindex_update_impl(
|
|
303
|
-
env: dict[str, str],
|
|
304
|
-
*,
|
|
305
|
-
full_reprocess: bool,
|
|
306
|
-
quiet: bool,
|
|
307
|
-
verbose: bool = True,
|
|
308
|
-
lance_project_root: Path | None = None,
|
|
309
|
-
on_progress: Callable[[ProgressEvent], None] | None = None,
|
|
310
|
-
on_progress_console: object | None = None,
|
|
311
|
-
) -> subprocess.CompletedProcess[str]:
|
|
312
|
-
exe = cocoindex_bin()
|
|
313
|
-
if not exe.is_file():
|
|
314
|
-
# 127 pre-spawn stub: never mark the vectors task running — emit a
|
|
315
|
-
# terminal failed event so the renderer doesn't leave it hung.
|
|
316
|
-
if on_progress is not None:
|
|
317
|
-
on_progress(ProgressEvent(kind="vectors", phase=None, pass_=None, done=None, total=None, status="failed", elapsed_s=None))
|
|
318
|
-
return subprocess.CompletedProcess(
|
|
319
|
-
args=[str(exe)],
|
|
320
|
-
returncode=127,
|
|
321
|
-
stdout="",
|
|
322
|
-
stderr=f"cocoindex not found: {exe}",
|
|
323
|
-
)
|
|
324
|
-
bd = _FLOW_DIR
|
|
325
|
-
flow = bd / "java_index_flow_lancedb.py"
|
|
326
|
-
if not flow.is_file():
|
|
327
|
-
if on_progress is not None:
|
|
328
|
-
on_progress(ProgressEvent(kind="vectors", phase=None, pass_=None, done=None, total=None, status="failed", elapsed_s=None))
|
|
329
|
-
return subprocess.CompletedProcess(
|
|
330
|
-
args=[],
|
|
331
|
-
returncode=126,
|
|
332
|
-
stdout="",
|
|
333
|
-
stderr=f"java_index_flow_lancedb.py not found under {bd}",
|
|
334
|
-
)
|
|
335
|
-
# Cap CocoIndex concurrency to avoid EMFILE ("too many open files") under
|
|
336
|
-
# default OS fd limits. See: https://github.com/HumanBean17/java-codebase-rag/issues/306
|
|
337
|
-
env = env.copy()
|
|
338
|
-
for _k, _v in cocoindex_subprocess_env_defaults().items():
|
|
339
|
-
env.setdefault(_k, _v)
|
|
340
|
-
cmd: list[str] = [str(exe), "update", COCOINDEX_TARGET]
|
|
341
|
-
if full_reprocess:
|
|
342
|
-
cmd.extend(["--full-reprocess", "-f"])
|
|
343
|
-
else:
|
|
344
|
-
cmd.append("-f")
|
|
345
|
-
if quiet:
|
|
346
|
-
cmd.append("-q")
|
|
347
|
-
return subprocess.run(
|
|
348
|
-
cmd,
|
|
349
|
-
cwd=str(bd),
|
|
350
|
-
env=env,
|
|
351
|
-
capture_output=True,
|
|
352
|
-
text=True,
|
|
353
|
-
)
|
|
354
|
-
|
|
355
|
-
t0 = time.perf_counter()
|
|
356
|
-
code = -1
|
|
357
|
-
out_s, err_s = "", ""
|
|
358
|
-
proc: subprocess.Popen[bytes] | None = None
|
|
359
|
-
try:
|
|
360
|
-
proc = subprocess.Popen(
|
|
361
|
-
cmd,
|
|
362
|
-
cwd=str(bd),
|
|
363
|
-
env=env,
|
|
364
|
-
stdout=subprocess.PIPE,
|
|
365
|
-
stderr=subprocess.PIPE,
|
|
366
|
-
bufsize=0,
|
|
367
|
-
)
|
|
368
|
-
# Vectors task is marked running only AFTER Popen succeeds — the flow's
|
|
369
|
-
# per-file ticks + approximate total stream in from the child via the
|
|
370
|
-
# relay (parsed by ProgressRelay, routed to on_progress).
|
|
371
|
-
out_s, err_s, code = _popen_capturing_stderr(
|
|
372
|
-
proc, verbose=verbose, on_progress=on_progress, on_progress_console=on_progress_console
|
|
373
|
-
)
|
|
374
|
-
finally:
|
|
375
|
-
# The flow cannot emit the terminal vectors event (no "all files done"
|
|
376
|
-
# hook in cocoindex flows), so the PARENT emits it here based on the
|
|
377
|
-
# cocoindex exit code. This drives clamp-on-completion + the phase
|
|
378
|
-
# transition to Optimize. Emitted even on a spawn failure (code stays
|
|
379
|
-
# -1 → failed) so the renderer's task never hangs at running.
|
|
380
|
-
if on_progress is not None:
|
|
381
|
-
elapsed = time.perf_counter() - t0
|
|
382
|
-
status = "done" if code == 0 else "failed"
|
|
383
|
-
on_progress(
|
|
384
|
-
ProgressEvent(
|
|
385
|
-
kind="vectors", phase=None, pass_=None, done=None, total=None,
|
|
386
|
-
status=status, elapsed_s=elapsed,
|
|
387
|
-
)
|
|
388
|
-
)
|
|
389
|
-
return subprocess.CompletedProcess(args=cmd, returncode=code, stdout=out_s, stderr=err_s)
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
def run_cocoindex_drop(env: dict[str, str], *, quiet: bool) -> subprocess.CompletedProcess[str]:
|
|
393
|
-
exe = cocoindex_bin()
|
|
394
|
-
if not exe.is_file():
|
|
395
|
-
return subprocess.CompletedProcess(
|
|
396
|
-
args=[str(exe)],
|
|
397
|
-
returncode=127,
|
|
398
|
-
stdout="",
|
|
399
|
-
stderr=f"cocoindex not found: {exe}",
|
|
400
|
-
)
|
|
401
|
-
bd = _FLOW_DIR
|
|
402
|
-
cmd = [str(exe), "drop", COCOINDEX_TARGET, "-f"]
|
|
403
|
-
if quiet:
|
|
404
|
-
cmd.append("-q")
|
|
405
|
-
return subprocess.run(
|
|
406
|
-
cmd,
|
|
407
|
-
cwd=str(bd),
|
|
408
|
-
env=env,
|
|
409
|
-
capture_output=True,
|
|
410
|
-
text=True,
|
|
411
|
-
)
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
def run_build_ast_graph(
|
|
415
|
-
*,
|
|
416
|
-
source_root: Path,
|
|
417
|
-
ladybug_path: Path,
|
|
418
|
-
verbose: bool,
|
|
419
|
-
quiet: bool = False,
|
|
420
|
-
env: dict[str, str] | None = None,
|
|
421
|
-
on_progress: Callable[[ProgressEvent], None] | None = None,
|
|
422
|
-
on_progress_console: object | None = None,
|
|
423
|
-
) -> subprocess.CompletedProcess[str]:
|
|
424
|
-
builder = _BUILDER_FILE
|
|
425
|
-
if not builder.is_file():
|
|
426
|
-
return subprocess.CompletedProcess(
|
|
427
|
-
args=[],
|
|
428
|
-
returncode=126,
|
|
429
|
-
stdout="",
|
|
430
|
-
stderr=f"build_ast_graph.py not found under {builder.parent}",
|
|
431
|
-
)
|
|
432
|
-
cmd: list[str] = [
|
|
433
|
-
sys.executable,
|
|
434
|
-
str(builder),
|
|
435
|
-
"--source-root",
|
|
436
|
-
str(source_root),
|
|
437
|
-
"--ladybug-path",
|
|
438
|
-
str(ladybug_path),
|
|
439
|
-
]
|
|
440
|
-
# Three-tier: --quiet (silent) / default (filtered progress) / --verbose (raw).
|
|
441
|
-
# Default passes --verbose so the builder emits per-pass progress lines,
|
|
442
|
-
# which the parent filters via _LineFilter. --verbose bypasses the filter.
|
|
443
|
-
if verbose or not quiet:
|
|
444
|
-
cmd.append("--verbose")
|
|
445
|
-
if quiet:
|
|
446
|
-
return subprocess.run(
|
|
447
|
-
cmd,
|
|
448
|
-
cwd=str(source_root),
|
|
449
|
-
env=env or os.environ.copy(),
|
|
450
|
-
capture_output=True,
|
|
451
|
-
text=True,
|
|
452
|
-
)
|
|
453
|
-
proc = subprocess.Popen(
|
|
454
|
-
cmd,
|
|
455
|
-
cwd=str(source_root),
|
|
456
|
-
env=env or os.environ.copy(),
|
|
457
|
-
stdout=subprocess.PIPE,
|
|
458
|
-
stderr=subprocess.PIPE,
|
|
459
|
-
bufsize=0,
|
|
460
|
-
)
|
|
461
|
-
out_s, err_s, code = _popen_capturing_stderr(
|
|
462
|
-
proc, verbose=verbose, on_progress=on_progress, on_progress_console=on_progress_console
|
|
463
|
-
)
|
|
464
|
-
if not verbose:
|
|
465
|
-
from java_codebase_rag.cli_format import bold_cyan, styled_check, styled_cross
|
|
466
|
-
marker = styled_check() if code == 0 else styled_cross()
|
|
467
|
-
print(f"{marker} {bold_cyan('[graph]')} done", file=sys.stderr, flush=True)
|
|
468
|
-
return subprocess.CompletedProcess(args=cmd, returncode=code, stdout=out_s, stderr=err_s)
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
def run_incremental_graph(
|
|
472
|
-
*,
|
|
473
|
-
source_root: Path,
|
|
474
|
-
ladybug_path: Path,
|
|
475
|
-
verbose: bool,
|
|
476
|
-
quiet: bool = False,
|
|
477
|
-
env: dict[str, str] | None = None,
|
|
478
|
-
on_progress: Callable[[ProgressEvent], None] | None = None,
|
|
479
|
-
on_progress_console: object | None = None,
|
|
480
|
-
) -> subprocess.CompletedProcess[str]:
|
|
481
|
-
"""Run incremental graph rebuild by passing --incremental flag to build_ast_graph.py."""
|
|
482
|
-
builder = _BUILDER_FILE
|
|
483
|
-
if not builder.is_file():
|
|
484
|
-
return subprocess.CompletedProcess(
|
|
485
|
-
args=[],
|
|
486
|
-
returncode=126,
|
|
487
|
-
stdout="",
|
|
488
|
-
stderr=f"build_ast_graph.py not found under {builder.parent}",
|
|
489
|
-
)
|
|
490
|
-
cmd: list[str] = [
|
|
491
|
-
sys.executable,
|
|
492
|
-
str(builder),
|
|
493
|
-
"--source-root",
|
|
494
|
-
str(source_root),
|
|
495
|
-
"--ladybug-path",
|
|
496
|
-
str(ladybug_path),
|
|
497
|
-
"--incremental",
|
|
498
|
-
]
|
|
499
|
-
# Three-tier: --quiet (silent) / default (filtered progress) / --verbose (raw).
|
|
500
|
-
# Default passes --verbose so the builder emits per-pass progress lines,
|
|
501
|
-
# which the parent filters via _LineFilter. --verbose bypasses the filter.
|
|
502
|
-
if verbose or not quiet:
|
|
503
|
-
cmd.append("--verbose")
|
|
504
|
-
if quiet:
|
|
505
|
-
return subprocess.run(
|
|
506
|
-
cmd,
|
|
507
|
-
cwd=str(source_root),
|
|
508
|
-
env=env or os.environ.copy(),
|
|
509
|
-
capture_output=True,
|
|
510
|
-
text=True,
|
|
511
|
-
)
|
|
512
|
-
proc = subprocess.Popen(
|
|
513
|
-
cmd,
|
|
514
|
-
cwd=str(source_root),
|
|
515
|
-
env=env or os.environ.copy(),
|
|
516
|
-
stdout=subprocess.PIPE,
|
|
517
|
-
stderr=subprocess.PIPE,
|
|
518
|
-
bufsize=0,
|
|
519
|
-
)
|
|
520
|
-
out_s, err_s, code = _popen_capturing_stderr(
|
|
521
|
-
proc, verbose=verbose, on_progress=on_progress, on_progress_console=on_progress_console
|
|
522
|
-
)
|
|
523
|
-
if not verbose:
|
|
524
|
-
from java_codebase_rag.cli_format import bold_cyan, styled_check, styled_cross
|
|
525
|
-
marker = styled_check() if code == 0 else styled_cross()
|
|
526
|
-
print(f"{marker} {bold_cyan('[increment]')} done", file=sys.stderr, flush=True)
|
|
527
|
-
return subprocess.CompletedProcess(args=cmd, returncode=code, stdout=out_s, stderr=err_s)
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
def clip(s: str, n: int) -> str:
|
|
531
|
-
return s[-n:] if len(s) > n else s
|