chore: move appdata folder to .claudeDo
This commit is contained in:
@@ -29,10 +29,10 @@ have none; this file plus the code is all there is for them.
|
|||||||
|
|
||||||
## Key Paths
|
## Key Paths
|
||||||
|
|
||||||
- DB: `~/.todo-app/todo.db`
|
- DB: `~/.claudeDo/todo.db`
|
||||||
- UI config: `~/.todo-app/ui.config.json`
|
- UI config: `~/.claudeDo/ui.config.json`
|
||||||
- Worker config: `~/.todo-app/worker.config.json`
|
- Worker config: `~/.claudeDo/worker.config.json`
|
||||||
- Logs: `~/.todo-app/logs/`
|
- Logs: `~/.claudeDo/logs/`
|
||||||
- Worktrees: configured per worker (sibling or central strategy)
|
- Worktrees: configured per worker (sibling or central strategy)
|
||||||
|
|
||||||
## Conventions
|
## Conventions
|
||||||
|
|||||||
@@ -374,7 +374,7 @@ updates`.
|
|||||||
|
|
||||||
## Where your data lives
|
## Where your data lives
|
||||||
|
|
||||||
Everything is under `%USERPROFILE%\.todo-app`:
|
Everything is under `%USERPROFILE%\.claudeDo`:
|
||||||
|
|
||||||
| Path | What |
|
| Path | What |
|
||||||
|---|---|
|
|---|---|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ Two independent reasons:
|
|||||||
`--abort`) is then misread as an unknown option.
|
`--abort`) is then misread as an unknown option.
|
||||||
2. A raw multi-line positional prompt truncates at its **first newline** regardless.
|
2. A raw multi-line positional prompt truncates at its **first newline** regardless.
|
||||||
|
|
||||||
A fresh task session's brief lives at `~/.todo-app/task-sessions/<taskId>/brief.md`
|
A fresh task session's brief lives at `~/.claudeDo/task-sessions/<taskId>/brief.md`
|
||||||
(`InteractiveLaunchSpecService.BuildFreshTaskArgsAsync`). A task with neither title nor
|
(`InteractiveLaunchSpecService.BuildFreshTaskArgsAsync`). A task with neither title nor
|
||||||
description skips the file **and** the positional arg entirely — it still gets `--session-id`.
|
description skips the file **and** the positional arg entirely — it still gets `--session-id`.
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ public sealed class AttachmentStore
|
|||||||
private readonly string _root;
|
private readonly string _root;
|
||||||
|
|
||||||
public AttachmentStore(string? root = null)
|
public AttachmentStore(string? root = null)
|
||||||
=> _root = root ?? Paths.Expand("~/.todo-app/attachments");
|
=> _root = root ?? Path.Combine(Paths.AppDataRoot(), "attachments");
|
||||||
|
|
||||||
public string Root => _root;
|
public string Root => _root;
|
||||||
|
|
||||||
|
|||||||
@@ -63,9 +63,9 @@ dir(s) via an optional `AttachmentStore` ctor param (defaults to the production
|
|||||||
|
|
||||||
- **ClaudeDoDbContext** — EF Core DbContext; WAL mode + foreign keys via `UseSqlite` options
|
- **ClaudeDoDbContext** — EF Core DbContext; WAL mode + foreign keys via `UseSqlite` options
|
||||||
- **IDbContextFactory\<ClaudeDoDbContext\>** — registered in DI; used by singleton consumers (e.g. the Worker hosted service)
|
- **IDbContextFactory\<ClaudeDoDbContext\>** — registered in DI; used by singleton consumers (e.g. the Worker hosted service)
|
||||||
- **Paths** — expands `~` and `%USERPROFILE%`, resolves relative paths. App root `~/.todo-app`
|
- **Paths** — expands `~` and `%USERPROFILE%`, resolves relative paths. App root `~/.claudeDo`
|
||||||
- **AppSettings** — loads `~/.todo-app/ui.config.json` (DbPath, SignalRUrl)
|
- **AppSettings** — loads `~/.claudeDo/ui.config.json` (DbPath, SignalRUrl)
|
||||||
- **AttachmentStore** — dependency-free file store, default root `~/.todo-app/attachments/<taskId>/`. `SaveAsync` enforces a 5 MB cap and a path-traversal/containment guard. Also `DeleteFile`, `DeleteTaskDir`, `TaskDir`, `Root`, `EnumerateTaskIds` (used by the worker orphan sweep). Attachment files live **outside** git worktrees intentionally.
|
- **AttachmentStore** — dependency-free file store, default root `~/.claudeDo/attachments/<taskId>/`. `SaveAsync` enforces a 5 MB cap and a path-traversal/containment guard. Also `DeleteFile`, `DeleteTaskDir`, `TaskDir`, `Root`, `EnumerateTaskIds` (used by the worker orphan sweep). Attachment files live **outside** git worktrees intentionally.
|
||||||
|
|
||||||
## Git
|
## Git
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,50 @@ public static class Paths
|
|||||||
return trimmed.Length == 0 || trimmed.EndsWith(':') ? path : trimmed;
|
return trimmed.Length == 0 || trimmed.EndsWith(':') ? path : trimmed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>~/.todo-app — parent directory for db, logs, config, sandbox, worktrees.</summary>
|
private const string DirName = ".claudeDo";
|
||||||
public static string AppDataRoot() =>
|
private const string LegacyDirName = ".todo-app";
|
||||||
Expand("~/.todo-app");
|
|
||||||
|
/// <summary>
|
||||||
|
/// ~/.claudeDo — parent directory for db, logs, config, sandbox, worktrees.
|
||||||
|
/// Falls back to the legacy ~/.todo-app while that one still exists and the new one
|
||||||
|
/// doesn't, so a failed <see cref="MigrateLegacyAppDataRoot"/> degrades to "keep using
|
||||||
|
/// the old folder" instead of silently starting over on an empty one.
|
||||||
|
/// </summary>
|
||||||
|
public static string AppDataRoot()
|
||||||
|
{
|
||||||
|
var root = Expand("~/" + DirName);
|
||||||
|
if (!Directory.Exists(root))
|
||||||
|
{
|
||||||
|
var legacy = Expand("~/" + LegacyDirName);
|
||||||
|
if (Directory.Exists(legacy)) return legacy;
|
||||||
|
}
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// One-time rename of ~/.todo-app to ~/.claudeDo, including the path literals stored
|
||||||
|
/// inside worker.config.json / ui.config.json. No-op once the new folder exists.
|
||||||
|
/// Called from the Worker at startup before anything opens a file under the root —
|
||||||
|
/// the single mover, so the UI never races it.
|
||||||
|
/// </summary>
|
||||||
|
public static void MigrateLegacyAppDataRoot() => MigrateLegacyAppDataRoot(Expand("~"));
|
||||||
|
|
||||||
|
internal static void MigrateLegacyAppDataRoot(string home)
|
||||||
|
{
|
||||||
|
var root = Path.Combine(home, DirName);
|
||||||
|
var legacy = Path.Combine(home, LegacyDirName);
|
||||||
|
if (Directory.Exists(root) || !Directory.Exists(legacy)) return;
|
||||||
|
|
||||||
|
// A locked file (e.g. an old worker still holding todo.db) fails the move; AppDataRoot
|
||||||
|
// then keeps returning the legacy path and the next start retries.
|
||||||
|
try { Directory.Move(legacy, root); }
|
||||||
|
catch (IOException) { return; }
|
||||||
|
catch (UnauthorizedAccessException) { return; }
|
||||||
|
|
||||||
|
foreach (var cfg in Directory.EnumerateFiles(root, "*.config.json"))
|
||||||
|
{
|
||||||
|
try { File.WriteAllText(cfg, File.ReadAllText(cfg).Replace(LegacyDirName, DirName)); }
|
||||||
|
catch (IOException) { /* best effort — the default already points at the new root */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ Each step implements `IInstallStep`; `InstallerService` runs them sequentially,
|
|||||||
`StopWorkerStep` → `DownloadAndExtractStep` → `RegisterAutostartStep` → `StartWorkerStep`
|
`StopWorkerStep` → `DownloadAndExtractStep` → `RegisterAutostartStep` → `StartWorkerStep`
|
||||||
|
|
||||||
**Uninstall** (`UninstallRunner`):
|
**Uninstall** (`UninstallRunner`):
|
||||||
Stop worker → remove legacy task/service → delete HKLM uninstall key + shortcuts → delete install dir (cmd.exe trampoline if uninstaller exe is inside it) → optionally delete `~/.todo-app`
|
Stop worker → remove legacy task/service → delete HKLM uninstall key + shortcuts → delete install dir (cmd.exe trampoline if uninstaller exe is inside it) → optionally delete `~/.claudeDo`
|
||||||
|
|
||||||
## Folder Layout
|
## Folder Layout
|
||||||
|
|
||||||
@@ -97,9 +97,9 @@ and restored if it fails.
|
|||||||
| Property | Default |
|
| Property | Default |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `InstallDirectory` | `C:\Program Files\ClaudeDo` |
|
| `InstallDirectory` | `C:\Program Files\ClaudeDo` |
|
||||||
| `DbPath` | `~/.todo-app/todo.db` |
|
| `DbPath` | `~/.claudeDo/todo.db` |
|
||||||
| `LogRoot` | `~/.todo-app/logs` |
|
| `LogRoot` | `~/.claudeDo/logs` |
|
||||||
| `SandboxRoot` | `~/.todo-app/sandbox` |
|
| `SandboxRoot` | `~/.claudeDo/sandbox` |
|
||||||
| `WorktreeRootStrategy` | `sibling` |
|
| `WorktreeRootStrategy` | `sibling` |
|
||||||
| `SignalRPort` | `47821` |
|
| `SignalRPort` | `47821` |
|
||||||
| `ExternalMcpPort` | `47822` |
|
| `ExternalMcpPort` | `47822` |
|
||||||
@@ -112,9 +112,9 @@ and restored if it fails.
|
|||||||
|
|
||||||
| Path | Content |
|
| Path | Content |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `~/.todo-app/worker.config.json` | Worker config |
|
| `~/.claudeDo/worker.config.json` | Worker config |
|
||||||
| `~/.todo-app/ui.config.json` | UI config |
|
| `~/.claudeDo/ui.config.json` | UI config |
|
||||||
| `~/.todo-app/todo.db` | SQLite DB (EF migrations) |
|
| `~/.claudeDo/todo.db` | SQLite DB (EF migrations) |
|
||||||
| `<InstallDir>\install.json` | Install manifest |
|
| `<InstallDir>\install.json` | Install manifest |
|
||||||
| `<InstallDir>\app\` | UI binaries |
|
| `<InstallDir>\app\` | UI binaries |
|
||||||
| `<InstallDir>\worker\` | Worker binaries |
|
| `<InstallDir>\worker\` | Worker binaries |
|
||||||
|
|||||||
@@ -29,19 +29,19 @@ internal static class JsonConfigFile
|
|||||||
public sealed class InstallerWorkerConfig
|
public sealed class InstallerWorkerConfig
|
||||||
{
|
{
|
||||||
[JsonPropertyName("db_path")]
|
[JsonPropertyName("db_path")]
|
||||||
public string DbPath { get; set; } = "~/.todo-app/todo.db";
|
public string DbPath { get; set; } = "~/.claudeDo/todo.db";
|
||||||
|
|
||||||
[JsonPropertyName("sandbox_root")]
|
[JsonPropertyName("sandbox_root")]
|
||||||
public string SandboxRoot { get; set; } = "~/.todo-app/sandbox";
|
public string SandboxRoot { get; set; } = "~/.claudeDo/sandbox";
|
||||||
|
|
||||||
[JsonPropertyName("log_root")]
|
[JsonPropertyName("log_root")]
|
||||||
public string LogRoot { get; set; } = "~/.todo-app/logs";
|
public string LogRoot { get; set; } = "~/.claudeDo/logs";
|
||||||
|
|
||||||
[JsonPropertyName("worktree_root_strategy")]
|
[JsonPropertyName("worktree_root_strategy")]
|
||||||
public string WorktreeRootStrategy { get; set; } = "sibling";
|
public string WorktreeRootStrategy { get; set; } = "sibling";
|
||||||
|
|
||||||
[JsonPropertyName("central_worktree_root")]
|
[JsonPropertyName("central_worktree_root")]
|
||||||
public string CentralWorktreeRoot { get; set; } = "~/.todo-app/worktrees";
|
public string CentralWorktreeRoot { get; set; } = "~/.claudeDo/worktrees";
|
||||||
|
|
||||||
[JsonPropertyName("queue_backstop_interval_ms")]
|
[JsonPropertyName("queue_backstop_interval_ms")]
|
||||||
public int QueueBackstopIntervalMs { get; set; } = 30_000;
|
public int QueueBackstopIntervalMs { get; set; } = 30_000;
|
||||||
@@ -78,7 +78,7 @@ public sealed class InstallerWorkerConfig
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class InstallerAppSettings
|
public sealed class InstallerAppSettings
|
||||||
{
|
{
|
||||||
public string DbPath { get; set; } = "~/.todo-app/todo.db";
|
public string DbPath { get; set; } = "~/.claudeDo/todo.db";
|
||||||
public string SignalRUrl { get; set; } = "http://127.0.0.1:47821/hub";
|
public string SignalRUrl { get; set; } = "http://127.0.0.1:47821/hub";
|
||||||
public string Language { get; set; } = "";
|
public string Language { get; set; } = "";
|
||||||
|
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ public sealed class InstallContext
|
|||||||
public bool LatestTagUnparseable { get; set; } // true if latest tag isn't a System.Version
|
public bool LatestTagUnparseable { get; set; } // true if latest tag isn't a System.Version
|
||||||
|
|
||||||
// PathsPage
|
// PathsPage
|
||||||
public string DbPath { get; set; } = "~/.todo-app/todo.db";
|
public string DbPath { get; set; } = "~/.claudeDo/todo.db";
|
||||||
public string LogRoot { get; set; } = "~/.todo-app/logs";
|
public string LogRoot { get; set; } = "~/.claudeDo/logs";
|
||||||
public string SandboxRoot { get; set; } = "~/.todo-app/sandbox";
|
public string SandboxRoot { get; set; } = "~/.claudeDo/sandbox";
|
||||||
public string WorktreeRootStrategy { get; set; } = "sibling";
|
public string WorktreeRootStrategy { get; set; } = "sibling";
|
||||||
public string CentralWorktreeRoot { get; set; } = "~/.todo-app/worktrees";
|
public string CentralWorktreeRoot { get; set; } = "~/.claudeDo/worktrees";
|
||||||
|
|
||||||
// ServicePage
|
// ServicePage
|
||||||
public int SignalRPort { get; set; } = 47_821;
|
public int SignalRPort { get; set; } = 47_821;
|
||||||
@@ -28,7 +28,7 @@ public sealed class InstallContext
|
|||||||
|
|
||||||
// UiSettingsPage
|
// UiSettingsPage
|
||||||
public string SignalRUrl { get; set; } = "http://127.0.0.1:47821/hub";
|
public string SignalRUrl { get; set; } = "http://127.0.0.1:47821/hub";
|
||||||
public string UiDbPath { get; set; } = "~/.todo-app/todo.db";
|
public string UiDbPath { get; set; } = "~/.claudeDo/todo.db";
|
||||||
|
|
||||||
// InstallPage
|
// InstallPage
|
||||||
public bool CreateDesktopShortcut { get; set; } = true;
|
public bool CreateDesktopShortcut { get; set; } = true;
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ public sealed class UninstallRunner
|
|||||||
|
|
||||||
// 6) Delete data dir (config + DB + logs) — only if user opted in.
|
// 6) Delete data dir (config + DB + logs) — only if user opted in.
|
||||||
// Prefer the manifest-recorded DataDir so a customised DbPath is honoured;
|
// Prefer the manifest-recorded DataDir so a customised DbPath is honoured;
|
||||||
// fall back to the default ~/.todo-app for older manifests.
|
// fall back to the default app data root for older manifests.
|
||||||
if (removeAppData)
|
if (removeAppData)
|
||||||
{
|
{
|
||||||
var manifest = InstallManifestStore.TryRead(_context.InstallDirectory);
|
var manifest = InstallManifestStore.TryRead(_context.InstallDirectory);
|
||||||
|
|||||||
@@ -17,11 +17,11 @@ public partial class PathsPageViewModel : ObservableObject, IInstallerPage
|
|||||||
public bool ShowInSettings => true;
|
public bool ShowInSettings => true;
|
||||||
public UserControl View => _view ??= new PathsPageView { DataContext = this };
|
public UserControl View => _view ??= new PathsPageView { DataContext = this };
|
||||||
|
|
||||||
[ObservableProperty] private string _dbPath = "~/.todo-app/todo.db";
|
[ObservableProperty] private string _dbPath = "~/.claudeDo/todo.db";
|
||||||
[ObservableProperty] private string _logRoot = "~/.todo-app/logs";
|
[ObservableProperty] private string _logRoot = "~/.claudeDo/logs";
|
||||||
[ObservableProperty] private string _sandboxRoot = "~/.todo-app/sandbox";
|
[ObservableProperty] private string _sandboxRoot = "~/.claudeDo/sandbox";
|
||||||
[ObservableProperty] private string _worktreeRootStrategy = "sibling";
|
[ObservableProperty] private string _worktreeRootStrategy = "sibling";
|
||||||
[ObservableProperty] private string _centralWorktreeRoot = "~/.todo-app/worktrees";
|
[ObservableProperty] private string _centralWorktreeRoot = "~/.claudeDo/worktrees";
|
||||||
[ObservableProperty] private string? _validationError;
|
[ObservableProperty] private string? _validationError;
|
||||||
|
|
||||||
public bool IsCentralVisible => WorktreeRootStrategy == "central";
|
public bool IsCentralVisible => WorktreeRootStrategy == "central";
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ public partial class UiSettingsPageViewModel : ObservableObject, IInstallerPage
|
|||||||
public UserControl View => _view ??= new UiSettingsPageView { DataContext = this };
|
public UserControl View => _view ??= new UiSettingsPageView { DataContext = this };
|
||||||
|
|
||||||
[ObservableProperty] private string _signalRUrl = "http://127.0.0.1:47821/hub";
|
[ObservableProperty] private string _signalRUrl = "http://127.0.0.1:47821/hub";
|
||||||
[ObservableProperty] private string _uiDbPath = "~/.todo-app/todo.db";
|
[ObservableProperty] private string _uiDbPath = "~/.claudeDo/todo.db";
|
||||||
[ObservableProperty] private bool _isSynced = true;
|
[ObservableProperty] private bool _isSynced = true;
|
||||||
[ObservableProperty] private string? _validationError;
|
[ObservableProperty] private string? _validationError;
|
||||||
|
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ public partial class SettingsViewModel : ObservableObject
|
|||||||
{
|
{
|
||||||
var dataNote = RemoveAppData
|
var dataNote = RemoveAppData
|
||||||
? "This will remove ClaudeDo AND delete all of your tasks, configuration, and database.\n\nContinue?"
|
? "This will remove ClaudeDo AND delete all of your tasks, configuration, and database.\n\nContinue?"
|
||||||
: "This will remove ClaudeDo. Your tasks, configuration, and database in ~/.todo-app will be kept.\n\nContinue?";
|
: "This will remove ClaudeDo. Your tasks, configuration, and database in ~/.claudeDo will be kept.\n\nContinue?";
|
||||||
|
|
||||||
var confirm = MessageBox.Show(
|
var confirm = MessageBox.Show(
|
||||||
dataNote,
|
dataNote,
|
||||||
|
|||||||
@@ -593,7 +593,7 @@
|
|||||||
"subtitle": "Führt die Systemprüfungen erneut aus, gegen deine installierte Konfiguration."
|
"subtitle": "Führt die Systemprüfungen erneut aus, gegen deine installierte Konfiguration."
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"removeUserData": "Benutzerdaten entfernen (Aufgaben, Logs, Konfigurationen in ~/.todo-app)",
|
"removeUserData": "Benutzerdaten entfernen (Aufgaben, Logs, Konfigurationen in ~/.claudeDo)",
|
||||||
"uninstall": "Deinstallieren",
|
"uninstall": "Deinstallieren",
|
||||||
"repair": "Reparieren",
|
"repair": "Reparieren",
|
||||||
"save": "Speichern",
|
"save": "Speichern",
|
||||||
|
|||||||
@@ -593,7 +593,7 @@
|
|||||||
"subtitle": "Re-run the environment checks against your installed configuration."
|
"subtitle": "Re-run the environment checks against your installed configuration."
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"removeUserData": "Remove user data (tasks, logs, configs in ~/.todo-app)",
|
"removeUserData": "Remove user data (tasks, logs, configs in ~/.claudeDo)",
|
||||||
"uninstall": "Uninstall",
|
"uninstall": "Uninstall",
|
||||||
"repair": "Repair",
|
"repair": "Repair",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ namespace ClaudeDo.Ui;
|
|||||||
|
|
||||||
public sealed class AppSettings
|
public sealed class AppSettings
|
||||||
{
|
{
|
||||||
public string DbPath { get; set; } = "~/.todo-app/todo.db";
|
public string DbPath { get; set; } = "~/.claudeDo/todo.db";
|
||||||
public string SignalRUrl { get; set; } = "http://127.0.0.1:47821/hub";
|
public string SignalRUrl { get; set; } = "http://127.0.0.1:47821/hub";
|
||||||
public string Language { get; set; } = "";
|
public string Language { get; set; } = "";
|
||||||
public string AccentPreset { get; set; } = "";
|
public string AccentPreset { get; set; } = "";
|
||||||
@@ -16,7 +16,7 @@ public sealed class AppSettings
|
|||||||
public string DiffViewMode { get; set; } = "unified";
|
public string DiffViewMode { get; set; } = "unified";
|
||||||
public bool DiffWrapLines { get; set; }
|
public bool DiffWrapLines { get; set; }
|
||||||
|
|
||||||
private static readonly string DefaultConfigPath = Paths.Expand("~/.todo-app/ui.config.json");
|
private static readonly string DefaultConfigPath = Paths.Expand("~/.claudeDo/ui.config.json");
|
||||||
|
|
||||||
/// Where this instance persists. Instance-level (not static) so tests can redirect it —
|
/// Where this instance persists. Instance-level (not static) so tests can redirect it —
|
||||||
/// the diff-viewer toggles call Save() on every flip.
|
/// the diff-viewer toggles call Save() on every flip.
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ new editor boilerplate there rather than copying it a third time.
|
|||||||
- **IPrimeScheduleApi** — prime-schedule CRUD.
|
- **IPrimeScheduleApi** — prime-schedule CRUD.
|
||||||
- **UpdateCheckService** — polls releases; `LastCheckStatus`/`LatestVersion`/`CheckNowAsync` feed the shell's update banner.
|
- **UpdateCheckService** — polls releases; `LastCheckStatus`/`LatestVersion`/`CheckNowAsync` feed the shell's update banner.
|
||||||
- **InheritanceResolver** — resolves the task → list → global override chain to `(value, source)` for the inherited badges.
|
- **InheritanceResolver** — resolves the task → list → global override chain to `(value, source)` for the inherited badges.
|
||||||
- **OperationTiming** — NDJSON sink (`~/.todo-app/logs/operation-timing.ndjson`, one rolled `.1` at 4 MB) behind every hub invoke and bulk DB path; each line carries `pid` because app restarts interleave in one file. Off by default — `Shared` only writes when the environment variable `CLAUDEDO_OP_TIMING=1` is set at process start (read once statically, no per-call overhead). Successful calls under 25 ms are dropped, **failed/cancelled ones always land** — a 1 ms cancelled `BindAsync` is churn signal, not noise. `DetailsIsland.BindAsync:<source>` carries the selection trigger (`TasksIslandViewModel.SelectionSource`, set via `SelectFrom` — never assign `SelectedTask` directly). `Shared` is a settable static singleton **on purpose** — `Ui.Tests`/`Worker.Tests` each carry a `TestSetup` module initializer that redirects it to a temp file before any test runs (still writing — the kill switch only gates `Shared`'s default, not the constructor), because both projects construct real ViewModels (`DetailsIslandViewModel`, `TasksIslandViewModel`) that call `Shared.Record` directly; without the redirect a single test run appends 50-100 lines straight into the live app's log (a day of ~1900 `BindAsync` lines, 91-96% cancelled, turned out to be 13 such test runs plus pre-fix-binary artifacts — real user-driven binds that day: 4).
|
- **OperationTiming** — NDJSON sink (`~/.claudeDo/logs/operation-timing.ndjson`, one rolled `.1` at 4 MB) behind every hub invoke and bulk DB path; each line carries `pid` because app restarts interleave in one file. Off by default — `Shared` only writes when the environment variable `CLAUDEDO_OP_TIMING=1` is set at process start (read once statically, no per-call overhead). Successful calls under 25 ms are dropped, **failed/cancelled ones always land** — a 1 ms cancelled `BindAsync` is churn signal, not noise. `DetailsIsland.BindAsync:<source>` carries the selection trigger (`TasksIslandViewModel.SelectionSource`, set via `SelectFrom` — never assign `SelectedTask` directly). `Shared` is a settable static singleton **on purpose** — `Ui.Tests`/`Worker.Tests` each carry a `TestSetup` module initializer that redirects it to a temp file before any test runs (still writing — the kill switch only gates `Shared`'s default, not the constructor), because both projects construct real ViewModels (`DetailsIslandViewModel`, `TasksIslandViewModel`) that call `Shared.Record` directly; without the redirect a single test run appends 50-100 lines straight into the live app's log (a day of ~1900 `BindAsync` lines, 91-96% cancelled, turned out to be 13 such test runs plus pre-fix-binary artifacts — real user-driven binds that day: 4).
|
||||||
- **RepoScanner**, **InstallArtifactLocator**/**InstallerLocator**/**WorkerLocator**, **ForegroundHelper** (Win32 foreground before launching a terminal), **FocusClearing**.
|
- **RepoScanner**, **InstallArtifactLocator**/**InstallerLocator**/**WorkerLocator**, **ForegroundHelper** (Win32 foreground before launching a terminal), **FocusClearing**.
|
||||||
|
|
||||||
## Converters
|
## Converters
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ Full flow, invariants, and model/effort/max-turns resolution (including the low-
|
|||||||
- **WorktreeManager** — worktrees on `claudedo/{taskId[:8]}` branches; commits with semantic messages, updates DB with head commit + diff stats
|
- **WorktreeManager** — worktrees on `claudedo/{taskId[:8]}` branches; commits with semantic messages, updates DB with head commit + diff stats
|
||||||
- **CommitMessageBuilder** — `{commitType}(slug): title\n\ndescription\n\nClaudeDo-Task: taskId`; `BuildMerge` is the merge-commit variant (`{commitType}(slug): merge title` + trailer). **Every merge caller passes a blank commit message on purpose** — `TaskMergeService` fills in `BuildMerge` from the task's commit type and its list's name, which is the only place that knows both. Don't reintroduce a caller-side literal.
|
- **CommitMessageBuilder** — `{commitType}(slug): title\n\ndescription\n\nClaudeDo-Task: taskId`; `BuildMerge` is the merge-commit variant (`{commitType}(slug): merge title` + trailer). **Every merge caller passes a blank commit message on purpose** — `TaskMergeService` fills in `BuildMerge` from the task's commit type and its list's name, which is the only place that knows both. Don't reintroduce a caller-side literal.
|
||||||
- **TaskResetService** — discards a failed task's worktree, resets the row to Idle, preserves run history
|
- **TaskResetService** — discards a failed task's worktree, resets the row to Idle, preserves run history
|
||||||
- **AgentFileService** — manages `~/.todo-app/agents/*.md`; list/refresh via SignalR
|
- **AgentFileService** — manages `~/.claudeDo/agents/*.md`; list/refresh via SignalR
|
||||||
- **LogWriter** — async StreamWriter wrapper, auto-creates parent dirs
|
- **LogWriter** — async StreamWriter wrapper, auto-creates parent dirs
|
||||||
|
|
||||||
Each CLI invocation is recorded in `task_runs` via `TaskRunRepository`. ⚠️ Token fields come from
|
Each CLI invocation is recorded in `task_runs` via `TaskRunRepository`. ⚠️ Token fields come from
|
||||||
@@ -240,14 +240,14 @@ non-obvious, behaviour-changing. A fixed bug is git history, not a finding.
|
|||||||
|
|
||||||
## Config
|
## Config
|
||||||
|
|
||||||
`~/.todo-app/worker.config.json`:
|
`~/.claudeDo/worker.config.json`:
|
||||||
|
|
||||||
- `db_path`, `sandbox_root`, `log_root`
|
- `db_path`, `sandbox_root`, `log_root`
|
||||||
- `worktree_root_strategy` (`sibling` | `central`), `central_worktree_root`
|
- `worktree_root_strategy` (`sibling` | `central`), `central_worktree_root`
|
||||||
- `queue_backstop_interval_ms` (30000) — also the gate/throttle recovery timer
|
- `queue_backstop_interval_ms` (30000) — also the gate/throttle recovery timer
|
||||||
- `signalr_port` (47821), `claude_bin`
|
- `signalr_port` (47821), `claude_bin`
|
||||||
- `usage_poll_interval_seconds` (60, clamped to min 15 on load)
|
- `usage_poll_interval_seconds` (60, clamped to min 15 on load)
|
||||||
- `online_inbox` — `enabled` (false by default; when false the entire `Online/` stack is not registered), `api_base_url` (must be HTTPS or loopback, validated at startup), `poll_interval_seconds` (60), `zitadel.authority`/`client_id`/`scopes`. The refresh token is **not** in this file — DPAPI-encrypted at `~/.todo-app/online-inbox.token`.
|
- `online_inbox` — `enabled` (false by default; when false the entire `Online/` stack is not registered), `api_base_url` (must be HTTPS or loopback, validated at startup), `poll_interval_seconds` (60), `zitadel.authority`/`client_id`/`scopes`. The refresh token is **not** in this file — DPAPI-encrypted at `~/.claudeDo/online-inbox.token`.
|
||||||
|
|
||||||
Per-list config (`list_config` in DB) provides defaults for `model`, `system_prompt`,
|
Per-list config (`list_config` in DB) provides defaults for `model`, `system_prompt`,
|
||||||
`agent_path`, `max_turns`, `session_skills`; tasks override each individually. `verify_command` is
|
`agent_path`, `max_turns`, `session_skills`; tasks override each individually. `verify_command` is
|
||||||
|
|||||||
@@ -9,20 +9,20 @@ namespace ClaudeDo.Worker.Config;
|
|||||||
public sealed class WorkerConfig
|
public sealed class WorkerConfig
|
||||||
{
|
{
|
||||||
[JsonPropertyName("db_path")]
|
[JsonPropertyName("db_path")]
|
||||||
public string DbPath { get; set; } = "~/.todo-app/todo.db";
|
public string DbPath { get; set; } = "~/.claudeDo/todo.db";
|
||||||
|
|
||||||
[JsonPropertyName("sandbox_root")]
|
[JsonPropertyName("sandbox_root")]
|
||||||
public string SandboxRoot { get; set; } = "~/.todo-app/sandbox";
|
public string SandboxRoot { get; set; } = "~/.claudeDo/sandbox";
|
||||||
|
|
||||||
[JsonPropertyName("log_root")]
|
[JsonPropertyName("log_root")]
|
||||||
public string LogRoot { get; set; } = "~/.todo-app/logs";
|
public string LogRoot { get; set; } = "~/.claudeDo/logs";
|
||||||
|
|
||||||
/// <summary>"sibling" → place worktrees next to the target repo; "central" → under <see cref="CentralWorktreeRoot"/>.</summary>
|
/// <summary>"sibling" → place worktrees next to the target repo; "central" → under <see cref="CentralWorktreeRoot"/>.</summary>
|
||||||
[JsonPropertyName("worktree_root_strategy")]
|
[JsonPropertyName("worktree_root_strategy")]
|
||||||
public string WorktreeRootStrategy { get; set; } = "sibling";
|
public string WorktreeRootStrategy { get; set; } = "sibling";
|
||||||
|
|
||||||
[JsonPropertyName("central_worktree_root")]
|
[JsonPropertyName("central_worktree_root")]
|
||||||
public string CentralWorktreeRoot { get; set; } = "~/.todo-app/worktrees";
|
public string CentralWorktreeRoot { get; set; } = "~/.claudeDo/worktrees";
|
||||||
|
|
||||||
[JsonPropertyName("queue_backstop_interval_ms")]
|
[JsonPropertyName("queue_backstop_interval_ms")]
|
||||||
public int QueueBackstopIntervalMs { get; set; } = 30_000;
|
public int QueueBackstopIntervalMs { get; set; } = 30_000;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ namespace ClaudeDo.Worker.Online;
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Persists the Zitadel refresh token encrypted with DPAPI (CurrentUser scope).
|
/// Persists the Zitadel refresh token encrypted with DPAPI (CurrentUser scope).
|
||||||
/// Windows-only; the file lives at ~/.todo-app/online-inbox.token.
|
/// Windows-only; the file lives at ~/.claudeDo/online-inbox.token.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[SupportedOSPlatform("windows")]
|
[SupportedOSPlatform("windows")]
|
||||||
public sealed class OnlineTokenStore
|
public sealed class OnlineTokenStore
|
||||||
|
|||||||
@@ -382,7 +382,7 @@ public sealed class PlanningSessionManager
|
|||||||
await File.WriteAllTextAsync(path, token, ct);
|
await File.WriteAllTextAsync(path, token, ct);
|
||||||
// Best-effort current-user-only ACL on Windows. On non-Windows the inherited
|
// Best-effort current-user-only ACL on Windows. On non-Windows the inherited
|
||||||
// perms from the parent dir apply; acceptable because sessionDir is already
|
// perms from the parent dir apply; acceptable because sessionDir is already
|
||||||
// under the user's home (~/.todo-app/sessions/).
|
// under the user's home (~/.claudeDo/sessions/).
|
||||||
if (OperatingSystem.IsWindows())
|
if (OperatingSystem.IsWindows())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
// wt.exe treats ';' as a tab/command delimiter in EVERY argument, regardless of
|
// wt.exe treats ';' as a tab/command delimiter in EVERY argument, regardless of
|
||||||
// quoting (microsoft/terminal#13264), so nothing containing ';' may appear on the wt
|
// quoting (microsoft/terminal#13264), so nothing containing ';' may appear on the wt
|
||||||
// command line. Every token we place there is a controlled constant or a filesystem
|
// command line. Every token we place there is a controlled constant or a filesystem
|
||||||
// path under ~/.todo-app/sessions/<taskId> — none can contain ';' — and each is
|
// path under ~/.claudeDo/sessions/<taskId> — none can contain ';' — and each is
|
||||||
// single-quoted for PowerShell.
|
// single-quoted for PowerShell.
|
||||||
//
|
//
|
||||||
// The free-text task brief is NEVER passed as an argument. An interactive `claude`
|
// The free-text task brief is NEVER passed as an argument. An interactive `claude`
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ var mutex = new Mutex(true, @"Local\ClaudeDoWorker", out var createdNew);
|
|||||||
if (!createdNew)
|
if (!createdNew)
|
||||||
return; // another instance already owns the port; exit 0
|
return; // another instance already owns the port; exit 0
|
||||||
|
|
||||||
|
Paths.MigrateLegacyAppDataRoot();
|
||||||
|
|
||||||
var cfg = WorkerConfig.Load();
|
var cfg = WorkerConfig.Load();
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
@@ -171,9 +173,7 @@ builder.Services.AddSingleton<QueueService>();
|
|||||||
builder.Services.AddHostedService(sp => sp.GetRequiredService<QueueService>());
|
builder.Services.AddHostedService(sp => sp.GetRequiredService<QueueService>());
|
||||||
|
|
||||||
// Planning session services.
|
// Planning session services.
|
||||||
var planningSessionsDir = Path.Combine(
|
var planningSessionsDir = Path.Combine(Paths.AppDataRoot(), "planning-sessions");
|
||||||
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
|
||||||
".todo-app", "planning-sessions");
|
|
||||||
builder.Services.AddSingleton(sp =>
|
builder.Services.AddSingleton(sp =>
|
||||||
new PlanningSessionManager(
|
new PlanningSessionManager(
|
||||||
sp.GetRequiredService<IDbContextFactory<ClaudeDoDbContext>>(),
|
sp.GetRequiredService<IDbContextFactory<ClaudeDoDbContext>>(),
|
||||||
|
|||||||
@@ -261,7 +261,7 @@ public sealed class InteractiveLaunchSpecService
|
|||||||
|
|
||||||
/// <summary>Builds a LaunchSpec for an embedded ConPTY "merge helper" session that drives the
|
/// <summary>Builds a LaunchSpec for an embedded ConPTY "merge helper" session that drives the
|
||||||
/// given tasks to a merged/Done state via the mcp__claudedo__* tools. Writes a per-session
|
/// given tasks to a merged/Done state via the mcp__claudedo__* tools. Writes a per-session
|
||||||
/// system prompt + task brief under ~/.todo-app/merge-helper-sessions/<guid> and exposes
|
/// system prompt + task brief under ~/.claudeDo/merge-helper-sessions/<guid> and exposes
|
||||||
/// that dir plus the list's repo dir via --add-dir. cwd is the list's working directory.
|
/// that dir plus the list's repo dir via --add-dir. cwd is the list's working directory.
|
||||||
/// handlerTaskId (the id returned by CreateMergeHelperTaskAsync) is rendered into the brief so
|
/// handlerTaskId (the id returned by CreateMergeHelperTaskAsync) is rendered into the brief so
|
||||||
/// the session can call handoff_list_handler/submit_task_for_review on its own handler task.
|
/// the session can call handoff_list_handler/submit_task_for_review on its own handler task.
|
||||||
|
|||||||
Reference in New Issue
Block a user