Skip to content

Commit 2a08b87

Browse files
author
Test User
committed
Merge pull request #1874: Fix #1873 FffIndexer middleware
2 parents 53728d4 + 836101d commit 2a08b87

81 files changed

Lines changed: 9049 additions & 222 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
# Implementation Plan: Local .terraphim/ Config as First-Priority Source
2+
3+
**Status**: Draft
4+
**Research Doc**: `.docs/research-1862-local-terraphim-config.md`
5+
**Author**: opencode (GLM-5.1)
6+
**Date**: 2026-05-25
7+
**Issue**: #1862
8+
**Estimated Effort**: 4-6 hours
9+
10+
## Overview
11+
12+
### Summary
13+
Extend `ProjectConfig` to auto-discover individual `role-*.json` and `thesaurus-*.json` files from `.terraphim/`, then wire this discovery into all three CLI tools.
14+
15+
### Approach
16+
Extend the existing `project::discover()` + `ProjectConfig` pattern rather than creating new infrastructure. Add filesystem scanning for role/thesaurus files, then consume in each tool.
17+
18+
### Scope
19+
**In Scope:**
20+
- Extend `ProjectConfig` with `load_from_dir()` to scan `role-*.json` files
21+
- Add `discover_thesaurus()` to find `thesaurus-*.json` by role name
22+
- Wire discovery into `terraphim_grep`, `terraphim_agent`, `terraphim_mcp_server`
23+
- Tests for new discovery functions
24+
25+
**Out of Scope:**
26+
- TOML config support
27+
- Config file watching
28+
- `terraphim_server` changes
29+
- Global `~/.terraphim/` config merge
30+
31+
**Avoid At All Cost:**
32+
- Adding new config file formats or manifest files
33+
- Changing existing CLI flag behaviour (flags must still work)
34+
- Modifying DeviceSettings persistence
35+
- Creating new crates
36+
37+
## Architecture
38+
39+
### Component Diagram
40+
```
41+
.terraphim/ terraphim_config::project
42+
role-*.json ----discover---> ProjectConfig::load_from_dir()
43+
thesaurus-*.json |
44+
kg/ +---> terraphim_grep (thesaurus + role + kg)
45+
+---> terraphim_agent (role merge)
46+
+---> terraphim_mcp_server (full config)
47+
```
48+
49+
### Data Flow
50+
```
51+
CLI invocation
52+
-> project::discover(None) -- walk up from CWD
53+
-> found .terraphim/
54+
-> ProjectConfig::load_from_dir(path) -- scan role-*.json, thesaurus-*.json
55+
-> merge with CLI flag overrides
56+
-> run tool with merged config
57+
```
58+
59+
### Key Design Decisions
60+
61+
| Decision | Rationale | Alternatives Rejected |
62+
|----------|-----------|----------------------|
63+
| Scan filesystem for role-*.json | Simple, no manifest needed | Require config.json with role list |
64+
| Role name from filename | `role-devops.json` -> "devops" role | Embed role name in JSON content |
65+
| Thesaurus matched by role name | `thesaurus-devops.json` matches "devops" | Single thesaurus for all roles |
66+
| CLI flags override project config | Escape hatch for developers | Project config always wins |
67+
68+
### Simplicity Check
69+
70+
**What if this could be easy?** It IS easy. We just need:
71+
1. A function that lists `role-*.json` files and parses them
72+
2. A function that finds `thesaurus-<role>.json` for a given role
73+
3. Calling these functions in main.rs of each tool
74+
75+
No new types, no new crates, no new formats. Just extending what exists.
76+
77+
## File Changes
78+
79+
### Modified Files
80+
| File | Changes |
81+
|------|---------|
82+
| `crates/terraphim_config/src/project.rs` | Add `load_from_dir()`, `discover_thesaurus_for_role()`, `discover_kg_path()` |
83+
| `crates/terraphim_grep/src/main.rs` | Add `.terraphim/` discovery to `find_default_thesaurus()`, role config, kg path |
84+
| `crates/terraphim_agent/src/service.rs` | Extend `merge_project_config()` to load individual role files |
85+
| `crates/terraphim_mcp_server/src/main.rs` | Add project config discovery before hardcoded profile |
86+
87+
### No New Files Needed
88+
89+
## API Design
90+
91+
### New Public Functions in `project.rs`
92+
93+
```rust
94+
/// Load a ProjectConfig by scanning .terraphim/ for role-*.json files.
95+
///
96+
/// If config.json exists, loads it first (backward compat).
97+
/// Then scans for role-*.json files and merges them in.
98+
/// Role name is derived from filename: role-devops.json -> "devops"
99+
pub fn load_from_dir(dir: &Path) -> Result<ProjectConfig, ProjectDiscoveryError>
100+
101+
/// Find the thesaurus file for a given role in .terraphim/
102+
///
103+
/// Looks for .terraphim/thesaurus-<role>.json
104+
/// Returns None if not found
105+
pub fn discover_thesaurus(dir: &Path, role_name: &str) -> Option<PathBuf>
106+
107+
/// Find the KG directory within .terraphim/
108+
///
109+
/// Looks for .terraphim/kg/
110+
/// Returns None if not found
111+
pub fn discover_kg_path(dir: &Path) -> Option<PathBuf>
112+
```
113+
114+
### No New Types Needed
115+
`ProjectConfig` already has `roles: HashMap<String, Role>` -- we just populate it from individual files.
116+
117+
## Test Strategy
118+
119+
### Unit Tests (in project.rs)
120+
| Test | Purpose |
121+
|------|---------|
122+
| `test_load_from_dir_reads_role_files` | Scans role-devops.json, role-rust-engineer.json |
123+
| `test_load_from_dir_merges_with_config_json` | config.json + role-*.json both present |
124+
| `test_load_from_dir_empty_is_ok` | No role files -> empty config |
125+
| `test_discover_thesaurus_found` | Returns thesaurus-devops.json for "devops" |
126+
| `test_discover_thesaurus_not_found` | Returns None for unknown role |
127+
| `test_discover_kg_path_found` | Returns .terraphim/kg/ |
128+
| `test_discover_kg_path_not_found` | Returns None |
129+
130+
### Integration Tests
131+
| Test | Location | Purpose |
132+
|------|----------|---------|
133+
| `test_grep_uses_project_thesaurus` | terraphim_grep tests | End-to-end: grep finds thesaurus from .terraphim/ |
134+
| `test_agent_merges_project_roles` | terraphim_agent tests | Agent loads role-*.json from project |
135+
136+
## Implementation Steps
137+
138+
### Step 1: Extend ProjectConfig (project.rs)
139+
**Files:** `crates/terraphim_config/src/project.rs`
140+
**Description:** Add `load_from_dir()`, `discover_thesaurus()`, `discover_kg_path()`
141+
**Tests:** 7 unit tests above
142+
**Estimated:** 1.5 hours
143+
144+
Key implementation:
145+
```rust
146+
pub fn load_from_dir(dir: &Path) -> Result<Self, ProjectDiscoveryError> {
147+
let mut config = Self {
148+
global_shortcut: None,
149+
roles: std::collections::HashMap::new(),
150+
};
151+
// Backward compat: load config.json if present
152+
let config_json = dir.join("config.json");
153+
if config_json.is_file() {
154+
config = Self::from_file(&config_json)?;
155+
}
156+
// Scan for role-*.json files
157+
for entry in std::fs::read_dir(dir)? {
158+
let entry = entry?;
159+
let name = entry.file_name().to_string_lossy().to_string();
160+
if name.starts_with("role-") && name.ends_with(".json") {
161+
let role_name = name.trim_start_matches("role-")
162+
.trim_end_matches(".json")
163+
.to_string();
164+
let role: Role = serde_json::from_str(&std::fs::read_to_string(entry.path())?)?;
165+
config.roles.insert(role_name, role);
166+
}
167+
}
168+
Ok(config)
169+
}
170+
```
171+
172+
### Step 2: Wire terraphim_grep (main.rs)
173+
**Files:** `crates/terraphim_grep/src/main.rs`
174+
**Description:** Extend `find_default_thesaurus()` to search `.terraphim/`, add role/kg discovery
175+
**Tests:** Existing tests still pass; manual verification
176+
**Dependencies:** Step 1
177+
**Estimated:** 1 hour
178+
179+
Changes to `find_default_thesaurus()`:
180+
```rust
181+
fn find_default_thesaurus() -> Option<PathBuf> {
182+
// Priority 1: .terraphim/ project config
183+
if let Ok(Some(dir)) = terraphim_config::project::discover(None) {
184+
// Use role name to find matching thesaurus
185+
if let Some(path) = terraphim_config::project::discover_thesaurus(&dir, &role_name) {
186+
return Some(path);
187+
}
188+
}
189+
// Priority 2: filesystem heuristic (existing code)
190+
// ...
191+
}
192+
```
193+
194+
Also add project discovery for `--role-config` and `--kg-path` defaults.
195+
196+
### Step 3: Wire terraphim_agent (service.rs)
197+
**Files:** `crates/terraphim_agent/src/service.rs`
198+
**Description:** Extend `merge_project_config()` to use `ProjectConfig::load_from_dir()`
199+
**Tests:** Existing tests still pass
200+
**Dependencies:** Step 1
201+
**Estimated:** 0.5 hours
202+
203+
Change `merge_project_config()`:
204+
```rust
205+
fn merge_project_config(config: &mut Config) {
206+
if let Ok(Some(path)) = terraphim_config::project::discover(None) {
207+
// Try load_from_dir first (scans role-*.json)
208+
let project_config = terraphim_config::project::ProjectConfig::load_from_dir(&path)
209+
.unwrap_or_else(|_| {
210+
// Fallback to config.json only
211+
let config_path = path.join("config.json");
212+
if config_path.is_file() {
213+
terraphim_config::project::ProjectConfig::from_file(&config_path)
214+
.unwrap_or_default()
215+
} else {
216+
Default::default()
217+
}
218+
});
219+
if !project_config.roles.is_empty() {
220+
log::info!("Merging {} project roles from '{}'",
221+
project_config.roles.len(), path.display());
222+
let builder = ConfigBuilder::from_config(
223+
config.clone(), DeviceSettings::new(), PathBuf::new(),
224+
);
225+
*config = builder.merge_with(&project_config)
226+
.build().unwrap_or_else(|_| config.clone());
227+
}
228+
}
229+
}
230+
```
231+
232+
### Step 4: Wire terraphim_mcp_server (main.rs)
233+
**Files:** `crates/terraphim_mcp_server/src/main.rs`
234+
**Description:** Add project config discovery before hardcoded profiles
235+
**Tests:** Existing tests still pass
236+
**Dependencies:** Step 1
237+
**Estimated:** 0.5 hours
238+
239+
```rust
240+
// Before profile selection, try project config
241+
if let Ok(Some(project_dir)) = terraphim_config::project::discover(None) {
242+
if let Ok(project_config) = ProjectConfig::load_from_dir(&project_dir) {
243+
if !project_config.roles.is_empty() {
244+
// Build Config from project roles instead of hardcoded profile
245+
// ...
246+
}
247+
}
248+
}
249+
// Fall back to --profile flag
250+
```
251+
252+
### Step 5: Build, Test, Clippy
253+
**Estimated:** 0.5 hours
254+
255+
## Rollback Plan
256+
Each step is additive. If any step breaks, revert the single commit for that step. No database migrations, no breaking changes.
257+
258+
## Open Items
259+
None -- research phase resolved all questions.

0 commit comments

Comments
 (0)