Skip to content

Commit ef34237

Browse files
HumanBean17claude
andauthored
plan: add DIRS-HIERARCHY execution plan (#267)
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 2339cc8 commit ef34237

1 file changed

Lines changed: 203 additions & 0 deletions

File tree

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
# Plan: Walk-up config discovery and configurable source root
2+
3+
Status: **active (planning)**. This plan implements
4+
[`propose/active/DIRS-HIERARCHY-PROPOSE.md`](../../propose/active/DIRS-HIERARCHY-PROPOSE.md)
5+
as a single PR.
6+
7+
Depends on: none.
8+
9+
## Goal
10+
11+
- Users can run CLI and MCP commands from any subdirectory within their project — the tool walks up to find `.java-codebase-rag.yml`, like git finds `.git`.
12+
- The YAML config gains an optional `source_root` field so the config can live separately from the Java source code, and the index dir auto-derives from the resolved source root.
13+
- Existing workflows where cwd = config dir produce identical behavior. No breaking changes.
14+
15+
## Principles (do not relitigate in review)
16+
17+
- **First match wins.** Closest config to cwd, not "most specific" or "deepest". Matches git behavior.
18+
- **`$HOME` is the inclusive boundary.** Check `$HOME` itself, do not walk past it.
19+
- **Walk-up is always-on** when no explicit source root is given (CLI flag or env var). No `--walk-up` opt-out flag.
20+
- **YAML `source_root` resolves relative to the config file directory.** CLI `--source-root` resolves relative to cwd. Different resolution bases are intentional — the precedence table handles priority.
21+
- **Index dir follows source root.** Default index dir = `<source_root>/.java-codebase-rag/`. This does not change; walk-up just changes how source root itself is found.
22+
- **No changes to `init` behavior** beyond a soft warning when a parent config exists.
23+
- **No changes to indexing, query, or graph-building logic.** This is config discovery only.
24+
25+
## PR breakdown - overview
26+
27+
| PR | Scope | Ontology bump | Areas of concern | Test buckets | Independent of |
28+
| --- | --- | --- | --- | --- | --- |
29+
| PR-1 | Walk-up config discovery + `source_root` YAML field | none | Precedence chain correctness; server/CLI parity; boundary conditions ($HOME, root) | unit tests for discovery + precedence + integration ||
30+
31+
Landing order: **PR-1** (single PR).
32+
33+
## Resolved design decisions
34+
35+
| Topic | Decision |
36+
| --- | --- |
37+
| Config file name checked | `.java-codebase-rag.yml` and `.java-codebase-rag.yaml` (existing `YAML_CONFIG_FILENAMES` tuple) |
38+
| Boundary for walk-up | `$HOME` inclusive — check `$HOME` itself but do not walk past it |
39+
| YAML field name | `source_root` — same name as CLI flag for conceptual consistency |
40+
| Resolution base for YAML `source_root` | Config file's parent directory (not cwd) |
41+
| `init` behavior | Unchanged — creates config + index in the specified directory. Only adds a soft warning if a parent config is detected |
42+
| Multiple nested configs | First match wins (closest to cwd). Mirrors git behavior |
43+
| New function location | `config.py` — all config resolution logic lives there already |
44+
| `discover_project_root` return | `Path | None` — returns the directory containing the config file, not the config file path itself |
45+
46+
---
47+
48+
# PR-1 — Walk-up config discovery and configurable source root
49+
50+
## File-by-file changes
51+
52+
### 1. `java_codebase_rag/config.py`
53+
54+
**New function: `discover_project_root(start: Path) -> Path | None`**
55+
56+
- Canonicalize `start` via `Path.resolve()`
57+
- Walk from `start` upward, checking each directory for files matching `YAML_CONFIG_FILENAMES`
58+
- First match returns that directory (the parent of the found config file, not the file path itself)
59+
- Stop at `$HOME` (inclusive — check `$HOME` itself) or filesystem root. Do not walk past `$HOME`.
60+
- Return `None` if no config found
61+
62+
**Modify: `resolve_operator_config()` — two-phase resolution**
63+
64+
The core change is separating *config file discovery* from *effective source root resolution*. The exact sequence:
65+
66+
1. **Phase 1 — find the config file directory.** If `source_root` is provided (CLI flag or env var), the config dir = that value (no walk-up). Otherwise, call `discover_project_root(Path.cwd())`. If walk-up found a config dir, use it. Otherwise fall back to `Path.cwd().resolve()` (unchanged behavior).
67+
2. **Load YAML** from the config dir via `load_yaml_mapping(config_dir)`.
68+
3. **Phase 2 — resolve effective source root.** Check for a `source_root` key in the YAML. If present, resolve it relative to the config dir (not cwd). The effective source root is then:
69+
- CLI `--source-root` (already handled — `source_root` is not `None` in phase 1, so phase 2 is skipped)
70+
- env `JAVA_CODEBASE_RAG_SOURCE_ROOT` (checked before walk-up in both `server.py` and `resolve_operator_config`)
71+
- YAML `source_root` (resolved relative to config dir)
72+
- Walk-up discovery result (= config dir itself, which is the default when no YAML override)
73+
- `Path.cwd()` (no config found, no YAML override)
74+
4. **Derive index dir** from the effective source root via `_resolve_index_dir_path()`. No edits to `_resolve_index_dir_path` itself — the caller ensures the effective source root (after YAML resolution) is what gets passed through.
75+
76+
**Note:** Do NOT introduce a `find_config_dir` wrapper. The two-phase logic lives directly in `resolve_operator_config()` for clarity. The only new public function is `discover_project_root()`.
77+
78+
### 2. `server.py`
79+
80+
**Modify: `_project_root()`**
81+
82+
- Current logic: env var → cwd fallback
83+
- New logic: env var → `discover_project_root(Path.cwd())` → cwd fallback
84+
- Import `discover_project_root` from `java_codebase_rag.config`
85+
86+
**Modify: `_resolve_lancedb_uri()`**
87+
88+
- Currently falls back to `Path.cwd() / ".java-codebase-rag"` when `JAVA_CODEBASE_RAG_INDEX_DIR` is unset.
89+
- After walk-up, this should use the discovered source root (via `_project_root()`) for consistency.
90+
- The server's `list_code_index_tables_payload()` calls `resolve_operator_config(source_root=_project_root())`, so index dir is derived from the effective source root. But `_resolve_lancedb_uri()` is called independently in some paths. Ensure it uses `_project_root()` instead of raw `Path.cwd()` when the env var is unset.
91+
92+
### 3. `java_codebase_rag/cli.py`
93+
94+
**Modify: `_parse_source_root()` / `_resolved_from_ns()`**
95+
96+
- `_parse_source_root()` stays the same (returns `None` when `--source-root` is not given)
97+
- `_resolved_from_ns()` already passes `source_root=root` to `resolve_operator_config()` — walk-up logic in `resolve_operator_config()` handles the `None` case
98+
99+
**Modify: `init` command handler**
100+
101+
- After resolving `cfg = _resolved_from_ns(args)`, check for a parent config by calling `discover_project_root(cfg.source_root.parent)` — this checks whether a config exists in any ancestor of the *resolved source root* (not the config dir, since `init` creates the config at the source root)
102+
- If found, emit a soft warning to stderr:
103+
> Warning: found existing config at `[parent]/.java-codebase-rag.yml`. Creating a new project here will create a separate index.
104+
105+
### 4. `mcp.json.example`
106+
107+
- Add a comment block showing the minimal zero-env-var config
108+
- Keep the existing full example as an alternative
109+
- Show both Claude Desktop and Claude Code variants
110+
111+
### 5. `README.md`
112+
113+
- Update the MCP host wiring section to mention walk-up discovery
114+
- Document the `source_root` YAML field
115+
- Update the minimal `.mcp.json` example to show that env vars are now optional
116+
117+
### 6. `docs/CONFIGURATION.md`
118+
119+
- Add `source_root` to the YAML config reference table
120+
- Document the walk-up discovery behavior
121+
- Update the precedence chain table to include the YAML `source_root` field
122+
123+
## Tests for PR-1
124+
125+
All new tests go in **`tests/test_config.py`** (new file). Tests that exercise `_project_root()` in `server.py` go in **`tests/test_mcp_server_project_root.py`** (new file) to keep MCP test concerns separate.
126+
127+
### Config discovery tests (`tests/test_config.py`)
128+
129+
1. `test_discover_project_root_finds_config_in_cwd` — config in cwd, returns cwd
130+
2. `test_discover_project_root_walks_up` — config in parent, returns parent
131+
3. `test_discover_project_root_stops_at_home_boundary` — config in `$HOME` itself, walk-up from subdirectory of `$HOME` finds it (inclusive boundary)
132+
4. `test_discover_project_root_not_found_above_home` — no config anywhere under `$HOME`, returns `None`
133+
5. `test_discover_project_root_not_found` — no config anywhere, returns `None`
134+
6. `test_discover_project_root_first_match_wins` — configs at two levels (cwd subdirectory has one, parent has another), closest to cwd wins
135+
136+
### Source root resolution tests (`tests/test_config.py`)
137+
138+
7. `test_source_root_from_yaml_relative``source_root: ../` resolves to parent of config dir
139+
8. `test_source_root_from_yaml_absolute``source_root: /abs/path` resolves to absolute path
140+
9. `test_source_root_precedence_cli_over_yaml` — CLI flag wins over YAML `source_root`
141+
10. `test_source_root_precedence_yaml_over_discovery` — YAML `source_root` wins over config dir default
142+
11. `test_source_root_precedence_env_over_yaml` — env var wins over YAML `source_root`
143+
12. `test_existing_behavior_unchanged` — no walk-up, cwd = config dir → identical behavior to today
144+
145+
### Server integration test (`tests/test_mcp_server_project_root.py`)
146+
147+
13. `test_project_root_uses_discover_when_env_unset``_project_root()` returns discovered config dir when `JAVA_CODEBASE_RAG_SOURCE_ROOT` is unset
148+
149+
## Definition of done (PR-1)
150+
151+
- [ ] `discover_project_root()` works with first-match-wins semantics, stops at `$HOME` (inclusive)
152+
- [ ] `source_root` YAML field is parsed and resolved relative to config dir
153+
- [ ] Precedence chain: CLI > env > YAML > discovery > cwd
154+
- [ ] `_project_root()` in `server.py` uses walk-up when env var is unset
155+
- [ ] `_resolve_lancedb_uri()` in `server.py` uses `_project_root()` instead of raw `Path.cwd()` for fallback
156+
- [ ] CLI commands work from subdirectories (walk-up finds config)
157+
- [ ] `init` emits soft warning when parent config detected
158+
- [ ] All 13 named tests pass
159+
- [ ] Existing test suite passes (no regressions)
160+
- [ ] `mcp.json.example` shows minimal zero-env-var config
161+
- [ ] README and CONFIGURATION docs updated
162+
163+
## Implementation step list
164+
165+
| # | Step | File(s) | Done when |
166+
| - | - | - | - |
167+
| 1 | Add `discover_project_root(start)` | `config.py`, `tests/test_config.py` | Tests 1–6 pass |
168+
| 2 | Add `source_root` YAML field parsing and resolution | `config.py`, `tests/test_config.py` | Tests 7–8 pass |
169+
| 3 | Wire precedence chain in `resolve_operator_config()` | `config.py`, `tests/test_config.py` | Tests 9–12 pass |
170+
| 4 | Update `_project_root()` to use walk-up | `server.py`, `tests/test_mcp_server_project_root.py` | Test 13 passes; server resolves source root via walk-up when env var unset |
171+
| 5 | Update `_resolve_lancedb_uri()` to use `_project_root()` fallback | `server.py` | Lance URI and source root derive from same discovered root |
172+
| 6 | Add `init` parent-config warning | `cli.py` | `init` prints warning when parent config exists |
173+
| 7 | Update `mcp.json.example` | `mcp.json.example` | Shows minimal zero-env-var config |
174+
| 8 | Update README and CONFIGURATION docs | `README.md`, `docs/CONFIGURATION.md` | Walk-up and `source_root` documented |
175+
| 9 | Run full validation | all | `ruff check` + `pytest tests -v` green |
176+
177+
---
178+
179+
# Cross-PR risks and mitigations
180+
181+
N/A — single PR.
182+
183+
# Out of scope
184+
185+
- Auto-detecting multiple systems and splitting indexes
186+
- Changing index directory structure
187+
- Global config or project registry
188+
- Changes to indexing, query, or graph-building logic
189+
- `init` command behavior changes beyond the parent-config warning
190+
- Changes to `build_ast_graph.py` or `search_lancedb.py`
191+
192+
# Whole-plan done definition
193+
194+
1. All 13 named tests pass.
195+
2. Existing test suite passes without `JAVA_CODEBASE_RAG_RUN_HEAVY`.
196+
3. `ruff check .` is clean.
197+
4. CLI and MCP server both resolve source root via walk-up from subdirectories.
198+
5. `mcp.json.example` shows a zero-env-var configuration.
199+
6. README and CONFIGURATION docs reflect the new behavior.
200+
201+
# Tracking
202+
203+
- `PR-1`: _pending_

0 commit comments

Comments
 (0)