From 7f96ae95085fd29be04c6d148c6f2fcf5e262986 Mon Sep 17 00:00:00 2001 From: mika kuns Date: Sat, 25 Apr 2026 10:10:50 +0200 Subject: [PATCH] feat(prompts): add editable system/planning/agent prompt files Introduces ~/.todo-app/prompts/{system,planning,agent}.md as the canonical location for prompt content. The settings modal exposes "Open in editor" shortcuts for each, and TaskRunner merges system.md (always) and agent.md (for "agent"-tagged tasks) into the effective system prompt alongside the existing global/list/task layers. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/ClaudeDo.Data/PromptFiles.cs | 58 +++++++++++++++++++ .../Modals/SettingsModalViewModel.cs | 20 +++++++ .../Views/Modals/SettingsModalView.axaml | 32 ++++++++++ src/ClaudeDo.Worker/Runner/TaskRunner.cs | 21 ++++--- 4 files changed, 124 insertions(+), 7 deletions(-) create mode 100644 src/ClaudeDo.Data/PromptFiles.cs diff --git a/src/ClaudeDo.Data/PromptFiles.cs b/src/ClaudeDo.Data/PromptFiles.cs new file mode 100644 index 0000000..d1a98cb --- /dev/null +++ b/src/ClaudeDo.Data/PromptFiles.cs @@ -0,0 +1,58 @@ +namespace ClaudeDo.Data; + +public enum PromptKind { System, Planning, Agent } + +public static class PromptFiles +{ + public static string Root => Path.Combine(Paths.AppDataRoot(), "prompts"); + + public static string PathFor(PromptKind kind) => kind switch + { + PromptKind.System => Path.Combine(Root, "system.md"), + PromptKind.Planning => Path.Combine(Root, "planning.md"), + PromptKind.Agent => Path.Combine(Root, "agent.md"), + _ => throw new ArgumentOutOfRangeException(nameof(kind)) + }; + + public static void EnsureExists(PromptKind kind) + { + Directory.CreateDirectory(Root); + var path = PathFor(kind); + if (File.Exists(path)) return; + File.WriteAllText(path, DefaultFor(kind)); + } + + public static string? ReadOrNull(PromptKind kind) + { + var path = PathFor(kind); + if (!File.Exists(path)) return null; + var content = File.ReadAllText(path).Trim(); + return string.IsNullOrEmpty(content) ? null : content; + } + + private static string DefaultFor(PromptKind kind) => kind switch + { + PromptKind.System => + "# System Prompt\n\n" + + "Baseline instructions appended to every task run.\n" + + "Edit this file to inject project-wide rules (style, conventions, hard constraints).\n", + PromptKind.Planning => + "You are a planning assistant for ClaudeDo.\n" + + "Your role is to help break down a task into smaller, actionable subtasks.\n" + + "Your final goal WILL ALWAYS be the creation of Subtasks.\n\n" + + "ALWAYS invoke the `superpowers:brainstorming` skill via the Skill tool at the\n" + + "start of every planning session, and follow its process end-to-end. It guides\n" + + "you through clarifying questions, approach exploration, and design approval\n" + + "BEFORE any subtasks are created. Do not create child tasks until the user has\n" + + "approved a design.\n\n" + + "NEVER change files yourself.\n\n" + + "ALWAYS use the available MCP tools (mcp__claudedo__*) to create child tasks once\n" + + "the design is approved. When you are done planning, finalize the session.\n\n" + + "Be concise and focused. Each subtask should be independently executable.\n", + PromptKind.Agent => + "# Agent Prompt\n\n" + + "Appended to the system prompt for tasks tagged \"agent\" (auto-queued runs).\n" + + "Use this for autonomous-execution rules that don't apply to manual runs.\n", + _ => "" + }; +} diff --git a/src/ClaudeDo.Ui/ViewModels/Modals/SettingsModalViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Modals/SettingsModalViewModel.cs index bfba7fc..93dd519 100644 --- a/src/ClaudeDo.Ui/ViewModels/Modals/SettingsModalViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/Modals/SettingsModalViewModel.cs @@ -38,6 +38,10 @@ public sealed partial class SettingsModalViewModel : ViewModelBase public string LogsFolderPath { get; } = Path.Combine(Paths.AppDataRoot(), "logs"); public string WorkerConfigPath { get; } = Path.Combine(Paths.AppDataRoot(), "worker.config.json"); + public string SystemPromptPath { get; } = PromptFiles.PathFor(PromptKind.System); + public string PlanningPromptPath { get; } = PromptFiles.PathFor(PromptKind.Planning); + public string AgentPromptPath { get; } = PromptFiles.PathFor(PromptKind.Agent); + public Action? CloseAction { get; set; } public SettingsModalViewModel(WorkerClient worker) @@ -199,4 +203,20 @@ public sealed partial class SettingsModalViewModel : ViewModelBase } catch { /* ignore */ } } + + [RelayCommand] + private void OpenPrompt(string? kindName) + { + if (!Enum.TryParse(kindName, ignoreCase: true, out var kind)) return; + try + { + PromptFiles.EnsureExists(kind); + var path = PromptFiles.PathFor(kind); + Process.Start(new ProcessStartInfo(path) { UseShellExecute = true }); + } + catch (Exception ex) + { + StatusMessage = $"Open failed: {ex.Message}"; + } + } } diff --git a/src/ClaudeDo.Ui/Views/Modals/SettingsModalView.axaml b/src/ClaudeDo.Ui/Views/Modals/SettingsModalView.axaml index 7aa874b..4256c04 100644 --- a/src/ClaudeDo.Ui/Views/Modals/SettingsModalView.axaml +++ b/src/ClaudeDo.Ui/Views/Modals/SettingsModalView.axaml @@ -201,6 +201,38 @@ + + + + + + + +