Skip to content

Manage worktree roots in global config via the tm CLI#83

Merged
0101 merged 19 commits into
mainfrom
config-audit
Jun 23, 2026
Merged

Manage worktree roots in global config via the tm CLI#83
0101 merged 19 commits into
mainfrom
config-audit

Conversation

@0101

@0101 0101 commented Jun 22, 2026

Copy link
Copy Markdown
Owner

Problem

Treemon's watched worktree roots were managed by PowerShell in a deploy-local .treemon.config and passed to the server as CLI args. This had several problems:

  • A stale orphan ~/.treemon/roots.json existed on disk but was read by no code.
  • PowerShell wrote config.json-style files with whole-file overwrites, risking clobbering server-written keys (collapsedRepos, canvas state, lastViewedHashes).
  • Confusing naming across .treemon.config, .treemon.json, and ~/.treemon/config.json.
  • Adding/removing a root went through PowerShell and required a stop+start.
  • Treemon's own worktrees had no testCommand, so the dashboard Test step showed "not configured".

Changes

Worktree roots now live in the global ~/.treemon/config.json under a worktreeRoots key, managed through the tm CLI, with the server as the single, locked writer.

  • Shared (Types.fs): IWorktreeApi gains addRoot / removeRoot / getRoots.
  • Server (WorktreeApi.fs): global-config roots read/write plus a write lock serializing all global-config writes (fixes the cross-process clobber); endpoint implementations; updateGlobalConfig now returns Result so a failed persist surfaces instead of a false Ok; TREEMON_CONFIG_DIR override for test isolation.
  • Server (Program.fs): startup resolves roots by priority CLI args > global config > one-time import of the orphan roots.json, persisting when the key is absent (distinct from explicitly empty); demo/fixture modes unchanged.
  • CLI (Cli/Program.fs): tm add / tm remove / tm roots subcommands; add/remove print an "applies on next server restart" hint and use a tri-state exit code (all-ok / all-failed / partial).
  • PowerShell (treemon.ps1): removed Save-Config, Get-SavedConfig, Resolve-WorktreeRoots, Add-Roots, Remove-Roots, and the legacy singular WorktreeRoot fallback; add / remove are thin shims to tm that restart prod only on success; start / dev no longer require a path; one-time .treemon.config migrate-then-delete; extracted Restart-ServerIfRunning / Test-WorktreeRootPaths helpers.
  • Docs: new spec docs/spec/worktree-roots-config.md; AGENTS.md, README.md, and docs/spec/worktree-monitor.md updated.

Restart-to-apply: tm add/remove persist immediately (single writer); the change takes effect on the next server (re)start, which the treemon.ps1 shims trigger automatically when the server is running.

Tests

  • Build clean (0 warnings / 0 errors); full unit suite green.
  • New unit coverage: WorktreeRootsConfigTests (roots round-trip, no-clobber, persistence-failure), ServerStartupResolutionTests (resolution priority, empty-vs-absent, orphan migration), CliTests FoldRootResultsTests (tri-state exit codes); shared withTempConfigDir hoisted into TestUtils.
  • A focused-review gate over the branch diff produced 6 fix tasks (2 real bugs: empty-vs-absent roots resurrection on restart; partial-batch restart suppression) — all fixed with regression tests.
  • E2E verification (.agents/verify/tm-config-audit-6o5.md): 8 falsifiable steps driven through the real tm CLI + Fable.Remoting API on an isolated server, with config-dir isolation independently proven via SHA-256 — PASS.

0101 added 13 commits June 19, 2026 17:37
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.
Copilot AI review requested due to automatic review settings June 22, 2026 14:27

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/getRoots to IWorktreeApi, implemented server-side with a globalConfigLock, presence-aware root reads (missing-key vs. explicit-empty), and Result-returning persistence so failures surface to the CLI.
  • Adds startup root resolution (CLI args > global config > one-time orphan roots.json import) that persists only when the worktreeRoots key is absent, plus tm add/remove/roots subcommands with a tri-state exit code.
  • Rewrites treemon.ps1 to delegate root management to tm, migrate-then-delete the legacy .treemon.config after 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

Comment thread src/Tests/WorktreeRootsConfigTests.fs Outdated
Comment thread src/Tests/WorktreeRootsConfigTests.fs Outdated
0101 added 6 commits June 22, 2026 16:42
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.
@0101 0101 merged commit a7b8529 into main Jun 23, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants