Skip to content

feat(config): index-dir config_source pointer for sibling-config discovery#383

Merged
HumanBean17 merged 1 commit into
masterfrom
feat/config-source-pointer
Jul 6, 2026
Merged

feat(config): index-dir config_source pointer for sibling-config discovery#383
HumanBean17 merged 1 commit into
masterfrom
feat/config-source-pointer

Conversation

@HumanBean17

Copy link
Copy Markdown
Owner

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:

~/MyProject/
    project-context/.java-codebase-rag.yml   # config (source_root: ../, index_dir: ../.java-codebase-rag)
    .java-codebase-rag/                       # state
    microservice-a/   microservice-b/

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 — dropping microservice_roots, role_overrides, cross_service_resolution, embedding.model, and hints. It doesn't crash only because the default index_dir = <source_root>/.java-codebase-rag happens to point at the right place.

Fix

The index dir now remembers which YAML built it:

  • Write: init / reprocess / increment (via _run_with_pipeline_progress) and install / update write <index_dir>/config_source — one line, the absolute path of the YAML — on code == 0. Best-effort, atomic (tmp + os.replace), no-op when no YAML was used.
  • Read: _effective_config_dir() is applied in all three branches of resolve_operator_config. When the anchor has no direct YAML, it follows the pointer and rebases config_dir to 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 touches config_dir only, never source_root, preserving the CLI/env "explicit source-root wins" contract.
  • Precedence: direct YAML at the anchor always wins; a stale pointer (target moved/deleted/wrong-named) is ignored with a one-line stderr hint and falls back to defaults.
  • Lifecycle: erase removes the pointer via a new OPERATOR_OWNED_INDEX_FILES constant, kept separate from build_ast_graph.BUILDER_OWNED_INDEX_FILES.

The single _effective_config_dir seam covers all four doors: jrag, operator CLI, MCP server, and installer.

Why rebase config_dir (not just load the YAML)

If config_dir stayed at the index-dir anchor while the YAML came from project-context/, then index_dir: ../.java-codebase-rag would resolve to ~/.java-codebase-rag — a silent wrong-store bug. Rebasing config_dir to the YAML's dir fixes this uniformly.

Verification

  • Full suite: 1078 passed, 15 skipped. The one unrelated failure (test_cli_lifecycle_stdout_invariant_erase_quiet) is environmental — it fails at shutil.which("java-codebase-rag") before any logic runs because .venv/bin isn't on the subprocess PATH; it passes when it is.
  • 16 new tests in 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), the jrag pre-discovery path, direct-YAML-wins, stale-pointer fallback, write lifecycle + failure-skip, round-trip, erase cleanup, #357-not-weakened, and MCP/CLI parity.
  • Real-binary check from microservice-a/src/main/java: with the pointer index_dir_source: "yaml" (config loaded); without it "default" (config silently lost — the pre-feature behavior).

Files

  • java_codebase_rag/config.pyCONFIG_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_source hook in _run_with_pipeline_progress; erase removal 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

…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 HumanBean17 merged commit 7100d2b into master Jul 6, 2026
3 checks passed
@HumanBean17 HumanBean17 deleted the feat/config-source-pointer branch July 6, 2026 07:01
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant