Commit 36b88ce
authored
🤖 feat: copy plan file when forking workspace (#1200)
Copy plan file when forking workspace so the forked workspace can
continue working with the same plan context.
**Changes:**
- Add `copyPlanFile` helper in `src/node/utils/runtime/helpers.ts`
(mirrors existing `movePlanFile` pattern)
- Call `copyPlanFile` in `workspaceService.fork()` after session files
are copied
---
<details>
<summary>📋 Implementation Plan</summary>
# Plan: Copy Plan File on Workspace Fork
## Summary
Add functionality to copy the source workspace's plan file when forking,
allowing the forked workspace to continue working with the same plan
context.
## Problem
When a workspace is forked via `/fork`, the following files are copied:
- `chat.jsonl` (chat history)
- `partial.json` (partial streaming state)
- `session-usage.json` (usage tracking)
However, the **plan file** (`~/.mux/plans/{project}/{workspace}.md`) is
**not** copied. This means the forked workspace starts without the plan
context, even though it inherits the chat history where the plan was
discussed.
## Solution
Add a `copyPlanFile` helper function and invoke it during the fork
operation in `workspaceService.fork()`.
## Implementation
### 1. Add `copyPlanFile` helper in `src/node/utils/runtime/helpers.ts`
Create a new function following the same pattern as `movePlanFile`:
```typescript
/**
* Copy a plan file from one workspace to another (e.g., during fork).
* Silently succeeds if source file doesn't exist.
*/
export async function copyPlanFile(
runtime: Runtime,
sourceWorkspaceName: string,
targetWorkspaceName: string,
projectName: string
): Promise<void> {
const sourcePath = getPlanFilePath(sourceWorkspaceName, projectName);
const targetPath = getPlanFilePath(targetWorkspaceName, projectName);
try {
await runtime.stat(sourcePath);
await execBuffered(runtime, `cp "${sourcePath}" "${targetPath}"`, { cwd: "/tmp", timeout: 5 });
} catch {
// No plan file to copy, that's fine
}
}
```
### 2. Update `src/node/services/workspaceService.ts`
1. Add import for `copyPlanFile`:
```typescript
import { movePlanFile, copyPlanFile } from
"@/node/utils/runtime/helpers";
```
2. Call `copyPlanFile` in the `fork()` method after successfully copying
session files (~line 924):
```typescript
// Copy plan file if it exists (uses workspace name, not ID)
await copyPlanFile(runtime, sourceMetadata.name, newName, projectName);
```
## File Changes
| File | Change |
|------|--------|
| `src/node/utils/runtime/helpers.ts` | Add `copyPlanFile` function (~15
LoC) |
| `src/node/services/workspaceService.ts` | Import + call `copyPlanFile`
(~3 LoC) |
**Net LoC estimate:** ~+18 lines
## Testing
The existing test patterns for `movePlanFile` in rename operations
provide a template. The change is straightforward:
- `copyPlanFile` follows the same error-handling pattern as
`movePlanFile`
- Silently succeeds if no plan file exists (idempotent)
- Uses runtime abstraction, so works for both local and SSH runtimes
Manual verification:
1. Create a workspace with a plan
2. Fork it with `/fork new-name`
3. Verify the new workspace has a copy of the plan file at
`~/.mux/plans/{project}/new-name.md`
## Edge Cases
- **No plan file exists**: Silent success (matches `movePlanFile`
behavior)
- **Target file already exists**: `cp` will overwrite, which is correct
for forks
- **SSH runtime**: Works via runtime abstraction (same as existing
`movePlanFile`)
</details>
---
_Generated with `mux` • Model: `anthropic:claude-opus-4-5` • Thinking:
`high`_
Signed-off-by: Thomas Kosiewski <[email protected]>1 parent cc478e5 commit 36b88ce
File tree
2 files changed
+44
-2
lines changed- src/node
- services
- utils/runtime
2 files changed
+44
-2
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
56 | 56 | | |
57 | 57 | | |
58 | 58 | | |
59 | | - | |
| 59 | + | |
60 | 60 | | |
61 | 61 | | |
62 | 62 | | |
| |||
960 | 960 | | |
961 | 961 | | |
962 | 962 | | |
| 963 | + | |
| 964 | + | |
| 965 | + | |
963 | 966 | | |
964 | 967 | | |
965 | 968 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
190 | 190 | | |
191 | 191 | | |
192 | 192 | | |
193 | | - | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
194 | 200 | | |
195 | 201 | | |
196 | 202 | | |
197 | 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 | + | |
0 commit comments