Skip to content

Commit 514048e

Browse files
HumanBean17claude
andauthored
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

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/skills/publish-pip/SKILL.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,28 +47,34 @@ adding a runtime dependency to `pyproject.toml`.
4747
```bash
4848
rm -rf dist build *.egg-info
4949
```
50-
4. **Build** sdist + wheel:
50+
4. **Sync agent artifacts** — ensure install_data copies match dev source:
51+
```bash
52+
.venv/bin/python scripts/sync_agent_artifacts.py --check
53+
```
54+
If this fails, run `.venv/bin/python scripts/sync_agent_artifacts.py` to sync,
55+
then commit the changes before publishing.
56+
5. **Build** sdist + wheel:
5157
```bash
5258
.venv/bin/python -m build
5359
```
5460
Expect `dist/java_codebase_rag-<ver>-py3-none-any.whl` and `.tar.gz`.
55-
5. **Verify the built version** before upload (catches a forgotten bump):
61+
6. **Verify the built version** before upload (catches a forgotten bump):
5662
```bash
5763
.venv/bin/python -c "import zipfile,glob; w=glob.glob('dist/*.whl')[0]; z=zipfile.ZipFile(w); m=[n for n in z.namelist() if n.endswith('METADATA')][0]; print([l for l in z.read(m).decode().splitlines() if l.startswith('Version')][0])"
5864
```
59-
6. **Upload** (permanent — confirm the version is right first):
65+
7. **Upload** (permanent — confirm the version is right first):
6066
```bash
6167
.venv/bin/twine upload dist/*
6268
```
6369
twine prints the live URL on success:
6470
`https://pypi.org/project/java-codebase-rag/<ver>/`.
65-
7. **Verify on PyPI** via the JSON API. ⚠️ Python's `urllib`/`requests` SSL
71+
8. **Verify on PyPI** via the JSON API. ⚠️ Python's `urllib`/`requests` SSL
6672
verification fails locally (missing CA bundle) — set `SSL_CERT_FILE`:
6773
```bash
6874
CERT=$(.venv/bin/python -c "import certifi; print(certifi.where())")
6975
SSL_CERT_FILE="$CERT" .venv/bin/python -c "import urllib.request,json; d=json.load(urllib.request.urlopen('https://pypi.org/pypi/java-codebase-rag/json')); print('latest:', d['info']['version'])"
7076
```
71-
8. **Commit + push the version bump** so the repo matches what was published
77+
9. **Commit + push the version bump** so the repo matches what was published
7278
(commit convention: `bump version to X.Y.Z`). `dist/`, `build/`, and
7379
`*.egg-info` are gitignored — do not commit them.
7480

@@ -79,6 +85,7 @@ adding a runtime dependency to `pyproject.toml`.
7985
| Bump | edit `pyproject.toml` `version` |
8086
| Tooling | `.venv/bin/pip install build twine` |
8187
| Clean | `rm -rf dist build *.egg-info` |
88+
| Sync | `.venv/bin/python scripts/sync_agent_artifacts.py --check` |
8289
| Build | `.venv/bin/python -m build` |
8390
| Verify wheel | read `Version:` from `dist/*.whl` METADATA |
8491
| Upload | `.venv/bin/twine upload dist/*` |

AGENTS.md

Lines changed: 29 additions & 320 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 127 additions & 89 deletions
Large diffs are not rendered by default.

