Skip to content

Commit 55da9ca

Browse files
HumanBean17claude
andcommitted
fix walk-up discovery to also detect index directory
discover_project_root() only looked for .java-codebase-rag.yml config files, missing .java-codebase-rag/ index directories. This caused MCP tools to fail when started from a microservice subdirectory because walk-up returned None and fell back to cwd (no index there). Empty index directories are skipped to avoid stale artifacts from falsely anchoring discovery. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 7913117 commit 55da9ca

3 files changed

Lines changed: 88 additions & 14 deletions

File tree

java_codebase_rag/cli.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -229,18 +229,23 @@ def _add_verbosity_flags(p: argparse.ArgumentParser) -> None:
229229

230230
def _cmd_init(args: argparse.Namespace) -> int:
231231
cfg = _resolved_from_ns(args)
232-
# Check for parent config
233-
from java_codebase_rag.config import discover_project_root, YAML_CONFIG_FILENAMES
232+
# Check for parent config or index
233+
from java_codebase_rag.config import discover_project_root, find_yaml_config_file
234234
parent_config_dir = discover_project_root(cfg.source_root.parent)
235235
if parent_config_dir is not None:
236-
parent_config = parent_config_dir / YAML_CONFIG_FILENAMES[0]
237-
if not parent_config.is_file():
238-
parent_config = parent_config_dir / YAML_CONFIG_FILENAMES[1]
239-
print(
240-
f"Warning: found existing config at {parent_config}. "
241-
f"Creating a new project here will create a separate index.",
242-
file=sys.stderr,
243-
)
236+
parent_config = find_yaml_config_file(parent_config_dir)
237+
if parent_config is not None:
238+
print(
239+
f"Warning: found existing config at {parent_config}. "
240+
f"Creating a new project here will create a separate index.",
241+
file=sys.stderr,
242+
)
243+
else:
244+
print(
245+
f"Warning: found existing index at {parent_config_dir / '.java-codebase-rag'}. "
246+
f"Creating a new project here will create a separate index.",
247+
file=sys.stderr,
248+
)
244249
_startup_hints(cfg)
245250
cfg.apply_to_os_environ()
246251
occupied, paths = index_dir_has_existing_artifacts(cfg.index_dir)

java_codebase_rag/config.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -123,20 +123,33 @@ def find_yaml_config_file(source_root: Path) -> Path | None:
123123
return None
124124

125125

126+
def _has_index_dir(directory: Path) -> bool:
127+
"""True if *directory* contains a non-empty ``.java-codebase-rag/`` index directory."""
128+
idx = directory / ".java-codebase-rag"
129+
return idx.is_dir() and any(idx.iterdir())
130+
131+
126132
def discover_project_root(start: Path) -> Path | None:
127-
"""Walk up from start to find the directory containing a config file.
133+
"""Walk up from start to find the directory containing a config file or index.
128134
129-
First match wins (closest to start). Stops at $HOME inclusive — checks $HOME
130-
itself but does not walk past it. Returns None if no config found.
135+
Looks for ``.java-codebase-rag.yml`` / ``.java-codebase-rag.yaml`` (preferred)
136+
or the ``.java-codebase-rag/`` index directory as a project boundary marker.
137+
138+
First match wins (closest to start). Config file takes priority over index
139+
directory at the same level. Stops at $HOME inclusive — checks $HOME itself
140+
but does not walk past it. Returns None if no marker found.
131141
"""
132142
start = start.resolve()
133143
home = Path.home().resolve()
134144

135145
current = start
136146
while True:
137-
# Check if current directory contains a config file
147+
# Config file is the primary anchor
138148
if find_yaml_config_file(current) is not None:
139149
return current
150+
# Index directory is the secondary anchor (supports indexes without config)
151+
if _has_index_dir(current):
152+
return current
140153

141154
# Stop if we've reached home (check home itself, but don't walk past it)
142155
if current == home:

tests/test_config.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,62 @@ def test_discover_project_root_first_match_wins(self, tmp_path):
8080
# Should find the closest config (subdir), not the parent (tmp_path)
8181
assert result == subdir
8282

83+
def test_discover_project_root_finds_nonempty_index_dir(self, tmp_path):
84+
"""Non-empty .java-codebase-rag/ directory acts as project anchor."""
85+
subdir = tmp_path / "microservice"
86+
subdir.mkdir()
87+
idx = tmp_path / ".java-codebase-rag"
88+
idx.mkdir()
89+
(idx / "code_graph.kuzu").write_bytes(b"\x00" * 16)
90+
91+
result = discover_project_root(subdir)
92+
assert result == tmp_path
93+
94+
def test_discover_project_root_skips_empty_index_dir(self, tmp_path):
95+
"""Empty .java-codebase-rag/ directory does not anchor the project."""
96+
subdir = tmp_path / "microservice"
97+
subdir.mkdir()
98+
# Empty index dir at subdir level
99+
empty_idx = subdir / ".java-codebase-rag"
100+
empty_idx.mkdir()
101+
# Real index at parent level
102+
real_idx = tmp_path / ".java-codebase-rag"
103+
real_idx.mkdir()
104+
(real_idx / "code_graph.kuzu").write_bytes(b"\x00" * 16)
105+
106+
result = discover_project_root(subdir)
107+
assert result == tmp_path
108+
109+
def test_discover_project_root_config_wins_over_index_dir(self, tmp_path):
110+
"""Config file takes priority over index dir at the same level."""
111+
subdir = tmp_path / "subdir"
112+
subdir.mkdir()
113+
# Index dir at tmp_path level
114+
idx = tmp_path / ".java-codebase-rag"
115+
idx.mkdir()
116+
(idx / "code_graph.kuzu").write_bytes(b"\x00" * 16)
117+
# Config at subdir level
118+
config_file = subdir / YAML_CONFIG_FILENAMES[0]
119+
config_file.write_text("# child config")
120+
121+
deep = subdir / "deep"
122+
deep.mkdir()
123+
result = discover_project_root(deep)
124+
# Config at subdir is closer and wins
125+
assert result == subdir
126+
127+
def test_discover_project_root_both_markers_same_level(self, tmp_path):
128+
"""When both config and index dir exist at same dir, both resolve correctly."""
129+
# Both markers in the same directory
130+
config_file = tmp_path / YAML_CONFIG_FILENAMES[0]
131+
config_file.write_text("# config")
132+
idx = tmp_path / ".java-codebase-rag"
133+
idx.mkdir()
134+
(idx / "code_graph.kuzu").write_bytes(b"\x00" * 16)
135+
136+
result = discover_project_root(tmp_path)
137+
assert result == tmp_path
138+
83139

84140
class TestSourceRootFromYaml:
85141
"""Tests for source_root YAML field parsing and resolution."""

0 commit comments

Comments
 (0)