Skip to content

Commit 84cef79

Browse files
HumanBean17claude
andauthored
Walk-up config discovery, configurable source root, and microservice auto-scope (#277)
* Update propose skill with brainstorming-inspired discovery process - Add Hard Gate: prevent writing proposals without discovery - Add systematic 8-step process with task tracking - Agent determines clarifying questions (not scripted) - Present 2-3 solution approaches with trade-offs - Draft sections incrementally with approval - Self-review after writing - User review gate before completion Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Add walk-up config discovery and microservice auto-scope design This commit adds the complete design specification for two related features that address workspace flexibility and codebase boundary awareness: Walk-up config discovery: - Tool finds .java-codebase-rag.yml by walking up from cwd (like git) - YAML gains optional source_root field for config/code separation - Index dir auto-derives from resolved source root - Precedence: CLI > env > YAML > walk-up > cwd Microservice auto-scope: - Automatically detects microservice from current working directory - Applies microservice filter to queries when inside a microservice - Prevents agents from seeing code outside their context - Explicit filters always override auto-detected scope Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Fix code review issues in walk-up and microservice auto-scope design This commit addresses all issues found in code review: Critical Issues Fixed: 1. Directory traversal logic - Added detailed algorithm specification with error handling for permissions, inaccessible home, and boundary conditions 2. Microservice detection at source_root - Clarified that cwd=source_root returns None with semantic rationale different from indexing behavior 3. ScopeManager lifecycle - Specified that scope is cached at startup and requires restart for directory changes Important Issues Fixed: 4. Permissions error handling - Added comprehensive error handling table 5. Path resolution documentation - Clarified how precedence interacts with different resolution bases 6. Multiple config files - Specified .yml takes precedence over .yaml 7. _resolve_lancedb_uri integration - Specified behavior when JAVA_CODEBASE_RAG_INDEX_DIR is set but JAVA_CODEBASE_RAG_SOURCE_ROOT is not Minor Issues Fixed: 8. Documentation update timing - Added timing specification to plan 9. Advisory message phrasing - Defined exact log and advisory message text 10. Test file organization - Added justification for separate test files Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Add walk-up config discovery, configurable source root, and microservice auto-scope Implement walk-up config discovery, configurable source root via YAML, and microservice auto-scope for MCP queries. Changes: - Add discover_project_root() to walk up directory tree finding config - Add source_root YAML field for config-source separation - Implement precedence chain: CLI > env > YAML > discovery > cwd - Update _project_root() in server.py to use walk-up discovery - Add detect_microservice_from_path() for microservice detection - Add ScopeManager class for automatic microservice scoping - Wire ScopeManager into all MCP tool wrappers - Add init command warning when parent config detected - Update documentation for walk-up, source_root, and auto-scope - Add mcp.json.example with zero-env-var configuration Tests: 21 new tests covering config discovery, precedence, and microservice scope. All tests pass, ruff check clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Fix config tests to clean environment variables Add monkeypatch.delenv() calls to tests that need clean environment. The conftest.py mcp_env fixture sets JAVA_CODEBASE_RAG_INDEX_DIR and JAVA_CODEBASE_RAG_SOURCE_ROOT at session level, which was causing test failures when resolve_operator_config(source_root=None) picked up these env vars instead of using walk-up discovery. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Address review comments from PR #277 Fixes: 1. Remove duplicate "Key Principles" section in SKILL.md 2. Rename apply_auto_scope parameter from 'filter' to 'node_filter' to avoid shadowing built-in 3. Add test_detect_scope_integration to exercise real detection flow 4. Fix detect_microservice_from_path to check cwd against YAML overrides 5. Add test_detect_scope_with_yaml_overrides to verify override behavior Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 2a77cd0 commit 84cef79

13 files changed

Lines changed: 1294 additions & 18 deletions

File tree

.agents/skills/propose/SKILL.md

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -234,15 +234,6 @@ Docs-only; baseline unchanged.
234234
- `TOOL-NAME-PROPOSE.md`
235235
- `ARCHITECTURE-CHANGE-PROPOSE.md`
236236

237-
## Final checklist
238-
239-
- [ ] Proposal file lives under `propose/active/`
240-
- [ ] Problem statement includes concrete examples
241-
- [ ] Schema/ontology/re-index impact is explicit
242-
- [ ] Open questions include `[TBD]` with recommendations
243-
- [ ] Out-of-scope section is present
244-
- [ ] Sequencing/follow-up path is clear
245-
246237
## Key Principles
247238

248239
- **One question at a time** — Don't overwhelm with multiple questions

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,9 @@ With the package installed, the console script `java-codebase-rag-mcp` is on you
9292
claude mcp add --transport stdio java-codebase-rag -- java-codebase-rag-mcp
9393
```
9494

95-
Then set env vars (`JAVA_CODEBASE_RAG_INDEX_DIR`, `JAVA_CODEBASE_RAG_SOURCE_ROOT`, `SBERT_MODEL`, …) in `.mcp.json` or your shell profile. For a project-scoped `.mcp.json` template, see [`mcp.json.example`](./mcp.json.example). Official docs: [Claude Code settings](https://docs.anthropic.com/en/docs/claude-code/settings).
95+
**Zero-env-var configuration:** The tool automatically walks up the directory tree to find `.java-codebase-rag.yml`, so you don't need to set `JAVA_CODEBASE_RAG_SOURCE_ROOT` when working from within a project. Just place the config file at your project root and the tool will find it. See [`mcp.json.example`](./mcp.json.example) for the minimal configuration.
96+
97+
If you need to override defaults, you can set env vars (`JAVA_CODEBASE_RAG_INDEX_DIR`, `JAVA_CODEBASE_RAG_SOURCE_ROOT`, `SBERT_MODEL`, …) in `.mcp.json` or your shell profile. For a full configuration template, see [`mcp.json.example`](./mcp.json.example). Official docs: [Claude Code settings](https://docs.anthropic.com/en/docs/claude-code/settings).
9698

9799
### Claude Desktop
98100

docs/CONFIGURATION.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,25 @@ For the architecture rationale (the GPS metaphor, three-layer design, future wor
2222

2323
The operator-facing surface is **six** variables (plus MCP-only `JAVA_CODEBASE_RAG_SOURCE_ROOT` below). Precedence for knobs that also exist as CLI flags or YAML entries is **CLI flag > env var > YAML > built-in default** (see [`JAVA-CODEBASE-RAG-CLI.md`](./JAVA-CODEBASE-RAG-CLI.md)).
2424

25+
### Config file discovery (walk-up)
26+
27+
The tool automatically walks up the directory tree from the current working directory to find `.java-codebase-rag.yml` (or `.yaml`), similar to how Git finds `.git`. This means you can run CLI commands and MCP queries from any subdirectory within your project — the tool will locate the config file automatically.
28+
29+
**Walk-up behavior:**
30+
- Starts from the current working directory and walks up the directory tree
31+
- Stops at `$HOME` (inclusive — checks `$HOME` itself but doesn't walk past it)
32+
- First match wins (closest config to cwd, not "most specific" or "deepest")
33+
- If no config is found, falls back to using the current directory
34+
35+
**Precedence for source root resolution:**
36+
1. CLI flag `--source-root` (highest priority)
37+
2. Environment variable `JAVA_CODEBASE_RAG_SOURCE_ROOT`
38+
3. YAML field `source_root` (resolved relative to config directory)
39+
4. Walk-up discovery result (config directory itself)
40+
5. Current working directory (fallback)
41+
42+
This walk-up behavior means you no longer need to set environment variables or pass flags when working from within a project — the tool finds the config automatically.
43+
2544
| Variable | Purpose |
2645
|---|---|
2746
| `JAVA_CODEBASE_RAG_INDEX_DIR` | Local filesystem **directory** for Lance tables, the Kuzu file `code_graph.kuzu`, and cocoindex state (`cocoindex.db`). Not a `lancedb://` or cloud URI — use a path. Default: `./.java-codebase-rag/` under the resolved Java tree root. |
@@ -58,6 +77,14 @@ A single file at the project root (the directory you pass as `--source-root`, or
5877

5978
# -------- Core knobs (mirror env vars; precedence: CLI > env > YAML > default) --------
6079

80+
# Source root: the Java project root. Useful when the config file lives
81+
# separately from the Java source code (e.g., monorepo with configs at repo root).
82+
# - Tilde (`~`) is expanded; `$VAR` is NOT (use absolute paths or `~`).
83+
# - Relative paths resolve against the config file's parent directory, not cwd.
84+
# - Env: JAVA_CODEBASE_RAG_SOURCE_ROOT. CLI: --source-root.
85+
# - Default: the directory containing this config file (for walk-up discovery).
86+
# source_root: ../java-project
87+
6188
# Index directory: where Lance tables, code_graph.kuzu, and cocoindex.db live.
6289
# - Tilde (`~`) is expanded; `$VAR` is NOT (use absolute paths or `~`).
6390
# - Relative paths resolve against source_root, not cwd.
@@ -95,6 +122,25 @@ microservice_roots:
95122
- chat-orchestrator
96123
- ranking
97124

125+
# Automatic microservice scope for queries (MCP server only)
126+
# When working from a microservice subdirectory, queries automatically scope
127+
# to that microservice — no manual filter needed. This provides correct
128+
# codebase boundaries for agents working on specific microservices.
129+
#
130+
# Behavior:
131+
# - At microservice root or inside a microservice subdirectory:
132+
# → Queries automatically scoped to that microservice
133+
# - At project root (above all microservices):
134+
# → Queries span all microservices with an advisory message
135+
# - Explicit microservice filters always override auto-detected scope
136+
#
137+
# The MCP server logs scope detection at startup:
138+
# [scope] Detected microservice: chat-core
139+
# [scope] Queries scoped to chat-core
140+
# Or at system level:
141+
# [scope] No microservice detected (at project root)
142+
# [scope] Queries will span all microservices
143+
98144
# -------- Cross-service edge resolution --------
99145

100146
# How the resolver treats auto-detected cross-service call edges. See §4.2.

graph_enrich.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1565,6 +1565,35 @@ def microservice_for_path(
15651565
return ""
15661566

15671567

1568+
def detect_microservice_from_path(cwd: Path, source_root: Path) -> str | None:
1569+
"""Detect microservice from cwd for query-time auto-scope.
1570+
1571+
Returns None if cwd is outside source_root, cwd IS source_root (system level),
1572+
or no microservice is detected. Otherwise returns the microservice name.
1573+
"""
1574+
cwd_resolved = cwd.resolve()
1575+
source_resolved = source_root.resolve()
1576+
1577+
# Check if cwd is outside source_root
1578+
try:
1579+
cwd_resolved.relative_to(source_resolved)
1580+
except ValueError:
1581+
return None
1582+
1583+
# Check if cwd IS source_root (at system level, no specific scope)
1584+
if cwd_resolved == source_resolved:
1585+
return None
1586+
1587+
# Check if cwd itself matches a YAML override (directory name matches microservice_roots)
1588+
overrides = load_microservice_overrides(source_resolved)
1589+
if overrides and cwd_resolved.name in overrides:
1590+
return cwd_resolved.name
1591+
1592+
# Call existing microservice_for_path to detect microservice from build markers
1593+
ms = microservice_for_path(str(cwd_resolved), source_resolved)
1594+
return ms if ms else None
1595+
1596+
15681597
# ---------- chunk enrichment ----------
15691598

15701599

java_codebase_rag/cli.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,18 @@ 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
234+
parent_config_dir = discover_project_root(cfg.source_root.parent)
235+
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+
)
232244
_startup_hints(cfg)
233245
cfg.apply_to_os_environ()
234246
occupied, paths = index_dir_has_existing_artifacts(cfg.index_dir)

java_codebase_rag/config.py

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

125125

126+
def discover_project_root(start: Path) -> Path | None:
127+
"""Walk up from start to find the directory containing a config file.
128+
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.
131+
"""
132+
start = start.resolve()
133+
home = Path.home().resolve()
134+
135+
current = start
136+
while True:
137+
# Check if current directory contains a config file
138+
if find_yaml_config_file(current) is not None:
139+
return current
140+
141+
# Stop if we've reached home (check home itself, but don't walk past it)
142+
if current == home:
143+
return None
144+
145+
# Stop if we've reached filesystem root
146+
parent = current.parent
147+
if parent == current:
148+
return None
149+
150+
current = parent
151+
152+
126153
def load_yaml_mapping(source_root: Path) -> dict[str, Any]:
127154
path = find_yaml_config_file(source_root)
128155
if path is None:
@@ -277,8 +304,36 @@ def resolve_operator_config(
277304
cli_embedding_model: str | None = None,
278305
cli_embedding_device: str | None = None,
279306
) -> ResolvedOperatorConfig:
280-
root = (source_root or Path.cwd()).expanduser().resolve()
281-
yaml_dict = load_yaml_mapping(root)
307+
# Phase 1: Find the config file directory
308+
if source_root is not None:
309+
# CLI flag provided: use it as both config_dir and effective source_root
310+
# (skip YAML source_root check - CLI wins)
311+
root = source_root.expanduser().resolve()
312+
config_dir = root
313+
yaml_dict = load_yaml_mapping(config_dir)
314+
else:
315+
# Check env var first
316+
env_raw = os.environ.get(ENV_SOURCE_ROOT, "").strip()
317+
if env_raw:
318+
root = Path(env_raw).expanduser().resolve()
319+
config_dir = root
320+
yaml_dict = load_yaml_mapping(config_dir)
321+
else:
322+
# Walk up to find config dir
323+
discovered = discover_project_root(Path.cwd())
324+
config_dir = discovered if discovered is not None else Path.cwd().resolve()
325+
# Load YAML from config dir
326+
yaml_dict = load_yaml_mapping(config_dir)
327+
328+
# Phase 2: Resolve effective source root
329+
# Check for YAML source_root field (resolved relative to config dir)
330+
yaml_source_root = yaml_dict.get("source_root")
331+
if isinstance(yaml_source_root, str) and yaml_source_root.strip():
332+
yroot = Path(yaml_source_root.strip()).expanduser()
333+
root = yroot.resolve() if yroot.is_absolute() else (config_dir / yroot).resolve()
334+
else:
335+
root = config_dir
336+
282337
index_dir, index_src = _resolve_index_dir_path(
283338
source_root=root, cli_index_dir=cli_index_dir, yaml_dict=yaml_dict
284339
)

mcp.json.example

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,28 @@
1111
}
1212
}
1313
}
14+
15+
// Minimal configuration with walk-up discovery (no env vars required):
16+
// The tool walks up from the current directory to find .java-codebase-rag.yml,
17+
// then uses the config's source_root (or the config directory itself) to find the index.
18+
// Just omit the "env" section entirely:
19+
//
20+
// {
21+
// "mcpServers": {
22+
// "java-codebase-rag": {
23+
// "type": "stdio",
24+
// "command": "java-codebase-rag-mcp"
25+
// }
26+
// }
27+
// }
28+
//
29+
// For Claude Code (which uses the same MCP protocol but different config format),
30+
// the minimal configuration in .mcp.json is similar:
31+
//
32+
// {
33+
// "mcpServers": {
34+
// "java-codebase-rag": {
35+
// "command": "java-codebase-rag-mcp"
36+
// }
37+
// }
38+
// }

0 commit comments

Comments
 (0)