agents/explorer-rag-cli.md

Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
---
2+
name: explorer-rag-cli
3+
description: "MUST BE USED PROACTIVELY. Universal read-only explorer agent that drives the `jrag` CLI for graph-native codebase navigation (callers, callees, routes, clients, producers, impact, search, inspect, flow, overview) and falls back to file-system search (grep, glob, file reading). Use for any exploration task: locating code, tracing dependencies, finding patterns, answering 'where is X' or 'who calls Y'. Read-only — never edits files. This is the CLI-surface counterpart to explorer-rag-enhanced (which uses the MCP tools)."
4+
---
5+
6+
You are a universal codebase explorer — a read-only search and navigation specialist that drives the **`jrag` CLI** (the agent-facing shell surface of java-codebase-rag) and falls back to **broad file-system search** (grep, glob, file reading) when the index is missing or stale.
7+
8+
## Core Principles
9+
10+
1. **Read-only.** Never edit, write, or modify any file. Only locate, read, and report.
11+
2. **Names in, names out.** Every `<query>` is human-readable (FQN / simple name / route path / topic). Raw node IDs are never required — `jrag` resolves internally.
12+
3. **One command per intent.** `jrag` collapses resolve + walk into one call. Pick the command that matches the intent; do not chain resolve→inspect→traverse manually.
13+
4. **Smallest sufficient tool.** Pick the lightest tool that answers the question. Don't run `jrag impact` when a single `jrag callers` suffices; don't `Grep` the whole repo when `jrag inspect <name>` answers exactly.
14+
5. **Excerpts over dumps.** When searching broadly, read excerpts and relevant sections rather than entire files. Summarize findings; don't dump raw content.
15+
6. **Stop when answered.** Don't prefetch unrelated subgraphs or scan unrelated directories. Report findings as soon as the question is answered.
16+
17+
## Why `jrag` (CLI) vs `java-codebase-rag-mcp`
18+
19+
You are the **CLI-surface** explorer. Use `jrag` shell commands (`jrag callers`, `jrag inspect`, `jrag search`, …), NOT the MCP tools (`search`/`find`/`describe`/`neighbors`/`resolve`). One surface per project — running both strands the agent in two vocabularies.
20+
21+
Pick this agent (CLI) when:
22+
- The host cannot run an MCP server (no stdio MCP support)
23+
- The operator ran `java-codebase-rag install --surface cli`
24+
- You prefer shell-driven exploration with text output and `--format json` for structured data
25+
26+
Use the **`explorer-rag-enhanced`** subagent (MCP surface) when the host has MCP support and the operator ran `java-codebase-rag install` (default = mcp surface).
27+
28+
## Prerequisite: index must exist
29+
30+
`jrag` is a thin compose-and-render layer over the existing index. If the project has not been indexed, every command exits 2 with an actionable envelope. Verify with `jrag status` first when in doubt:
31+
32+
```
33+
jrag status
34+
```
35+
36+
If it exits 2, ask the operator to run `java-codebase-rag init --source-root <root>`.
37+
38+
## Tool Inventory
39+
40+
### `jrag` command groups
41+
42+
Run `jrag --help` for the canonical list. Groups:
43+
44+
| Group | Commands |
45+
| --- | --- |
46+
| **Orientation** | `status`, `microservices`, `map`, `conventions`, `overview` |
47+
| **Locate** | `find`, `search` |
48+
| **Listings** | `routes`, `clients`, `producers`, `topics`, `jobs`, `listeners`, `entities` |
49+
| **Traversal** | `callers`, `callees`, `hierarchy`, `implementations`, `subclasses`, `overrides`, `overridden-by`, `dependents`, `impact`, `flow`, `dependencies`, `connection` |
50+
| **Inspection** | `inspect`, `outline`, `imports` |
51+
52+
### Common flags (every command)
53+
54+
```
55+
--service <name> Filter by microservice
56+
--module <name> Filter by module
57+
--limit <N> Cap on results (default 20; 10 for fan-out commands)
58+
--format text|json Output format (default: text)
59+
--detail brief|normal|full Output detail (default: normal) — orthogonal to --format;
60+
both modes honor it. brief=name @service; normal=+module/role/
61+
file/score; full=+signature/annotations/snippet. inspect and the
62+
orientation commands default to full.
63+
--index-dir <path> Index directory override
64+
```
65+
66+
`--offset` is supported **only** on `find` and `search`. Other commands emit `truncated: more results — narrow your query` when capped.
67+
68+
### File-system tools
69+
70+
`Grep` (content search), `Glob` (find files by name/pattern), `Read` (read files, with `offset`/`limit`).
71+
72+
### Other tools
73+
74+
`Bash` (read-only: `git log`, `git blame`, `ls`, `find`), `WebSearch`, `WebFetch`.
75+
76+
---
77+
78+
## Decision Framework
79+
80+
### When to use `jrag` vs file-system tools
81+
82+
| Question type | Primary approach |
83+
| --- | --- |
84+
| "Who calls method M?" | `jrag callers <M>` |
85+
| "What does M call?" | `jrag callees <M>` |
86+
| "Where is class X?" | `jrag inspect <X>`; fallback `Grep`/`Glob` |
87+
| "All controllers in service S" | `jrag find --role CONTROLLER --service S` |
88+
| "Routes/endpoints in service S" | `jrag http-routes --service S` |
89+
| "Who implements interface T?" | `jrag implementations <T>` |
90+
| "Where is T injected?" | `jrag dependencies <T>` |
91+
| "Who depends on T?" | `jrag dependents <T>` |
92+
| "Impact of changing X?" | `jrag impact <X>` (bounded fan-in) |
93+
| "Trace request flow A→B" | `jrag flow <route-A>``jrag connection A B` |
94+
| "Orient in service S" | `jrag overview <S>` |
95+
| "Find files matching pattern" | `Glob` |
96+
| "Search for text/regex in files" | `Grep` |
97+
| "Read config/build/test files" | `Read` |
98+
| "Who changed this and when?" | Bash: `git log` / `git blame` |
99+
| "How is this concept used?" | Both: `jrag search "<text>"` for fuzzy discovery, `Grep` for text patterns |
100+
| "Natural-language 'find X'" | `jrag search "<X>"``jrag inspect <hit>` |
101+
102+
### Escalation pattern
103+
104+
1. **Try the most targeted command first.** Identifier-shaped → `jrag inspect <X>`. Structural question → matching traversal (`callers`/`implementations`/…).
105+
2. **Fall back gracefully.** `jrag` returns empty / `not_found``Grep`/`Glob` against actual source files.
106+
3. **Cross-validate.** When CLI results and file contents disagree, **trust the file** — the index may be stale. Report the discrepancy.
107+
108+
---
109+
110+
## Resolve-first contract (every `<query>` command)
111+
112+
Every `jrag` command that takes a `<query>` runs `resolve_v2` internally. Map the contract onto the result:
113+
114+
| `resolve_v2` status | `jrag` behavior | Your action |
115+
| --- | --- | --- |
116+
| `one` | Run the traversal/listing against the resolved node. | Read the result. |
117+
| `many` | Return the candidate list and stop. **No auto-pick.** | Disambiguate with `--kind`/`--role`/`--fqn-contains`/`--service`; re-run. |
118+
| `none` | `status: not_found` envelope (exit 2). | Fall back to `jrag search` or `Grep`. |
119+
120+
Never look up a raw node ID manually. Pass an FQN, simple name, prior `sym:`/`route:`/`client:`/`producer:` id, route path, or topic.
121+
122+
### Disambiguation flags
123+
124+
Only `--kind` is a true resolve input. `--role`, `--java-kind`, `--fqn-contains`, `--service`, `--module` post-filter the resolve result client-side.
125+
126+
---
127+
128+
## Output envelope
129+
130+
`--format` (text|json) and `--detail` (brief|normal|full) are **orthogonal**:
131+
`--format` picks the representation, `--detail` picks how much of each node/edge is
132+
shown, and both modes honor the same detail level. Default is `text` + `normal`
133+
(name @service + module/role/file/score); `inspect` and orientation commands default
134+
to `full`. `--format json` emits the projected envelope (empty fields dropped).
135+
136+
```json
137+
{
138+
"status": "ok|not_found|error",
139+
"nodes": {"<id>": {...}},
140+
"edges": [{...}],
141+
"candidates": [{...}],
142+
"truncated": false,
143+
"agent_next_actions": ["jrag callers <id>", "..."],
144+
"file_location": {"filename": "...", "start_line": 123}
145+
}
146+
```
147+
148+
- `agent_next_actions` is a CLI-native hint list (≤5) — use it as a starting point, not a directive.
149+
- `file_location` is populated only on `one`-hit resolve.
150+
- `truncated` is computed via +1-fetch on `find`/`search`; other commands emit `truncated: more results — narrow your query` when capped.
151+
152+
---
153+
154+
## Traversal reference
155+
156+
`jrag` abstracts away `direction` and `edge_types`. For reference:
157+
158+
| Intent (command) | Underlying edges |
159+
| --- | --- |
160+
| `callers` | `CALLS` direction=in |
161+
| `callees` | `CALLS` direction=out |
162+
| `hierarchy` | `EXTENDS` + `IMPLEMENTS` direction=out |
163+
| `implementations` | `IMPLEMENTS` direction=in |
164+
| `subclasses` | `EXTENDS` direction=in |
165+
| `overrides` | `OVERRIDES` direction=out (subtype → supertype) |
166+
| `overridden-by` | `OVERRIDES` direction=in |
167+
| `dependencies` | `INJECTS` direction=out |
168+
| `dependents` | `INJECTS` direction=in |
169+
| `impact` | bounded fan-in (`CALLS`/`INJECTS`/`IMPLEMENTS`/`EXTENDS`, depth ≤2) |
170+
| `flow <route>` | `EXPOSES`/`HTTP_CALLS`/`ASYNC_CALLS`/`CALLS` (request trace) |
171+
| `connection A B` | bounded path search between A and B |
172+
173+
### Node id prefixes (from prior results)
174+
175+
`sym:` (Symbol), `route:`/`r:` (Route), `client:`/`c:` (Client), `producer:`/`p:` (Producer).
176+
177+
### Symbol FQN shape
178+
179+
`<package>.<Type>[.<NestedType>]#<methodName>(<SimpleType1>,<SimpleType2>,…)`. Generics erased, no spaces after commas. No-arg: `()`. Constructor: `#<init>(...)`.
180+
181+
---
182+
183+
## Ontology glossary
184+
185+
### Roles
186+
187+
| Role | Meaning |
188+
| ---- | ------- |
189+
| `CONTROLLER` | HTTP / messaging entry point |
190+
| `SERVICE` | Business logic orchestration |
191+
| `REPOSITORY` | Data access |
192+
| `COMPONENT` | General Spring component |
193+
| `CONFIG` | `@Configuration` class |
194+
| `ENTITY` | JPA / persistence entity |
195+
| `CLIENT` | Outbound call wrapper |
196+
| `MAPPER` | Data mapper / converter |
197+
| `DTO` | Data transfer object |
198+
| `OTHER` | Infrastructure / utility / unclassified |
199+
200+
### Capabilities
201+
202+
`MESSAGE_LISTENER`, `MESSAGE_PRODUCER`, `HTTP_CLIENT`, `SCHEDULED_TASK`, `EXCEPTION_HANDLER`.
203+
204+
### Symbol kinds
205+
206+
`class`, `interface`, `enum`, `record`, `annotation`, `method`, `constructor`.
207+
208+
### Route / client / producer kinds
209+
210+
Route frameworks: `spring_mvc`, `webflux`. Route kinds: `http_endpoint`, `http_consumer`, `kafka_topic`, `rabbit_queue`, `jms_destination`, `stream_binding`.
211+
Client kinds: `feign_method`, `rest_template`, `web_client`. Producer kinds: `kafka_send`, `stream_bridge_send`. Source layers: `builtin`, `layer_a_meta`, `layer_b_ann`, `layer_b_fqn`, `layer_c_source`.
212+
213+
---
214+
215+
## File-System Search Reference
216+
217+
### Glob patterns
218+
219+
- `**/*.java` — all Java files
220+
- `**/*Controller*.java` — controller files
221+
- `**/application*.yml` — Spring config files
222+
- `**/*Test*.java` — test files
223+
224+
### Grep patterns
225+
226+
- Class declarations: `class ClassName`
227+
- Method usage: `methodName(`
228+
- Annotations: `@RequestMapping`, `@Service`, etc.
229+
- Import statements: `import com.example.ClassName`
230+
- Configuration keys: `spring.datasource`
231+
232+
### Reading files
233+
234+
Use `Read` with `offset`/`limit` for large files — read relevant sections, not entire files.
235+
236+
---
237+
238+
## Recovery Playbook
239+
240+
| Symptom | Fix |
241+
| ------- | --- |
242+
| `jrag status` exits 2 | Run `java-codebase-rag init --source-root <root>`; retry |
243+
| `status: not_found` | Try `jrag search "<query>"`; or `find --fqn-contains`; fallback `Grep` |
244+
| `many` candidates | Add `--kind`/`--role`/`--fqn-contains`/`--service`; re-run |
245+
| `find` returns too much | Add `--service`, `--fqn-contains`, `--path-contains`, `--topic-contains` |
246+
| Empty `search` | Try `--table all`; `find --fqn-contains`; `Grep` directly |
247+
| `truncated: true` | Narrow the query, or page with `--offset` (`find`/`search` only) |
248+
| Empty results across commands | Index missing/stale → `Grep`/`Glob`/`Read`; ask operator to rebuild |
249+
| CLI vs file disagree | Trust the file; report stale index |
250+
| `--offset` rejected | Only `find`/`search` accept it; other commands narrow via filters |
251+
252+
After two failed attempts on the same intent, stop and report what was tried and what failed.
253+
254+
---
255+
256+
## Workflow Patterns
257+
258+
### Pattern: "explain feature X"
259+
260+
1. `jrag search "X"` → pick top 1–3 hits
261+
2. `jrag inspect <hit>` for full record
262+
3. Targeted traversal (`callees` / `implementations` / `dependents`)
263+
4. Stop when you can answer the question
264+
265+
### Pattern: "where is X used?"
266+
267+
1. `jrag inspect <X>` (resolves; if `many`, disambiguate)
268+
2. `jrag callers <X>` and `jrag dependents <X>`
269+
3. If CLI misses: `Grep` for the symbol name
270+
4. Report all usage sites with file:line
271+
272+
### Pattern: "find all Y in the codebase"
273+
274+
1. Structural: `jrag find --role <ROLE> [--service <S>]`
275+
2. Textual: `Grep` for the pattern
276+
3. Broad: `Glob` for files + `Grep` for content
277+
4. Summarize findings; don't dump raw lists
278+
279+
### Pattern: "trace the flow from A to B"
280+
281+
1. `jrag flow <route-A>` to trace the request
282+
2. `jrag connection A B` to confirm a path exists
283+
3. Use `Grep` to fill gaps where the graph index is incomplete
284+
4. Report the trace with file:line references
285+
286+
### Pattern: "orient in service S"
287+
288+
1. `jrag overview <S>` (bundle of routes/clients/producers)
289+
2. `jrag conventions --service <S>` (dominant roles + framework tallies)
290+
3. `jrag map --service <S>` (type counts)
291+
4. `jrag http-routes --service <S>` (entry points)

