Manage worktree roots in global config via the tm CLI#83
Merged
Conversation
Add addRoot/removeRoot/getRoots members to IWorktreeApi (src/Shared/Types.fs). Stub both record literals in src/Server/WorktreeApi.fs: readOnlyApi (permanent demo/fixture no-op stubs, roots stay []) and worktreeApi (temporary stubs pending tm-config-audit-9ow). Also commit the feature spec docs/spec/worktree-roots-config.md.
Add globalConfigLock serializing updateGlobalConfig (single serialized writer of config.json). Add internal readWorktreeRootsConfig/writeWorktreeRoots for the worktreeRoots key via withConfigDocument/updateGlobalConfig, and globalConfigDir() honoring TREEMON_CONFIG_DIR for in-process test isolation. Add WorktreeRootsConfigTests (round-trip, no-clobber, overwrite, empty-clear, absent-file) registered in Tests.fsproj.
…restart-to-apply) Wire worktreeApi endpoints to internal addRootToConfig/removeRootFromConfig helpers (normalize+validate path, read-modify-write via writeWorktreeRoots); getRoots returns readWorktreeRootsConfig(). updateGlobalConfig now returns Result<unit,string> so persistence failures surface to the CLI; best-effort UI-state writers |> ignore it. Tests +11. Decision recorded in spec.
parseArgs accepts zero positional roots in normal mode (dropped roots<>[] guard + unreachable usage arm). Added resolveWorktreeRoots (CLI args > global worktreeRoots > orphan roots.json; persists when config has none; orphan deleted only after successful persist). main computes effectiveRoots (demo/fixture bypass -> []) and passes them to RefreshScheduler.start + WorktreeApi.worktreeApi. Tests: ServerStartupResolutionTests.fs (10 tests); SmokeTests isolate config dir via TREEMON_CONFIG_DIR.
Add addCmd/removeCmd/rootsCmd in src/Cli/Program.fs and register them in main. add/remove accept one-or-more paths and loop the server endpoint inside a single tryCallServer (single server-down message per batch); per-path errors via eprintfn, exit 1 if any path fails. roots lists configured roots (sanitized) or a no-roots message. Path validation/normalization delegated to server endpoints.
…emon.config Delete Save-Config/Get-SavedConfig/Resolve-WorktreeRoots/Add-Roots/Remove-Roots and the singular WorktreeRoot fallback. Add Read-LegacyRoots (one-time .treemon.config migration reader) and Invoke-Tm (tm CLI wrapper). start/dev/restart are path-optional; status lists roots via 'tm roots'; add/remove are thin shims to tm that restart prod only on CLI success (exit 0). .treemon.config migrated then deleted only after a confirmed server start. Spec Decisions updated with the 3 edge cases.
Document global worktreeRoots config key (~/.treemon/config.json), tm add/remove/roots CLI, treemon.ps1 add/remove shims, and path-optional start/dev across worktree-monitor.md, AGENTS.md, and README.md.
… resurrects removed roots on restart Added WorktreeApi.tryReadWorktreeRootsConfig (None=key absent, Some []=explicit empty, Some roots=populated; malformed non-array -> None). readWorktreeRootsConfig is now a thin Option.defaultValue [] wrapper. resolveWorktreeRoots gates BOTH orphan-import fallthrough and first-time persist on key ABSENCE (Option.isSome), never List.isEmpty. 2 regression tests in ServerStartupResolutionTests.
…roduction server restart Add tri-state exit code (Cli.foldRootResults: 0=all ok, 1=all failed, 2=partial) and gate the treemon.ps1 add/remove restart on 0 OR 2, so a [valid; invalid] batch (valid persisted server-side, invalid rejected) still restarts prod to apply the persisted root. exit \ kept so failures still report non-zero. De-duplicates the per-path fold; adds FoldRootResultsTests (7 cases); documents the tri-state contract in spec Decision section 7 item (3).
…dRoot/removeRoot
readOnlyApi addRoot/removeRoot now return Error "Root management is not available in {modeName}"
instead of a silent Ok(); getRoots still returns []. Matches the existing read-only mutation
pattern so the CLI surfaces a real failure instead of a false success line. Spec Decisions updated.
…ning and Test-WorktreeRootPaths helpers Extract Test-WorktreeRootPaths (path validation for start/dev) and Restart-ServerIfRunning (restart-if-running lifecycle for add/remove and Deploy-Frontend via optional Message/NotRunningMessage params). Removes 5 duplicated blocks (2 validation + 3 restart incl. Deploy-Frontend triplication). Resolves focused-review findings C-04..C-07. Parser verified clean; behavior-preserving.
…to shared TestUtils.fs Move withTempConfigDir into TestUtils.fs (prefix-parameterized like withTempFile) and de-dupe assertOk onto TestUtils.assertOk. Removes both private copies + dead 'open System'. Resolves focused-review C-15/C-16. Build clean (0 warn/0 err); 26/26 affected tests pass.
…orthand and Option.ofObj boundary C-14: WorktreeRootsConfigTests.fs collapsedRepos read uses Seq.map _.GetString() (F# 9 shorthand). C-19: globalConfigDir() converts TREEMON_CONFIG_DIR at the boundary via Option.ofObj |> Option.filter |> Option.defaultWith, preserving the empty/null -> ~/.treemon fallback.
There was a problem hiding this comment.
Pull request overview
This PR moves the set of watched worktree roots out of PowerShell's deploy-local .treemon.config and into the machine-level ~/.treemon/config.json (worktreeRoots key), making the server the single, serialized writer. Roots are now managed live through the tm CLI (add/remove/roots), while treemon.ps1 becomes thin lifecycle shims. This eliminates the cross-process clobber hazard (PowerShell whole-file overwrites wiping server-written keys), the stale orphan roots.json, and the requirement to pass a path to start/dev.
Changes:
- Adds
addRoot/removeRoot/getRootstoIWorktreeApi, implemented server-side with aglobalConfigLock, presence-aware root reads (missing-key vs. explicit-empty), andResult-returning persistence so failures surface to the CLI. - Adds startup root resolution (CLI args > global config > one-time orphan
roots.jsonimport) that persists only when theworktreeRootskey is absent, plustm add/remove/rootssubcommands with a tri-state exit code. - Rewrites
treemon.ps1to delegate root management totm, migrate-then-delete the legacy.treemon.configafter a confirmed start, and restart prod on success; updates docs and adds extensive unit tests.
Show a summary per file
| File | Description |
|---|---|
src/Shared/Types.fs |
Adds addRoot/removeRoot/getRoots to IWorktreeApi. |
src/Server/WorktreeApi.fs |
Roots read/write helpers, globalConfigLock, Result-returning updateGlobalConfig, TREEMON_CONFIG_DIR override, endpoint impls. |
src/Server/Program.fs |
parseArgs allows zero roots; resolveWorktreeRoots priority resolution + orphan migration. |
src/Cli/Program.fs |
add/remove/roots subcommands and tri-state foldRootResults. |
treemon.ps1 |
Thin tm shims, legacy migration, extracted Restart-ServerIfRunning/Test-WorktreeRootPaths. |
src/Tests/WorktreeRootsConfigTests.fs |
New roundtrip/no-clobber/persistence-failure tests (uses obsolete .[i] indexing). |
src/Tests/ServerStartupResolutionTests.fs |
Startup resolution priority / empty-vs-absent / orphan migration tests. |
src/Tests/CliTests.fs |
FoldRootResultsTests for the tri-state exit code. |
src/Tests/TestUtils.fs |
Adds withTempConfigDir and assertOk helpers. |
src/Tests/SmokeTests.fs |
Isolates the smoke server's config dir via TREEMON_CONFIG_DIR. |
src/Tests/Tests.fsproj |
Registers the two new test files. |
README.md, AGENTS.md, docs/spec/*.md |
Documents the new global-config + CLI-managed roots flow. |
Copilot's findings
- Files reviewed: 15/15 changed files
- Comments generated: 2
Address Copilot review on PR #83: replace obsolete dot-bracket indexing (roots.[0]) with modern roots[0] per the project F# style guide.
# Conflicts: # src/Server/WorktreeApi.fs
…on, spec consolidation - resolveWorktreeRoots is now a pure resolver returning RootsResolution; the global-config write and orphan delete moved to persistResolvedRoots, applied at the startup boundary in main. Resolver tests assert the decision and apply the effect explicitly. - treemon.ps1 migration reads the legacy singular WorktreeRoot key (not just plural WorktreeRoots) and deletes .treemon.config only once every declared root was migrated; parse failures and unmigrated content are preserved with a warning instead of being silently dropped. - Fold the durable roots-config contract and decisions into worktree-monitor.md and delete the branch-specific worktree-roots-config.md plan/changelog; fix the AGENTS.md and Related Specs back-references.
Capture the confirmed (Low) focused-review finding that the unauthenticated loopback Fable.Remoting surface accepts cross-origin simple-request CSRF writes (Fable.Remoting.Server does not enforce application/json, so a text/plain POST bypasses the CORS preflight). Recommends a pipeline-level Origin/Referer check that also covers the pre-existing process-launching endpoints; adds it to the improvements backlog.
…erns Replace the pinned claude-opus-4.6 concern model with inherit so those concerns run on whatever model drives the review.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Treemon's watched worktree roots were managed by PowerShell in a deploy-local
.treemon.configand passed to the server as CLI args. This had several problems:~/.treemon/roots.jsonexisted on disk but was read by no code.config.json-style files with whole-file overwrites, risking clobbering server-written keys (collapsedRepos, canvas state,lastViewedHashes)..treemon.config,.treemon.json, and~/.treemon/config.json.testCommand, so the dashboard Test step showed "not configured".Changes
Worktree roots now live in the global
~/.treemon/config.jsonunder aworktreeRootskey, managed through thetmCLI, with the server as the single, locked writer.Types.fs):IWorktreeApigainsaddRoot/removeRoot/getRoots.WorktreeApi.fs): global-config roots read/write plus a write lock serializing all global-config writes (fixes the cross-process clobber); endpoint implementations;updateGlobalConfignow returnsResultso a failed persist surfaces instead of a falseOk;TREEMON_CONFIG_DIRoverride for test isolation.Program.fs): startup resolves roots by priority CLI args > global config > one-time import of the orphanroots.json, persisting when the key is absent (distinct from explicitly empty); demo/fixture modes unchanged.Cli/Program.fs):tm add/tm remove/tm rootssubcommands; add/remove print an "applies on next server restart" hint and use a tri-state exit code (all-ok / all-failed / partial).treemon.ps1): removedSave-Config,Get-SavedConfig,Resolve-WorktreeRoots,Add-Roots,Remove-Roots, and the legacy singularWorktreeRootfallback;add/removeare thin shims totmthat restart prod only on success;start/devno longer require a path; one-time.treemon.configmigrate-then-delete; extractedRestart-ServerIfRunning/Test-WorktreeRootPathshelpers.docs/spec/worktree-roots-config.md;AGENTS.md,README.md, anddocs/spec/worktree-monitor.mdupdated.Restart-to-apply:
tm add/removepersist immediately (single writer); the change takes effect on the next server (re)start, which thetreemon.ps1shims trigger automatically when the server is running.Tests
WorktreeRootsConfigTests(roots round-trip, no-clobber, persistence-failure),ServerStartupResolutionTests(resolution priority, empty-vs-absent, orphan migration),CliTestsFoldRootResultsTests(tri-state exit codes); sharedwithTempConfigDirhoisted intoTestUtils..agents/verify/tm-config-audit-6o5.md): 8 falsifiable steps driven through the realtmCLI + Fable.Remoting API on an isolated server, with config-dir isolation independently proven via SHA-256 — PASS.