java-codebase-rag 0.11.2__py3-none-any.whl → 0.12.1__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.1.dist-info/METADATA +35 -0
- java_codebase_rag-0.12.1.dist-info/RECORD +4 -0
- {java_codebase_rag-0.11.2.dist-info → java_codebase_rag-0.12.1.dist-info}/WHEEL +1 -1
- 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 -2825
- java_codebase_rag/ast/brownfield_events.py +0 -58
- java_codebase_rag/ast/chunk_heuristics.py +0 -62
- java_codebase_rag/cli.py +0 -1215
- 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 -4471
- java_codebase_rag/graph/graph_enrich.py +0 -1937
- 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 -477
- java_codebase_rag/index/__init__.py +0 -0
- java_codebase_rag/index/java_index_flow_lancedb.py +0 -734
- 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 -108
- 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 -4531
- 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 -884
- 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 -523
- 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 -370
- java_codebase_rag-0.11.2.dist-info/METADATA +0 -331
- java_codebase_rag-0.11.2.dist-info/RECORD +0 -71
- java_codebase_rag-0.11.2.dist-info/entry_points.txt +0 -4
- java_codebase_rag-0.11.2.dist-info/licenses/LICENSE +0 -21
- java_codebase_rag-0.11.2.dist-info/top_level.txt +0 -1
- /java_codebase_rag/__init__.py → /java_codebase_rag-0.12.1.dist-info/top_level.txt +0 -0
java_codebase_rag/cli.py
DELETED
|
@@ -1,1215 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
# Heavy imports (`server`, `pr_analysis`, `path_filtering.LayeredIgnore`,
|
|
4
|
-
# `build_ast_graph`) stay lazy inside handlers so `java-codebase-rag --help` stays fast.
|
|
5
|
-
|
|
6
|
-
import argparse
|
|
7
|
-
import asyncio
|
|
8
|
-
import json
|
|
9
|
-
import os
|
|
10
|
-
import pprint
|
|
11
|
-
import shutil
|
|
12
|
-
import sys
|
|
13
|
-
import time
|
|
14
|
-
from pathlib import Path
|
|
15
|
-
from typing import Any, Callable
|
|
16
|
-
|
|
17
|
-
from java_codebase_rag.config import (
|
|
18
|
-
OPERATOR_OWNED_INDEX_FILES,
|
|
19
|
-
ResolvedOperatorConfig,
|
|
20
|
-
describe_path_sizes,
|
|
21
|
-
emit_legacy_env_hints_if_present,
|
|
22
|
-
emit_legacy_yaml_hint_if_needed,
|
|
23
|
-
index_dir_has_existing_artifacts,
|
|
24
|
-
resolve_operator_config,
|
|
25
|
-
write_config_source_pointer,
|
|
26
|
-
)
|
|
27
|
-
from java_codebase_rag._fdlimit import raise_fd_limit
|
|
28
|
-
from java_codebase_rag._version import version_string
|
|
29
|
-
from java_codebase_rag.pipeline import (
|
|
30
|
-
VECTORS_SKIPPED_GRAPH_ONLY as _VECTORS_SKIPPED_GRAPH_ONLY,
|
|
31
|
-
clip,
|
|
32
|
-
is_cocoindex_preflight_blocker,
|
|
33
|
-
is_graph_preflight_blocker,
|
|
34
|
-
run_build_ast_graph,
|
|
35
|
-
run_cocoindex_drop,
|
|
36
|
-
run_cocoindex_update,
|
|
37
|
-
run_incremental_graph,
|
|
38
|
-
)
|
|
39
|
-
from java_codebase_rag.graph.java_ontology import VALID_UNRESOLVED_CALL_REASONS
|
|
40
|
-
|
|
41
|
-
LADYBUG_INCREMENTAL_TRACKING_ISSUE_URL = "https://github.com/HumanBean17/java-codebase-rag/issues/73"
|
|
42
|
-
|
|
43
|
-
_INCREMENT_WARNING_LINES = (
|
|
44
|
-
"WARNING: AST graph (LadybugDB) incremental rebuild is not yet implemented.",
|
|
45
|
-
"The graph reflects the index state from the last `init` or `reprocess`,",
|
|
46
|
-
"which means `find`, `neighbors`, and `describe` may return stale results",
|
|
47
|
-
"for files changed since then.",
|
|
48
|
-
"",
|
|
49
|
-
"Lance vector index has been updated incrementally and is current.",
|
|
50
|
-
"",
|
|
51
|
-
"For an up-to-date graph, run:",
|
|
52
|
-
" java-codebase-rag reprocess",
|
|
53
|
-
"",
|
|
54
|
-
"Track progress on LadybugDB incremental rebuild:",
|
|
55
|
-
f" {LADYBUG_INCREMENTAL_TRACKING_ISSUE_URL}",
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
_REFRESH_DEPRECATION = (
|
|
59
|
-
"WARN: 'refresh' is deprecated; use 'reprocess'. "
|
|
60
|
-
"This alias will be removed in the next release."
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
_REPROCESS_DRIFT_VECTORS_ONLY = (
|
|
64
|
-
"java-codebase-rag reprocess: rebuilt vectors only; graph (code_graph.lbug) was NOT rebuilt "
|
|
65
|
-
"and may now reflect a stale source snapshot."
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def _reprocess_drift_graph_only_line(index_dir: Path) -> str:
|
|
70
|
-
return (
|
|
71
|
-
"java-codebase-rag reprocess: rebuilt graph only; vectors (Lance tables under "
|
|
72
|
-
f"{index_dir}) were NOT rebuilt and may now reflect a stale source snapshot."
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
def _reprocess_exit_code(payload: dict[str, Any]) -> int:
|
|
77
|
-
if payload.get("success"):
|
|
78
|
-
return 0
|
|
79
|
-
phases_run = payload.get("phases_run") or []
|
|
80
|
-
if not phases_run:
|
|
81
|
-
return 2
|
|
82
|
-
return 1
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
# Preflight detection delegates to pipeline.is_cocoindex_preflight_blocker /
|
|
86
|
-
# is_graph_preflight_blocker, which are co-located with the stub CompletedProcess shapes
|
|
87
|
-
# they must match (missing cocoindex / flow / build_ast_graph.py).
|
|
88
|
-
def _is_cocoindex_preflight_blocker(coco: Any) -> bool:
|
|
89
|
-
"""True when ``run_cocoindex_update`` returned without spawning cocoindex."""
|
|
90
|
-
return is_cocoindex_preflight_blocker(coco)
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def _is_graph_preflight_blocker(g: Any) -> bool:
|
|
94
|
-
"""True when ``run_build_ast_graph`` returned without spawning the builder."""
|
|
95
|
-
return is_graph_preflight_blocker(g)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def _emit_reprocess_selective_tty(*, mode: str) -> None:
|
|
99
|
-
if mode == "vectors":
|
|
100
|
-
print("Rebuilt: vectors")
|
|
101
|
-
print("Skipped: graph (use `java-codebase-rag reprocess --graph-only` or `reprocess` to refresh)")
|
|
102
|
-
else:
|
|
103
|
-
print("Rebuilt: graph")
|
|
104
|
-
print("Skipped: vectors (use `java-codebase-rag reprocess --vectors-only` or `reprocess` to refresh)")
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def _reprocess_success_message(mode: str | None, payload: dict[str, Any]) -> str:
|
|
108
|
-
"""Concise message for a successful reprocess.
|
|
109
|
-
|
|
110
|
-
Prefers an explicit ``message`` set by the pipeline (e.g. the graph-only-
|
|
111
|
-
install note from ``run_refresh_pipeline``); otherwise derives from the
|
|
112
|
-
selective mode. Mirrors ``init``/``increment``, which emit a one-line
|
|
113
|
-
success payload rather than re-dumping captured subprocess logs.
|
|
114
|
-
"""
|
|
115
|
-
explicit = payload.get("message")
|
|
116
|
-
if isinstance(explicit, str) and explicit:
|
|
117
|
-
return explicit
|
|
118
|
-
if mode == "vectors":
|
|
119
|
-
return "reprocess completed (vectors only; graph not rebuilt)"
|
|
120
|
-
if mode == "graph":
|
|
121
|
-
return "reprocess completed (graph only; vectors not rebuilt)"
|
|
122
|
-
return "reprocess completed"
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
def _emit_reprocess_outcome(payload: dict[str, Any], *, selective_tty_mode: str | None = None) -> None:
|
|
126
|
-
if not payload.get("success"):
|
|
127
|
-
# Failure: emit the full payload so captured subprocess stdout/stderr
|
|
128
|
-
# (cocoindex/graph logs) survive for debugging.
|
|
129
|
-
_emit(payload)
|
|
130
|
-
return
|
|
131
|
-
# Success: the progress renderer / stderr already surfaced the phase logs,
|
|
132
|
-
# so emit a concise structured payload (matching init/increment) instead of
|
|
133
|
-
# re-dumping them as JSON noise. In a TTY the partial modes additionally
|
|
134
|
-
# print a one-line Rebuilt/Skipped summary.
|
|
135
|
-
if selective_tty_mode and sys.stdout.isatty():
|
|
136
|
-
_emit_reprocess_selective_tty(mode=selective_tty_mode)
|
|
137
|
-
return
|
|
138
|
-
_emit({"success": True, "message": _reprocess_success_message(selective_tty_mode, payload)})
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
_PIPELINE_SEP = "\u00b7"
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
def _pipeline_header(subcommand: str, cfg: ResolvedOperatorConfig) -> None:
|
|
145
|
-
from java_codebase_rag.cli_format import bold
|
|
146
|
-
|
|
147
|
-
root = cfg.source_root.resolve()
|
|
148
|
-
idx = cfg.index_dir.resolve()
|
|
149
|
-
print(
|
|
150
|
-
bold(f"java-codebase-rag {subcommand} {_PIPELINE_SEP} source={root} {_PIPELINE_SEP} index={idx}"),
|
|
151
|
-
file=sys.stderr,
|
|
152
|
-
flush=True,
|
|
153
|
-
)
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
def _pipeline_footer(subcommand: str, started: float, exit_code: int) -> None:
|
|
157
|
-
from java_codebase_rag.cli_format import bold, styled_check, styled_cross
|
|
158
|
-
|
|
159
|
-
elapsed = time.perf_counter() - started
|
|
160
|
-
marker = styled_check() if exit_code == 0 else styled_cross()
|
|
161
|
-
print(
|
|
162
|
-
f"{marker} {bold(f'java-codebase-rag {subcommand} {_PIPELINE_SEP} finished in {elapsed:.2f}s')}"
|
|
163
|
-
+ (f" (exit={exit_code})" if exit_code != 0 else ""),
|
|
164
|
-
file=sys.stderr,
|
|
165
|
-
flush=True,
|
|
166
|
-
)
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
# Subcommands that build/refresh an index and therefore should record which YAML
|
|
170
|
-
# was used (so a later discovery run from a sibling/cwd can relocate it).
|
|
171
|
-
_CONFIG_SOURCE_RECORDING_SUBCOMMANDS = frozenset({"init", "increment", "reprocess"})
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
def _maybe_record_config_source(
|
|
175
|
-
subcommand: str, cfg: ResolvedOperatorConfig, code: int
|
|
176
|
-
) -> None:
|
|
177
|
-
"""On a successful index build, remember the YAML path inside the index dir."""
|
|
178
|
-
if code == 0 and subcommand in _CONFIG_SOURCE_RECORDING_SUBCOMMANDS:
|
|
179
|
-
write_config_source_pointer(
|
|
180
|
-
index_dir=cfg.index_dir, yaml_config_path=cfg.yaml_config_path
|
|
181
|
-
)
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
def _run_with_pipeline_progress(
|
|
185
|
-
subcommand: str,
|
|
186
|
-
cfg: ResolvedOperatorConfig,
|
|
187
|
-
*,
|
|
188
|
-
quiet: bool,
|
|
189
|
-
verbose: bool = False,
|
|
190
|
-
work: Callable[["PipelineProgress | None"], int],
|
|
191
|
-
) -> int:
|
|
192
|
-
"""Run ``work`` under the unified progress renderer (default TTY mode only).
|
|
193
|
-
|
|
194
|
-
``work`` receives a :class:`PipelineProgress` whose ``on_progress`` callback
|
|
195
|
-
should be forwarded to the graph/vectors pipeline helpers so their
|
|
196
|
-
``JCIRAG_PROGRESS`` events feed the renderer. In ``--quiet`` or ``--verbose``
|
|
197
|
-
mode the context is ``None`` (no Live region: quiet is silent, verbose
|
|
198
|
-
raw-relays subprocess output).
|
|
199
|
-
"""
|
|
200
|
-
if quiet or verbose:
|
|
201
|
-
code = int(work(None))
|
|
202
|
-
_maybe_record_config_source(subcommand, cfg, code)
|
|
203
|
-
return code
|
|
204
|
-
from java_codebase_rag.progress import build_index_progress_context
|
|
205
|
-
|
|
206
|
-
# PR-3 owns all three tasks in order: Vectors → Optimize → Graph. The vectors
|
|
207
|
-
# task is fed by the cocoindex child's per-file ticks + approximate total
|
|
208
|
-
# (subprocess transport, parsed by ProgressRelay); the optimize task is fed
|
|
209
|
-
# in-process by lance_optimize; the graph task is fed by the build_ast_graph
|
|
210
|
-
# child (subprocess transport). A task only becomes visible/running once its
|
|
211
|
-
# first event arrives.
|
|
212
|
-
renderer, on_progress, console = build_index_progress_context()
|
|
213
|
-
progress = PipelineProgress(renderer=renderer)
|
|
214
|
-
progress.on_progress = on_progress
|
|
215
|
-
progress.console = console
|
|
216
|
-
|
|
217
|
-
_pipeline_header(subcommand, cfg)
|
|
218
|
-
t0 = time.perf_counter()
|
|
219
|
-
code = 0
|
|
220
|
-
# start() always flips _started (the non-TTY fallback is a no-op for Live but
|
|
221
|
-
# still needs the flag so apply() routes to the concise-line printer). The
|
|
222
|
-
# TTY Live region is entered inside start() only when the console is a TTY.
|
|
223
|
-
renderer.start()
|
|
224
|
-
try:
|
|
225
|
-
code = int(work(progress))
|
|
226
|
-
return code
|
|
227
|
-
except BaseException as exc:
|
|
228
|
-
# Keep footer aligned with process outcome (main maps unhandled Exception -> exit 2).
|
|
229
|
-
if isinstance(exc, SystemExit):
|
|
230
|
-
c = exc.code
|
|
231
|
-
if isinstance(c, int):
|
|
232
|
-
code = c
|
|
233
|
-
elif c in (None, False):
|
|
234
|
-
code = 0
|
|
235
|
-
else:
|
|
236
|
-
code = 1
|
|
237
|
-
elif code == 0:
|
|
238
|
-
code = 2
|
|
239
|
-
raise
|
|
240
|
-
finally:
|
|
241
|
-
renderer.stop()
|
|
242
|
-
_pipeline_footer(subcommand, t0, code)
|
|
243
|
-
_maybe_record_config_source(subcommand, cfg, code)
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
class PipelineProgress:
|
|
247
|
-
"""Progress context handed to ``work``: the renderer + a ready ``on_progress``.
|
|
248
|
-
|
|
249
|
-
``on_progress``/``console`` are wired by :func:`_run_with_pipeline_progress`
|
|
250
|
-
and should be forwarded to the pipeline helpers' ``on_progress`` /
|
|
251
|
-
``on_progress_console`` parameters. ``console`` is the renderer's stderr
|
|
252
|
-
``rich.Console`` so the subprocess drain routes non-progress lines through
|
|
253
|
-
``console.print`` while the Live region is up (single-writer invariant).
|
|
254
|
-
"""
|
|
255
|
-
|
|
256
|
-
def __init__(self, *, renderer: "object | None") -> None:
|
|
257
|
-
self.renderer = renderer
|
|
258
|
-
self.on_progress: "Callable | None" = None
|
|
259
|
-
self.console: "object | None" = None
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
def _jsonable(value: Any) -> Any:
|
|
263
|
-
if hasattr(value, "model_dump"):
|
|
264
|
-
return value.model_dump()
|
|
265
|
-
if isinstance(value, Path):
|
|
266
|
-
return str(value)
|
|
267
|
-
raise TypeError(f"Object of type {type(value).__name__} is not JSON serializable")
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
def _to_payload(value: Any) -> Any:
|
|
271
|
-
if hasattr(value, "model_dump"):
|
|
272
|
-
return value.model_dump()
|
|
273
|
-
return value
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
def _emit(value: Any) -> None:
|
|
277
|
-
payload = _to_payload(value)
|
|
278
|
-
if sys.stdout.isatty():
|
|
279
|
-
print(pprint.pformat(payload, sort_dicts=True))
|
|
280
|
-
return
|
|
281
|
-
print(json.dumps(payload, default=_jsonable, sort_keys=True, indent=None))
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
def _emit_increment_ladybug_warning() -> None:
|
|
285
|
-
for line in _INCREMENT_WARNING_LINES:
|
|
286
|
-
print(line, file=sys.stderr)
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
def _parse_source_root(ns: argparse.Namespace) -> Path | None:
|
|
290
|
-
if ns.source_root:
|
|
291
|
-
return Path(ns.source_root).expanduser().resolve()
|
|
292
|
-
return None
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
def _resolved_from_ns(ns: argparse.Namespace) -> ResolvedOperatorConfig:
|
|
296
|
-
root = _parse_source_root(ns)
|
|
297
|
-
return resolve_operator_config(
|
|
298
|
-
source_root=root,
|
|
299
|
-
cli_index_dir=ns.index_dir,
|
|
300
|
-
cli_embedding_model=getattr(ns, "embedding_model", None),
|
|
301
|
-
cli_embedding_device=getattr(ns, "embedding_device", None),
|
|
302
|
-
)
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
def _startup_hints(cfg: ResolvedOperatorConfig) -> None:
|
|
306
|
-
emit_legacy_env_hints_if_present()
|
|
307
|
-
emit_legacy_yaml_hint_if_needed(cfg.source_root)
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
def _add_index_embedding_flags(p: argparse.ArgumentParser) -> None:
|
|
311
|
-
p.add_argument("--source-root", type=str, default=None, help="Java repository root (default: cwd)")
|
|
312
|
-
p.add_argument("--index-dir", type=str, default=None, help="Index directory (Lance + LadybugDB + cocoindex state)")
|
|
313
|
-
p.add_argument("--embedding-model", type=str, default=None, help="Override SBERT_MODEL / YAML embedding.model")
|
|
314
|
-
p.add_argument("--embedding-device", type=str, default=None, help="Override SBERT_DEVICE / YAML embedding.device")
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
def _add_verbosity_flags(p: argparse.ArgumentParser) -> None:
|
|
318
|
-
g = p.add_mutually_exclusive_group()
|
|
319
|
-
g.add_argument(
|
|
320
|
-
"--quiet", "-q",
|
|
321
|
-
action="store_true",
|
|
322
|
-
dest="quiet",
|
|
323
|
-
help="Suppress stderr progress relay; stdout payload unchanged.",
|
|
324
|
-
)
|
|
325
|
-
g.add_argument(
|
|
326
|
-
"--verbose", "-v",
|
|
327
|
-
action="store_true",
|
|
328
|
-
dest="verbose",
|
|
329
|
-
help="Show full subprocess output (Lance warnings, brownfield events, progress bars).",
|
|
330
|
-
)
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
def _cmd_init(args: argparse.Namespace) -> int:
|
|
334
|
-
cfg = _resolved_from_ns(args)
|
|
335
|
-
# Check for parent config or index
|
|
336
|
-
from java_codebase_rag.config import discover_project_root, find_yaml_config_file
|
|
337
|
-
parent_config_dir = discover_project_root(cfg.source_root.parent)
|
|
338
|
-
if parent_config_dir is not None:
|
|
339
|
-
parent_config = find_yaml_config_file(parent_config_dir)
|
|
340
|
-
if parent_config is not None:
|
|
341
|
-
print(
|
|
342
|
-
f"Warning: found existing config at {parent_config}. "
|
|
343
|
-
f"Creating a new project here will create a separate index.",
|
|
344
|
-
file=sys.stderr,
|
|
345
|
-
)
|
|
346
|
-
else:
|
|
347
|
-
print(
|
|
348
|
-
f"Warning: found existing index at {parent_config_dir / '.java-codebase-rag'}. "
|
|
349
|
-
f"Creating a new project here will create a separate index.",
|
|
350
|
-
file=sys.stderr,
|
|
351
|
-
)
|
|
352
|
-
_startup_hints(cfg)
|
|
353
|
-
cfg.apply_to_os_environ()
|
|
354
|
-
occupied, paths = index_dir_has_existing_artifacts(cfg.index_dir)
|
|
355
|
-
if occupied:
|
|
356
|
-
_emit(
|
|
357
|
-
{
|
|
358
|
-
"success": False,
|
|
359
|
-
"message": (
|
|
360
|
-
"init refused: index paths already exist. "
|
|
361
|
-
"Use `java-codebase-rag reprocess` to rebuild in place, "
|
|
362
|
-
"or `java-codebase-rag erase --yes` then `init` for a clean slate."
|
|
363
|
-
),
|
|
364
|
-
"non_empty_paths": paths,
|
|
365
|
-
}
|
|
366
|
-
)
|
|
367
|
-
return 2
|
|
368
|
-
cfg.index_dir.mkdir(parents=True, exist_ok=True)
|
|
369
|
-
|
|
370
|
-
def work(progress: "PipelineProgress | None") -> int:
|
|
371
|
-
env = cfg.subprocess_env()
|
|
372
|
-
verbose = bool(args.verbose)
|
|
373
|
-
coco = run_cocoindex_update(
|
|
374
|
-
env,
|
|
375
|
-
full_reprocess=False,
|
|
376
|
-
quiet=bool(args.quiet),
|
|
377
|
-
verbose=verbose,
|
|
378
|
-
lance_project_root=None if args.quiet else cfg.source_root,
|
|
379
|
-
on_progress=progress.on_progress if progress is not None else None,
|
|
380
|
-
on_progress_console=progress.console if progress is not None else None,
|
|
381
|
-
)
|
|
382
|
-
# Graph-only install (cocoindex absent, e.g. macOS Intel): skip the vectors phase
|
|
383
|
-
# and proceed to the graph build rather than failing — the graph layer is the
|
|
384
|
-
# supported surface there. A genuine non-zero cocoindex exit still fails.
|
|
385
|
-
vectors_skipped = _is_cocoindex_preflight_blocker(coco)
|
|
386
|
-
if coco.returncode != 0 and not vectors_skipped:
|
|
387
|
-
_emit(
|
|
388
|
-
{
|
|
389
|
-
"success": False,
|
|
390
|
-
"exit_code": coco.returncode,
|
|
391
|
-
"stdout": clip(coco.stdout, 8000),
|
|
392
|
-
"stderr": clip(coco.stderr, 8000),
|
|
393
|
-
"message": f"cocoindex exit {coco.returncode}",
|
|
394
|
-
}
|
|
395
|
-
)
|
|
396
|
-
return 1
|
|
397
|
-
if vectors_skipped:
|
|
398
|
-
print(_VECTORS_SKIPPED_GRAPH_ONLY, file=sys.stderr, flush=True)
|
|
399
|
-
if not args.quiet:
|
|
400
|
-
print(file=sys.stderr, flush=True)
|
|
401
|
-
g = run_build_ast_graph(
|
|
402
|
-
source_root=cfg.source_root,
|
|
403
|
-
ladybug_path=cfg.ladybug_path,
|
|
404
|
-
verbose=verbose,
|
|
405
|
-
quiet=bool(args.quiet),
|
|
406
|
-
env=env,
|
|
407
|
-
on_progress=progress.on_progress if progress is not None else None,
|
|
408
|
-
on_progress_console=progress.console if progress is not None else None,
|
|
409
|
-
)
|
|
410
|
-
if g.returncode != 0:
|
|
411
|
-
_emit(
|
|
412
|
-
{
|
|
413
|
-
"success": False,
|
|
414
|
-
"exit_code": g.returncode,
|
|
415
|
-
"stdout": clip(g.stdout, 4000),
|
|
416
|
-
"stderr": clip(g.stderr, 4000),
|
|
417
|
-
"message": f"graph builder exit {g.returncode}",
|
|
418
|
-
}
|
|
419
|
-
)
|
|
420
|
-
return 1
|
|
421
|
-
_emit(
|
|
422
|
-
{
|
|
423
|
-
"success": True,
|
|
424
|
-
"message": (
|
|
425
|
-
"init completed (graph-only; vectors skipped — vector stack not installed)"
|
|
426
|
-
if vectors_skipped
|
|
427
|
-
else "init completed"
|
|
428
|
-
),
|
|
429
|
-
}
|
|
430
|
-
)
|
|
431
|
-
return 0
|
|
432
|
-
|
|
433
|
-
return _run_with_pipeline_progress(
|
|
434
|
-
"init", cfg, quiet=bool(args.quiet), verbose=bool(args.verbose), work=work
|
|
435
|
-
)
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
def _cmd_increment(args: argparse.Namespace) -> int:
|
|
439
|
-
cfg = _resolved_from_ns(args)
|
|
440
|
-
_startup_hints(cfg)
|
|
441
|
-
cfg.apply_to_os_environ()
|
|
442
|
-
|
|
443
|
-
# Check for --vectors-only flag
|
|
444
|
-
vectors_only = bool(getattr(args, "vectors_only", False))
|
|
445
|
-
if vectors_only:
|
|
446
|
-
_emit_increment_ladybug_warning()
|
|
447
|
-
|
|
448
|
-
def work(progress: "PipelineProgress | None") -> int:
|
|
449
|
-
env = cfg.subprocess_env()
|
|
450
|
-
coco = run_cocoindex_update(
|
|
451
|
-
env,
|
|
452
|
-
full_reprocess=False,
|
|
453
|
-
quiet=bool(args.quiet),
|
|
454
|
-
verbose=bool(args.verbose),
|
|
455
|
-
lance_project_root=None if args.quiet else cfg.source_root,
|
|
456
|
-
on_progress=progress.on_progress if progress is not None else None,
|
|
457
|
-
on_progress_console=progress.console if progress is not None else None,
|
|
458
|
-
)
|
|
459
|
-
vectors_skipped = _is_cocoindex_preflight_blocker(coco)
|
|
460
|
-
if coco.returncode != 0 and not vectors_skipped:
|
|
461
|
-
_emit(
|
|
462
|
-
{
|
|
463
|
-
"success": False,
|
|
464
|
-
"exit_code": coco.returncode,
|
|
465
|
-
"stdout": clip(coco.stdout, 8000),
|
|
466
|
-
"stderr": clip(coco.stderr, 8000),
|
|
467
|
-
"message": f"cocoindex exit {coco.returncode}",
|
|
468
|
-
}
|
|
469
|
-
)
|
|
470
|
-
return 1
|
|
471
|
-
if vectors_skipped:
|
|
472
|
-
print(_VECTORS_SKIPPED_GRAPH_ONLY, file=sys.stderr, flush=True)
|
|
473
|
-
|
|
474
|
-
# If --vectors-only is set, skip graph update
|
|
475
|
-
if vectors_only:
|
|
476
|
-
if vectors_skipped:
|
|
477
|
-
_emit(
|
|
478
|
-
{
|
|
479
|
-
"success": True,
|
|
480
|
-
"message": "increment skipped: vector stack not installed (graph-only mode)",
|
|
481
|
-
}
|
|
482
|
-
)
|
|
483
|
-
return 0
|
|
484
|
-
_emit({"success": True, "message": "increment completed (Lance only; graph may be stale — see stderr)"})
|
|
485
|
-
return 0
|
|
486
|
-
|
|
487
|
-
# Run incremental graph update
|
|
488
|
-
g = run_incremental_graph(
|
|
489
|
-
source_root=cfg.source_root,
|
|
490
|
-
ladybug_path=cfg.ladybug_path,
|
|
491
|
-
verbose=bool(args.verbose),
|
|
492
|
-
quiet=bool(args.quiet),
|
|
493
|
-
env=env,
|
|
494
|
-
on_progress=progress.on_progress if progress is not None else None,
|
|
495
|
-
on_progress_console=progress.console if progress is not None else None,
|
|
496
|
-
)
|
|
497
|
-
|
|
498
|
-
# Check if incremental fell back to full rebuild
|
|
499
|
-
if g.returncode == 0 and g.stdout:
|
|
500
|
-
# Parse stdout to check for full_fallback mode
|
|
501
|
-
# The incremental_rebuild function returns a JSON payload with mode field
|
|
502
|
-
try:
|
|
503
|
-
result = json.loads(g.stdout.strip())
|
|
504
|
-
if result.get("mode") == "full_fallback":
|
|
505
|
-
print(
|
|
506
|
-
"[increment] fell back to full graph rebuild — this is normal after schema changes or first run",
|
|
507
|
-
file=sys.stderr,
|
|
508
|
-
flush=True,
|
|
509
|
-
)
|
|
510
|
-
except (json.JSONDecodeError, ValueError):
|
|
511
|
-
# If parsing fails, continue silently
|
|
512
|
-
pass
|
|
513
|
-
|
|
514
|
-
if g.returncode != 0:
|
|
515
|
-
_emit(
|
|
516
|
-
{
|
|
517
|
-
"success": False,
|
|
518
|
-
"exit_code": g.returncode,
|
|
519
|
-
"stdout": clip(g.stdout, 4000),
|
|
520
|
-
"stderr": clip(g.stderr, 4000),
|
|
521
|
-
"message": f"graph builder exit {g.returncode}",
|
|
522
|
-
}
|
|
523
|
-
)
|
|
524
|
-
return 1
|
|
525
|
-
|
|
526
|
-
_emit(
|
|
527
|
-
{
|
|
528
|
-
"success": True,
|
|
529
|
-
"message": (
|
|
530
|
-
"increment completed (graph only; vectors skipped — vector stack not installed)"
|
|
531
|
-
if vectors_skipped
|
|
532
|
-
else "increment completed (Lance + graph updated)"
|
|
533
|
-
),
|
|
534
|
-
}
|
|
535
|
-
)
|
|
536
|
-
return 0
|
|
537
|
-
|
|
538
|
-
return _run_with_pipeline_progress(
|
|
539
|
-
"increment", cfg, quiet=bool(args.quiet), verbose=bool(args.verbose), work=work
|
|
540
|
-
)
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
def _cmd_reprocess(args: argparse.Namespace) -> int:
|
|
544
|
-
cfg = _resolved_from_ns(args)
|
|
545
|
-
_startup_hints(cfg)
|
|
546
|
-
cfg.apply_to_os_environ()
|
|
547
|
-
|
|
548
|
-
def work(progress: "PipelineProgress | None") -> int:
|
|
549
|
-
env = cfg.subprocess_env()
|
|
550
|
-
verbose = bool(args.verbose)
|
|
551
|
-
vectors_only = bool(getattr(args, "vectors_only", False))
|
|
552
|
-
graph_only = bool(getattr(args, "graph_only", False))
|
|
553
|
-
|
|
554
|
-
if vectors_only:
|
|
555
|
-
coco = run_cocoindex_update(
|
|
556
|
-
env, full_reprocess=True, quiet=bool(args.quiet), verbose=verbose,
|
|
557
|
-
on_progress=progress.on_progress if progress is not None else None,
|
|
558
|
-
on_progress_console=progress.console if progress is not None else None,
|
|
559
|
-
)
|
|
560
|
-
if _is_cocoindex_preflight_blocker(coco):
|
|
561
|
-
payload: dict[str, Any] = {
|
|
562
|
-
"success": False,
|
|
563
|
-
"exit_code": None,
|
|
564
|
-
"stdout": clip(coco.stdout, 8000),
|
|
565
|
-
"stderr": clip(coco.stderr, 8000),
|
|
566
|
-
"message": coco.stderr.strip() or f"cocoindex setup exit {coco.returncode}",
|
|
567
|
-
"graph_exit_code": None,
|
|
568
|
-
"graph_stdout": "",
|
|
569
|
-
"graph_stderr": "",
|
|
570
|
-
"phases_run": [],
|
|
571
|
-
}
|
|
572
|
-
_emit_reprocess_outcome(payload)
|
|
573
|
-
return _reprocess_exit_code(payload)
|
|
574
|
-
ok = coco.returncode == 0
|
|
575
|
-
payload = {
|
|
576
|
-
"success": ok,
|
|
577
|
-
"exit_code": coco.returncode,
|
|
578
|
-
"stdout": clip(coco.stdout, 8000),
|
|
579
|
-
"stderr": clip(coco.stderr, 8000),
|
|
580
|
-
"message": None if ok else f"cocoindex exit {coco.returncode}",
|
|
581
|
-
"graph_exit_code": None,
|
|
582
|
-
"graph_stdout": "",
|
|
583
|
-
"graph_stderr": "",
|
|
584
|
-
"phases_run": ["vectors"],
|
|
585
|
-
}
|
|
586
|
-
if ok:
|
|
587
|
-
print(_REPROCESS_DRIFT_VECTORS_ONLY, file=sys.stderr)
|
|
588
|
-
_emit_reprocess_outcome(payload, selective_tty_mode="vectors" if ok else None)
|
|
589
|
-
return _reprocess_exit_code(payload)
|
|
590
|
-
|
|
591
|
-
if graph_only:
|
|
592
|
-
g = run_build_ast_graph(
|
|
593
|
-
source_root=cfg.source_root,
|
|
594
|
-
ladybug_path=cfg.ladybug_path,
|
|
595
|
-
verbose=verbose,
|
|
596
|
-
quiet=bool(args.quiet),
|
|
597
|
-
env=env,
|
|
598
|
-
on_progress=progress.on_progress if progress is not None else None,
|
|
599
|
-
on_progress_console=progress.console if progress is not None else None,
|
|
600
|
-
)
|
|
601
|
-
if _is_graph_preflight_blocker(g):
|
|
602
|
-
payload = {
|
|
603
|
-
"success": False,
|
|
604
|
-
"exit_code": None,
|
|
605
|
-
"stdout": "",
|
|
606
|
-
"stderr": "",
|
|
607
|
-
"message": g.stderr.strip() or f"graph builder setup exit {g.returncode}",
|
|
608
|
-
"graph_exit_code": None,
|
|
609
|
-
"graph_stdout": clip(g.stdout, 4000),
|
|
610
|
-
"graph_stderr": clip(g.stderr, 4000),
|
|
611
|
-
"phases_run": [],
|
|
612
|
-
}
|
|
613
|
-
_emit_reprocess_outcome(payload)
|
|
614
|
-
return _reprocess_exit_code(payload)
|
|
615
|
-
ok = g.returncode == 0
|
|
616
|
-
payload = {
|
|
617
|
-
"success": ok,
|
|
618
|
-
"exit_code": None,
|
|
619
|
-
"stdout": "",
|
|
620
|
-
"stderr": "",
|
|
621
|
-
"message": None if ok else f"graph builder exit {g.returncode}",
|
|
622
|
-
"graph_exit_code": g.returncode,
|
|
623
|
-
"graph_stdout": clip(g.stdout, 4000),
|
|
624
|
-
"graph_stderr": clip(g.stderr, 4000),
|
|
625
|
-
"phases_run": ["graph"],
|
|
626
|
-
}
|
|
627
|
-
if ok:
|
|
628
|
-
print(_reprocess_drift_graph_only_line(cfg.index_dir), file=sys.stderr)
|
|
629
|
-
_emit_reprocess_outcome(payload, selective_tty_mode="graph" if ok else None)
|
|
630
|
-
return _reprocess_exit_code(payload)
|
|
631
|
-
|
|
632
|
-
from java_codebase_rag.mcp import server # lazy: pulls sentence_transformers/torch/lancedb/ladybug
|
|
633
|
-
|
|
634
|
-
result = asyncio.run(
|
|
635
|
-
server.run_refresh_pipeline(
|
|
636
|
-
quiet=bool(args.quiet),
|
|
637
|
-
verbose=verbose,
|
|
638
|
-
on_progress=progress.on_progress if progress is not None else None,
|
|
639
|
-
on_progress_console=progress.console if progress is not None else None,
|
|
640
|
-
)
|
|
641
|
-
)
|
|
642
|
-
payload = result.model_dump()
|
|
643
|
-
_emit_reprocess_outcome(payload)
|
|
644
|
-
return _reprocess_exit_code(payload)
|
|
645
|
-
|
|
646
|
-
return _run_with_pipeline_progress(
|
|
647
|
-
"reprocess", cfg, quiet=bool(args.quiet), verbose=bool(args.verbose), work=work
|
|
648
|
-
)
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
def _cmd_install(args: argparse.Namespace) -> int:
|
|
652
|
-
from java_codebase_rag.installer import run_install
|
|
653
|
-
|
|
654
|
-
return run_install(
|
|
655
|
-
non_interactive=bool(args.non_interactive),
|
|
656
|
-
agents=args.agent, # list of str (may be empty)
|
|
657
|
-
scope=args.scope,
|
|
658
|
-
model=args.model,
|
|
659
|
-
surface=args.surface,
|
|
660
|
-
source_root=None, # None means cwd; installer confirms interactively
|
|
661
|
-
quiet=bool(args.quiet),
|
|
662
|
-
verbose=bool(args.verbose),
|
|
663
|
-
)
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
def _cmd_update(args: argparse.Namespace) -> int:
|
|
667
|
-
from java_codebase_rag.installer import run_update
|
|
668
|
-
|
|
669
|
-
return run_update(
|
|
670
|
-
force=bool(args.force),
|
|
671
|
-
dry_run=bool(args.dry_run),
|
|
672
|
-
quiet=bool(args.quiet),
|
|
673
|
-
verbose=bool(args.verbose),
|
|
674
|
-
surface=args.surface,
|
|
675
|
-
)
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
def _rm_any(path: Path) -> None:
|
|
679
|
-
"""Remove ``path`` whether it is a regular file, directory, or symlink.
|
|
680
|
-
|
|
681
|
-
``code_graph.lbug`` is a single regular file in this repo, but kuzu may lay
|
|
682
|
-
the graph out as a directory; ``cocoindex.db`` is always a directory.
|
|
683
|
-
``shutil.rmtree`` is a silent no-op on a regular file and ``Path.unlink``
|
|
684
|
-
raises ``IsADirectoryError`` on a directory, so a type-blind delete left
|
|
685
|
-
index artifacts on disk (issue #346). A symlinked directory is unlinked, not
|
|
686
|
-
recursed into, so the link target is never followed. Failures are warned to
|
|
687
|
-
stderr rather than swallowed, so erase does not report success while leaving
|
|
688
|
-
an artifact behind (the exact failure mode issue #346 reported).
|
|
689
|
-
"""
|
|
690
|
-
try:
|
|
691
|
-
if path.is_dir() and not path.is_symlink():
|
|
692
|
-
shutil.rmtree(path)
|
|
693
|
-
elif path.exists() or path.is_symlink():
|
|
694
|
-
path.unlink(missing_ok=True)
|
|
695
|
-
except OSError as exc:
|
|
696
|
-
print(f"warning: failed to remove {path}: {exc}", file=sys.stderr)
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
def _cmd_erase(args: argparse.Namespace) -> int:
|
|
700
|
-
cfg = _resolved_from_ns(args)
|
|
701
|
-
_startup_hints(cfg)
|
|
702
|
-
cfg.apply_to_os_environ()
|
|
703
|
-
# Lazy import: build_ast_graph transitively pulls numpy/ladybug/pyarrow/
|
|
704
|
-
# tree_sitter (~54ms), and these filenames are only needed on the erase path.
|
|
705
|
-
# Keeping it out of the top-level import lets `java-codebase-rag --help` (and
|
|
706
|
-
# every other command) stay fast -- see the lazy-import invariant atop this file.
|
|
707
|
-
from java_codebase_rag.graph.build_ast_graph import BUILDER_OWNED_INDEX_FILES
|
|
708
|
-
builder_paths = [cfg.ladybug_path.parent / name for name in BUILDER_OWNED_INDEX_FILES]
|
|
709
|
-
operator_paths = [cfg.index_dir / name for name in OPERATOR_OWNED_INDEX_FILES]
|
|
710
|
-
to_describe: list[Path] = [
|
|
711
|
-
cfg.ladybug_path,
|
|
712
|
-
cfg.cocoindex_db,
|
|
713
|
-
*builder_paths,
|
|
714
|
-
*operator_paths,
|
|
715
|
-
]
|
|
716
|
-
if cfg.index_dir.is_dir():
|
|
717
|
-
try:
|
|
718
|
-
import lancedb
|
|
719
|
-
|
|
720
|
-
db = lancedb.connect(str(cfg.index_dir.resolve()))
|
|
721
|
-
for name in db.list_tables():
|
|
722
|
-
to_describe.append(cfg.index_dir / name)
|
|
723
|
-
except Exception:
|
|
724
|
-
pass
|
|
725
|
-
rows = describe_path_sizes(to_describe)
|
|
726
|
-
summary_lines = [f" {p}: {sz} bytes" for p, sz in rows] or [" (nothing to delete under resolved index dir)"]
|
|
727
|
-
print("Will delete:", file=sys.stderr)
|
|
728
|
-
print("\n".join(summary_lines), file=sys.stderr)
|
|
729
|
-
if not args.yes:
|
|
730
|
-
if not sys.stdin.isatty():
|
|
731
|
-
print(
|
|
732
|
-
"java-codebase-rag erase: non-interactive stdin; pass --yes to confirm.",
|
|
733
|
-
file=sys.stderr,
|
|
734
|
-
)
|
|
735
|
-
return 2
|
|
736
|
-
try:
|
|
737
|
-
ans = input("Delete these paths? [y/N]: ").strip().lower()
|
|
738
|
-
except EOFError:
|
|
739
|
-
# Non-interactive stdin that nonetheless reported isatty() == True
|
|
740
|
-
# (the Windows NUL device is a character device, so isatty() lies).
|
|
741
|
-
# Treat it as a refusal instead of crashing with an EOF traceback.
|
|
742
|
-
print(
|
|
743
|
-
"java-codebase-rag erase: non-interactive stdin; pass --yes to confirm.",
|
|
744
|
-
file=sys.stderr,
|
|
745
|
-
)
|
|
746
|
-
return 2
|
|
747
|
-
if ans not in ("y", "yes"):
|
|
748
|
-
print("Aborted.", file=sys.stderr)
|
|
749
|
-
return 2
|
|
750
|
-
|
|
751
|
-
def work(progress: "PipelineProgress | None") -> int:
|
|
752
|
-
env = cfg.subprocess_env()
|
|
753
|
-
drop = run_cocoindex_drop(env, quiet=bool(args.quiet))
|
|
754
|
-
if drop.returncode == 127:
|
|
755
|
-
print(
|
|
756
|
-
"java-codebase-rag erase: cocoindex CLI not found next to this Python; "
|
|
757
|
-
"skipped `cocoindex drop` — cocoindex.db (if any) was not removed by CocoIndex.",
|
|
758
|
-
file=sys.stderr,
|
|
759
|
-
)
|
|
760
|
-
elif drop.returncode != 0:
|
|
761
|
-
print(clip(drop.stderr, 4000), file=sys.stderr)
|
|
762
|
-
# Remove the LadybugDB graph, the cocoindex state store, and every
|
|
763
|
-
# builder-owned bookkeeping file next to code_graph.lbug (the content-hash
|
|
764
|
-
# store, its atomic-write temp, and the incremental crash marker). Each is
|
|
765
|
-
# removed by type (see _rm_any): code_graph.lbug is a file here but may be
|
|
766
|
-
# a dir under kuzu, while cocoindex.db is a directory — a type-blind delete
|
|
767
|
-
# silently no-oped on one or the other, and the builder files were never
|
|
768
|
-
# targeted at all (issues #346 / #349 / #350). The list comes from
|
|
769
|
-
# build_ast_graph.BUILDER_OWNED_INDEX_FILES so erase and the builder cannot drift.
|
|
770
|
-
_rm_any(cfg.ladybug_path)
|
|
771
|
-
_rm_any(cfg.cocoindex_db)
|
|
772
|
-
for builder_path in builder_paths:
|
|
773
|
-
_rm_any(builder_path)
|
|
774
|
-
# Operator-owned index files (the config_source pointer recording which
|
|
775
|
-
# YAML built this index) — owned by the CLI/installer, not the graph
|
|
776
|
-
# builder, so removed here rather than via BUILDER_OWNED_INDEX_FILES.
|
|
777
|
-
for operator_path in operator_paths:
|
|
778
|
-
_rm_any(operator_path)
|
|
779
|
-
if cfg.index_dir.is_dir():
|
|
780
|
-
try:
|
|
781
|
-
import lancedb
|
|
782
|
-
|
|
783
|
-
db = lancedb.connect(str(cfg.index_dir.resolve()))
|
|
784
|
-
for name in list(db.list_tables()):
|
|
785
|
-
try:
|
|
786
|
-
db.drop_table(name)
|
|
787
|
-
except Exception as exc:
|
|
788
|
-
print(f"warning: failed to drop Lance table {name!r}: {exc}", file=sys.stderr)
|
|
789
|
-
except Exception:
|
|
790
|
-
pass
|
|
791
|
-
_emit({"success": True, "message": "erase completed"})
|
|
792
|
-
return 0
|
|
793
|
-
|
|
794
|
-
return _run_with_pipeline_progress("erase", cfg, quiet=bool(args.quiet), verbose=bool(getattr(args, "verbose", False)), work=work)
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
def _cmd_meta(args: argparse.Namespace) -> int:
|
|
798
|
-
from java_codebase_rag.mcp import server # lazy
|
|
799
|
-
|
|
800
|
-
cfg = _resolved_from_ns(args)
|
|
801
|
-
_startup_hints(cfg)
|
|
802
|
-
cfg.apply_to_os_environ()
|
|
803
|
-
from java_codebase_rag.graph.ladybug_queries import LadybugGraph # lazy
|
|
804
|
-
|
|
805
|
-
LadybugGraph._instance = None
|
|
806
|
-
LadybugGraph._instance_path = None
|
|
807
|
-
payload = server._graph_meta_output().model_dump()
|
|
808
|
-
payload["embedding_model"] = cfg.embedding_model
|
|
809
|
-
payload["embedding_device"] = cfg.embedding_device
|
|
810
|
-
payload["embedding_model_source"] = cfg.embedding_model_source
|
|
811
|
-
payload["embedding_device_source"] = cfg.embedding_device_source
|
|
812
|
-
payload["index_dir"] = str(cfg.index_dir.resolve())
|
|
813
|
-
payload["ladybug_path"] = str(cfg.ladybug_path.resolve())
|
|
814
|
-
payload["index_dir_source"] = cfg.index_dir_source
|
|
815
|
-
payload["hints_enabled"] = cfg.hints_enabled
|
|
816
|
-
payload["hints_enabled_source"] = cfg.hints_enabled_source
|
|
817
|
-
_emit(payload)
|
|
818
|
-
return 0 if payload.get("success") else 2
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
def _cmd_tables(args: argparse.Namespace) -> int:
|
|
822
|
-
from java_codebase_rag.mcp import server # lazy
|
|
823
|
-
|
|
824
|
-
cfg = _resolved_from_ns(args)
|
|
825
|
-
_startup_hints(cfg)
|
|
826
|
-
cfg.apply_to_os_environ()
|
|
827
|
-
payload = server.list_code_index_tables_payload().model_dump()
|
|
828
|
-
_emit(payload)
|
|
829
|
-
return 0
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
def _cmd_diagnose_ignore(args: argparse.Namespace) -> int:
|
|
833
|
-
from java_codebase_rag.mcp import server # lazy
|
|
834
|
-
from java_codebase_rag.graph.path_filtering import LayeredIgnore # lazy
|
|
835
|
-
|
|
836
|
-
cfg = _resolved_from_ns(args)
|
|
837
|
-
_startup_hints(cfg)
|
|
838
|
-
cfg.apply_to_os_environ()
|
|
839
|
-
root = server._project_root()
|
|
840
|
-
raw = Path(args.path)
|
|
841
|
-
try:
|
|
842
|
-
abs_path = raw.resolve() if raw.is_absolute() else (root / raw).resolve()
|
|
843
|
-
except OSError as exc:
|
|
844
|
-
_emit({"success": False, "message": f"Invalid path: {exc}"})
|
|
845
|
-
return 1
|
|
846
|
-
li = LayeredIgnore(root)
|
|
847
|
-
_emit(li.diagnose_dict(abs_path))
|
|
848
|
-
return 0
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
def _read_diff_text(args: argparse.Namespace) -> str:
|
|
852
|
-
if args.diff_file:
|
|
853
|
-
return Path(args.diff_file).read_text(encoding="utf-8")
|
|
854
|
-
if args.diff_stdin:
|
|
855
|
-
return sys.stdin.read()
|
|
856
|
-
raise ValueError("Provide exactly one of --diff-file or --diff-stdin")
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
def _cmd_unresolved_calls_list(args: argparse.Namespace) -> int:
|
|
860
|
-
cfg = _resolved_from_ns(args)
|
|
861
|
-
_startup_hints(cfg)
|
|
862
|
-
cfg.apply_to_os_environ()
|
|
863
|
-
from java_codebase_rag.graph.ladybug_queries import LadybugGraph # lazy
|
|
864
|
-
|
|
865
|
-
if not LadybugGraph.exists():
|
|
866
|
-
_emit({"success": False, "message": "LadybugDB graph not found"})
|
|
867
|
-
return 1
|
|
868
|
-
graph = LadybugGraph.get()
|
|
869
|
-
rows = graph.list_unresolved_call_sites(
|
|
870
|
-
method_id=args.method_id,
|
|
871
|
-
reason=args.reason,
|
|
872
|
-
microservice=args.microservice,
|
|
873
|
-
callee_simple=args.callee_simple,
|
|
874
|
-
limit=int(args.limit),
|
|
875
|
-
)
|
|
876
|
-
# Drop the raw caller symbol id: the row already carries the agent-facing
|
|
877
|
-
# ``caller_fqn``, so ``caller_id`` is redundant noise in an operator-facing
|
|
878
|
-
# report. The call-site ``id`` (``ucs:``) is kept — it's each site's
|
|
879
|
-
# primary key, not a caller reference.
|
|
880
|
-
sites = [
|
|
881
|
-
{k: v for k, v in row.items() if k != "caller_id"}
|
|
882
|
-
for row in rows
|
|
883
|
-
]
|
|
884
|
-
_emit({"success": True, "count": len(sites), "sites": sites})
|
|
885
|
-
return 0
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
def _cmd_unresolved_calls_stats(args: argparse.Namespace) -> int:
|
|
889
|
-
cfg = _resolved_from_ns(args)
|
|
890
|
-
_startup_hints(cfg)
|
|
891
|
-
cfg.apply_to_os_environ()
|
|
892
|
-
from java_codebase_rag.graph.ladybug_queries import LadybugGraph # lazy
|
|
893
|
-
|
|
894
|
-
if not LadybugGraph.exists():
|
|
895
|
-
_emit({"success": False, "message": "LadybugDB graph not found"})
|
|
896
|
-
return 1
|
|
897
|
-
graph = LadybugGraph.get()
|
|
898
|
-
buckets = graph.stats_unresolved_call_sites(by=args.by)
|
|
899
|
-
total = sum(int(r.get("n") or 0) for r in buckets)
|
|
900
|
-
_emit({"success": True, "total": total, "by": args.by, "buckets": buckets})
|
|
901
|
-
return 0
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
def _cmd_analyze_pr(args: argparse.Namespace) -> int:
|
|
905
|
-
cfg = _resolved_from_ns(args)
|
|
906
|
-
_startup_hints(cfg)
|
|
907
|
-
cfg.apply_to_os_environ()
|
|
908
|
-
try:
|
|
909
|
-
diff_text = _read_diff_text(args)
|
|
910
|
-
except Exception as exc:
|
|
911
|
-
_emit({"success": False, "message": str(exc)})
|
|
912
|
-
return 1
|
|
913
|
-
if not diff_text.strip():
|
|
914
|
-
_emit({"success": False, "message": "Diff is empty"})
|
|
915
|
-
return 1
|
|
916
|
-
from java_codebase_rag.analysis import pr_analysis # lazy
|
|
917
|
-
from java_codebase_rag.graph.ladybug_queries import LadybugGraph # lazy
|
|
918
|
-
|
|
919
|
-
if not LadybugGraph.exists():
|
|
920
|
-
_emit({"success": False, "message": "LadybugDB graph not found"})
|
|
921
|
-
return 1
|
|
922
|
-
graph = LadybugGraph.get()
|
|
923
|
-
report = pr_analysis.analyze_pr_pipeline(graph, diff_text)
|
|
924
|
-
_emit(pr_analysis.pr_report_to_dict(report))
|
|
925
|
-
return 0
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
def build_parser() -> argparse.ArgumentParser:
|
|
929
|
-
description = (
|
|
930
|
-
"java-codebase-rag — graph-native code intelligence for Java microservices.\n\n"
|
|
931
|
-
"Lifecycle commands stream subprocess progress to stderr (including relayed child stdout); "
|
|
932
|
-
"--quiet suppresses that stream; stdout remains the machine-readable payload.\n\n"
|
|
933
|
-
"Lifecycle (manage the index):\n"
|
|
934
|
-
" init Create a fresh index from a Java repository.\n"
|
|
935
|
-
" increment Pick up changes since the last index update (Lance + graph).\n"
|
|
936
|
-
" reprocess Full vector + graph rebuild (default); optional --vectors-only / --graph-only.\n"
|
|
937
|
-
" erase Delete the index from disk.\n\n"
|
|
938
|
-
"Introspection (inspect the index):\n"
|
|
939
|
-
" meta Print ontology version, edge counts, and table summary.\n"
|
|
940
|
-
" tables List Lance tables and row counts.\n"
|
|
941
|
-
" diagnose-ignore Show which ignore-pattern layer decided a path's fate.\n"
|
|
942
|
-
" unresolved-calls List or aggregate receiver-failure call sites (not in CALLS).\n\n"
|
|
943
|
-
"Analysis (work with code changes):\n"
|
|
944
|
-
" analyze-pr Compute blast-radius + risk score for a unified diff.\n\n"
|
|
945
|
-
"Run `java-codebase-rag <command> --help` for command-specific options."
|
|
946
|
-
)
|
|
947
|
-
parser = argparse.ArgumentParser(
|
|
948
|
-
prog="java-codebase-rag",
|
|
949
|
-
description=description,
|
|
950
|
-
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
951
|
-
exit_on_error=False,
|
|
952
|
-
)
|
|
953
|
-
parser.add_argument(
|
|
954
|
-
"--version",
|
|
955
|
-
action="version",
|
|
956
|
-
version=version_string(parser.prog),
|
|
957
|
-
)
|
|
958
|
-
subparsers = parser.add_subparsers(dest="subcommand")
|
|
959
|
-
|
|
960
|
-
init = subparsers.add_parser(
|
|
961
|
-
"init",
|
|
962
|
-
help="Create a fresh index from a Java repository.",
|
|
963
|
-
description=(
|
|
964
|
-
"First-time index creation. Refuses if the resolved index directory "
|
|
965
|
-
"already contains a LadybugDB graph or Lance tables. Exit 2 on refusal."
|
|
966
|
-
),
|
|
967
|
-
)
|
|
968
|
-
_add_index_embedding_flags(init)
|
|
969
|
-
_add_verbosity_flags(init)
|
|
970
|
-
init.set_defaults(handler=_cmd_init)
|
|
971
|
-
|
|
972
|
-
install = subparsers.add_parser(
|
|
973
|
-
"install",
|
|
974
|
-
help="Interactive setup wizard: config, MCP registration, skill/agent deployment, indexing.",
|
|
975
|
-
description=(
|
|
976
|
-
"Interactive setup wizard that guides users through: Java source detection, "
|
|
977
|
-
"embedding model selection, agent host configuration, artifact deployment, "
|
|
978
|
-
"and YAML config generation. Use --non-interactive for CI/automation."
|
|
979
|
-
),
|
|
980
|
-
)
|
|
981
|
-
install.add_argument(
|
|
982
|
-
"--non-interactive",
|
|
983
|
-
action="store_true",
|
|
984
|
-
help="Run without prompts (requires --agent).",
|
|
985
|
-
)
|
|
986
|
-
install.add_argument(
|
|
987
|
-
"--agent",
|
|
988
|
-
choices=["claude-code", "qwen-code", "gigacode"],
|
|
989
|
-
default=[],
|
|
990
|
-
action="append",
|
|
991
|
-
help="Agent host to configure (can be passed multiple times).",
|
|
992
|
-
)
|
|
993
|
-
install.add_argument(
|
|
994
|
-
"--scope",
|
|
995
|
-
choices=["project", "user"],
|
|
996
|
-
default=None,
|
|
997
|
-
help="Installation scope (default: project).",
|
|
998
|
-
)
|
|
999
|
-
install.add_argument(
|
|
1000
|
-
"--model",
|
|
1001
|
-
type=str,
|
|
1002
|
-
default=None,
|
|
1003
|
-
help="Embedding model path or 'auto' (default: auto).",
|
|
1004
|
-
)
|
|
1005
|
-
install.add_argument(
|
|
1006
|
-
"--surface",
|
|
1007
|
-
choices=["mcp", "cli"],
|
|
1008
|
-
default=None,
|
|
1009
|
-
help=(
|
|
1010
|
-
"Agent surface to install: 'mcp' (stdio MCP server + explore-codebase "
|
|
1011
|
-
"skill + explorer-rag-enhanced subagent) or 'cli' (jrag console-script "
|
|
1012
|
-
"skill + explorer-rag-cli subagent, no MCP entry). Omit to choose "
|
|
1013
|
-
"interactively; non-interactive mode defaults to 'mcp'."
|
|
1014
|
-
),
|
|
1015
|
-
)
|
|
1016
|
-
_add_verbosity_flags(install)
|
|
1017
|
-
install.set_defaults(handler=_cmd_install)
|
|
1018
|
-
|
|
1019
|
-
update = subparsers.add_parser(
|
|
1020
|
-
"update",
|
|
1021
|
-
help="Refresh shipped artifacts (skill, agent, MCP entry) after pip upgrade.",
|
|
1022
|
-
description=(
|
|
1023
|
-
"Post-upgrade refresh: overwrites skill and agent files with the latest "
|
|
1024
|
-
"shipped versions and updates the MCP command path. If an index exists, "
|
|
1025
|
-
"also runs an incremental Lance + graph catch-up (same as `increment`). "
|
|
1026
|
-
"Use --dry-run to preview changes without writing. Pass --surface to "
|
|
1027
|
-
"switch between the mcp and cli surfaces (migrates artifacts + marker). "
|
|
1028
|
-
"Requires a prior `install` run."
|
|
1029
|
-
),
|
|
1030
|
-
)
|
|
1031
|
-
update.add_argument(
|
|
1032
|
-
"--force",
|
|
1033
|
-
action="store_true",
|
|
1034
|
-
help="Overwrite all artifacts even if content matches.",
|
|
1035
|
-
)
|
|
1036
|
-
update.add_argument(
|
|
1037
|
-
"--dry-run",
|
|
1038
|
-
action="store_true",
|
|
1039
|
-
help="Print changes without writing files.",
|
|
1040
|
-
)
|
|
1041
|
-
update.add_argument(
|
|
1042
|
-
"--surface",
|
|
1043
|
-
choices=["mcp", "cli"],
|
|
1044
|
-
default=None,
|
|
1045
|
-
help=(
|
|
1046
|
-
"Switch agent surface: 'mcp' or 'cli'. Tears down the old surface's "
|
|
1047
|
-
"artifacts and deploys the new surface's (also rewrites the install "
|
|
1048
|
-
"marker). Omit to keep the current surface; on a TTY you'll be prompted."
|
|
1049
|
-
),
|
|
1050
|
-
)
|
|
1051
|
-
_add_verbosity_flags(update)
|
|
1052
|
-
update.set_defaults(handler=_cmd_update)
|
|
1053
|
-
|
|
1054
|
-
increment = subparsers.add_parser(
|
|
1055
|
-
"increment",
|
|
1056
|
-
help="Pick up changes since the last index update.",
|
|
1057
|
-
description="Runs cocoindex catch-up and incremental LadybugDB graph update. Use --vectors-only to skip graph update.",
|
|
1058
|
-
)
|
|
1059
|
-
_add_index_embedding_flags(increment)
|
|
1060
|
-
_add_verbosity_flags(increment)
|
|
1061
|
-
increment.add_argument(
|
|
1062
|
-
"--vectors-only",
|
|
1063
|
-
action="store_true",
|
|
1064
|
-
help="Run only cocoindex catch-up (Lance); skip graph update.",
|
|
1065
|
-
)
|
|
1066
|
-
increment.set_defaults(handler=_cmd_increment)
|
|
1067
|
-
|
|
1068
|
-
reprocess = subparsers.add_parser(
|
|
1069
|
-
"reprocess",
|
|
1070
|
-
help="Rebuild vectors and/or LadybugDB (default: both full phases).",
|
|
1071
|
-
description=(
|
|
1072
|
-
"Default: full Lance reprocess (cocoindex --full-reprocess) then full LadybugDB graph rebuild. "
|
|
1073
|
-
"Use --vectors-only or --graph-only to run a single phase (mutually exclusive)."
|
|
1074
|
-
),
|
|
1075
|
-
)
|
|
1076
|
-
_add_index_embedding_flags(reprocess)
|
|
1077
|
-
_add_verbosity_flags(reprocess)
|
|
1078
|
-
_rex = reprocess.add_mutually_exclusive_group()
|
|
1079
|
-
_rex.add_argument(
|
|
1080
|
-
"--vectors-only",
|
|
1081
|
-
action="store_true",
|
|
1082
|
-
help="Run only the Lance/cocoindex full reprocess phase (no graph builder).",
|
|
1083
|
-
)
|
|
1084
|
-
_rex.add_argument(
|
|
1085
|
-
"--graph-only",
|
|
1086
|
-
action="store_true",
|
|
1087
|
-
help="Run only build_ast_graph.py (no cocoindex / Lance reprocess).",
|
|
1088
|
-
)
|
|
1089
|
-
reprocess.set_defaults(handler=_cmd_reprocess)
|
|
1090
|
-
|
|
1091
|
-
erase = subparsers.add_parser(
|
|
1092
|
-
"erase",
|
|
1093
|
-
help="Delete the index from disk.",
|
|
1094
|
-
description="Runs cocoindex drop, removes LadybugDB, and drops Lance tables. Requires --yes or TTY confirmation.",
|
|
1095
|
-
)
|
|
1096
|
-
_add_index_embedding_flags(erase)
|
|
1097
|
-
erase.add_argument("--yes", action="store_true", help="Confirm destructive deletion (required in CI)")
|
|
1098
|
-
_add_verbosity_flags(erase)
|
|
1099
|
-
erase.set_defaults(handler=_cmd_erase)
|
|
1100
|
-
|
|
1101
|
-
meta = subparsers.add_parser("meta", help="Print graph meta and embedding resolution.")
|
|
1102
|
-
_add_index_embedding_flags(meta)
|
|
1103
|
-
meta.set_defaults(handler=_cmd_meta)
|
|
1104
|
-
|
|
1105
|
-
tables = subparsers.add_parser("tables", help="List Lance tables and row counts.")
|
|
1106
|
-
_add_index_embedding_flags(tables)
|
|
1107
|
-
tables.set_defaults(handler=_cmd_tables)
|
|
1108
|
-
|
|
1109
|
-
diagnose = subparsers.add_parser(
|
|
1110
|
-
"diagnose-ignore",
|
|
1111
|
-
help="Show which ignore-pattern layer decided the fate of a path.",
|
|
1112
|
-
)
|
|
1113
|
-
_add_index_embedding_flags(diagnose)
|
|
1114
|
-
diagnose.add_argument("path", type=str)
|
|
1115
|
-
diagnose.set_defaults(handler=_cmd_diagnose_ignore)
|
|
1116
|
-
|
|
1117
|
-
analyze = subparsers.add_parser("analyze-pr", help="Blast-radius + risk score for a unified diff.")
|
|
1118
|
-
_add_index_embedding_flags(analyze)
|
|
1119
|
-
group = analyze.add_mutually_exclusive_group(required=True)
|
|
1120
|
-
group.add_argument("--diff-file", type=str)
|
|
1121
|
-
group.add_argument("--diff-stdin", action="store_true")
|
|
1122
|
-
analyze.set_defaults(handler=_cmd_analyze_pr)
|
|
1123
|
-
|
|
1124
|
-
unresolved = subparsers.add_parser(
|
|
1125
|
-
"unresolved-calls",
|
|
1126
|
-
help="List or aggregate UnresolvedCallSite rows (receiver-failure call sites).",
|
|
1127
|
-
)
|
|
1128
|
-
_add_index_embedding_flags(unresolved)
|
|
1129
|
-
unresolved_sub = unresolved.add_subparsers(dest="unresolved_command", required=True)
|
|
1130
|
-
|
|
1131
|
-
uc_list = unresolved_sub.add_parser("list", help="List unresolved call sites.")
|
|
1132
|
-
_add_index_embedding_flags(uc_list)
|
|
1133
|
-
uc_list.add_argument("--method-id", type=str, default=None, help="Caller Symbol id")
|
|
1134
|
-
uc_list.add_argument(
|
|
1135
|
-
"--reason",
|
|
1136
|
-
type=str,
|
|
1137
|
-
default=None,
|
|
1138
|
-
choices=sorted(VALID_UNRESOLVED_CALL_REASONS),
|
|
1139
|
-
help="Filter by UnresolvedCallSite.reason",
|
|
1140
|
-
)
|
|
1141
|
-
uc_list.add_argument("--microservice", type=str, default=None)
|
|
1142
|
-
uc_list.add_argument("--callee-simple", type=str, default=None, dest="callee_simple")
|
|
1143
|
-
uc_list.add_argument("--limit", type=int, default=100)
|
|
1144
|
-
uc_list.set_defaults(handler=_cmd_unresolved_calls_list)
|
|
1145
|
-
|
|
1146
|
-
uc_stats = unresolved_sub.add_parser("stats", help="Aggregate unresolved call site counts.")
|
|
1147
|
-
_add_index_embedding_flags(uc_stats)
|
|
1148
|
-
uc_stats.add_argument(
|
|
1149
|
-
"--by",
|
|
1150
|
-
type=str,
|
|
1151
|
-
choices=("reason", "microservice", "caller_role"),
|
|
1152
|
-
default="reason",
|
|
1153
|
-
)
|
|
1154
|
-
uc_stats.set_defaults(handler=_cmd_unresolved_calls_stats)
|
|
1155
|
-
|
|
1156
|
-
return parser
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
def main(argv: list[str] | None = None) -> int:
|
|
1160
|
-
raise_fd_limit()
|
|
1161
|
-
raw = list(argv if argv is not None else sys.argv[1:])
|
|
1162
|
-
if raw and raw[0] == "refresh":
|
|
1163
|
-
print(_REFRESH_DEPRECATION, file=sys.stderr)
|
|
1164
|
-
raw[0] = "reprocess"
|
|
1165
|
-
parser = build_parser()
|
|
1166
|
-
try:
|
|
1167
|
-
args = parser.parse_args(raw)
|
|
1168
|
-
except SystemExit as e:
|
|
1169
|
-
if e.code in (0, None):
|
|
1170
|
-
return 0
|
|
1171
|
-
return int(e.code) if isinstance(e.code, int) else 2
|
|
1172
|
-
except argparse.ArgumentError as exc:
|
|
1173
|
-
print(f"java-codebase-rag: {exc}", file=sys.stderr)
|
|
1174
|
-
return 2
|
|
1175
|
-
handler = getattr(args, "handler", None)
|
|
1176
|
-
if handler is None:
|
|
1177
|
-
parser.print_help()
|
|
1178
|
-
return 2
|
|
1179
|
-
try:
|
|
1180
|
-
return int(handler(args))
|
|
1181
|
-
except Exception as exc: # pragma: no cover - defensive top-level guard
|
|
1182
|
-
_emit({"success": False, "exit_code": 2, "message": f"internal error: {exc}"})
|
|
1183
|
-
return 2
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
def _console_script_main() -> None:
|
|
1187
|
-
"""Real CLI entry: terminate without interpreter finalization.
|
|
1188
|
-
|
|
1189
|
-
A pyarrow/lance worker thread (loaded via lancedb in lifecycle commands) can
|
|
1190
|
-
outlive CPython finalization in a one-shot CLI subprocess and trip
|
|
1191
|
-
``PyGILState_Release`` (SIGABRT, exit -6). Flushing + ``os._exit`` skips that
|
|
1192
|
-
racy teardown — the command has already done its work and emitted its result.
|
|
1193
|
-
``main()`` stays return-based so in-process test callers (``cli.main(...)``)
|
|
1194
|
-
keep working.
|
|
1195
|
-
|
|
1196
|
-
``KeyboardInterrupt`` (Ctrl+C during a long indexing step) is caught here
|
|
1197
|
-
rather than left to propagate: an uncaught interrupt bypasses this function
|
|
1198
|
-
and runs full interpreter finalization (traceback + thread teardown),
|
|
1199
|
-
whereas routing it through the same flush + ``os._exit`` path gives a clean,
|
|
1200
|
-
immediate exit (code 130) and avoids the finalization-time SIGABRT noted
|
|
1201
|
-
above for commands that loaded lancedb.
|
|
1202
|
-
"""
|
|
1203
|
-
try:
|
|
1204
|
-
rc = main()
|
|
1205
|
-
except KeyboardInterrupt:
|
|
1206
|
-
sys.stderr.write("\nInterrupted.\n")
|
|
1207
|
-
sys.stderr.flush()
|
|
1208
|
-
rc = 130
|
|
1209
|
-
sys.stdout.flush()
|
|
1210
|
-
sys.stderr.flush()
|
|
1211
|
-
os._exit(rc)
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
if __name__ == "__main__":
|
|
1215
|
-
_console_script_main()
|