1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
//! CLI-side managed-workspace provisioning that honours the #3455 worktree
//! opt-out (#4300).
//!
//! Why: two `tm` entry points create base clones and per-session worktrees
//! entirely IN THE CLI PROCESS — `tm launch` (which never asks the daemon)
//! and the daemon-unreachable guided fallback (which runs precisely because
//! the daemon is down). Both predate #3455's per-project `worktree: false`
//! opt-out and neither consulted it, so the setting was silently conditional
//! on the daemon being up: a project registered with `worktree: false` still
//! got a clone and a worktree from either path. Concentrating the decision
//! and the provisioning here means the opt-out is read once per launch, in
//! the SAME order the daemon uses (`managed_routes::lifecycle`, ahead of
//! `try_inproject_spawn`) — BEFORE `ensure_base_clone`, so an opted-out
//! project creates neither a worktree nor a base clone.
//! What: [`ManagedWorkspace`] names the two outcomes; [`provision`] is the
//! shared dispatcher that acts on an already-resolved decision;
//! [`provision_for_launch`] serves `tm launch` (which has already resolved
//! the base-clone path) and [`provision_for_fallback`] serves the guided
//! fallback (which parses `owner/repo` itself and reports daemon-specific
//! remediation). Each entry point resolves the decision EXACTLY once and passes
//! the answer down — so the operator-facing notice and the filesystem action can
//! never disagree.
//!
//! The two entry points answer DIFFERENT questions, and #5274 separated them.
//! [`provision_for_launch`] decides SESSION PLACEMENT, which since #5274 is the
//! main checkout unless the operator passed `tm launch --worktree`; it takes
//! that request as a parameter and consults no registry at all. Only
//! [`provision_for_fallback`] still reads
//! [`trusty_mpm::project::worktree_enabled_for_origin_at`], because it is
//! answering #1724's question instead — may bare `tm`, with the daemon down,
//! deploy `CLAUDE.md` / `.mcp.json` / `.claude/` into whatever checkout the
//! operator happens to be standing in? — where the registry's `worktree: false`
//! is the project TELLING it yes. The two paths still compose: the fallback ends
//! by calling `launch()` with its already-provisioned worktree as the cwd, so
//! `provision_for_launch` names that protected directory rather than the live
//! checkout.
//! Scope note: this deliberately does NOT change #3455's concurrency
//! behaviour. `spawn_managed_on_main` WARNS (never refuses) on a second
//! session against one main checkout, and that stays the rule here — nothing
//! in this module refuses a launch.
//! Test: `managed_workspace_tests.rs`.
use ;
use inproject;
use ManagedSessionId;
/// Where a CLI-provisioned managed session will actually run.
///
/// Why: the two outcomes have materially different lifecycles — a worktree is
/// framework-owned and disposable, the operator's main checkout is neither —
/// so callers are forced to distinguish them rather than receiving a bare
/// `PathBuf` that hides which one they got.
/// What: `Worktree` carries `<base>/.worktrees/<session-id>`; `MainCheckout`
/// carries the checkout root the session will run in — since #5274 the default
/// outcome for `tm launch`, and still the #4300 outcome for a project that
/// registered `worktree: false` on the daemon-unreachable fallback.
/// Test: `managed_workspace_tests.rs`.
pub
/// Act on an already-resolved isolation decision.
///
/// Why: the opt-out check MUST run before [`inproject::ensure_base_clone`].
/// Checking afterwards would still leave a full base clone on disk for a
/// project that asked for no isolation at all — the wasted-disk complaint
/// #3455 was filed about; the daemon's `spawn_managed_routed` documents the
/// same ordering for the same reason. Taking `isolate` as a parameter (rather
/// than re-reading the registry here) guarantees the notice each caller
/// printed describes the action actually taken.
/// What: `isolate == false` returns [`ManagedWorkspace::MainCheckout`] having
/// touched nothing on disk; otherwise ensures the base clone and adds a
/// UUID-named per-session worktree. Errors are the raw `inproject` strings so
/// each caller can wrap them with its own remediation text.
/// Test: `managed_workspace_tests.rs`.
async
/// Provision the workspace for `tm launch` (#4300 call site 1, #5274).
///
/// Why: `tm launch` runs the whole provisioning flow in-process and never asks
/// the daemon, so it decides placement itself and must reach the SAME answer
/// the daemon's `spawn_managed_routed` would. Since #5274 that answer is the
/// project's main checkout unless the operator asked otherwise, so this takes
/// the request as a parameter (`tm launch --worktree`) rather than reading the
/// registry: the project's `worktree` flag governs AGENT isolation, and letting
/// it decide session placement here would put the CLI and the daemon back on
/// two different rules.
/// What: resolves the REPO ROOT of `cwd`, prints the notice for whichever branch
/// applies, then delegates to [`provision`] with the caller-resolved `base_path`
/// (`inproject::base_clone_path(owner, repo)`). Errors are mapped to `tm
/// launch`'s wording.
///
/// The root resolution matters and is not incidental: `get_origin_url` succeeds
/// at ANY depth, so `cd repo/src && tm launch` would otherwise deploy `.claude`,
/// the project hooks and the tmux cwd into `repo/src` rather than `repo` —
/// landing tm's furniture exactly where the operator did not ask for it.
/// [`super::guided::find_git_root`] is the SAME helper the daemon-unreachable
/// fallback's classifier uses, so both CLI entry points cannot disagree about
/// which directory the project is. A `cwd` that is not inside a working tree (or
/// a machine with no `git` on PATH) falls back to `cwd` itself — the pre-#4300
/// behaviour, never worse.
///
/// #1724 survives the change unchanged, and by construction. The guided
/// daemon-unreachable fallback composes onto this function: it provisions a
/// protected worktree itself via [`provision_for_fallback`] and then calls
/// `launch()` WITH that worktree as `cwd`, so `find_git_root` resolves to the
/// worktree and `MainCheckout` here names the already-protected directory, never
/// the operator's live checkout. The three `tests_behavior_b` cases cover it.
/// Test: `provision_for_launch_without_a_request_uses_the_main_checkout`,
/// `provision_for_launch_from_subdirectory_targets_repo_root`,
/// `provision_for_launch_explicit_request_creates_worktree`,
/// `provision_for_launch_ignores_a_registered_worktree_true_project`.
pub async
/// Provision the workspace for the daemon-unreachable guided fallback
/// (#4300 call site 2).
///
/// Why: bare `tm` with the daemon down must still never write framework files
/// into a live checkout it was not told it could (#1724) — but a project
/// registered with `worktree: false` HAS told us exactly that, and the daemon
/// would have honoured it via `spawn_managed_on_main`. Consulting the same
/// registry here is what makes the setting independent of daemon liveness.
/// What: parses `owner/repo` from `origin_url`, reads the opt-out, and
/// delegates to [`provision`] with `git_root` (the repo ROOT, not the
/// possibly-nested cwd) as the opt-out target; errors carry the fallback's
/// "start the daemon" remediation.
/// Test: `provision_for_fallback_opted_out_creates_no_clone_and_no_worktree`,
/// `provision_for_fallback_unset_creates_worktree_not_live_checkout`,
/// `provision_for_fallback_other_projects_optout_does_not_leak`.
pub async