diff --git a/src/ClaudeDo.Worker/Planning/PlanningSessionManager.cs b/src/ClaudeDo.Worker/Planning/PlanningSessionManager.cs index 2906ec5..0ddd0cb 100644 --- a/src/ClaudeDo.Worker/Planning/PlanningSessionManager.cs +++ b/src/ClaudeDo.Worker/Planning/PlanningSessionManager.cs @@ -203,4 +203,54 @@ public sealed class PlanningSessionManager sb.AppendLine("Please analyze this task and break it down into concrete subtasks."); return sb.ToString(); } + + private static string BranchNameFor(string taskId) => + $"claudedo/planning/{taskId.Replace("-", "")}"; + + private string WorktreePathFor(string taskId, string strategy, string? centralRootOverride, string listWorkingDir) + { + var centralRoot = !string.IsNullOrWhiteSpace(centralRootOverride) + ? centralRootOverride! + : _cfg.CentralWorktreeRoot; + + var raw = strategy.Equals("central", StringComparison.OrdinalIgnoreCase) + ? Path.Combine(centralRoot, "planning", taskId) + : Path.Combine(Path.GetDirectoryName(listWorkingDir)!, ".claudedo-worktrees", "planning", taskId); + + return Path.GetFullPath(raw); + } + + private static string TokenFilePathFor(string sessionDir) => + Path.Combine(sessionDir, "token"); + + private static async Task WriteTokenFileAsync(string path, string token, CancellationToken ct) + { + await File.WriteAllTextAsync(path, token, ct); + // Best-effort current-user-only ACL on Windows. On non-Windows the inherited + // perms from the parent dir apply; acceptable because sessionDir is already + // under the user's home (~/.todo-app/sessions/). + if (OperatingSystem.IsWindows()) + { + try + { + var fi = new FileInfo(path); + var ac = fi.GetAccessControl(); + ac.SetAccessRuleProtection(isProtected: true, preserveInheritance: false); + var me = System.Security.Principal.WindowsIdentity.GetCurrent().User!; + ac.AddAccessRule(new System.Security.AccessControl.FileSystemAccessRule( + me, + System.Security.AccessControl.FileSystemRights.FullControl, + System.Security.AccessControl.AccessControlType.Allow)); + fi.SetAccessControl(ac); + } + catch { /* ACL hardening is best-effort */ } + } + } + + private static async Task ReadTokenFileAsync(string path, CancellationToken ct) + { + if (!File.Exists(path)) + throw new InvalidOperationException($"Token file missing: {path}"); + return (await File.ReadAllTextAsync(path, ct)).Trim(); + } } \ No newline at end of file