feat(config): index-dir config_source pointer for sibling-config discovery#383
Merged
Conversation
…overy Walk-up discovery is ancestor-only (like git), so a config living in a sibling dir of the Java tree (e.g. project-context/ beside the microservices and .java-codebase-rag/) is invisible when the agent's cwd is inside a microservice: discovery anchors on the index dir and silently falls back to defaults, dropping microservice_roots / role_overrides / embedding / hints. The index dir now remembers which YAML built it. init / reprocess / increment (and install / update) write <index_dir>/config_source (one absolute path). When discovery anchors on the index dir but finds no YAML beside it, _effective_config_dir follows the pointer and rebases config_dir to the YAML's directory -- so relative source_root / index_dir / embedding.model resolve against the YAML's home, not the index anchor (which would overshoot to $HOME/.java-codebase-rag). A direct YAML at the anchor always wins; a stale pointer (target moved/deleted) is ignored with a one-line stderr hint and falls back to defaults. The rebase touches config_dir only, never source_root, so the CLI/env "explicit source_root wins / YAML source_root field skipped" contract is preserved -- covering all four doors (jrag, operator CLI, MCP, installer) through one seam in resolve_operator_config. erase removes the pointer via a new OPERATOR_OWNED_INDEX_FILES constant, kept separate from the builder-owned set. Co-Authored-By: Claude <noreply@anthropic.com>
HumanBean17
added a commit
that referenced
this pull request
Jul 6, 2026
…marker (#387) jrag._resolve_cfg passed source_root=discover_project_root(cwd) into resolve_operator_config, which only honors JAVA_CODEBASE_RAG_SOURCE_ROOT when source_root is None. So any ancestor .java-codebase-rag marker silently overrode the env var that pipeline.subprocess_env sets for the cocoindex child (and that operators/tests rely on), making `jrag imports/outline <rel-file>` resolve against the wrong root → "file not found", exit 2. Pass source_root=None instead, mirroring cli._resolved_from_ns: resolution is now env var → YAML source_root → discover_project_root(cwd). Interactive use (discovery fallback) is unchanged; the #383 config_source pointer is preserved. Regression test creates a non-empty ancestor marker + sets the env var and asserts the env var wins (fails on pre-fix code, passes with the fix). Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Walk-up config discovery is ancestor-only (like
git), so a config living in a sibling directory of the Java tree is invisible when the agent's cwd is inside a microservice — the layout battle-tested in discussion:From
microservice-a/, walk-up anchors on~/MyProject/via the non-empty.java-codebase-rag/index dir, finds no YAML there, and silently falls back to built-in defaults — droppingmicroservice_roots,role_overrides,cross_service_resolution,embedding.model, andhints. It doesn't crash only because the defaultindex_dir = <source_root>/.java-codebase-raghappens to point at the right place.Fix
The index dir now remembers which YAML built it:
init/reprocess/increment(via_run_with_pipeline_progress) andinstall/updatewrite<index_dir>/config_source— one line, the absolute path of the YAML — oncode == 0. Best-effort, atomic (tmp +os.replace), no-op when no YAML was used._effective_config_dir()is applied in all three branches ofresolve_operator_config. When the anchor has no direct YAML, it follows the pointer and rebasesconfig_dirto the YAML's directory — so relative fields (source_root,index_dir,embedding.model) resolve against the YAML's home, not the index anchor. The rebase touchesconfig_dironly, neversource_root, preserving the CLI/env "explicit source-root wins" contract.eraseremoves the pointer via a newOPERATOR_OWNED_INDEX_FILESconstant, kept separate frombuild_ast_graph.BUILDER_OWNED_INDEX_FILES.The single
_effective_config_dirseam covers all four doors:jrag, operator CLI, MCP server, and installer.Why rebase
config_dir(not just load the YAML)If
config_dirstayed at the index-dir anchor while the YAML came fromproject-context/, thenindex_dir: ../.java-codebase-ragwould resolve to~/.java-codebase-rag— a silent wrong-store bug. Rebasingconfig_dirto the YAML's dir fixes this uniformly.Verification
test_cli_lifecycle_stdout_invariant_erase_quiet) is environmental — it fails atshutil.which("java-codebase-rag")before any logic runs because.venv/binisn't on the subprocessPATH; it passes when it is.test_config.py/test_java_codebase_rag_cli.py/test_mcp_server_project_root.py: the index_dir-overshoot regression, discovery via pointer (source_root=None), thejragpre-discovery path, direct-YAML-wins, stale-pointer fallback, write lifecycle + failure-skip, round-trip, erase cleanup,#357-not-weakened, and MCP/CLI parity.microservice-a/src/main/java: with the pointerindex_dir_source: "yaml"(config loaded); without it"default"(config silently lost — the pre-feature behavior).Files
java_codebase_rag/config.py—CONFIG_SOURCE_FILENAME,OPERATOR_OWNED_INDEX_FILES,_config_dir_from_pointer,_effective_config_dir,write_config_source_pointer,ResolvedOperatorConfig.yaml_config_path, 3-branch rebase.java_codebase_rag/cli.py—_maybe_record_config_sourcehook in_run_with_pipeline_progress;eraseremoval loop.java_codebase_rag/installer.py— write hook after install/update index.java_codebase_rag/jrag.py— docstring.docs/CONFIGURATION.md— walk-up block + Tips bullet.🤖 Generated with Claude Code