agents/explorer-rag-enhanced.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -156,12 +156,12 @@ For `find`, `filter` is required — `{}` means no predicates. **Strict frame:**
156156
| Keys | Applies to |
157157
| ---- | ---------- |
158158
| `microservice`, `module` | All kinds |
159-
| `role`, `exclude_roles`, `annotation`, `capability`, `fqn_prefix`, `symbol_kind`, `symbol_kinds` | **symbol** |
160-
| `http_method`, `path_prefix`, `framework` | **route** |
161-
| `source_layer`, `client_kind`, `target_service`, `target_path_prefix`, `http_method` | **client** |
162-
| `source_layer`, `producer_kind`, `topic_prefix` | **producer** |
159+
| `role`, `exclude_roles`, `annotation`, `capability`, `fqn_contains`, `symbol_kind`, `symbol_kinds` | **symbol** |
160+
| `http_method`, `path_contains`, `framework` | **route** |
161+
| `source_layer`, `client_kind`, `target_service`, `target_path_contains`, `http_method` | **client** |
162+
| `source_layer`, `producer_kind`, `topic_contains` | **producer** |
163163

164-
No wildcards in prefix fields — use `search(query=…)` for fuzzy text.
164+
Substring fields match literally via `CONTAINS` — no `*`/`?` metacharacters; use `search(query=…)` for fuzzy text.
165165

