Skip to content

Commit cdc49bd

Browse files
fix cli progress review issues: honest footer, gated heavy tests, quiet help
Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent d9363ff commit cdc49bd

5 files changed

Lines changed: 88 additions & 19 deletions

File tree

build_ast_graph.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,7 @@ def pass1_parse(root: Path, tables: GraphTables, *, verbose: bool) -> dict[str,
422422
except ValueError:
423423
slow_sec = 0.0
424424
with _VerbosePassHeartbeats("[pass1]", verbose=verbose):
425-
if slow_sec > 0:
425+
if verbose and slow_sec > 0:
426426
time.sleep(slow_sec)
427427
for p in iter_java_source_files(root, ignore=ignore):
428428
n_files += 1
@@ -1415,15 +1415,15 @@ def pass4_routes(
14151415
if verbose:
14161416
_verbose_stderr_line(_PASS4_START)
14171417
with _VerbosePassHeartbeats("[pass4]", verbose=verbose):
1418-
1418+
14191419
for ast in asts.values():
14201420
stats.routes_skipped_unresolved += ast.routes_skipped_unresolved
1421-
1421+
14221422
routes_by_id: dict[str, RouteRow] = {}
14231423
exposes_seen: set[tuple[str, str]] = set()
1424-
1424+
14251425
http_kinds = frozenset({"http_endpoint", "http_consumer"})
1426-
1426+
14271427
for member in sorted(tables.members, key=lambda m: m.node_id):
14281428
if member.decl.is_constructor:
14291429
continue
@@ -1504,13 +1504,13 @@ def pass4_routes(
15041504
strategy=decl.resolution_strategy,
15051505
),
15061506
)
1507-
1507+
15081508
tables.routes_rows = sorted(routes_by_id.values(), key=lambda r: r.id)
1509-
1509+
15101510
for row in tables.routes_rows:
15111511
stats.by_framework[row.framework] += 1
15121512
stats.by_kind[row.kind] += 1
1513-
1513+
15141514
n_routes = len(tables.routes_rows)
15151515
if n_routes:
15161516
stats.routes_resolved_pct = 100.0 * sum(
@@ -1522,7 +1522,7 @@ def pass4_routes(
15221522
else:
15231523
stats.routes_resolved_pct = 100.0
15241524
stats.routes_from_brownfield_pct = 0.0
1525-
1525+
15261526
by_layer: dict[str, int] = defaultdict(int)
15271527
for row in tables.routes_rows:
15281528
by_layer[row.source_layer] += 1
@@ -1752,7 +1752,7 @@ def _phantom_async_route_id(call: OutgoingCallDecl) -> str:
17521752
tables.call_edge_stats.async_calls_total += 1
17531753
tables.call_edge_stats.async_calls_by_client_kind[call.client_kind] += 1
17541754
tables.call_edge_stats.async_calls_by_strategy[strategy] += 1
1755-
1755+
17561756
tables.routes_rows = sorted(route_rows, key=lambda r: r.id)
17571757
tables.client_rows = sorted(tables.client_rows, key=lambda c: c.id)
17581758
tables.declares_client_rows = sorted(
@@ -2024,7 +2024,7 @@ def _micro_factor(member: MemberEntry | None) -> float:
20242024
tables.call_edge_stats.http_calls_match_breakdown[row.match] += 1
20252025
if row.match == "cross_service":
20262026
tables.call_edge_stats.cross_service_calls_total += 1
2027-
2027+
20282028
for row in tables.async_call_rows:
20292029
if row.match != "unresolved":
20302030
continue
@@ -2070,7 +2070,7 @@ def _micro_factor(member: MemberEntry | None) -> float:
20702070
tables.call_edge_stats.async_calls_match_breakdown[row.match] += 1
20712071
if row.match == "cross_service":
20722072
tables.call_edge_stats.cross_service_calls_total += 1
2073-
2073+
20742074
inbound_route_ids = {r.route_id for r in tables.http_call_rows} | {r.route_id for r in tables.async_call_rows}
20752075
tables.routes_rows = sorted(
20762076
[

java_codebase_rag/cli.py

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,19 @@ def _run_with_pipeline_progress(
132132
try:
133133
code = int(work())
134134
return code
135+
except BaseException as exc:
136+
# Keep footer aligned with process outcome (main maps unhandled Exception -> exit 2).
137+
if isinstance(exc, SystemExit):
138+
c = exc.code
139+
if isinstance(c, int):
140+
code = c
141+
elif c in (None, False):
142+
code = 0
143+
else:
144+
code = 1
145+
elif code == 0:
146+
code = 2
147+
raise
135148
finally:
136149
_pipeline_footer(subcommand, t0, code)
137150

@@ -560,7 +573,11 @@ def build_parser() -> argparse.ArgumentParser:
560573
),
561574
)
562575
_add_index_embedding_flags(init)
563-
init.add_argument("--quiet", action="store_true")
576+
init.add_argument(
577+
"--quiet",
578+
action="store_true",
579+
help="Suppress stderr progress relay; stdout payload unchanged.",
580+
)
564581
init.set_defaults(handler=_cmd_init)
565582

566583
increment = subparsers.add_parser(
@@ -569,7 +586,11 @@ def build_parser() -> argparse.ArgumentParser:
569586
description="Runs cocoindex catch-up (no full reprocess). Does not rebuild Kuzu; see stderr warning.",
570587
)
571588
_add_index_embedding_flags(increment)
572-
increment.add_argument("--quiet", action="store_true")
589+
increment.add_argument(
590+
"--quiet",
591+
action="store_true",
592+
help="Suppress stderr progress relay; stdout payload unchanged.",
593+
)
573594
increment.set_defaults(handler=_cmd_increment)
574595

575596
reprocess = subparsers.add_parser(
@@ -581,7 +602,11 @@ def build_parser() -> argparse.ArgumentParser:
581602
),
582603
)
583604
_add_index_embedding_flags(reprocess)
584-
reprocess.add_argument("--quiet", action="store_true")
605+
reprocess.add_argument(
606+
"--quiet",
607+
action="store_true",
608+
help="Suppress stderr progress relay; stdout payload unchanged.",
609+
)
585610
_rex = reprocess.add_mutually_exclusive_group()
586611
_rex.add_argument(
587612
"--vectors-only",
@@ -602,7 +627,11 @@ def build_parser() -> argparse.ArgumentParser:
602627
)
603628
_add_index_embedding_flags(erase)
604629
erase.add_argument("--yes", action="store_true", help="Confirm destructive deletion (required in CI)")
605-
erase.add_argument("--quiet", action="store_true")
630+
erase.add_argument(
631+
"--quiet",
632+
action="store_true",
633+
help="Suppress stderr progress relay; stdout payload unchanged.",
634+
)
606635
erase.set_defaults(handler=_cmd_erase)
607636

608637
meta = subparsers.add_parser("meta", help="Print graph meta and embedding resolution.")

tests/README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,11 @@ The session-scoped fixtures in `conftest.py` materialize Kuzu (and, where needed
6262
The heavier end-to-end test that runs `cocoindex` + a real LanceDB index is
6363
gated behind `JAVA_CODEBASE_RAG_RUN_HEAVY=1` because it downloads the embedding
6464
model on first run and indexes the corpus from scratch (~minute on a
65-
warm cache, several minutes cold).
65+
warm cache, several minutes cold). The same gate applies to full
66+
`java-codebase-rag` lifecycle subprocess checks in
67+
`tests/test_cli_progress_stdout_invariant.py` and the cocoindex portion of
68+
`tests/test_cli_quiet_parity.py` so default `pytest tests` stays fast when
69+
`cocoindex` is installed.
6670

6771
```bash
6872
JAVA_CODEBASE_RAG_RUN_HEAVY=1 .venv/bin/pytest tests -v

tests/test_cli_progress_stdout_invariant.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ async def communicate(self) -> tuple[bytes, bytes]:
115115
assert "idx_out" in out.stdout
116116

117117

118+
@pytest.mark.skipif(os.environ.get("JAVA_CODEBASE_RAG_RUN_HEAVY", "").strip() != "1", reason="cocoindex lifecycle; set JAVA_CODEBASE_RAG_RUN_HEAVY=1")
118119
@pytest.mark.skipif(not _cocoindex_available(), reason="cocoindex not installed in venv")
119120
def test_cli_lifecycle_stdout_invariant_init(corpus_root: Path, tmp_path: Path) -> None:
120121
baseline = (_FIXTURE_DIR / "init_quiet_success.stdout.txt").read_text(encoding="utf-8")
@@ -197,6 +198,7 @@ def test_cli_lifecycle_stdout_invariant_erase_quiet(tmp_path: Path) -> None:
197198
assert proc.stdout.strip() == '{"message": "erase completed", "success": true}'
198199

199200

201+
@pytest.mark.skipif(os.environ.get("JAVA_CODEBASE_RAG_RUN_HEAVY", "").strip() != "1", reason="cocoindex lifecycle; set JAVA_CODEBASE_RAG_RUN_HEAVY=1")
200202
@pytest.mark.skipif(not _cocoindex_available(), reason="cocoindex not installed in venv")
201203
def test_cli_lifecycle_stdout_invariant_init_increment_reprocess_when_cocoindex(
202204
tmp_path: Path,
@@ -240,3 +242,34 @@ def test_cli_lifecycle_stdout_invariant_init_increment_reprocess_when_cocoindex(
240242
assert rep_payload.get("success") is True
241243
assert isinstance(rep_payload.get("stdout"), str)
242244
assert isinstance(rep_payload.get("graph_stderr"), str)
245+
246+
247+
def test_pipeline_footer_reflects_exception_before_propagate(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
248+
from java_codebase_rag.config import resolve_operator_config
249+
250+
import java_codebase_rag.cli as cli_mod
251+
252+
cfg = resolve_operator_config(source_root=tmp_path, cli_index_dir=str(tmp_path / "ix_footer"))
253+
codes: list[int] = []
254+
255+
def capture_footer(_sub: str, _t0: float, code: int) -> None:
256+
codes.append(code)
257+
258+
monkeypatch.setattr(cli_mod, "_pipeline_footer", capture_footer)
259+
260+
def boom() -> int:
261+
raise RuntimeError("simulated handler failure")
262+
263+
with pytest.raises(RuntimeError, match="simulated handler failure"):
264+
cli_mod._run_with_pipeline_progress("reprocess", cfg, quiet=False, work=boom)
265+
assert codes == [2]
266+
267+
codes.clear()
268+
269+
def exit5() -> int:
270+
raise SystemExit(5)
271+
272+
with pytest.raises(SystemExit) as excinfo:
273+
cli_mod._run_with_pipeline_progress("reprocess", cfg, quiet=False, work=exit5)
274+
assert excinfo.value.code == 5
275+
assert codes == [5]

tests/test_cli_quiet_parity.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
import pytest
1313

14+
from java_codebase_rag import cli as cli_mod
15+
1416
REPO = Path(__file__).resolve().parent.parent
1517
BUILDER = REPO / "build_ast_graph.py"
1618
FIXTURE_ROOT = REPO / "tests" / "fixtures" / "call_graph_smoke"
@@ -85,7 +87,6 @@ def test_pipeline_header_footer_present(tmp_path: Path) -> None:
8587
pytest.skip("java-codebase-rag entrypoint not on PATH")
8688
idx = tmp_path / "idx_pf"
8789
idx.mkdir()
88-
(idx / "stub.txt").write_text("x", encoding="utf-8")
8990
env = os.environ.copy()
9091
env["JAVA_CODEBASE_RAG_INDEX_DIR"] = str(idx)
9192
env["JAVA_CODEBASE_RAG_SOURCE_ROOT"] = str(tmp_path)
@@ -122,6 +123,8 @@ def test_cli_quiet_stderr_baseline_per_subcommand(tmp_path: Path, corpus_root: P
122123
assert r_erase.returncode == 0, r_erase.stderr + r_erase.stdout
123124
_assert_quiet_stderr_no_progress_markers(r_erase.stderr)
124125

126+
if os.environ.get("JAVA_CODEBASE_RAG_RUN_HEAVY", "").strip() != "1":
127+
pytest.skip("cocoindex init/increment/reprocess quiet checks; set JAVA_CODEBASE_RAG_RUN_HEAVY=1")
125128
if not _cocoindex_available():
126129
pytest.skip("cocoindex CLI missing — skip init/increment/reprocess quiet checks")
127130

@@ -156,7 +159,7 @@ def test_cli_quiet_stderr_baseline_per_subcommand(tmp_path: Path, corpus_root: P
156159
)
157160
assert r_inc.returncode == 0, r_inc.stderr + r_inc.stdout
158161
_assert_quiet_stderr_no_progress_markers(r_inc.stderr)
159-
assert "WARNING: AST graph (Kuzu) incremental rebuild is not yet implemented." in r_inc.stderr
162+
assert r_inc.stderr == "\n".join(cli_mod._INCREMENT_WARNING_LINES) + "\n"
160163

161164
r_rep = subprocess.run(
162165
[exe, "reprocess", "--source-root", str(corpus_root), "--index-dir", str(idx_life), "--quiet"],

0 commit comments

Comments
 (0)