-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Labels
epic:config-parsingConfig file discovery, parsing, and data modelsConfig file discovery, parsing, and data modelsepic:rust-migrationRust + Iced migration infrastructureRust + Iced migration infrastructurephase:1Phase 1 — Read-only explorerPhase 1 — Read-only explorerpriority:highMust-have for MVPMust-have for MVPtype:migrationDirect port of existing Swift functionalityDirect port of existing Swift functionality
Description
Context
LegacyConfig represents ~/.claude.json — the global config with projects map, preferences, customApiKeyResponses, and global MCP servers. ProjectEntry is a single project within that map.
Important: The path field on ProjectEntry is NOT in the JSON — it comes from the key in the projects HashMap. The all_projects() method must populate it from the map key.
Ported from: Fig/Sources/Models/ProjectEntry.swift, Fig/Sources/Models/LegacyConfig.swift
What to implement
File paths
fig-core/src/models/project_entry.rsfig-core/src/models/legacy_config.rs
Rust struct definitions
// project_entry.rs
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
pub struct ProjectEntry {
/// Not serialized in JSON — populated from the map key in LegacyConfig.projects
#[serde(skip)]
pub path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "allowedTools")]
pub allowed_tools: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "hasTrustDialogAccepted")]
pub has_trust_dialog_accepted: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub history: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "mcpServers")]
pub mcp_servers: Option<HashMap<String, MCPServer>>,
#[serde(flatten)]
pub additional_properties: HashMap<String, Value>,
}
impl ProjectEntry {
pub fn name(&self) -> Option<&str> {
self.path.as_ref()
.and_then(|p| std::path::Path::new(p).file_name()?.to_str())
}
pub fn has_mcp_servers(&self) -> bool {
self.mcp_servers.as_ref().map(|s| !s.is_empty()).unwrap_or(false)
}
pub fn mcp_server_count(&self) -> usize {
self.mcp_servers.as_ref().map(|s| s.len()).unwrap_or(0)
}
}
// legacy_config.rs
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
pub struct LegacyConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub projects: Option<HashMap<String, ProjectEntry>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "customApiKeyResponses")]
pub custom_api_key_responses: Option<HashMap<String, Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub preferences: Option<HashMap<String, Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "mcpServers")]
pub mcp_servers: Option<HashMap<String, MCPServer>>,
#[serde(flatten)]
pub additional_properties: HashMap<String, Value>,
}
impl LegacyConfig {
/// Returns all project paths from the projects map.
pub fn project_paths(&self) -> Vec<String> {
self.projects.as_ref()
.map(|p| p.keys().cloned().collect())
.unwrap_or_default()
}
/// Returns all projects with their path populated from map keys.
pub fn all_projects(&self) -> Vec<ProjectEntry> {
self.projects.as_ref()
.map(|p| p.iter().map(|(path, entry)| {
let mut e = entry.clone();
e.path = Some(path.clone());
e
}).collect())
.unwrap_or_default()
}
pub fn global_server_names(&self) -> Vec<String> {
self.mcp_servers.as_ref()
.map(|s| s.keys().cloned().collect())
.unwrap_or_default()
}
pub fn project(&self, path: &str) -> Option<ProjectEntry> {
self.projects.as_ref()?.get(path).map(|entry| {
let mut e = entry.clone();
e.path = Some(path.to_string());
e
})
}
}Acceptance criteria
- Parses real
~/.claude.jsoncontent -
all_projects()populatespathfrom map keys - Round-trip preserves
customApiKeyResponsesandpreferencesas opaqueValue -
pathfield is#[serde(skip)]so it's not serialized to JSON
Test requirements
test_legacy_config_round_triptest_project_entry_name_extraction— name derived from pathtest_all_projects_populates_pathstest_project_entry_serde_skip_path— path not in JSON outputtest_legacy_config_global_servers
Dependencies
Blocks
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
epic:config-parsingConfig file discovery, parsing, and data modelsConfig file discovery, parsing, and data modelsepic:rust-migrationRust + Iced migration infrastructureRust + Iced migration infrastructurephase:1Phase 1 — Read-only explorerPhase 1 — Read-only explorerpriority:highMust-have for MVPMust-have for MVPtype:migrationDirect port of existing Swift functionalityDirect port of existing Swift functionality