using System.IO; using System.Text.Json; using System.Text.Json.Serialization; namespace AxCopilot.Services.Agent; internal static class WorktreeStateStore { private const string StateRelativePath = ".ax/worktree_state.json"; internal sealed class WorktreeState { [JsonPropertyName("root")] public string Root { get; set; } = ""; [JsonPropertyName("active")] public string Active { get; set; } = ""; [JsonPropertyName("updatedAt")] public DateTime UpdatedAt { get; set; } = DateTime.Now; } private static string GetPath(string root) => Path.Combine(root, StateRelativePath); internal static string ResolveRoot(string current) { try { var dir = new DirectoryInfo(Path.GetFullPath(current)); while (dir != null) { var candidate = dir.FullName; var statePath = GetPath(candidate); if (File.Exists(statePath)) { var state = Load(candidate); if (!string.IsNullOrWhiteSpace(state.Root)) { var resolvedRoot = Path.GetFullPath(state.Root); if (Directory.Exists(resolvedRoot)) return resolvedRoot; } return candidate; } dir = dir.Parent; } } catch { // ignore and fallback } return Path.GetFullPath(current); } internal static WorktreeState Load(string root) { var path = GetPath(root); if (!File.Exists(path)) return new WorktreeState { Root = root, Active = root, UpdatedAt = DateTime.Now }; try { var json = TextFileCodec.ReadAllText(path).Text; var state = JsonSerializer.Deserialize(json); return state ?? new WorktreeState { Root = root, Active = root, UpdatedAt = DateTime.Now }; } catch { return new WorktreeState { Root = root, Active = root, UpdatedAt = DateTime.Now }; } } internal static void Save(string root, WorktreeState state) { var path = GetPath(root); var dir = Path.GetDirectoryName(path); if (!string.IsNullOrWhiteSpace(dir) && !Directory.Exists(dir)) Directory.CreateDirectory(dir); state.Root = root; state.UpdatedAt = DateTime.Now; File.WriteAllText(path, JsonSerializer.Serialize(state, new JsonSerializerOptions { WriteIndented = true }), TextFileCodec.Utf8NoBom); } }