Skip to content

Commit 7a2a0ef

Browse files
HumanBean17claude
andcommitted
fix(jrag): robust subcommand detection in argparse-error envelope
The previous cmd-prefix heuristic picked the first non-dash argv token, which misidentified the ``json`` in ``jrag --format json`` (no subcommand) as the subcommand and prefixed the error with ``json:``. Replaced the hand rolled scanner with a minimal pre-parser (parse_known_args with a parser that only knows --format/--detail) so flag VALUES are consumed before the subcommand is selected. The leftover argv then has the real subcommand as its first non-dash token. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 739e332 commit 7a2a0ef

1 file changed

Lines changed: 24 additions & 25 deletions

File tree

java_codebase_rag/jrag.py

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -205,28 +205,25 @@ def error(self, message: str) -> None: # type: ignore[override]
205205
raise argparse.ArgumentError(None, message)
206206

207207

208-
def _detect_flag(raw: list[str], flag: str) -> str | None:
209-
"""Best-effort scan of raw argv for a ``--flag <value>`` / ``--flag=<value>``.
208+
_PREPARSE_PARSER = argparse.ArgumentParser(add_help=False)
209+
_PREPARSE_PARSER.add_argument("--format", default=None)
210+
_PREPARSE_PARSER.add_argument("--detail", default=None)
210211

211-
Used by :func:`main` to honor ``--format`` / ``--detail`` when argparse
212-
bailed before populating ``args`` (missing required positional). Returns
213-
``None`` when absent. Stops at the first positional after the subcommand
214-
name to avoid picking up a token that is itself a positional value.
212+
213+
def _preparse_render_flags(raw: list[str]) -> tuple[str | None, str | None, list[str]]:
214+
"""Extract ``--format`` / ``--detail`` from raw argv via a minimal parser.
215+
216+
Used by :func:`main` to honor render flags when argparse bailed before
217+
populating ``args`` (missing required positional, unknown subcommand).
218+
Returns ``(format, detail, leftover_argv)`` where ``leftover_argv`` has the
219+
consumed flag tokens stripped so the first remaining non-dash token is the
220+
subcommand name (not a flag value like the ``json`` in ``--format json``).
215221
"""
216-
i = 0
217-
# Skip the subcommand name (first non-dash token).
218-
while i < len(raw) and raw[i].startswith("-"):
219-
i += 1
220-
if i < len(raw):
221-
i += 1 # consume the subcommand name
222-
while i < len(raw):
223-
tok = raw[i]
224-
if tok == flag and i + 1 < len(raw):
225-
return raw[i + 1]
226-
if tok.startswith(f"{flag}="):
227-
return tok.split("=", 1)[1]
228-
i += 1
229-
return None
222+
try:
223+
ns, leftover = _PREPARSE_PARSER.parse_known_args(raw)
224+
return ns.format, ns.detail, list(leftover)
225+
except Exception:
226+
return None, None, list(raw)
230227

231228

232229
def build_parser() -> argparse.ArgumentParser:
@@ -4132,11 +4129,13 @@ def main(argv: list[str] | None = None) -> int:
41324129
from java_codebase_rag.jrag_envelope import Envelope
41334130
from java_codebase_rag.jrag_render import render
41344131

4135-
fmt = _detect_flag(raw, "--format") or "text"
4136-
detail = _detect_flag(raw, "--detail") or "normal"
4137-
# argparse's message is the bare clause (e.g. "the following arguments
4138-
# are required: query"); prefix with the subcommand context when we can.
4139-
cmd = next((t for t in raw if not t.startswith("-")), None)
4132+
fmt, detail, leftover = _preparse_render_flags(raw)
4133+
fmt = fmt or "text"
4134+
detail = detail or "normal"
4135+
# The subcommand is the first non-dash token in the leftover (flag
4136+
# values already consumed by the pre-parser), so we don't mis-prefix
4137+
# with a value like ``json`` from ``--format json``.
4138+
cmd = next((t for t in leftover if not t.startswith("-")), None)
41404139
msg = str(exc).strip() or "usage error"
41414140
if cmd and not msg.startswith(cmd):
41424141
msg = f"{cmd}: {msg}"

0 commit comments

Comments
 (0)