Skip to content

Commit bcf6c4e

Browse files
committed
## CHANGES
- Added brand-new cross-platform guide covering Windows, Linux, macOS, SSH gotchas 🌍 - Documented SSH remote constraints: no chokidar watch, stdin prompts, path norms 🛰️ - Standardized path best-practices: `path.join`, `path.posix`, delimiters, tilde expansion 🧭 - Clarified shell execution differences: default shells, which/where lookup, permissions 🐚 - Codified agent portability quirks: session IDs, storage locations, resume flags 📦 - Introduced keyboard/input pitfalls: macOS Alt keycodes, Windows command-length limits ⌨️ - Added Git cross-platform warnings: stat differences and case-sensitivity traps 🧩 - Upgraded collaboration rules: assumptions, confusion stops, pushback, scope discipline 🤝 - Added Sentry/error-handling guidance: bubble unexpected errors, report with context 🔎 - Refreshed docs structure references to match current `src/` layout and modules 🗺️
1 parent d5ad08e commit bcf6c4e

File tree

3 files changed

+307
-33
lines changed

3 files changed

+307
-33
lines changed

CLAUDE-PLATFORM.md

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
# CLAUDE-PLATFORM.md
2+
3+
Cross-platform and multi-environment considerations for the Maestro codebase. For the main guide, see [[CLAUDE.md]].
4+
5+
---
6+
7+
## Platform Compatibility Matrix
8+
9+
| Feature | macOS | Windows | Linux | SSH Remote |
10+
|---------|-------|---------|-------|------------|
11+
| Claude Code | Full | Full | Full | Full |
12+
| OpenAI Codex | Full | Full | Full | Full |
13+
| OpenCode | Full | Full | Full | Full |
14+
| Factory Droid | Full | Full | Full | Full |
15+
| File watching (chokidar) | Yes | Yes | Yes | **No** |
16+
| Git worktrees | Yes | Yes | Yes | Yes |
17+
| PTY terminal | Yes | Yes | Yes | N/A |
18+
19+
---
20+
21+
## Critical Platform Gotchas
22+
23+
### 1. Path Handling
24+
25+
**Path separators differ:**
26+
```typescript
27+
// WRONG - hardcoded separator
28+
const fullPath = folder + '/' + filename;
29+
30+
// CORRECT - use path.join or path.posix for SSH
31+
import * as path from 'path';
32+
const fullPath = path.join(folder, filename); // Local
33+
const remotePath = path.posix.join(folder, filename); // SSH remote
34+
```
35+
36+
**Path delimiters differ:**
37+
```typescript
38+
// Windows uses ';', Unix uses ':'
39+
const delimiter = path.delimiter; // Use this, don't hardcode
40+
```
41+
42+
**Tilde expansion:**
43+
```typescript
44+
// Node.js fs does NOT expand ~ automatically
45+
import { expandTilde } from '../shared/pathUtils';
46+
const expanded = expandTilde('~/.config/file'); // Always use this
47+
```
48+
49+
**Minimum path lengths:**
50+
```typescript
51+
// Validation must account for platform differences
52+
const minPathLength = process.platform === 'win32' ? 4 : 5; // C:\a vs /a/b
53+
```
54+
55+
**Windows reserved names:**
56+
```typescript
57+
// CON, PRN, AUX, NUL, COM1-9, LPT1-9 are invalid on Windows
58+
const reservedNames = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i;
59+
```
60+
61+
### 2. Shell Detection & Execution
62+
63+
**Default shells differ:**
64+
```typescript
65+
// Windows: $SHELL doesn't exist; default to PowerShell
66+
const defaultShell = process.platform === 'win32' ? 'powershell.exe' : 'bash';
67+
```
68+
69+
**Command lookup differs:**
70+
```typescript
71+
// 'which' on Unix, 'where' on Windows
72+
const command = process.platform === 'win32' ? 'where' : 'which';
73+
```
74+
75+
**Executable permissions:**
76+
```typescript
77+
// Unix requires X_OK check; Windows does not
78+
if (process.platform !== 'win32') {
79+
await fs.promises.access(filePath, fs.constants.X_OK);
80+
}
81+
```
82+
83+
**Windows shell execution:**
84+
```typescript
85+
// Some Windows commands need shell: true
86+
const useShell = isWindows && needsWindowsShell(command);
87+
```
88+
89+
### 3. SSH Remote Execution
90+
91+
**Two SSH identifiers with different lifecycles:**
92+
```typescript
93+
// sshRemoteId: Set AFTER AI agent spawns (via onSshRemote callback)
94+
// sessionSshRemoteConfig.remoteId: Set BEFORE spawn (user configuration)
95+
96+
// WRONG - fails for terminal-only SSH sessions
97+
const sshId = session.sshRemoteId;
98+
99+
// CORRECT - works for all SSH sessions
100+
const sshId = session.sshRemoteId || session.sessionSshRemoteConfig?.remoteId;
101+
```
102+
103+
**File watching not available for SSH:**
104+
```typescript
105+
// chokidar can't watch remote directories
106+
if (sshRemoteId) {
107+
// Use polling instead of file watching
108+
}
109+
```
110+
111+
**Prompts must go via stdin for SSH:**
112+
```typescript
113+
// IMPORTANT: ALL agent prompts are passed via stdin passthrough for SSH.
114+
// This avoids shell escaping issues and command line length limits.
115+
if (isSSH) {
116+
// Pass prompt via stdin, not as command line argument
117+
}
118+
```
119+
120+
**Path resolution on remote:**
121+
```typescript
122+
// Don't resolve paths locally when operating on remote
123+
// The remote may have different filesystem structure
124+
if (isRemote) {
125+
// Use path as-is, normalize slashes only
126+
}
127+
```
128+
129+
### 4. Agent-Specific Differences
130+
131+
**Session ID terminology:**
132+
```typescript
133+
// Claude Code: session_id
134+
// Codex: thread_id
135+
// Different field names, same concept
136+
```
137+
138+
**Storage locations differ per platform:**
139+
```typescript
140+
// Claude Code: ~/.claude/projects/<encoded-path>/
141+
// Codex: ~/.codex/sessions/YYYY/MM/DD/*.jsonl
142+
// OpenCode:
143+
// - macOS/Linux: ~/.config/opencode/storage/
144+
// - Windows: %APPDATA%/opencode/storage/
145+
```
146+
147+
**Resume flags differ:**
148+
```typescript
149+
// Claude Code: --resume <session-id>
150+
// Codex: resume <thread_id> (subcommand, not flag)
151+
// OpenCode: --session <session-id>
152+
```
153+
154+
**Read-only mode flags differ:**
155+
```typescript
156+
// Claude Code: --permission-mode plan
157+
// Codex: --sandbox read-only
158+
// OpenCode: --agent plan
159+
```
160+
161+
### 5. Keyboard & Input
162+
163+
**macOS Alt key produces special characters:**
164+
```typescript
165+
// Alt+L = '¬', Alt+P = 'π', Alt+U = 'ü'
166+
// Must use e.code for Alt key combos, not e.key
167+
if (e.altKey && process.platform === 'darwin') {
168+
const key = e.code.replace('Key', '').toLowerCase(); // 'KeyL' -> 'l'
169+
}
170+
```
171+
172+
**Windows command line length limit:**
173+
```typescript
174+
// cmd.exe has ~8KB command line limit
175+
// Use sendPromptViaStdin to bypass this for long prompts
176+
```
177+
178+
### 6. Git Operations
179+
180+
**Git stat format differs:**
181+
```typescript
182+
// GNU stat vs BSD stat have different format specifiers
183+
// Use git commands that work cross-platform
184+
```
185+
186+
**Case sensitivity:**
187+
```typescript
188+
// macOS with case-insensitive filesystem:
189+
// Renaming "readme.md" to "README.md" may not trigger expected events
190+
```
191+
192+
---
193+
194+
## Testing Checklist
195+
196+
When making changes that involve any of the above areas, verify:
197+
198+
- [ ] Works on macOS (primary development platform)
199+
- [ ] Works on Windows (PowerShell default, path separators)
200+
- [ ] Works on Linux (standard Unix behavior)
201+
- [ ] Works with SSH remote sessions (no file watching, stdin passthrough)
202+
- [ ] Path handling uses `path.join` or `path.posix` as appropriate
203+
- [ ] No hardcoded path separators (`/` or `\`)
204+
- [ ] Shell commands use platform-appropriate lookup (`which`/`where`)
205+
- [ ] Agent-specific code handles all supported agents, not just Claude
206+
207+
---
208+
209+
## Key Files for Platform Logic
210+
211+
| Concern | Primary Files |
212+
|---------|---------------|
213+
| Path utilities | `src/shared/pathUtils.ts` |
214+
| Shell detection | `src/main/utils/shellDetector.ts` |
215+
| WSL detection | `src/main/utils/wslDetector.ts` |
216+
| CLI detection | `src/main/utils/cliDetection.ts` |
217+
| SSH spawn wrapper | `src/main/utils/ssh-spawn-wrapper.ts` |
218+
| SSH command builder | `src/main/utils/ssh-command-builder.ts` |
219+
| Agent path probing | `src/main/agents/path-prober.ts` |
220+
| Windows diagnostics | `src/main/debug-package/collectors/windows-diagnostics.ts` |
221+
| Safe exec | `src/main/utils/execFile.ts` |

CLAUDE-WIZARD.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,16 +87,16 @@ The Wizard maintains two types of state:
8787
- Cleared on completion or when user chooses "Just Quit"
8888

8989
**State Save Triggers:**
90-
- Auto-save: When `currentStep` changes (step > 1) - `WizardContext.tsx:791`
91-
- Manual save: User clicks "Save & Exit" - `MaestroWizard.tsx:147`
90+
- Auto-save: When `currentStep` changes (step > 1) - `WizardContext.tsx` useEffect with `saveResumeState()`
91+
- Manual save: User clicks "Save & Exit" - `MaestroWizard.tsx` `handleConfirmExit()`
9292

9393
**State Clear Triggers:**
94-
- Wizard completion: `App.tsx:4681` + `WizardContext.tsx:711`
95-
- User quits: "Just Quit" button - `MaestroWizard.tsx:168`
94+
- Wizard completion: `App.tsx` wizard completion handler + `WizardContext.tsx` `COMPLETE_WIZARD` action
95+
- User quits: "Quit without saving" button - `MaestroWizard.tsx` `handleQuitWithoutSaving()`
9696
- User starts fresh: "Start Fresh" in resume modal - `App.tsx` resume handlers
9797

9898
**Opening Wizard Logic:**
99-
The `openWizard()` function in `WizardContext.tsx:528-535` handles state initialization:
99+
The `openWizard()` function in `WizardContext.tsx` handles state initialization:
100100
```typescript
101101
// If previous wizard was completed, reset in-memory state first
102102
if (state.isComplete === true) {

0 commit comments

Comments
 (0)