Commit 514048e
feat(jrag): ship agent-facing jrag CLI (JRAG-CLI plan, 9 PRs) (#377)
* docs(plans): add JRAG-CLI proposal + plan (9-PR agent-facing CLI)
Adds propose/JRAG-CLI-PROPOSE.md and plans/active/PLAN-JRAG-CLI.md for a
new `jrag` agent-facing CLI: a thin compose-and-render layer over
resolve_v2 + the MCP v2 handlers + LadybugGraph, internalizing resolve so
every command takes a human identifier (FQN / simple name / route path /
topic), never a raw node id.
9 PRs (0a, 0b, 1a, 1b, 2, 3a, 3b, 4, 5); in-process (no daemon); no
ontology bump / re-index. Plan is grounded against current source and was
revised after a 5-subagent adversarial review (6 blockers + ~7 highs
folded in: offset un-globalized, overrides/overridden-by direction fixed,
resolve_operator_config reuse, fd-limit, pydantic->dict boundary, PR-5
completeness).
Co-Authored-By: Claude <noreply@anthropic.com>
* chore(install): single-source agent artifacts + sync check (PR-JRAG-0a)
* refactor(resolve): extract resolve_v2 to resolve_service.py (PR-JRAG-0b)
Lift the resolve pipeline out of mcp_v2.py into a transport-agnostic,
neutral-named root module so the CLI's resolve-first layer imports
resolve_service and cannot silently re-implement the pipeline.
Moved to resolve_service.py:
- resolve_v2 + ResolveOutput + ResolveCandidate + ResolveStatus
- All resolve-only private helpers (validate/parse/collect/dedupe/rank/finalize)
- Resolve-only constants (_RESOLVE_*, _*_RESOLVE_RETURN projections)
Moved to graph_types.py (neutral shared module, breaks the load-time cycle
that the plan's "NodeRef stays in mcp_v2" line would have created):
- NodeRef (used by Edge.other, describe_v2, and ResolveCandidate/Output)
- StructuredHint (needed by _to_structured_hints)
- Shared helpers: _hints_or_skip, _node_ref_from_row, _resolve_node_kind,
_node_kind_from_id, _to_structured_hints, set_hints_enabled +
the _hints_enabled flag (single source of truth — previously duplicated,
which was a latent bug since server.py only set it on mcp_v2)
mcp_v2.py re-exports resolve_v2/ResolveOutput/ResolveCandidate/ResolveStatus
(from resolve_service) and NodeRef/StructuredHint/set_hints_enabled
(from graph_types) so every existing importer is unchanged. Zero MCP SDK
imports in any of the three modules. No call site changed.
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(cli): jrag entry point + envelope/render + status (PR-JRAG-1a)
Ships the frozen foundation every later JRAG-CLI PR builds on:
- `java_codebase_rag/jrag_envelope.py` - lean `@dataclass` Envelope (not pydantic),
`resolve_query` (resolve-first mapper: one->proceed+file_location, many->candidates
capped at 10 with reason, none->not_found with `jrag search` hint; auto-pick forbidden),
`normalize_enum` (+ explicit lookup tables for client_kind/producer_kind/source_layer
confirmed against java_ontology/graph_enrich), `mark_truncated` (+1-fetch helper),
`simple_name` (fqn.rsplit('.', 1)[-1] - NodeRef has no `name`), `to_envelope_rows`
(pydantic->dict boundary via `.model_dump()` once).
- `java_codebase_rag/jrag_render.py` - fresh text renderer (listing omits FQN, traversal
shows `root:` + edge rows with `conf:` only on CALLS/HTTP_CALLS/ASYNC_CALLS, inspect
renders ALL keys alphabetical, ambiguous carries reason/no file, scalar fallback),
`tiered_name` (simple name -> name @service -> FQN), truncated hints
("narrow your query" vs "use --offset <N>").
- `java_codebase_rag/jrag.py` - `build_parser` (no global `--offset` - added only to
find/search in PR-1b/PR-4; only `status` registered in 1a), `_resolve_cfg`
(reuses cocoindex-free `resolve_operator_config` + `apply_to_os_environ`),
`_load_graph` (exists check -> `_IndexNotFound`; ontology-mismatch RuntimeError ->
`_IndexStale`; both surface as actionable envelopes), `main` (raise_fd_limit first;
argparse.ArgumentError -> exit 1, handler exception -> exit 2 with status:error
envelope to stdout AND traceback to stderr - deliberate divergence from operator
CLI's traceback-swallowing), `_console_script_main` (os._exit wrapper for the
lancedb/pyarrow worker-thread teardown race), `_cmd_status`.
- `pyproject.toml` - `[project.scripts] jrag = "java_codebase_rag.jrag:_console_script_main"`.
- `README.md` - "## jrag (agent CLI, preview)" subsection.
Lazy-import invariant: `build_parser()` imports no backend modules - the sentinel
`python -c "import java_codebase_rag.jrag as j; j.build_parser()"` loads no
torch/sentence_transformers/mcp_v2. Verified.
Tests: 42 focused tests across test_jrag_{envelope,render,status}.py - all named
tests 1-22 from the brief plus extras (mark_truncated boundaries, simple_name on
pydantic-via-boundary, tiered_name tiers, status text-format envelope,
--offset rejected on status subparser and before subcommand). ruff clean.
Operator CLI tests still pass 62/62 (no regressions).
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(cli): PR-JRAG-1a review — honest offset tests + generic render dispatch (PR-JRAG-1a)
Addresses review Important findings + trivially-correct Minors on top of bdfc670.
Important #1 - honest offset-not-global coverage
- Deleted `test_offset_is_not_a_global_flag` (`jrag callers --offset 5`):
`callers` is not registered in 1a, so argparse rejected the *subcommand*
before seeing `--offset`. The test passed for the WRONG reason and would
not catch a regression that added `--offset` to the `common` parent parser.
The contract is honestly covered by three siblings:
* `test_offset_not_accepted_on_status_subparser` (status IS registered)
* `test_offset_not_accepted_before_subcommand` (the key "not on parent"
test - `jrag --offset 5 status`)
* `test_jrag_help_lists_status_subcommand` (asserts `--offset` absent
from `--help`).
Important #2 - stop overloading `edge_summary` as the inspect-shape signal
- Approach (b): made the dispatch signal STRUCTURAL, not name-based.
`_render_text_shape` now routes to `_render_inspect` when any node carries
any dict-typed value; `_render_inspect` renders ANY dict-typed value as
an indented alphabetical section (not just `edge_summary`). `edge_summary`
is no longer special - it is reserved for PR-JRAG-3 real edge data and is
one of many possible section sources.
- `_cmd_status` no longer stuffs `{counts, edges}` under `edge_summary`;
they are top-level dict-valued fields on the index node. Test updated to
read `index["counts"]` (not `index["edge_summary"]["counts"]`) and assert
`edge_summary` is NOT populated by status.
Minors:
- jrag_render.py: removed dead `if TYPE_CHECKING: pass` block + unused
`TYPE_CHECKING` import.
- jrag_envelope.py `to_envelope_rows`: replaced `dict(item)` fallback with
`raise TypeError(...)` so a non-pydantic/non-dict item surfaces a
backend-contract bug instead of silently coercing.
- jrag_render.py `_render_text_shape`: added a one-line precedence comment
documenting that `ok + root` wins over `ok + nested-dict nodes` by design.
- jrag_envelope.py `to_dict`: copy semantics now uniform - all collection
fields shallow-copied (nodes/edges/candidates/agent_next_actions/warnings)
so the returned dict is a stable snapshot at call time.
Verification:
- Focused: 41/41 pass (down 1 from 42 - the misleading test, expected).
- ruff clean.
- Sentinels green (no mcp / cocoindex import in jrag*.py; build_parser
loads no torch/sentence_transformers/mcp_v2).
- Smoke: `jrag status` against empty index -> actionable `error: ...` envelope,
exit 2 (unchanged).
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(cli): PR-JRAG-1a — explicit inspect render dispatch (shape hint)
Re-review flagged a real foot-gun in the structural dispatch I chose in
6c3b58e: "any dict-valued node field -> inspect" fires before the listing
fallback, so a listing envelope whose nodes carry ANY nested-dict field
after .model_dump() (Symbol nodes WILL: source_range, annotations,
capabilities, metadata, etc.) silently routes the whole listing to
_render_inspect, which renders FQN alphabetically - exactly what
_render_listing is contractually forbidden from doing. Silent mis-render,
no error, on a frozen contract.
The fix - make inspect dispatch EXPLICIT:
- render() gains shape: str | None = None. Passing shape="inspect" routes
to _render_inspect; None falls back to structural inference
(root -> traversal, nodes/noun -> listing, else scalar). The fuzzy
any(isinstance(v, dict) for v in n.values()) predicate is GONE.
- _cmd_status now passes shape="inspect" (its rendered output is unchanged
- the status test still asserts index["ontology_version"] == 17 and
index["counts"] is non-empty).
- test_render_inspect_edge_summary_alphabetical now passes shape="inspect"
explicitly (no longer relies on the structural predicate).
- New regression test test_render_listing_with_dict_valued_node_does_not_route_to_inspect
constructs a listing node with realistic dict-valued fields (annotations,
source_range - the exact Symbol shape that triggered the foot-gun) and
asserts the listing contract holds (FQN omitted, only name + @service).
Verified it would have FAILED under the old any-dict predicate.
Minors:
- Envelope.to_dict docstring tightened to match behavior. Top-level
collection fields are shallow-copied, but node/candidate VALUES are shared
references - mutating a node dict in place DOES propagate to a prior
snapshot. Docstring now says so and points to copy.deepcopy for callers
needing true isolation (envelope is short-lived in practice, so shared
references are not a hazard).
- to_envelope_rows grep confirmed safe: only 2 callers (both in tests,
passing pydantic NodeRef or plain dict). The TypeError raise on unknown
types is safe - no production callers in 1a.
Verification:
- Focused: 42/42 pass (41 prior + 1 new regression test). ruff clean.
- Sentinels green (no mcp/cocoindex import in jrag*.py; build_parser loads
no torch/sentence_transformers/mcp_v2; jrag --help in 0.02s).
- Smoke: jrag status against missing index -> actionable error envelope,
exit 2 (unchanged).
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(cli): jrag find + inspect (PR-JRAG-1b)
Implemented find (query mode + filter mode) and inspect commands:
- find has two modes:
- Query mode (positional <query>): calls g.find_by_name_or_fqn with
fuzzy fallback and post-filters for role/java-kind/annotation/etc.
- Filter mode: builds NodeFilter from flags and calls find_v2.
- Kind inference from domain flags (--http-method→route, etc.)
- Contradiction detection (error envelope when domain flags conflict)
- --offset support in filter mode only; limit capped at 499.
- inspect:
- resolve-first via resolve_query (one/many/none contract)
- describe_v2 for full node details + edge_summary
- file_location populated from resolve_query
- Added next_actions_hook no-op stub to jrag_envelope.py (filled by PR-4).
- All 13 tests pass (test_jrag_locate.py) covering exact/fuzzy/find/
filter/offset/limit/inspect/edge_summary/file_location.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(cli): PR-JRAG-1b — error on non-symbol query mode, defer --fuzzy
Review fixes for PR-JRAG-1b:
- Query mode + non-symbol kind (explicit OR inferred from --http-method/
--client-kind/--producer-kind/etc.) now returns status:error (exit 2)
explaining find_by_name_or_fqn is Symbol-only, telling the user to drop
the positional <query> and use filter mode. Previously silently empty.
- Removed --fuzzy flag from the find subparser + the unimplementable
exact→prefix→contains fallback (find_by_name_or_fqn is exact-only;
NodeFilter only has fqn_prefix). Added to PLAN-JRAG-CLI Out of scope.
- --framework/--source-layer in query mode now surface a warnings[] entry
instead of being silently dropped (SymbolHit lacks those fields).
- Cleaned up query-mode kind_map: only symbol sub-kinds from --java-kind
remain; route/client/producer entries removed (would never match Symbols).
- test_inspect_returns_edge_summary_with_composed_keys now inspects
ChatAssignmentPort#requestAssignment and asserts OVERRIDDEN_BY composed
key present with out>0 (was only checking edge_summary is a dict).
- Added comment on the dict→list→truncate→dict flow in _cmd_find_query_mode.
Tests: removed test_find_fuzzy_falls_back_to_prefix; added
test_find_query_mode_with_non_symbol_kind_returns_error and
test_find_query_mode_framework_and_source_layer_warn. 56 passed, ruff clean.
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(cli): jrag listing tier (PR-JRAG-2)
* fix(cli): PR-JRAG-2 — real listeners --topic-prefix + shared listing helper
Review follow-up on top of ee71fff. Four fixes:
1. (Critical) listeners --topic-prefix now filters for real. The previous
implementation was a documented no-op stub (SymbolHit carries no topic).
Edge-model investigation showed the listener->topic path is:
listener_class -[:DECLARES]-> listener_method -[:EXPOSES]-> Route(topic)
Resolved via a focused graph._rows() Cypher lookup (same pattern as
jrag_envelope._node_file_location). neighbors_v2 was infeasible here:
ASYNC_CALLS run Producer->Route (outbound from producers, so direction="in"
on producers yields nothing), and the EXPOSES Route's topic property is
not projected onto the returned NodeRef.
2. (Important) Extracted shared helpers to kill 7x scaffolding duplication:
_load_graph_or_error (cfg/load/error frame), _clamped_limit (limit clamp),
_render_listing (+1-fetch truncation + envelope + render),
_symbol_hit_to_dict (SymbolHit->dict). routes/clients/producers/jobs/entities
now share the frame; topics and listeners stay bespoke (compose-heavy).
3. (Important) topics now emits a warning when producers lack a topic
("N producer(s) had no topic and were excluded") so the empty-topic case
is distinguishable from the no-producers case.
4. (Minor) Lifted the consumer-fetch limit=100 magic number to the named
module constant _CONSUMER_FETCH_LIMIT (200), reused by topics --consumer-in
and the listeners pre-filter fetch.
New test: test_listeners_topic_prefix_narrows asserts the filter narrows
the listener set (3 -> 1) and matches the known fixture pair
(ComplianceReviewListener consumes 'banking.chat.compliance.review').
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(cli): PR-JRAG-2 — topics --consumer-in via EXPOSES (not ASYNC_CALLS-in)
topics --consumer-in was shipping the same silent-wrong-results defect just
fixed for listeners: it traversed ASYNC_CALLS inbound to Producer nodes,
which is the wrong edge model (ASYNC_CALLS run Producer -> Route per
java_ontology.py:415-416), so it returned empty on every graph.
A consumer of a topic IS a listener, so the EXPOSES-based resolver proved
on listeners is the correct path. Three changes:
1. Generalized the listener-topic resolver into _resolve_topic_consumers
(graph, *, topic, microservice=None, prefix=False) -> list[dict]. Returns
consumer dicts with id/fqn/kind/microservice. _listener_ids_for_topic_prefix
is now a thin wrapper that intersects the result with the pre-fetched
SymbolHit ids.
2. Rewired topics --consumer-in: for each producer-grouped topic, call
_resolve_topic_consumers(topic=topic_name, microservice=consumer_in) for
an exact-match lookup. Removed the dead neighbors_v2(producer_ids, "in",
["ASYNC_CALLS"]) call and the misleading "retained for graphs where
inbound ASYNC_CALLS exist" comment.
3. Made the test non-vacuous: test_topics_consumer_in_resolves_consumers_via_exposes
replaces the rc==0-only test. Fixture reality: no producer topic literal
overlaps a listener topic literal (unresolved constants vs resolved
literals), so the test calls _resolve_topic_consumers directly on the known
resolved pair: topic='banking.chat.compliance.review' + microservice='chat-core'
must return ComplianceReviewListener (also verified for prefix='banking.chat').
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(cli): PR-JRAG-2 — correct topics --consumer-in help text (EXPOSES, not ASYNC_CALLS)
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(cli): jrag direct-backend traversals (PR-JRAG-3a)
Add 11 resolve-first traversal subcommands to jrag: callers, callees,
hierarchy, implementations, subclasses, overrides, overridden-by,
dependents, impact, decompose, flow. Each resolves via resolve_query,
calls a LadybugGraph method (or neighbors_v2 for the override axis),
then renders via the traversal shape (envelope.root + edge rows).
--offset is registered on no traversal subparser.
Verified every backend signature against source (ladybug_queries.py /
mcp_v2.py / java_ontology.py). Two brief adaptations:
* find_implementors DOES accept a capability kwarg (brief claimed
otherwise) -- --capability is PUSHED DOWN on `implementations`.
* CALLS edges are intra-CODEBASE not intra-SERVICE (ontology:286) --
flow help/test 15 frame it as a data property, validated by the
presence of cross-service chat-assign->chat-core endpoints.
OVERRIDES direction confirmed: overrider -> declaration, so out=dispatch
UP (overrides) and in=dispatch DOWN (overridden-by) -- brief correct.
--service is a client-side post-filter + warnings[] on callers-Route
(find_route_callers ignores microservice once route_id set) and on
impact (impact_analysis has no microservice param). --include-external
symmetric on callers/callees; --depth clamped 1..3 on decompose,
--max-hops clamped 1..8 on flow.
Fix: ambiguous resolve (rc=0) no longer falls through to the backend;
all 11 handlers check `if rrc or node is None:`.
tests/test_jrag_traversal_direct.py: 17 named tests (bank-chat fixture).
Focused jrag suite (6 files): 86 passed. ruff clean. Lazy-import
invariant holds (build_parser loads no heavy modules).
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(cli): PR-JRAG-3a — hierarchy/decompose rendering + silent-flag warnings
Address review Approved-with-fixes (6 fixes on top of 4f803e8):
Fix 1 (Important): _render_traversal now honors the `direction` and `stage`
fields it was already passed. Edges carrying `direction` (hierarchy) render
under `↑ supertypes:` / `↓ subtypes:` headers; edges carrying `stage`
(decompose) render under `stage 0 (seed):` / `stage N (role):` headers.
Other traversals stay flat (grouping is conditional on the field being
present). Factored out _format_edge_line to deduplicate the per-row format.
Fix 2 (Important): tests 5 (hierarchy) and 14 (decompose) now assert the
RENDERED structure in --format text (the ↑/↓ and `stage N:` headers must
appear in stdout), not just JSON data presence. Previously they'd pass even
if the renderer never emitted a tree/waterfall.
Fix 3 (Important): --service/--module on hierarchy/overrides/overridden-by/
flow now emit a warnings[] entry explaining the flag isn't applied (structural
edges / no microservice predicate), via shared _warn_unapplied_scope helper.
Plan principle "inapplicable flags never silently ignored" satisfied.
Fix 4 (Important): decompose --limit (when set to non-default) emits a warning
("--limit does not apply to decompose; use --max-stage to cap per-stage
breadth") instead of silently dropping it. Default --limit stays silent.
Fix 5 (Important): hierarchy now applies the limit PER DIRECTION (limit up +
limit down) instead of on the combined list, so a full `up` can no longer
starve `down` behind truncated=True.
Fix 6 (Minor): DRY'd the 11x kind-guard into _require_kind(node, *,
expected, kinds, args, hint="") -> int | None. Applied to 9 handlers
(callers keeps its inline Symbol-or-Route dispatch guard).
Added test 18 (test_inapplicable_flags_emit_warnings) covering Fixes 3+4.
Focused jrag suite: 87 passed. ruff clean. Lazy invariant holds.
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(cli): jrag compose traversals + connection + outline/imports (PR-JRAG-3b)
Add five command surfaces on top of PR-JRAG-3a, all resolve-first (or, for
connection, an explicitly-documented resolve-first EXCEPTION):
* callees Client/Producer variant — Client root dispatches via
neighbors_v2([id],"out",["HTTP_CALLS"]) reaching :Route; Producer root
via neighbors_v2([id],"out",["ASYNC_CALLS"]) reaching the kafka_topic
:Route (NOT :Producer). Symbol path is unchanged from 3a. --include-external
is a warned no-op on the Client/Producer path (edges are to :Route, which
is always in-graph).
* dependencies — neighbors_v2([id],"out",["INJECTS"]) = types this class
injects (Symbol -> Symbol). --service/--module warned (structural edge).
* connection <microservice> — multi-section inbound:/outbound: view. First
positional is a microservice NAME (resolve_v2 NEVER run on it). Inbound =
list_clients(target_service=svc) + find_route_callers on this service's
listener EXPOSES topic Routes; outbound = list_clients(microservice=svc) +
list_producers(microservice=svc). --inbound/--outbound/--both (default both),
--http-method, --calls-service. Synthetic microservice root so the
traversal-shape + section-grouped rendering fire.
* outline <file> — find_symbols_in_file_range(start_line=1, end_line=2**31-1).
start_line=1 is mandatory (backend returns [] for start_line<1). Unbounded
(no --limit cap); missing file is graceful.
* imports <file> — tree-sitter parse via ast_java.parse_java, walk
explicit_imports (dict: simple_name -> FQN), resolve each FQN via resolve_v2.
Static/wildcard imports rendered as unresolved_import rows. File resolved
via cfg.source_root / file_arg (or absolute path).
Renderer extension: _render_traversal gained a third grouping branch keyed on
edge["section"] (alongside stage and direction), emitting inbound:/outbound:
headers for connection. Chosen over a new shape per the global-context guidance.
Backend signatures + edge directions verified against source at PR-JRAG-3b
time: HTTP_CALLS=Client->Route (java_ontology.py:352), ASYNC_CALLS=Producer->
Route (386), INJECTS=Symbol->Symbol (216), EXPOSES=Symbol->Route (294). All
four brief edge claims confirmed correct.
Tests: tests/test_jrag_traversal_compose.py ships 12 named tests (bank-chat
fixture). The full focused jrag suite (99 tests across 7 files) passes serially
in 62s; ruff clean. jrag --help stays fast (22ms) and loads zero heavy modules
(torch/sentence_transformers/mcp_v2/ladybug_queries/cocoindex/resolve_service/
ast_java).
Minor seed correction in tests (not a brief gap): ConfigurableChatAssignment
injects ChatEngineProperties, NOT ChatAssignmentPort. ClientMessageProcessor
is the class that injects ChatAssignmentPort; test 3 uses it as the seed.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(cli): PR-JRAG-3b — inbound default, --calls-service loophole, imports text marker (PR-JRAG-3b)
Address PR-JRAG-3b review (3 Important + bundled Minors). No Critical; the
functional correctness from the initial commit was strong (signatures verified,
section renderer guard explicit/safe). Follow-up on top of 1e5241f.
Fix 1 (Important): connection default direction is now --inbound (brief-faithful).
- Handler: `direction = getattr(args, "direction", None) or "inbound"` (was "both").
- Subparser description rewritten so it's internally consistent (default
--inbound; --outbound / --both are opt-ins; the previous text contradicted
itself between "Inbound (default)" and "no direction flag renders both").
- Help text on --inbound/--outbound/--both updated to state the default clearly.
- Test 6 (test_connection_both_default) rewritten to assert default == --inbound
(default MUST equal explicit --inbound; --both is the explicit opt-in).
Fix 2 (Important): --calls-service outbound loophole tightened.
- Previous predicate `(target_service == calls_service) or not target_service`
matched unresolved clients (empty target_service, e.g. AuditLogClient);
silent-wrong-results. Split into two predicates:
* Clients: STRICT `_calls_service_match_out_client` — `target_service ==
calls_service` exactly. Unresolved clients are EXCLUDED.
* Producers: kept (no service target on ASYNC channels) with ONE warnings[]
entry: "--calls-service does not filter producers (...); N producer(s) kept
visible". The warning fires only when producers_bypass_calls_service and
there are producers to keep.
- Help text on --calls-service and --http-method now states they apply to HTTP
callers only and producers bypass with a warning.
- N+1 producer fetch in the inbound-async loop cached per caller_microservice.
- New test 13 (test_connection_calls_service_outbound_excludes_unresolved_clients)
asserts: chat-core clients KEPT, AuditLogClient EXCLUDED, producers KEPT
with the warning.
Fix 3 (Important): text-mode imports distinguishes resolved vs unresolved.
- _render_listing now appends " (unresolved)" to nodes with kind ==
"unresolved_import". Without this marker, resolved Symbols and unresolved
placeholders rendered identically in text mode (only JSON distinguished).
- New test 14 (test_imports_text_mode_marks_unresolved) asserts the marker
appears on Spring imports (unresolved) and NOT on JoinOperatorRequest
(resolved graph Symbol).
Fix 4 (Minors, bundled):
- Test 3 docstring corrected to match the seed (ClientMessageProcessor injects
ChatAssignmentPort, NOT ConfigurableChatAssignment).
- imports --limit warning mirrored from outline (warns when --limit is set
away from the default; the full import block is always returned).
- dependencies --include-external flag ADDED for surface symmetry with
callers/callees; the handler emits a warning (INJECTS is structural Symbol
-> Symbol with no external-exclusion analog).
- Help text rewritten to document the --calls-service / --http-method HTTP-only
scope and the producer-bypass warning.
Tests: 14/14 in test_jrag_traversal_compose.py (12 + 2 new); focused jrag suite
101/101 (was 99 + 2 new) passes serially in 65s; ruff clean; build_parser
loads zero heavy modules.
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(cli): jrag orientation + search + hints + packaging (PR-JRAG-4)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(cli): PR-JRAG-4 — render next_actions in text + e2e hint test (PR-JRAG-4)
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(install): --surface mcp|cli branching + CLI skill/subagent (PR-JRAG-5)
Add Surface (Literal['mcp','cli']) branching to the installer so
'java-codebase-rag install --surface cli' deploys the jrag CLI skill +
subagent instead of the MCP entry. Fixes the update regression where a
CLI-only install was invisible (no MCP entry to scan → fatal exit 2).
Installer:
- Surface + ConfiguredHost NamedTuple (host, scope, surface)
- ARTIFACT_MANIFEST single source iterated by deploy_artifacts and
refresh_artifacts; 'surface="mcp"' keyword-only default preserves the
6 direct-call sites in test_installer.py
- .java-codebase-rag.hosts marker file written at install; read by
detect_configured_hosts (returns list[ConfiguredHost]); legacy MCP-entry
scan + surface='mcp' fallback for pre-marker installs
- run_update unpacks (host, scope, surface) and passes surface= to refresh
- resolve_mcp_command surface-conditional: cli resolves 'jrag' via
shutil.which and skips the MCP-binary SystemExit(2); prompt + display
name parameterized via _surface_binary
- select_surface wizard step + --surface flag; handle_rerun prefill from
marker via _prior_surface_from_marker
CLI skill + subagent (canonical at dev root, synced via PR-JRAG-0a):
- skills/explore-codebase-cli/SKILL.md (jrag shell vocabulary)
- agents/explorer-rag-cli.md (Claude Code subagent driving jrag)
- scripts/sync_agent_artifacts.py SYNC_MAP extended with the new skill
- install_data copies byte-equal to dev source (drift gate green)
Tests + docs:
- tests/test_installer_surface.py: 10 named tests + 3 supplementary
- tests/test_installer.py: 3 TestDetectConfiguredHosts cases migrated
from 2-tuple unpack to the 3-field ConfiguredHost NamedTuple
- tests/test_agent_skills_static.py: EXPECTED_SKILL_DIRS adds
explore-codebase-cli; TestCliSkillFrontmatter validates the new skill's
frontmatter (MCP-vocabulary tests stay scoped to explore-codebase)
- tests/test_install_data_sync.py: synthetic temp workspaces seed the
new skills/explore-codebase-cli source dir
- AGENTS.md, skills/README.md, README.md: document the two surfaces
Verification:
- ruff check . : All checks passed
- sync_agent_artifacts.py --check : All agent artifacts in sync
- focused tests (5 files) : 123 passed, 2 skipped (heavy integration)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(cli): drop unimplemented --brief/--fields/--count/--exists; fix install --verbose mock (final review)
- Remove four unimplemented flags (--brief, --fields, --count, --exists) from the
common parent parser in jrag.py. These flags were registered but never read by
any handler, violating the "inapplicable flags never silently ignored" principle.
- Remove references to these flags from skills/explore-codebase-cli/SKILL.md and
agents/explorer-rag-cli.md documentation.
- Fix test_cmd_install_forwards_verbose_flag mock signature to accept surface="mcp"
(matching run_install's new signature from PR-5), and assert the default forwarding.
- Run sync_agent_artifacts.py to propagate documentation changes to install_data.
Fixes #END_OF_PLAN_FULL_SUITE
* fix(jrag): user-reported bugs + review findings (post-PR-377)
Three bugs surfaced while testing jrag, plus two findings from the fresh
4-reviewer fan-out folded in.
1. `java-codebase-rag install` never prompted for CLI-vs-MCP surface: the
`--surface` flag defaulted to "mcp", so argparse always populated
args.surface and select_surface returned immediately, bypassing the
interactive wizard (and ignoring the marker prefill on re-run). Default
is now None; interactive prompts, non-interactive still falls back to
"mcp" inside select_surface.
2. `jrag` usage text leaked internal PR-tracking tags ("Status command
(PR-JRAG-1a)", "PR-JRAG-3b adds Client/Producer variants") and listed
only `status` under a stale heading. Rewritten with a grouped command
list (health / locate / listings / traversal / orientation / search) and
no internal tags; the callees epilog now describes semantics, not backend
calls.
3. `jrag routes` (and clients/producers/topics) rendered blank names —
routes carry `path`/`method`, not `fqn`, so simple_name returned "".
New display_name() picks the identifying field per node kind
(METHOD path / member -> topic / member -> target / fqn); used by the
listing, tiered_name (traversal targets), and ambiguous renderers.
Reviewer findings:
- installer _write_hosts_marker uses os.replace (not os.rename) so the re-run
overwrite path works on Windows too (PR #371 fixed this pattern elsewhere).
- test_installer_surface + test_cmd_install_forwards_verbose_flag updated
for the --surface default=None contract.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(jrag): address fresh-review findings A-G (silent-wrong-output + test debt)
Folded review fan-out (review-fresh-1..4) findings into one commit.
Code fixes (silent-wrong-output / spec-compliance):
- A: resolve_query returned `ambiguous` with empty candidates when a
post-filter rejected every `many` candidate; now `not_found` with the
filter-failure message (an empty ambiguous list had no narrowing value).
- B: _cmd_connection wrapped list_by_capability(MESSAGE_LISTENER) in a bare
`except: listener_hits = []` -> silent wrong-results; now warns-and-continues
so an empty async inbound section is distinguishable from "no listeners".
- C: find query-mode truncation was decided on the POST-filtered list, so a
post-filter that dropped rows silently cleared `truncated`; now decided on
the raw name/FQN fetch (limit+1), with a warning when post-filters apply
after a capped fetch. find filter-mode didn't slice out.results -> displayed
limit+1 rows at the boundary; now slices to limit and sets truncated from
has_more_results OR len>limit.
- D: overrides/overridden-by set direction="up"/"down" on edge rows, tripping
the renderer's has_direction guard -> mislabeled flat lists as
`↑ supertypes:` / `↓ subtypes:`. Dropped the direction key (flat is correct).
- E: jrag_hints result_edges fallback emitted self-hints (after `callers` it
suggested `jrag callers` again). Added current_command param (plumbed via
next_actions_hook + _emit_traversal) to skip the self-hint; the inverse
direction (`callees` after `callers`) is kept — that's the useful signal.
- F: warnings[] were JSON-only (the renderer never emitted them in text), so
the "inapplicable flags never silently ignored" spec was unenforced for text
consumers. The renderer now appends `warning:` lines; status/microservices/
map/conventions/topics/impact now warn on inherited flags they don't apply.
Tests (G + pinning):
- Pin A/D/E/F with focused regression tests.
- Strengthen vacuous assertions: test_resolve_service (7/10 skipped -> use the
ladybug_db_path fixture; tautological `status in (one,many,none)` -> per-branch
contracts), test_jrag_listing (truncated now actually verified; client_kind
enum must not accept un-normalized "feign"; routes must have `path`),
test_jrag_locate (find_by_capability/annotation prove narrowing vs.
unfiltered; inspect-ambiguous asserts each branch instead of `elif ok: pass`),
test_jrag_orientation test 7 (overview --as vacuous `if status==ok` guard ->
unconditional dispatch assertion).
Verified: 138 focused jrag/resolve tests green; full suite 1006 passed / 16
failed, all 16 pre-existing or fixture-pollution (4 TestPR4IndexProgress are
order-dependent pollution from test_java_codebase_rag_cli init/erase tests
running against the real corpus_root — tracked as a separate follow-up, not a
regression from this commit).
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(jrag): end-to-end CLI bug sweep from bank-chat-system audit
Address every non-design finding (A-L). search: populate _score from
_distance in the non-hybrid path (was 0.0 for all hits; sort was already
correct). resolve: auto-pick class over constructor when survivors are a
class plus its own ctors; argless method FQNs now resolve via prefix match.
normalize_enum: framework/java_kind -> lowercase (role/capability stay
UPPER) so routes --framework and search --java-kind filter work. Bad enum
filters now return a clean status:error envelope instead of a traceback.
routes listing gains [http]/[kafka] type tags, backfills microservice from
filename, and falls back to filename so no blank lines. connection default
is --both (services no longer look connectionless). map gains --by
{microservice,module}. decompose auto-promotes a method seed to its owning
type. inspect renderer recurses nested dicts / list-of-dicts. Suppress
search stderr noise (tqdm, resource_tracker warning, fail-loud diagnostic).
overview bare-arg gives a helpful error. Update the 3 tests whose
assertions encoded the old buggy contracts.
Co-Authored-By: Claude <noreply@anthropic.com>
* docs: trim AGENTS.md to the python-env essentials
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(cli): --detail orthogonality (brief|normal|full) for text+json (PR-JRAG-6)
Split the concern conflated by --format {text,json} into two orthogonal axes:
--format picks representation, --detail picks how much of each node/edge. Both
modes honor the same detail level through one projection seam (project_envelope),
invoked once in render() before JSON and text dispatch.
- jrag_envelope: project_node/project_edge/project_envelope + _drop_empty +
_compose_file; category key-sets (_BRIEF/_NORMAL/full). to_dict/to_json stay
verbatim — projection is a separate transform.
- jrag_render: render() projects once; listing shows inline module/role/file/
score at normal, per-row block at full; edges append mechanism/all attrs.
- jrag: --detail flag (default normal); inspect + orientation subparsers
set_defaults(detail="full"); all 45 render calls thread detail=args.detail;
_symbol_hit_to_dict carries the full SymbolHit (file at normal, signature/
annotations/capabilities at full).
- Fixes "text too terse" (normal shows file/score) and "json dumps 50-line
snippet + 10 empty fields" (_drop_empty inside node dicts; snippet is full-only).
- Tests: 8 projector + 6 orthogonality/text-level tests; existing render tests
updated for the new default. 142 jrag tests + 86 skill/operator tests green;
ruff clean; token-budget re-pinned under normal.
- skill/agent docs document --detail; install_data synced; drift test green.
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(cli): id-free agent surface — strip graph node ids from text + json
jrag is resolve-first (agents pass FQN/simple-name/route/topic, never raw
ids), but 40-hex graph node ids leaked on both surfaces. Strip them at the
CLI output boundary; MCP and the graph backend stay id-based internally.
- envelope: project_node drops id/*_id recursively at every detail level;
to_json rekeys nodes by natural key (FQN / METHOD path / member_fqn->target
/ topic:<name> / file), root becomes that key, edges carry `target` instead
of other_id. to_dict stays id-keyed for internal/debug use.
- render: _render_inspect and _render_ambiguous no longer print ids.
- jrag: fix argparse --detail default footbug — parents=[common] shared the
--detail Action by reference, so status.set_defaults(detail="full")
mutated it for every subparser. New _common_parser() factory gives each
subparser its own action; listing commands declare detail="full".
- fixture: add @CodebaseProducer to FollowUpKafkaPublisher#publishCompliance
Review so its topic resolves (Java constant-ref resolution is engine gap #378).
- AGENTS.md: rule to erase stale tests/*/.java-codebase-rag* before tests.
- README: id-free JSON envelope example.
- tests: drop id/parent_id from expected field sets; other_id -> target.
Verified: 31/31 commands id-free on text+json; 147 passed in the jrag +
brownfield subset; MCP/resolve tests green (scope stayed CLI-side).
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(jrag): disambiguate callees/callers rows + honor --detail in traversal text
callees/callers text rendered bare method names (getId x4, process x5,
create x2) with no declaring class, and diverged from JSON at the same
--detail level (JSON carried module/role/file; text showed only
`name @service conf`). --detail full was a no-op for traversals (byte-
identical to brief).
- display_name: method symbols (pkg.Class#m(args)) now render as
Class#m, folding the declaring class into the label at every detail
tier. Classes/routes/clients/producers unchanged.
- _format_edge_line -> _format_edge_rows: mirror _render_listing's
detail handling (PR-JRAG-6 fixed listings but not traversals). normal
appends the target node's module/role/symbol_kind/file/score inline;
full emits a nested per-edge content block (signature/annotations/
modifiers/package). Root line enriched the same way via shared
_node_normal_extras / _node_full_rows helpers.
Tests: 3 new in test_jrag_render.py (method label, normal text/json
field parity, full block); full suite 1039 passed, 14 skipped.
Co-Authored-By: Claude <noreply@anthropic.com>
* docs: refresh README + AGENTS.md, drop reports/ and automation/
- README: converge overlapping tool/command sections into a single
"Tools & commands at a glance" inventory (all 5 MCP tools + the full
jrag catalog); remove "5-minute walkthrough" and "Wire into an MCP
host"; lead with the MCP-or-CLI duality; drop the automation/ row.
- AGENTS.md: shorten python env; add docs map (operator/internal split),
a full-suite test-cost note, and a shipped-artifacts note for
skills/ and agents/.
- Remove docs/reports/ and automation/; clean the stale automation/
reference in tests/README.md.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(cli): force UTF-8 stdio on Windows so glyph output stops crashing
jrag/cli renderers and sync_agent_artifacts emit non-ASCII glyphs (hierarchy
tree headers ↑/↓, ✓ success marker, →/… listing lines). On Windows the
console-script stdout defaults to the ANSI codepage (cp1252), which can't
encode them, so print() raised UnicodeEncodeError and the process exited
non-zero — failing test_hierarchy_renders_tree_both_directions and
test_install_data_artifacts_in_sync_with_dev_source on windows-latest
(ubuntu/macos already default to UTF-8).
- _stdio.py: force_utf8_stdio() reconfigures stdout/stderr to UTF-8 (no-op
on Unix); called from the console-script entry points so in-process
main() callers keep host-supplied streams.
- sync_agent_artifacts.py: inline the same reconfigure (stdlib-only dev script).
- tests: decode subprocess output as UTF-8 in the two glyph-asserting
harnesses (text=True alone used the locale ANSI codepage on Windows).
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(jrag): honest empty envelopes + undoubled names + HTTP-only routes
Phase 0 of the cross-service caller-flow fix.
- display_name no longer doubles the class when the backend name already
carries Class#member (was Controller#Controller#method in ambiguity lists).
- callers on a server-exposed route with no in-repo callers now reports
"external entrypoint — no in-repo callers" (text) / is_external_entrypoint
(json) instead of a bare, bug-looking "0 callers".
- routes defaults to controller-exposed HTTP routes only (excludes kafka
topics and client http_endpoint mirrors); --include-kafka opts back in.
Filtering lives in LadybugGraph.list_routes behind opt-in params so
library/MCP callers are unchanged.
Verified against tests/bank-chat-system via plain jrag; 196 tests pass.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(jrag): cross-service route callers resolve + name their callers
Phase 1 of the cross-service caller-flow fix — the 2-step north-star:
`jrag routes` then `jrag callers '<route>'` (no flags) returns who calls
a route, including cross-service callers, NAMED.
Backend (ladybug_queries.py):
- find_route_callers / trace_request_flow are now mirror-agnostic. Strict
HTTP_CALLS/ASYNC_CALLS edge queries (carry confidence/match/raw_uri) are
UNIONed with path/topic-based queries that match Client/Producer nodes by
the entry route's microservice + path_template/topic; dedup by
caller_node_id so edge rows win and path rows fill gaps. Cross-service
edges that terminate at a client-side mirror Route no longer return 0.
- RouteCaller carries declaring_symbol_fqn; callers are identified by the
declaring Symbol (the method that owns the Client/Producer), not the
call-site path — mirrors trace_request_flow so `callers` names WHO calls.
Resolve (resolve_service.py):
- _drop_route_mirrors: a `/path` query no longer resolves to both a client
mirror and the server route (was "ambiguous", blocking the no-flags flow).
Mirrors (no EXPOSES, empty microservice) colliding with a server-exposed
route are dropped; genuine cross-ms same-path ambiguity still surfaces.
- Client roots reachable by name/FQN via DECLARES_CLIENT; short Class#method
(no package) resolves via fqn-contains.
- resolve_v2 + all matchers accept microservice/module scope.
Envelope/handler (jrag_envelope.py, jrag.py):
- resolve_query threads microservice/module; --service narrows route
resolve (callers opts in via apply_scope) instead of post-filtering
caller microservice (which silently dropped cross-service callers).
- _cmd_callees: CLIENT-role symbols aggregate outbound HTTP targets.
Verified against bank-chat-system: `jrag callers '/chat/joinOperator'` →
ChatCoreFeignClient#joinOperator + ChatCoreJoinClient#joinOperator (both
@chat-assign); `/chat/assign` still external-entrypoint; flow inbound named.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(jrag): root-kind-aware next-action hints + listing breadcrumbs
Phase 2 of the cross-service caller-flow fix (T4).
next_actions (jrag_hints.py):
- New root_kind param (default None, back-compat). Route roots short-circuit
to [flow, inspect] (minus the current command) — they previously got
`callees <route>` from HTTP_CALLS, which errors on a route root.
- _KIND_ALLOWLIST filters every emitted hint: client/producer → {callees,
inspect}; symbol/unknown → unfiltered. Dead-end hints are gone.
- DECLARES_CLIENT / DECLARES_PRODUCER now map out→callees (a Symbol that
declares a client/producer; callees aggregates their HTTP targets since
Phase 1). EXPOSES stays unmapped (no clean command).
next_actions_hook (jrag_envelope.py):
- Threads root_kind into next_actions.
- Listing commands (root is None) now emit template breadcrumbs
(routes→callers/flow, clients/producers→callees, topics→listeners).
Rendering: listings print the `next:` lines (jrag_render._render_listing).
Verified: callers/flow on a route hint flow+inspect (never callees);
routes lists hint `jrag callers '<path>'`; inspect of a feign client hints
callees. 137 tests pass (+10 new).
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(jrag): Java-kind guards, search score floor, dedup, scope/validation
Phase 3 — the consistency sweep (T6 + T5/T7/T8 remainders).
- _require_kind now validates Java kind/role (NodeRef carries symbol_kind +
role): implementations expects interface, subclasses expects class/interface,
overrides/overridden-by expect method. A class arg no longer silently
returns empty — it errors with the resolved vs expected Java kind.
- search: --min-score floor (default 0.0 drops negative-score noise; a
nonsense query now returns 0 instead of 20 noise hits) and every hit
carries file=<path>:<line> (SearchHit gains filename/start_line in mcp_v2).
- callees edges deduped by (other_id, edge_type); empty/None other_id dropped
(was emitting duplicate + phantom call-site edges).
- outline resolves <file> via _resolve_source_path (parity with imports).
- conventions forwards --service to the Route framework tally (was global).
- connection/overview validate the microservice against microservice_counts —
a bogus service errors "unknown microservice" instead of a silent empty ok.
overview --service <valid> now defaults the subject to that service.
158 relevant tests pass (+8 regression); full suite run separately.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(installer): capture index-progress tests at fd level (capfd)
The PR4 index-progress tests asserted on stderr text captured via
contextlib.redirect_stderr(io.StringIO()). The progress renderer writes
through rich.Console(stderr=True) — fd 2 — and contextlib.redirect_stderr
only swaps the sys.stderr Python object, so under pytest's DEFAULT fd-level
capture the renderer's bytes bypass the StringIO and the assertions fail
(they passed under -s, masking the fragility).
Switch the five affected TestPR4IndexProgress tests to pytest's capfd
(fd-level capture): they now see the real rendered bytes regardless of
capture mode. Four were failing under default capture (no -s) in the full
suite; the fifth (stdout_contract) was passing vacuously (empty captured
output satisfied the "X not in out" asserts). The two update tests that
genuinely passed are left unchanged.
Full suite now: 1060 passed, 9 skipped, 0 failed (was 1056 + 4 failed).
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(jrag): replace *-prefix filters with *-contains (substring match)
The structural string filters (--path-prefix, --calls-path-prefix,
--topic-prefix, --fqn-prefix) matched with STARTS WITH only, so a user who
knew a path/topic/FQN segment but not its anchored start (e.g. route
/chat/assign searched as /assign) got 0 rows. Substring matching (Cypher
CONTAINS) strictly subsumes prefix for these naturally-rooted fields.
Renames fqn_prefix/path_prefix/target_path_prefix/topic_prefix -> *_contains
across the whole filter pipeline: NodeFilter model, list_* SQL pushdown,
_node_matches_filter post-filter, _symbol_where_from_filter, hint emission,
the resolve-time fqn post-filter (jrag_envelope), CLI flags (incl. the
resolve_parent parser covering 15 commands), the MCP schema (server.py),
tests, operator docs, and the shipped skills/agents + install_data mirrors.
- Deletes _validate_no_wildcards + 3 call sites: dead under literal CONTAINS.
- producer_topic_prefix (resolution strategy enum) and indexer/resolve
internals (class_path_prefix, path_prefix_seed) intentionally untouched.
- Clean break: old *_prefix keys/flags removed, not aliased.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(conftest): enforce editable install to kill stale-CLI rediscovery
Every session rediscovers that the venv carries a stale non-editable copy of
java_codebase_rag in site-packages: pytest passes (conftest inserts the repo
on sys.path[0]) but the jrag/java-codebase-rag console scripts import the
stale copy. Add a pytest_configure guard that imports in a clean subprocess
(cwd=$HOME, no PYTHONPATH) mirroring what the console scripts see, and fails
collection with the one-line fix when it doesn't resolve to the repo source.
Checks against BUNDLE_DIR/java_codebase_rag, not BUNDLE_DIR: the venv lives
at .venv under the repo, so a site-packages copy is also "under" BUNDLE_DIR
and a naive is_relative_to(BUNDLE_DIR) check silently passes the stale copy.
AGENTS.md notes the editable-only rule so agents fix silently instead of
reporting.
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor(jrag): rename routes/clients → http-routes/http-clients, drop --include-kafka
The `routes` and `clients` subcommands were HTTP-only but their names didn't
say so — confusing beside the self-describing async siblings (producers/
topics/listers). `routes` also leaked Kafka topic routes via `--include-kafka`,
contradicting its "List HTTP routes" help and the dedicated `topics` command.
- Hard-cut rename: `routes` → `http-routes`, `clients` → `http-clients`. Old
names now rejected by argparse (no aliases).
- Drop `--include-kafka`; pin `include_kafka=False` in `_cmd_routes` to keep
the HTTP-only behavior (the backend default is True — dropping the kwarg
would have silently re-admitted Kafka topics).
- Rename `_LISTING_BREADCRUMBS` keys in lockstep — keyed by `args.command`
via `add_subparsers(dest="command")`, so a mismatch would silently empty
`agent_next_actions`.
- CLI-surface only: backend `list_routes`/`list_clients` and the
`include_kafka` parameter are unchanged.
- Synced shipped agent/skill artifacts (agents/, skills/) → install_data/.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(jrag): --detail brief/normal/inspect/overview contract fixes
Tasks 9/10/11 from the --detail audit:
Task 9 (BLOCKER): status/microservices/map/conventions --detail brief
emitted empty nodes ({"index":{}}) because project_node's brief/normal
scalar allow-list dropped the dict/list-valued sections that ARE the
payload. Added rollup detection: nodes with no fqn/name/path/topic/
member_fqn keep nested dict + list-of-dict sections at every level.
Subject nodes (inspect/listing/traversal) keep the strict scalar gate.
Task 10.1: find <fqn> --detail full now carries signature/annotations/
modifiers/package (populated from SymbolHit; the projector trims them at
brief/normal). Previously the node never carried the content fields, so
the contract's "(when present on the node)" was vacuously empty.
Task 10.2: inspect brief/normal/full now differ. _cmd_inspect flattens
NodeRecord's nested `data` sub-dict to the top level (renaming
data.kind -> symbol_kind to match find/search) so the projector's
identity/classification keys can reach name/module/role/file. Brief=
identity (4 keys); normal=+module/role/symbol_kind/file (8); full=
+signature/annotations/modifiers/package/edge_summary (11). Previously
brief==normal (only outer kind/fqn escaped).
Task 10.3: overview microservice/topic bundles now honor --detail.
Built as rollups (no fqn/name/path/topic; kind stays for self-id) so
project_node keeps the bundle dict + sample lists. Command-side sample
sizing varies content: brief=counts only, normal=+3 samples, full=+5.
Task 11.1: search --detail brief shows scores. Added `score` to
_BRIEF_NODE_KEYS (identity-adjacent; listing/traversal rows built from
NodeRef carry no score, so this only affects SearchHit) and a brief-tier
inline extras render path so the score surfaces in text too.
Task 11.2 (imports --format json multi-root): did not reproduce — the
current _cmd_imports already folds warnings into envelope.warnings and
emits a single JSON root. Verified with parse-error file + static-import
file: single root, parses cleanly.
Tests updated to match the new contracts (score in brief; overview topic
shape). 151 jrag-relevant tests pass.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(jrag): flag/UX contract + empty-result discoverability + hints backfill
Task 13 — flag correctness:
- search --framework: framework is route-only on NodeFilter, so applying it
to a symbol result set crashed pydantic validation. Pulled framework OUT of
the NodeFilter; it now post-filters hits by mapping the tag back onto the
declaring type's annotations (spring_mvc -> @RestController etc.) via one
focused graph lookup. Unknown framework -> clean error envelope (no crash).
- outline --limit: was documented "unbounded by design" and silently ignored.
Now truncates the entry count and sets `truncated` (parity with listings).
Task 14 — empty-result discoverability:
- subclasses <interface>: returned 0 with no cross-ref. Now emits a
`next: jrag implementations <fqn>` hint when the root is an interface
(implementations is the command that finds IMPLEMENTS-in classes).
- find <partial>: query mode is exact-match only; a partial name returned 0
with no guidance. Now emits both a `message` and an agent_next_action
suggesting `jrag find --fqn-contains <query>` (substring fallback).
Kept exact-match default (documented; what FQN resolution expects).
Task 15 — error/UX contract:
- argparse missing-positional: bypassed --format json with raw usage text.
Added _EnvelopeArgumentParser (error() raises instead of dumping usage);
main() now catches ArgumentError, emits a status:error envelope honoring
--format to stdout (parity with overview/find), mirrors a terse line to
stderr for shell users, and exits non-zero (2).
- Exit-code alignment (DECISION): not_found -> 0 (agents read the envelope,
resolve legitimately found nothing); missing-required-positional -> 2;
missing-index -> 2 (unchanged). All "nothing matched" paths now exit 0;
all "misuse / can't proceed" paths exit non-zero.
- status/microservices accepted --service/--module/--limit then warned no-op.
Now REJECT them at parse time via a new _core_parser (keeps --format /
--detail / --index-dir) so the surface is honest.
Task 16 — next: hint backfill + map label:
- Added agent_next_actions to jobs/listeners/entities (per-row
`jrag inspect <fqn>`), microservices (`jrag map`/`jrag conventions`),
map (`jrag overview <scope>`/`jrag conventions`), conventions
(`jrag find --role <top>`/`jrag map`), outline (per-row inspect),
search (per-hit inspect). Text renderer shows <=2; JSON carries <=5.
- map --module X previously labeled group_by: microservice while the user
was asking about a module. Now --module (without explicit --by) implies
the module axis, so the label matches the actual grouping axis.
Task 12 — verified NOT bugs (false positives in spec):
- impact closure works (impact_analysis does reverse transitive
INJECTS|IMPLEMENTS|EXTENDS closure; EventProcessor returns root + 12
implementors). The spec's AssignQueueEntity example has 0 inbound edges,
so root-only is correct.
- connection --inbound works (chat-core returns 3 inbound HTTP clients).
- topics --consumer-in works with a valid microservice (chat-core returns
ComplianceReviewListener); the spec's `chat-engine` is a MODULE.
Tests: 318 passed / 4 skipped (jrag+traversal+listing+orientation subset);
149 passed / 4 skipped on a second targeted sweep
(map/conventions/search/outline/subclasses/find/microservices/jobs/listeners/
entities/overview/inspect). No regressions.
Co-Authored-By: Claude <noreply@anthropic.com>
* 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>
* fix(jrag): auto --service scope from cwd (parity with MCP ScopeManager)
Consumers can build the index at the system level (above microservices)
but work inside one microservice. MCP silently defaults `--service` to
the cwd-derived microservice (server.py ScopeManager -> find/search/
neighbors); the CLI did not, so cross-service results leaked in (e.g.
`search "audit log"` from chat-assign/ returned 14 chat-core + 2
chat-assign hits; `find Client` returned the chat-core entity).
Mirror MCP rather than reinvent:
- _apply_auto_scope reuses graph_enrich.detect_microservice_from_path +
the indexed-set validation guard (keep candidate on empty/error,
None at system root); defaults args.service to the cwd microservice
on the 16 commands where --service is a result filter (find/search,
7 listings, 7 traversals). Orientation/structural/subject/file
commands stay unscoped. Explicit --service always wins.
- --no-auto-scope flag + JRAG_NO_AUTO_SCOPE env escape hatch.
- Transparency: stderr line + envelope warnings[] notice (6 seams,
incl. topics which builds its own envelope, not via _render_listing).
- impact post-filter warning gated on _service_user so auto-injected
scope filters results without the caveat noise the user didn't ask for.
- Resolve semantics unchanged (matches MCP, which also doesn't
auto-scope resolve); cross-service resolve ambiguity on inspect/
callees is a pre-existing, separate gap.
Verified: tests/test_jrag_auto_scope.py (10 cases) + full suite
(1063 passed, 14 skipped).
Co-Authored-By: Claude <noreply@anthropic.com>
* revert(cli): drop operator-CLI force_utf8_stdio (regressed test on Windows)
The preventive force_utf8_stdio() in cli._console_script_main (added in
31328d6) made the operator CLI emit UTF-8, but test_cli_quiet_parity.py
captures `java-codebase-rag` output with bare text=True, which on Windows
decodes via the cp1252 locale codepage. The UTF-8 middle-dot (·, c2 b7)
then decoded to "·" and the regex `erase · source=` stopped matching —
failing test_pipeline_header_footer_present, which had passed pre-fix.
The operator CLI was not crashing on Windows before (no originally-failing
test exercised it; the ✓/· footer rendered fine under cp1252 decoding), so
the change fixed a non-bug. Revert it; keep the jrag + sync_agent_artifacts
fixes that the two real failures required.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(jrag): inspect forwards --service/--module to resolve_query
inspect inherited --service/--module/--limit from _common_parser but,
unlike every other command, neither applied nor warned on them. --limit is
genuinely N/A (describe_v2 returns one node); --service/--module were missed
wiring — resolve_query already accepts them to disambiguate cross-service
name collisions, and find + the traversal commands already forward them.
Now forwards microservice=args.service or "", module=args.module or "" so an
ambiguous simple name (e.g. UserController in service-a vs service-b) resolves
to a single node when --service is given. No-flag path unchanged ("" matches
resolve_query's default).
Adds test_inspect_service_flag_disambiguates_collision on the
route_extraction_smoke fixture (UserController as smoke.a.* / smoke.b.*).
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent c7e2e6f commit 514048e
68 files changed
Lines changed: 17667 additions & 3979 deletions
File tree
- .agents/skills/publish-pip
- agents
- automation
- cursor_propose_only
- tests
- docs
- reports
- review/completed
- java_codebase_rag
- install_data
- agents
- skills
- explore-codebase-cli
- explore-codebase
- plans/active
- propose
- scripts
- skills
- explore-codebase-cli
- explore-codebase
- tests
- bank-chat-system/chat-core/chat-engine/src/main/java/com/bank/chat/engine/kafka
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
47 | 47 | | |
48 | 48 | | |
49 | 49 | | |
50 | | - | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
51 | 57 | | |
52 | 58 | | |
53 | 59 | | |
54 | 60 | | |
55 | | - | |
| 61 | + | |
56 | 62 | | |
57 | 63 | | |
58 | 64 | | |
59 | | - | |
| 65 | + | |
60 | 66 | | |
61 | 67 | | |
62 | 68 | | |
63 | 69 | | |
64 | 70 | | |
65 | | - | |
| 71 | + | |
66 | 72 | | |
67 | 73 | | |
68 | 74 | | |
69 | 75 | | |
70 | 76 | | |
71 | | - | |
| 77 | + | |
72 | 78 | | |
73 | 79 | | |
74 | 80 | | |
| |||
79 | 85 | | |
80 | 86 | | |
81 | 87 | | |
| 88 | + | |
82 | 89 | | |
83 | 90 | | |
84 | 91 | | |
| |||
Large diffs are not rendered by default.
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
156 | 156 | | |
157 | 157 | | |
158 | 158 | | |
159 | | - | |
160 | | - | |
161 | | - | |
162 | | - | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
163 | 163 | | |
164 | | - | |
| 164 | + | |
165 | 165 | | |
166 | 166 | | |
167 | 167 | | |
| |||
260 | 260 | | |
261 | 261 | | |
262 | 262 | | |
263 | | - | |
264 | | - | |
265 | | - | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
266 | 266 | | |
267 | 267 | | |
268 | 268 | | |
| |||
This file was deleted.
0 commit comments