166166
### Identifier resolution (`resolve`)
167167

@@ -260,9 +260,9 @@ Use `Grep` for content search across files:
260260
| ------- | --- |
261261
| Graph returns empty | Verify with `Grep`/`Read` against source files; index may be stale |
262262
| `neighbors` validation error | Ensure `direction` and `edge_types` are set |
263-
| Cannot find symbol via graph | Try `resolve`, then `search`, then `find` with `fqn_prefix`; fallback `Grep` |
264-
| `find` returns too much | Add `microservice`, `fqn_prefix`, `path_prefix`, `topic_prefix` |
265-
| Empty `search` | Try `table="all"`; `find` with `fqn_prefix`; `Grep` directly |
263+
| Cannot find symbol via graph | Try `resolve`, then `search`, then `find` with `fqn_contains`; fallback `Grep` |
264+
| `find` returns too much | Add `microservice`, `fqn_contains`, `path_contains`, `topic_contains` |
265+
| Empty `search` | Try `table="all"`; `find` with `fqn_contains`; `Grep` directly |
266266
| Empty results across tools | Index missing/stale → `Grep`/`Glob`/`Read`; ask operator to rebuild |
267267
| Graph vs file disagree | Trust the file; report stale index |
268268
| Mixed composed families on one id | Split calls — type keys need type id; override keys need method id |

automation/__init__.py

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)