From 4f26177f3267d261fc6efefb48ad2fd77b205609 Mon Sep 17 00:00:00 2001 From: Raza Rauf Date: Wed, 25 Feb 2026 12:10:25 -0600 Subject: [PATCH 1/2] style: run Prettier over entire codebase Also fix pre-existing ESLint errors in e2e test files (unused vars, empty patterns, unused imports). --- .github/instructions/memory.instruction.md | 2 +- .github/workflows/ci.yml | 2 +- .github/workflows/release.yml | 8 +- .prettierrc | 12 +- AGENT_SUPPORT.md | 530 +- ARCHITECTURE.md | 808 +- BUILDING_WINDOWS.md | 23 +- CLAUDE-AGENTS.md | 45 +- CLAUDE-FEATURES.md | 57 +- CLAUDE-PATTERNS.md | 67 +- CLAUDE-PERFORMANCE.md | 83 +- CLAUDE-PLATFORM.md | 79 +- CLAUDE-SESSION.md | 175 +- CLAUDE-WIZARD.md | 42 +- CLAUDE.md | 163 +- CONSTITUTION.md | 16 +- CONTRIBUTING.md | 239 +- README.md | 27 +- SECURITY.md | 17 +- SYMPHONY_ISSUES.md | 11 + SYMPHONY_REGISTRY.md | 112 +- THEMES.md | 146 +- build/README.md | 2 + .../large-figure-gradient-2.icon/icon.json | 95 +- .../icon.json | 79 +- .../maestro-circle-bckgrnd.icon/icon.json | 82 +- .../maestro-purple-figure.icon/icon.json | 97 +- docs/about/overview.md | 37 +- docs/achievements.md | 69 +- docs/autorun-playbooks.md | 12 +- docs/cli.md | 84 +- docs/context-management.md | 63 +- docs/director-notes.md | 48 +- docs/docs.json | 266 +- docs/document-graph.md | 36 +- docs/encore-features.md | 4 +- docs/examples/local-manifest.json | 104 +- docs/features.md | 2 +- docs/general-usage.md | 63 +- docs/getting-started.md | 2 + docs/git-worktrees.md | 50 +- docs/group-chat.md | 12 +- docs/history.md | 35 +- docs/installation.md | 14 +- docs/keyboard-shortcuts.md | 255 +- docs/local-manifest.md | 50 +- docs/mcp-server.md | 33 +- docs/multi-claude.md | 20 +- docs/openspec-commands.md | 40 +- docs/performance-profiling.md | 12 +- docs/playbook-exchange.md | 24 +- docs/provider-notes.md | 63 +- docs/releases.md | 442 +- docs/remote-access.md | 10 +- docs/screenshots.md | 6 + docs/slash-commands.md | 165 +- docs/speckit-commands.md | 27 +- docs/ssh-remote-execution.md | 68 +- docs/symphony.md | 87 +- docs/troubleshooting.md | 60 +- docs/usage-dashboard.md | 38 +- e2e/autorun-batch.spec.ts | 1268 +- e2e/autorun-editing.spec.ts | 1488 +- e2e/autorun-sessions.spec.ts | 1572 +- e2e/autorun-setup.spec.ts | 582 +- e2e/fixtures/electron-app.ts | 1227 +- eslint.config.mjs | 159 +- package-lock.json | 40262 ++++++++-------- package.json | 634 +- playwright.config.ts | 100 +- postcss.config.mjs | 10 +- scripts/build-cli.mjs | 84 +- scripts/build-preload.mjs | 40 +- scripts/generate-prompts.mjs | 101 +- scripts/notarize.js | 50 +- scripts/refresh-openspec.mjs | 307 +- scripts/refresh-speckit.mjs | 361 +- scripts/set-version.mjs | 48 +- scripts/sync-release-notes.mjs | 274 +- src/prompts/autorun-default.md | 48 +- src/prompts/context-grooming.md | 14 +- src/prompts/context-summarize.md | 10 + src/prompts/context-transfer.md | 15 +- src/prompts/director-notes.md | 38 +- src/prompts/group-chat-moderator-synthesis.md | 2 + src/prompts/group-chat-moderator-system.md | 2 + src/prompts/group-chat-participant-request.md | 5 + src/prompts/group-chat-participant.md | 8 +- src/prompts/maestro-system-prompt.md | 13 +- src/prompts/openspec/metadata.json | 10 +- src/prompts/openspec/openspec.apply.md | 4 +- src/prompts/openspec/openspec.archive.md | 4 +- src/prompts/openspec/openspec.help.md | 29 +- src/prompts/openspec/openspec.implement.md | 3 + src/prompts/openspec/openspec.proposal.md | 10 +- src/prompts/speckit/metadata.json | 10 +- src/prompts/speckit/speckit.analyze.md | 8 +- src/prompts/speckit/speckit.clarify.md | 118 +- src/prompts/speckit/speckit.constitution.md | 2 +- src/prompts/speckit/speckit.help.md | 11 +- src/prompts/speckit/speckit.implement.md | 3 + src/prompts/speckit/speckit.plan.md | 2 +- src/prompts/speckit/speckit.specify.md | 214 +- src/prompts/speckit/speckit.tasks.md | 1 + src/prompts/tab-naming.md | 1 + src/prompts/wizard-document-generation.md | 12 +- .../wizard-inline-iterate-generation.md | 12 +- src/prompts/wizard-inline-iterate.md | 8 + src/prompts/wizard-inline-new.md | 7 + src/prompts/wizard-inline-system.md | 9 + src/prompts/wizard-system-continuation.md | 2 + src/prompts/wizard-system.md | 15 + src/renderer/components/SettingsModal.tsx | 5 +- src/renderer/components/SymphonyModal.tsx | 3 +- .../UsageDashboard/AutoRunStats.tsx | 6 +- .../UsageDashboard/SessionStats.tsx | 6 +- .../UsageDashboard/TasksByHourChart.tsx | 5 +- src/renderer/docs/app-tsx-inventory.md | 1476 +- src/renderer/index.css | 692 +- src/renderer/index.html | 304 +- src/renderer/public/splash.js | 27 +- src/web/index.css | 434 +- src/web/index.html | 177 +- src/web/public/manifest.json | 152 +- src/web/public/sw.js | 328 +- symphony-registry.json | 128 +- tailwind.config.mjs | 23 +- tsconfig.cli.json | 40 +- tsconfig.json | 34 +- tsconfig.lint.json | 17 +- tsconfig.main.json | 32 +- vite.config.web.mts | 306 +- vitest.config.mts | 72 +- vitest.e2e.config.ts | 32 +- vitest.integration.config.ts | 52 +- vitest.performance.config.mts | 38 +- 136 files changed, 29899 insertions(+), 29027 deletions(-) diff --git a/.github/instructions/memory.instruction.md b/.github/instructions/memory.instruction.md index e4da59c94..a52607337 100644 --- a/.github/instructions/memory.instruction.md +++ b/.github/instructions/memory.instruction.md @@ -2,4 +2,4 @@ applyTo: '**' --- -Remembered number: 42 \ No newline at end of file +Remembered number: 42 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index acb7c1aab..a989a6880 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: cache: 'npm' - run: npm ci - run: npm run build:prompts - - run: npx prettier --check "src/**/*.{ts,tsx}" + - run: npx prettier --check . - run: npx eslint src/ - run: npm run lint # TypeScript type checking diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2a014d3bd..51a6df9bf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,8 +5,8 @@ name: Release Maestro on: push: tags: - - 'v*.*.*' # Semantic version tags (e.g., v1.0.0) - - '20*' # Date-based tags (e.g., 2025-11-27) + - 'v*.*.*' # Semantic version tags (e.g., v1.0.0) + - '20*' # Date-based tags (e.g., 2025-11-27) workflow_dispatch: permissions: @@ -412,7 +412,7 @@ jobs: npm_config_arch: arm64 npm_config_target_arch: arm64 npm_config_build_from_source: true - USE_SYSTEM_FPM: "true" + USE_SYSTEM_FPM: 'true' BUILD_VERSION: ${{ steps.version.outputs.VERSION }} run: | # Critical: Clean build directories to force fresh compilation @@ -729,7 +729,7 @@ jobs: with: webhook: ${{ secrets.DISCORD_WEBHOOK_URL }} nodetail: true - title: "🎉 Maestro ${{ github.ref_name }} Released!" + title: '🎉 Maestro ${{ github.ref_name }} Released!' description: | ${{ steps.release_notes.outputs.notes }} diff --git a/.prettierrc b/.prettierrc index 31238688c..54f3bcbbd 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,8 +1,8 @@ { - "useTabs": true, - "tabWidth": 2, - "semi": true, - "singleQuote": true, - "trailingComma": "es5", - "printWidth": 100 + "useTabs": true, + "tabWidth": 2, + "semi": true, + "singleQuote": true, + "trailingComma": "es5", + "printWidth": 100 } diff --git a/AGENT_SUPPORT.md b/AGENT_SUPPORT.md index 647a9ebb1..5f213284b 100644 --- a/AGENT_SUPPORT.md +++ b/AGENT_SUPPORT.md @@ -8,15 +8,15 @@ This guide explains how to add support for a new AI coding agent (provider) in M The multi-provider refactoring has established the pluggable architecture for supporting multiple AI agents: -| Component | Status | Description | -|-----------|--------|-------------| -| Capability System | ✅ Complete | `AgentCapabilities` interface, capability gating in UI | -| Generic Identifiers | ✅ Complete | `claudeSessionId` → `agentSessionId` across 47+ files | -| Session Storage | ✅ Complete | `AgentSessionStorage` interface, Claude + OpenCode implementations | -| Output Parsers | ✅ Complete | `AgentOutputParser` interface, Claude + OpenCode parsers | -| Error Handling | ✅ Complete | `AgentError` types, detection patterns, recovery UI | -| IPC API | ✅ Complete | `window.maestro.agentSessions.*` replaces `claude.*` | -| UI Capability Gates | ✅ Complete | Features hidden/shown based on agent capabilities | +| Component | Status | Description | +| ------------------- | ----------- | ------------------------------------------------------------------ | +| Capability System | ✅ Complete | `AgentCapabilities` interface, capability gating in UI | +| Generic Identifiers | ✅ Complete | `claudeSessionId` → `agentSessionId` across 47+ files | +| Session Storage | ✅ Complete | `AgentSessionStorage` interface, Claude + OpenCode implementations | +| Output Parsers | ✅ Complete | `AgentOutputParser` interface, Claude + OpenCode parsers | +| Error Handling | ✅ Complete | `AgentError` types, detection patterns, recovery UI | +| IPC API | ✅ Complete | `window.maestro.agentSessions.*` replaces `claude.*` | +| UI Capability Gates | ✅ Complete | Features hidden/shown based on agent capabilities | ### Adding a New Agent @@ -48,12 +48,12 @@ See detailed instructions below. Use these terms consistently throughout the codebase: -| Term | Definition | -|------|------------| -| **Maestro Agent** | A configured AI assistant in Maestro (e.g., "My Claude Assistant") | -| **Provider** | The underlying AI service (Claude Code, OpenCode, Codex, Gemini CLI) | +| Term | Definition | +| -------------------- | ---------------------------------------------------------------------------- | +| **Maestro Agent** | A configured AI assistant in Maestro (e.g., "My Claude Assistant") | +| **Provider** | The underlying AI service (Claude Code, OpenCode, Codex, Gemini CLI) | | **Provider Session** | A conversation session managed by the provider (e.g., Claude's `session_id`) | -| **Tab** | A Maestro UI tab that maps 1:1 to a Provider Session | +| **Tab** | A Maestro UI tab that maps 1:1 to a Provider Session | **Hierarchy:** `Maestro Agent → Provider → Provider Sessions → Tabs` @@ -107,41 +107,41 @@ Each agent declares capabilities that determine which UI features are available. // src/main/agent-capabilities.ts interface AgentCapabilities { - // Core features - supportsResume: boolean; // Can resume previous conversations - supportsReadOnlyMode: boolean; // Has a plan/read-only mode - supportsJsonOutput: boolean; // Emits structured JSON for parsing - supportsSessionId: boolean; // Emits session ID for tracking - - // Advanced features - supportsImageInput: boolean; // Can receive images in prompts - supportsSlashCommands: boolean; // Has discoverable slash commands - supportsSessionStorage: boolean; // Persists provider sessions we can browse - supportsCostTracking: boolean; // Reports token costs - supportsUsageStats: boolean; // Reports token counts - - // Streaming behavior - supportsBatchMode: boolean; // Runs per-message (vs persistent process) - supportsStreaming: boolean; // Streams output incrementally - - // Message classification - supportsResultMessages: boolean; // Distinguishes final result from intermediary + // Core features + supportsResume: boolean; // Can resume previous conversations + supportsReadOnlyMode: boolean; // Has a plan/read-only mode + supportsJsonOutput: boolean; // Emits structured JSON for parsing + supportsSessionId: boolean; // Emits session ID for tracking + + // Advanced features + supportsImageInput: boolean; // Can receive images in prompts + supportsSlashCommands: boolean; // Has discoverable slash commands + supportsSessionStorage: boolean; // Persists provider sessions we can browse + supportsCostTracking: boolean; // Reports token costs + supportsUsageStats: boolean; // Reports token counts + + // Streaming behavior + supportsBatchMode: boolean; // Runs per-message (vs persistent process) + supportsStreaming: boolean; // Streams output incrementally + + // Message classification + supportsResultMessages: boolean; // Distinguishes final result from intermediary } ``` ### Capability-to-UI Feature Mapping -| Capability | UI Feature | Hidden When False | -|------------|------------|-------------------| -| `supportsReadOnlyMode` | Read-only toggle | Toggle hidden | -| `supportsSessionStorage` | Sessions browser tab | Tab hidden | -| `supportsResume` | Resume button | Button disabled | -| `supportsCostTracking` | Cost widget | Widget hidden | -| `supportsUsageStats` | Token usage display | Display hidden | -| `supportsImageInput` | Image attachment button | Button hidden | -| `supportsSlashCommands` | Slash command autocomplete | Autocomplete disabled | -| `supportsSessionId` | Session ID pill | Pill hidden | -| `supportsResultMessages` | Show only final result | Shows all messages | +| Capability | UI Feature | Hidden When False | +| ------------------------ | -------------------------- | --------------------- | +| `supportsReadOnlyMode` | Read-only toggle | Toggle hidden | +| `supportsSessionStorage` | Sessions browser tab | Tab hidden | +| `supportsResume` | Resume button | Button disabled | +| `supportsCostTracking` | Cost widget | Widget hidden | +| `supportsUsageStats` | Token usage display | Display hidden | +| `supportsImageInput` | Image attachment button | Button hidden | +| `supportsSlashCommands` | Slash command autocomplete | Autocomplete disabled | +| `supportsSessionId` | Session ID pill | Pill hidden | +| `supportsResultMessages` | Show only final result | Shows all messages | ### Context Window Configuration @@ -150,17 +150,18 @@ For agents where context window size varies by model (like OpenCode or Codex), M **Configuration Location:** Settings → Agent Configuration → Context Window Size **How It Works:** + 1. **Parser-reported value:** If the agent reports `contextWindow` in JSON output, that value takes priority 2. **User configuration:** If the parser doesn't report context window, the user-configured value is used 3. **Hidden when zero:** If no value is configured (0), the context usage widget is hidden entirely **Agent-Specific Behavior:** -| Agent | Default Context Window | Notes | -|-------|----------------------|-------| -| Claude Code | 200,000 | Always reported in JSON output | -| Codex | 200,000 | Default for GPT-5.x models; user can override in settings | -| OpenCode | 128,000 | Default for common models (GPT-4, etc.); user can override in settings | +| Agent | Default Context Window | Notes | +| ----------- | ---------------------- | ---------------------------------------------------------------------- | +| Claude Code | 200,000 | Always reported in JSON output | +| Codex | 200,000 | Default for GPT-5.x models; user can override in settings | +| OpenCode | 128,000 | Default for common models (GPT-4, etc.); user can override in settings | **Adding Context Window Config to an Agent:** @@ -230,6 +231,7 @@ your-agent run --format json "say hello" 2>&1 | head -20 ``` Document: + - [ ] How to get JSON output - [ ] Session ID field name and format - [ ] How to resume a session @@ -242,24 +244,24 @@ Edit `src/main/agent-detector.ts`: ```typescript const AGENT_DEFINITIONS: AgentConfig[] = [ - // ... existing agents - { - id: 'your-agent', - name: 'Your Agent', - binaryName: 'your-agent', - command: 'your-agent', - args: [], - - // CLI argument builders - batchModePrefix: ['run'], // Subcommand for batch mode - jsonOutputArgs: ['--format', 'json'], // JSON output flag - resumeArgs: (sessionId) => ['--session', sessionId], - readOnlyArgs: ['--mode', 'readonly'], - - // Runtime (set by detection) - available: false, - path: undefined, - }, + // ... existing agents + { + id: 'your-agent', + name: 'Your Agent', + binaryName: 'your-agent', + command: 'your-agent', + args: [], + + // CLI argument builders + batchModePrefix: ['run'], // Subcommand for batch mode + jsonOutputArgs: ['--format', 'json'], // JSON output flag + resumeArgs: (sessionId) => ['--session', sessionId], + readOnlyArgs: ['--mode', 'readonly'], + + // Runtime (set by detection) + available: false, + path: undefined, + }, ]; ``` @@ -269,21 +271,21 @@ Edit `src/main/agent-capabilities.ts`: ```typescript const AGENT_CAPABILITIES: Record = { - // ... existing agents - 'your-agent': { - supportsResume: true, // If --session works - supportsReadOnlyMode: true, // If readonly mode exists - supportsJsonOutput: true, // If JSON output works - supportsSessionId: true, // If session ID in output - supportsImageInput: false, // Start false, enable if supported - supportsSlashCommands: false, - supportsSessionStorage: false, // Enable if you implement storage - supportsCostTracking: false, // Enable if API-based with costs - supportsUsageStats: true, // If token counts in output - supportsBatchMode: true, - supportsStreaming: true, - supportsResultMessages: false, // Enable if result vs intermediary distinction - }, + // ... existing agents + 'your-agent': { + supportsResume: true, // If --session works + supportsReadOnlyMode: true, // If readonly mode exists + supportsJsonOutput: true, // If JSON output works + supportsSessionId: true, // If session ID in output + supportsImageInput: false, // Start false, enable if supported + supportsSlashCommands: false, + supportsSessionStorage: false, // Enable if you implement storage + supportsCostTracking: false, // Enable if API-based with costs + supportsUsageStats: true, // If token counts in output + supportsBatchMode: true, + supportsStreaming: true, + supportsResultMessages: false, // Enable if result vs intermediary distinction + }, }; ``` @@ -295,56 +297,56 @@ Create `src/main/parsers/your-agent-output-parser.ts`: import { AgentOutputParser, ParsedEvent } from './agent-output-parser'; export class YourAgentOutputParser implements AgentOutputParser { - parseJsonLine(line: string): ParsedEvent | null { - try { - const event = JSON.parse(line); - - // Map your agent's event types to Maestro's ParsedEvent - switch (event.type) { - case 'your_text_event': - return { - type: 'text', - sessionId: event.sessionId, - text: event.content, - raw: event, - }; - - case 'your_tool_event': - return { - type: 'tool_use', - sessionId: event.sessionId, - toolName: event.tool, - toolState: event.state, - raw: event, - }; - - case 'your_finish_event': - return { - type: 'result', - sessionId: event.sessionId, - text: event.finalText, - usage: { - input: event.tokens?.input ?? 0, - output: event.tokens?.output ?? 0, - }, - raw: event, - }; - - default: - return null; - } - } catch { - return null; - } - } - - isResultMessage(event: ParsedEvent): boolean { - return event.type === 'result'; - } - - extractSessionId(event: ParsedEvent): string | null { - return event.sessionId ?? null; - } + parseJsonLine(line: string): ParsedEvent | null { + try { + const event = JSON.parse(line); + + // Map your agent's event types to Maestro's ParsedEvent + switch (event.type) { + case 'your_text_event': + return { + type: 'text', + sessionId: event.sessionId, + text: event.content, + raw: event, + }; + + case 'your_tool_event': + return { + type: 'tool_use', + sessionId: event.sessionId, + toolName: event.tool, + toolState: event.state, + raw: event, + }; + + case 'your_finish_event': + return { + type: 'result', + sessionId: event.sessionId, + text: event.finalText, + usage: { + input: event.tokens?.input ?? 0, + output: event.tokens?.output ?? 0, + }, + raw: event, + }; + + default: + return null; + } + } catch { + return null; + } + } + + isResultMessage(event: ParsedEvent): boolean { + return event.type === 'result'; + } + + extractSessionId(event: ParsedEvent): string | null { + return event.sessionId ?? null; + } } ``` @@ -356,16 +358,16 @@ Edit `src/main/parsers/agent-output-parser.ts`: import { YourAgentOutputParser } from './your-agent-output-parser'; export function getOutputParser(agentId: string): AgentOutputParser { - switch (agentId) { - case 'claude-code': - return new ClaudeOutputParser(); - case 'opencode': - return new OpenCodeOutputParser(); - case 'your-agent': - return new YourAgentOutputParser(); - default: - return new GenericOutputParser(); - } + switch (agentId) { + case 'claude-code': + return new ClaudeOutputParser(); + case 'opencode': + return new OpenCodeOutputParser(); + case 'your-agent': + return new YourAgentOutputParser(); + default: + return new GenericOutputParser(); + } } ``` @@ -375,19 +377,9 @@ Edit `src/main/parsers/error-patterns.ts`: ```typescript export const YOUR_AGENT_ERROR_PATTERNS = { - auth_expired: [ - /authentication failed/i, - /invalid.*key/i, - /please login/i, - ], - token_exhaustion: [ - /context.*exceeded/i, - /too many tokens/i, - ], - rate_limited: [ - /rate limit/i, - /too many requests/i, - ], + auth_expired: [/authentication failed/i, /invalid.*key/i, /please login/i], + token_exhaustion: [/context.*exceeded/i, /too many tokens/i], + rate_limited: [/rate limit/i, /too many requests/i], }; ``` @@ -399,18 +391,18 @@ If your agent stores sessions in browseable files, create `src/main/storage/your import { AgentSessionStorage, AgentSession } from '../agent-session-storage'; export class YourAgentSessionStorage implements AgentSessionStorage { - async listSessions(projectPath: string): Promise { - // Find and parse session files - const sessionDir = this.getSessionDir(projectPath); - // ... implementation - } - - async readSession(projectPath: string, sessionId: string): Promise { - // Read and parse session file - // ... implementation - } - - // ... other methods + async listSessions(projectPath: string): Promise { + // Find and parse session files + const sessionDir = this.getSessionDir(projectPath); + // ... implementation + } + + async readSession(projectPath: string, sessionId: string): Promise { + // Read and parse session file + // ... implementation + } + + // ... other methods } ``` @@ -437,10 +429,10 @@ npm run dev Agents may emit **intermediary messages** (streaming, tool calls) and **result messages** (final response). Configure display behavior via `supportsResultMessages`: -| supportsResultMessages | Behavior | -|------------------------|----------| -| `true` | Only show result messages prominently; collapse intermediary | -| `false` | Show all messages as they stream | +| supportsResultMessages | Behavior | +| ---------------------- | ------------------------------------------------------------ | +| `true` | Only show result messages prominently; collapse intermediary | +| `false` | Show all messages as they stream | ### CLI Argument Builders @@ -448,20 +440,20 @@ The `AgentConfig` supports several argument builder patterns: ```typescript interface AgentConfig { - // Static arguments always included - args: string[]; + // Static arguments always included + args: string[]; - // Subcommand prefix for batch mode (e.g., ['run'] for opencode) - batchModePrefix?: string[]; + // Subcommand prefix for batch mode (e.g., ['run'] for opencode) + batchModePrefix?: string[]; - // Arguments for JSON output - jsonOutputArgs?: string[]; + // Arguments for JSON output + jsonOutputArgs?: string[]; - // Function to build resume arguments - resumeArgs?: (sessionId: string) => string[]; + // Function to build resume arguments + resumeArgs?: (sessionId: string) => string[]; - // Arguments for read-only mode - readOnlyArgs?: string[]; + // Arguments for read-only mode + readOnlyArgs?: string[]; } ``` @@ -470,6 +462,7 @@ interface AgentConfig { Your output parser should emit these normalized event types. See [`src/main/parsers/agent-output-parser.ts`](src/main/parsers/agent-output-parser.ts) for the canonical `ParsedEvent` interface definition. Key event types: + - `init` - Agent initialization (may contain session ID, available commands) - `text` - Text content to display to user - `tool_use` - Agent is using a tool (file read, bash, etc.) @@ -492,14 +485,14 @@ Maestro has unified error handling for agent failures. Your agent should integra ### Error Types -| Error Type | When to Detect | -|------------|----------------| -| `auth_expired` | API key invalid, login required | -| `token_exhaustion` | Context window full | -| `rate_limited` | Too many requests | -| `network_error` | Connection failed | -| `agent_crashed` | Non-zero exit code | -| `permission_denied` | Operation not allowed | +| Error Type | When to Detect | +| ------------------- | ------------------------------- | +| `auth_expired` | API key invalid, login required | +| `token_exhaustion` | Context window full | +| `rate_limited` | Too many requests | +| `network_error` | Connection failed | +| `agent_crashed` | Non-zero exit code | +| `permission_denied` | Operation not allowed | ### Adding Error Detection @@ -536,29 +529,29 @@ Create `src/__tests__/parsers/your-agent-output-parser.test.ts`: import { YourAgentOutputParser } from '../../main/parsers/your-agent-output-parser'; describe('YourAgentOutputParser', () => { - const parser = new YourAgentOutputParser(); - - it('parses text events', () => { - const line = '{"type": "your_text_event", "sessionId": "123", "content": "Hello"}'; - const event = parser.parseJsonLine(line); - - expect(event).toEqual({ - type: 'text', - sessionId: '123', - text: 'Hello', - raw: expect.any(Object), - }); - }); - - it('extracts session ID', () => { - const event = { type: 'text', sessionId: 'abc-123', raw: {} }; - expect(parser.extractSessionId(event)).toBe('abc-123'); - }); - - it('detects auth errors', () => { - const error = parser.detectError('Error: authentication failed'); - expect(error?.type).toBe('auth_expired'); - }); + const parser = new YourAgentOutputParser(); + + it('parses text events', () => { + const line = '{"type": "your_text_event", "sessionId": "123", "content": "Hello"}'; + const event = parser.parseJsonLine(line); + + expect(event).toEqual({ + type: 'text', + sessionId: '123', + text: 'Hello', + raw: expect.any(Object), + }); + }); + + it('extracts session ID', () => { + const event = { type: 'text', sessionId: 'abc-123', raw: {} }; + expect(parser.extractSessionId(event)).toBe('abc-123'); + }); + + it('detects auth errors', () => { + const error = parser.detectError('Error: authentication failed'); + expect(error?.type).toBe('auth_expired'); + }); }); ``` @@ -580,22 +573,24 @@ describe('YourAgentOutputParser', () => { ### Claude Code ✅ Fully Implemented -| Aspect | Value | -|--------|-------| -| Binary | `claude` | -| JSON Output | `--output-format stream-json` | -| Resume | `--resume ` | -| Read-only | `--permission-mode plan` | -| Session ID Field | `session_id` (snake_case) | -| Session Storage | `~/.claude/projects//` | +| Aspect | Value | +| ---------------- | ------------------------------------ | +| Binary | `claude` | +| JSON Output | `--output-format stream-json` | +| Resume | `--resume ` | +| Read-only | `--permission-mode plan` | +| Session ID Field | `session_id` (snake_case) | +| Session Storage | `~/.claude/projects//` | **Implementation Status:** + - ✅ Output Parser: `src/main/parsers/claude-output-parser.ts` - ✅ Session Storage: `src/main/storage/claude-session-storage.ts` - ✅ Error Patterns: `src/main/parsers/error-patterns.ts` - ✅ All capabilities enabled **JSON Event Types:** + - `system` (init) → session_id, slash_commands - `assistant` → streaming content - `result` → final response, modelUsage @@ -604,17 +599,17 @@ describe('YourAgentOutputParser', () => { ### OpenCode 🔄 Stub Ready -| Aspect | Value | -|--------|-------| -| Binary | `opencode` | -| JSON Output | `--format json` | -| Resume | `--session ` | -| Read-only | `--agent plan` | -| Session ID Field | `sessionID` (camelCase) | -| Session Storage | ✅ File-based (see below) | -| YOLO Mode | ✅ Auto-enabled in batch mode | -| Model Selection | `--model provider/model` | -| Config File | `~/.config/opencode/opencode.json` or project `opencode.json` | +| Aspect | Value | +| ---------------- | ------------------------------------------------------------- | +| Binary | `opencode` | +| JSON Output | `--format json` | +| Resume | `--session ` | +| Read-only | `--agent plan` | +| Session ID Field | `sessionID` (camelCase) | +| Session Storage | ✅ File-based (see below) | +| YOLO Mode | ✅ Auto-enabled in batch mode | +| Model Selection | `--model provider/model` | +| Config File | `~/.config/opencode/opencode.json` or project `opencode.json` | **YOLO Mode (Auto-Approval) Details:** @@ -653,6 +648,7 @@ OpenCode stores session data in `~/.local/share/opencode/storage/` with the foll ``` **Key findings:** + - **CLI Commands:** `opencode session list`, `opencode export `, `opencode import ` - **Project IDs:** SHA1 hash of project path (e.g., `ca85ff7c488724e85fc5b4be14ba44a0f6ce5b40`) - **Session IDs:** Format `ses_{base62-ish}` (e.g., `ses_4d585107dffeO9bO3HvMdvLYyC`) @@ -662,12 +658,14 @@ OpenCode stores session data in `~/.local/share/opencode/storage/` with the foll - **Token tracking:** Available in message metadata with `input`, `output`, `reasoning`, and cache fields **Implementation Status:** + - ✅ Output Parser: `src/main/parsers/opencode-output-parser.ts` (based on expected format) - ⏳ Session Storage: `src/main/storage/opencode-session-storage.ts` (stub, needs implementation using storage paths above) - ⏳ Error Patterns: Placeholder, needs real-world testing - ⏳ Capabilities: Set to minimal defaults; `supportsSessionStorage` can be enabled once storage is implemented **JSON Event Types:** + - `step_start` → session start (includes snapshot reference) - `text` → streaming content - `reasoning` → model thinking/chain-of-thought @@ -677,6 +675,7 @@ OpenCode stores session data in `~/.local/share/opencode/storage/` with the foll **Provider & Model Configuration:** OpenCode supports 75+ LLM providers including local models via Ollama, LM Studio, and llama.cpp. Configuration is stored in: + - **Global config:** `~/.config/opencode/opencode.json` - **Per-project config:** `opencode.json` in project root - **Custom path:** Via `OPENCODE_CONFIG` environment variable @@ -687,27 +686,28 @@ Configuration files are merged, with project config overriding global config for ```json { - "$schema": "https://opencode.ai/config.json", - "model": "ollama/qwen3:8b-16k", - "provider": { - "ollama": { - "npm": "@ai-sdk/openai-compatible", - "name": "Ollama (local)", - "options": { - "baseURL": "http://localhost:11434/v1" - }, - "models": { - "qwen3:8b-16k": { - "name": "Qwen3 8B", - "tools": true - } - } - } - } + "$schema": "https://opencode.ai/config.json", + "model": "ollama/qwen3:8b-16k", + "provider": { + "ollama": { + "npm": "@ai-sdk/openai-compatible", + "name": "Ollama (local)", + "options": { + "baseURL": "http://localhost:11434/v1" + }, + "models": { + "qwen3:8b-16k": { + "name": "Qwen3 8B", + "tools": true + } + } + } + } } ``` **Key Configuration Options:** + - `npm`: Provider package (use `@ai-sdk/openai-compatible` for OpenAI-compatible APIs) - `options.baseURL`: API endpoint URL - `models..tools`: Enable tool calling support (critical for agentic use) @@ -744,6 +744,7 @@ Then reference the custom model name in OpenCode config. ``` **Model Selection Methods:** + 1. **Command-line:** `opencode run --model ollama/qwen3:8b-16k "prompt"` 2. **Config file:** Set `"model": "provider/model"` in opencode.json 3. **Interactive:** Use `/models` command in interactive mode @@ -753,12 +754,14 @@ Model ID format: `provider_id/model_id` (e.g., `ollama/llama2`, `anthropic/claud **Maestro Integration Considerations:** Since OpenCode supports multiple providers/models, Maestro should consider: + 1. **Model selection UI:** Add model dropdown when OpenCode is selected, populated from config or `opencode models` command 2. **Default config generation:** Optionally generate `~/.config/opencode/opencode.json` for Ollama on first use 3. **Per-session model:** Pass `--model` flag based on user selection 4. **Provider status:** Detect which providers are configured and available **Documentation Sources:** + - [OpenCode Config Docs](https://opencode.ai/docs/config/) - [OpenCode Providers Docs](https://opencode.ai/docs/providers/) - [OpenCode Models Docs](https://opencode.ai/docs/models/) @@ -770,6 +773,7 @@ Since OpenCode supports multiple providers/models, Maestro should consider: **Status:** Not yet implemented **To Add:** + 1. Agent definition in `agent-detector.ts` 2. Capabilities in `agent-capabilities.ts` 3. Output parser for Gemini JSON format @@ -779,26 +783,28 @@ Since OpenCode supports multiple providers/models, Maestro should consider: ### Codex ✅ Fully Implemented -| Aspect | Value | -|--------|-------| -| Binary | `codex` | -| JSON Output | `--json` | -| Batch Mode | `exec` subcommand | -| Resume | `resume ` (v0.30.0+) | -| Read-only | `--sandbox read-only` | -| YOLO Mode | `--dangerously-bypass-approvals-and-sandbox` (enabled by default) | -| Session ID Field | `thread_id` (from `thread.started` event) | -| Session Storage | `~/.codex/sessions/YYYY/MM/DD/*.jsonl` | -| Context Window | 128K tokens | -| Pricing | o4-mini: $1.10/$4.40 per million tokens (input/output) | +| Aspect | Value | +| ---------------- | ----------------------------------------------------------------- | +| Binary | `codex` | +| JSON Output | `--json` | +| Batch Mode | `exec` subcommand | +| Resume | `resume ` (v0.30.0+) | +| Read-only | `--sandbox read-only` | +| YOLO Mode | `--dangerously-bypass-approvals-and-sandbox` (enabled by default) | +| Session ID Field | `thread_id` (from `thread.started` event) | +| Session Storage | `~/.codex/sessions/YYYY/MM/DD/*.jsonl` | +| Context Window | 128K tokens | +| Pricing | o4-mini: $1.10/$4.40 per million tokens (input/output) | **Implementation Status:** + - ✅ Output Parser: `src/main/parsers/codex-output-parser.ts` (42 tests) - ✅ Session Storage: `src/main/storage/codex-session-storage.ts` (8 tests) - ✅ Error Patterns: `src/main/parsers/error-patterns.ts` (25 tests) - ✅ All capabilities enabled **JSON Event Types:** + - `thread.started` → session_id (`thread_id`), initialization - `turn.started` → processing indicator - `item.completed (agent_message)` → final text response @@ -808,12 +814,14 @@ Since OpenCode supports multiple providers/models, Maestro should consider: - `turn.completed` → token usage (`input_tokens`, `output_tokens`, `reasoning_output_tokens`, `cached_input_tokens`) **Unique Features:** + - **Reasoning Tokens:** Reports `reasoning_output_tokens` separately from `output_tokens`, displayed in UI - **Three Sandbox Levels:** `read-only`, `workspace-write`, `danger-full-access` - **Cached Input Discount:** 75% discount on cached input tokens ($0.275/million) - **YOLO Mode Default:** Full system access enabled by default in Maestro **Command Line Pattern:** + ```bash # Basic execution codex exec --json -C /path/to/project "prompt" @@ -826,6 +834,7 @@ codex exec --json resume "continue" ``` **Documentation Sources:** + - [Codex CLI GitHub](https://github.com/openai/codex) - [OpenAI API Pricing](https://openai.com/api/pricing/) @@ -836,6 +845,7 @@ codex exec --json resume "continue" **Status:** Not yet implemented **To Add:** + 1. Agent definition in `agent-detector.ts` 2. Capabilities in `agent-capabilities.ts` (likely local model, no cost tracking) 3. Output parser for Qwen JSON format diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 8d037a79a..7650f41dd 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -84,35 +84,35 @@ Maestro uses Electron's main/renderer split with strict context isolation. Node.js backend with full system access: -| File | Purpose | -|------|---------| -| `index.ts` | App entry, IPC handlers, window management | -| `process-manager.ts` | PTY and child process spawning | -| `web-server.ts` | Fastify HTTP/WebSocket server for mobile remote control | -| `agent-detector.ts` | Auto-detect CLI tools via PATH | -| `preload.ts` | Secure IPC bridge via contextBridge | -| `tunnel-manager.ts` | Cloudflare tunnel management for secure remote access | -| `themes.ts` | Theme definitions for web interface (mirrors renderer themes) | -| `utils/execFile.ts` | Safe command execution utility | -| `utils/logger.ts` | System logging with levels | -| `utils/shellDetector.ts` | Detect available shells | -| `utils/terminalFilter.ts` | Strip terminal control sequences | -| `utils/cliDetection.ts` | CLI tool detection (cloudflared, gh) | -| `utils/networkUtils.ts` | Network utilities for local IP detection | +| File | Purpose | +| ------------------------- | ------------------------------------------------------------- | +| `index.ts` | App entry, IPC handlers, window management | +| `process-manager.ts` | PTY and child process spawning | +| `web-server.ts` | Fastify HTTP/WebSocket server for mobile remote control | +| `agent-detector.ts` | Auto-detect CLI tools via PATH | +| `preload.ts` | Secure IPC bridge via contextBridge | +| `tunnel-manager.ts` | Cloudflare tunnel management for secure remote access | +| `themes.ts` | Theme definitions for web interface (mirrors renderer themes) | +| `utils/execFile.ts` | Safe command execution utility | +| `utils/logger.ts` | System logging with levels | +| `utils/shellDetector.ts` | Detect available shells | +| `utils/terminalFilter.ts` | Strip terminal control sequences | +| `utils/cliDetection.ts` | CLI tool detection (cloudflared, gh) | +| `utils/networkUtils.ts` | Network utilities for local IP detection | ### Renderer Process (`src/renderer/`) React frontend with no direct Node.js access: -| Directory | Purpose | -|-----------|---------| -| `components/` | React UI components | -| `hooks/` | Custom React hooks (15 hooks - see [Custom Hooks](#custom-hooks)) | -| `services/` | IPC wrappers (git.ts, process.ts) | -| `contexts/` | React contexts (LayerStackContext, ToastContext) | -| `constants/` | Themes, shortcuts, modal priorities | -| `types/` | TypeScript definitions | -| `utils/` | Frontend utilities | +| Directory | Purpose | +| ------------- | ----------------------------------------------------------------- | +| `components/` | React UI components | +| `hooks/` | Custom React hooks (15 hooks - see [Custom Hooks](#custom-hooks)) | +| `services/` | IPC wrappers (git.ts, process.ts) | +| `contexts/` | React contexts (LayerStackContext, ToastContext) | +| `constants/` | Themes, shortcuts, modal priorities | +| `types/` | TypeScript definitions | +| `utils/` | Frontend utilities | ### Agent Model (Session Interface) @@ -120,11 +120,11 @@ Each agent runs **two processes simultaneously**: ```typescript interface Session { - id: string; // Unique identifier - aiPid: number; // AI agent process (suffixed -ai) - terminalPid: number; // Terminal process (suffixed -terminal) - inputMode: 'ai' | 'terminal'; // Which process receives input - // ... other fields + id: string; // Unique identifier + aiPid: number; // AI agent process (suffixed -ai) + terminalPid: number; // Terminal process (suffixed -terminal) + inputMode: 'ai' | 'terminal'; // Which process receives input + // ... other fields } ``` @@ -211,6 +211,7 @@ The `ProcessManager` class (`src/main/process-manager.ts`) handles two process t ### PTY Processes (via `node-pty`) Used for terminal sessions with full shell emulation: + - `toolType: 'terminal'` - Supports resize, ANSI escape codes, interactive shell - Spawned with shell (zsh, bash, fish, etc.) @@ -218,6 +219,7 @@ Used for terminal sessions with full shell emulation: ### Child Processes (via `child_process.spawn`) Used for AI assistants: + - All non-terminal tool types - Direct stdin/stdout/stderr capture - **Security**: Uses `spawn()` with `shell: false` @@ -225,6 +227,7 @@ Used for AI assistants: ### Batch Mode (Claude Code) Claude Code runs in batch mode with `--print --output-format json`: + - Prompt passed as CLI argument - Process exits after response - JSON response parsed for result and usage stats @@ -232,6 +235,7 @@ Claude Code runs in batch mode with `--print --output-format json`: ### Stream-JSON Mode (with images) When images are attached: + - Uses `--input-format stream-json --output-format stream-json` - Message sent via stdin as JSONL - Supports multimodal input @@ -261,48 +265,48 @@ Centralized modal/overlay management with predictable Escape key handling. ### Architecture -| File | Purpose | -|------|---------| -| `hooks/useLayerStack.ts` | Core layer management hook | +| File | Purpose | +| -------------------------------- | ------------------------------------- | +| `hooks/useLayerStack.ts` | Core layer management hook | | `contexts/LayerStackContext.tsx` | Global Escape handler (capture phase) | -| `constants/modalPriorities.ts` | Priority values for all modals | -| `types/layer.ts` | Layer type definitions | +| `constants/modalPriorities.ts` | Priority values for all modals | +| `types/layer.ts` | Layer type definitions | ### Modal Priority Hierarchy ```typescript const MODAL_PRIORITIES = { - STANDING_OVATION: 1100, // Achievement celebration overlay - CONFIRM: 1000, // Highest - confirmation dialogs - PLAYBOOK_DELETE_CONFIRM: 950, - PLAYBOOK_NAME: 940, - RENAME_INSTANCE: 900, - RENAME_TAB: 880, - RENAME_GROUP: 850, - CREATE_GROUP: 800, - NEW_INSTANCE: 750, - AGENT_PROMPT_COMPOSER: 730, - PROMPT_COMPOSER: 710, - QUICK_ACTION: 700, // Command palette (Cmd+K) - TAB_SWITCHER: 690, - AGENT_SESSIONS: 680, - EXECUTION_QUEUE_BROWSER: 670, - BATCH_RUNNER: 660, - SHORTCUTS_HELP: 650, - HISTORY_HELP: 640, - AUTORUNNER_HELP: 630, - HISTORY_DETAIL: 620, - ABOUT: 600, - PROCESS_MONITOR: 550, - LOG_VIEWER: 500, - SETTINGS: 450, - GIT_DIFF: 200, - GIT_LOG: 190, - LIGHTBOX: 150, - FILE_PREVIEW: 100, - SLASH_AUTOCOMPLETE: 50, - TEMPLATE_AUTOCOMPLETE: 40, - FILE_TREE_FILTER: 30, // Lowest + STANDING_OVATION: 1100, // Achievement celebration overlay + CONFIRM: 1000, // Highest - confirmation dialogs + PLAYBOOK_DELETE_CONFIRM: 950, + PLAYBOOK_NAME: 940, + RENAME_INSTANCE: 900, + RENAME_TAB: 880, + RENAME_GROUP: 850, + CREATE_GROUP: 800, + NEW_INSTANCE: 750, + AGENT_PROMPT_COMPOSER: 730, + PROMPT_COMPOSER: 710, + QUICK_ACTION: 700, // Command palette (Cmd+K) + TAB_SWITCHER: 690, + AGENT_SESSIONS: 680, + EXECUTION_QUEUE_BROWSER: 670, + BATCH_RUNNER: 660, + SHORTCUTS_HELP: 650, + HISTORY_HELP: 640, + AUTORUNNER_HELP: 630, + HISTORY_DETAIL: 620, + ABOUT: 600, + PROCESS_MONITOR: 550, + LOG_VIEWER: 500, + SETTINGS: 450, + GIT_DIFF: 200, + GIT_LOG: 190, + LIGHTBOX: 150, + FILE_PREVIEW: 100, + SLASH_AUTOCOMPLETE: 50, + TEMPLATE_AUTOCOMPLETE: 40, + FILE_TREE_FILTER: 30, // Lowest }; ``` @@ -320,47 +324,47 @@ const onCloseRef = useRef(onClose); onCloseRef.current = onClose; useEffect(() => { - if (modalOpen) { - const id = registerLayer({ - type: 'modal', - priority: MODAL_PRIORITIES.YOUR_MODAL, - blocksLowerLayers: true, - capturesFocus: true, - focusTrap: 'strict', // 'strict' | 'lenient' | 'none' - ariaLabel: 'Your Modal Name', - onEscape: () => onCloseRef.current(), - }); - layerIdRef.current = id; - return () => unregisterLayer(id); - } -}, [modalOpen, registerLayer, unregisterLayer]); // onClose NOT in deps + if (modalOpen) { + const id = registerLayer({ + type: 'modal', + priority: MODAL_PRIORITIES.YOUR_MODAL, + blocksLowerLayers: true, + capturesFocus: true, + focusTrap: 'strict', // 'strict' | 'lenient' | 'none' + ariaLabel: 'Your Modal Name', + onEscape: () => onCloseRef.current(), + }); + layerIdRef.current = id; + return () => unregisterLayer(id); + } +}, [modalOpen, registerLayer, unregisterLayer]); // onClose NOT in deps ``` ### Layer Types ```typescript type ModalLayer = { - type: 'modal'; - priority: number; - blocksLowerLayers: boolean; - capturesFocus: boolean; - focusTrap: 'strict' | 'lenient' | 'none'; - ariaLabel?: string; - onEscape: () => void; - onBeforeClose?: () => Promise; - isDirty?: boolean; - parentModalId?: string; + type: 'modal'; + priority: number; + blocksLowerLayers: boolean; + capturesFocus: boolean; + focusTrap: 'strict' | 'lenient' | 'none'; + ariaLabel?: string; + onEscape: () => void; + onBeforeClose?: () => Promise; + isDirty?: boolean; + parentModalId?: string; }; type OverlayLayer = { - type: 'overlay'; - priority: number; - blocksLowerLayers: boolean; - capturesFocus: boolean; - focusTrap: 'strict' | 'lenient' | 'none'; - ariaLabel?: string; - onEscape: () => void; - allowClickOutside: boolean; + type: 'overlay'; + priority: number; + blocksLowerLayers: boolean; + capturesFocus: boolean; + focusTrap: 'strict' | 'lenient' | 'none'; + ariaLabel?: string; + onEscape: () => void; + allowClickOutside: boolean; }; ``` @@ -370,12 +374,12 @@ Components like FilePreview handle internal search in their onEscape: ```typescript onEscape: () => { - if (searchOpen) { - setSearchOpen(false); // First Escape closes search - } else { - closePreview(); // Second Escape closes preview - } -} + if (searchOpen) { + setSearchOpen(false); // First Escape closes search + } else { + closePreview(); // Second Escape closes preview + } +}; ``` --- @@ -391,6 +395,7 @@ Maestro uses 15 custom hooks for state management and functionality. Manages all application settings with automatic persistence. **What it manages:** + - LLM settings (provider, model, API key) - Agent settings (default agent, custom agent paths) - Shell settings (default shell) @@ -407,6 +412,7 @@ Manages all application settings with automatic persistence. Manages agents and groups with CRUD operations. **Key methods:** + - `createNewSession(agentId, workingDir, name)` - Creates new agent with dual processes - `deleteSession(id, showConfirmation)` - Delete agent with confirmation - `toggleInputMode()` - Switch between AI and terminal mode @@ -419,6 +425,7 @@ Manages agents and groups with CRUD operations. Manages file tree refresh/filter state and git-related file metadata. **Key methods:** + - `refreshFileTree(sessionId)` - Reload directory tree and return change stats - `refreshGitFileState(sessionId)` - Refresh tree + git repo metadata - `filteredFileTree` - Derived tree based on filter string @@ -428,6 +435,7 @@ Manages file tree refresh/filter state and git-related file metadata. Manages Auto Run batch execution logic. **Key methods:** + - `startBatchRun(config)` - Start batch document processing - `stopBatchRun()` - Stop current batch run - `pauseBatchRun()` / `resumeBatchRun()` - Pause/resume execution @@ -439,6 +447,7 @@ Manages Auto Run batch execution logic. Core layer management for modals and overlays. **Key methods:** + - `registerLayer(config)` - Register a modal/overlay - `unregisterLayer(id)` - Remove a layer - `updateLayerHandler(id, handler)` - Update escape handler @@ -520,6 +529,7 @@ User-defined prompt macros that expand when typed. The built-in slash commands ( ### Overview Custom AI Commands are prompt templates that: + - Start with `/` prefix - Expand to full prompts when selected - Support template variables (e.g., `{{date}}`, `{{time}}`, `{{cwd}}`) @@ -531,17 +541,18 @@ Commands are defined in Settings → Custom AI Commands: ```typescript interface CustomAICommand { - command: string; // e.g., "/review" - description: string; // Shown in autocomplete - prompt: string; // The expanded prompt text - aiOnly?: boolean; // Only show in AI mode - terminalOnly?: boolean; // Only show in terminal mode + command: string; // e.g., "/review" + description: string; // Shown in autocomplete + prompt: string; // The expanded prompt text + aiOnly?: boolean; // Only show in AI mode + terminalOnly?: boolean; // Only show in terminal mode } ``` ### Template Variables Commands support these template variables: + - `{{date}}` - Current date (YYYY-MM-DD) - `{{time}}` - Current time (HH:MM:SS) - `{{datetime}}` - Combined date and time @@ -583,24 +594,24 @@ Themes defined in `src/renderer/constants/themes.ts`. ```typescript interface Theme { - id: ThemeId; - name: string; - mode: 'light' | 'dark' | 'vibe'; - colors: { - bgMain: string; // Main content background - bgSidebar: string; // Sidebar background - bgActivity: string; // Accent background - border: string; // Border colors - textMain: string; // Primary text - textDim: string; // Secondary text - accent: string; // Accent color - accentDim: string; // Dimmed accent - accentText: string; // Accent text color - accentForeground: string; // Text ON accent backgrounds (contrast) - success: string; // Success state (green) - warning: string; // Warning state (yellow) - error: string; // Error state (red) - }; + id: ThemeId; + name: string; + mode: 'light' | 'dark' | 'vibe'; + colors: { + bgMain: string; // Main content background + bgSidebar: string; // Sidebar background + bgActivity: string; // Accent background + border: string; // Border colors + textMain: string; // Primary text + textDim: string; // Secondary text + accent: string; // Accent color + accentDim: string; // Dimmed accent + accentText: string; // Accent text color + accentForeground: string; // Text ON accent backgrounds (contrast) + success: string; // Success state (green) + warning: string; // Warning state (yellow) + error: string; // Error state (red) + }; } ``` @@ -613,13 +624,15 @@ interface Theme { ### Usage Use inline styles for theme colors: + ```typescript style={{ color: theme.colors.textMain }} // Correct ``` Use Tailwind for layout: + ```typescript -className="flex items-center gap-2" // Correct +className = 'flex items-center gap-2'; // Correct ``` --- @@ -629,11 +642,13 @@ className="flex items-center gap-2" // Correct Settings stored via `electron-store`: **Locations:** + - **macOS**: `~/Library/Application Support/maestro/` - **Windows**: `%APPDATA%/maestro/` - **Linux**: `~/.config/maestro/` **Files:** + - `maestro-settings.json` - User preferences - `maestro-sessions.json` - Agent persistence - `maestro-groups.json` - Agent groups @@ -642,19 +657,22 @@ Settings stored via `electron-store`: ### Adding New Settings 1. Add state in `useSettings.ts`: + ```typescript const [mySetting, setMySettingState] = useState(defaultValue); ``` 2. Create wrapper function: + ```typescript const setMySetting = (value: MyType) => { - setMySettingState(value); - window.maestro.settings.set('mySetting', value); + setMySettingState(value); + window.maestro.settings.set('mySetting', value); }; ``` 3. Load in useEffect: + ```typescript const saved = await window.maestro.settings.get('mySetting'); if (saved !== undefined) setMySettingState(saved); @@ -671,6 +689,7 @@ Browse and resume Claude Code provider sessions from `~/.claude/projects/`. ### Path Encoding Claude Code encodes project paths by replacing `/` with `-`: + - `/Users/pedram/Projects/Maestro` → `-Users-pedram-Projects-Maestro` ### IPC Handlers @@ -682,16 +701,16 @@ const sessions = await window.maestro.claude.listSessions(projectPath); // Read messages with pagination const { messages, total, hasMore } = await window.maestro.claude.readSessionMessages( - projectPath, - sessionId, - { offset: 0, limit: 20 } + projectPath, + sessionId, + { offset: 0, limit: 20 } ); // Search sessions const results = await window.maestro.claude.searchSessions( - projectPath, - 'query', - 'all' // 'title' | 'user' | 'assistant' | 'all' + projectPath, + 'query', + 'all' // 'title' | 'user' | 'assistant' | 'all' ); // Get global stats across all Claude projects (with streaming updates) @@ -701,8 +720,8 @@ const stats = await window.maestro.claude.getGlobalStats(); // Subscribe to streaming updates during stats calculation const unsubscribe = window.maestro.claude.onGlobalStatsUpdate((stats) => { - console.log(`Progress: ${stats.totalSessions} sessions, $${stats.totalCostUsd.toFixed(2)}`); - if (stats.isComplete) console.log('Stats calculation complete'); + console.log(`Progress: ${stats.totalSessions} sessions, $${stats.totalCostUsd.toFixed(2)}`); + if (stats.isComplete) console.log('Stats calculation complete'); }); // Call unsubscribe() to stop listening ``` @@ -721,74 +740,74 @@ File-based document runner for automating multi-step tasks. Users configure a fo ### Component Architecture -| Component | Purpose | -|-----------|---------| -| `AutoRun.tsx` | Main panel showing current document with edit/preview modes | -| `AutoRunSetupModal.tsx` | First-time setup for selecting the Runner Docs folder | -| `AutoRunDocumentSelector.tsx` | Dropdown for switching between markdown documents | -| `BatchRunnerModal.tsx` | Configuration modal for running multiple Auto Run documents | -| `PlaybookNameModal.tsx` | Modal for naming saved playbook configurations | -| `PlaybookDeleteConfirmModal.tsx` | Confirmation modal for playbook deletion | -| `useBatchProcessor.ts` | Hook managing batch execution logic | +| Component | Purpose | +| -------------------------------- | ----------------------------------------------------------- | +| `AutoRun.tsx` | Main panel showing current document with edit/preview modes | +| `AutoRunSetupModal.tsx` | First-time setup for selecting the Runner Docs folder | +| `AutoRunDocumentSelector.tsx` | Dropdown for switching between markdown documents | +| `BatchRunnerModal.tsx` | Configuration modal for running multiple Auto Run documents | +| `PlaybookNameModal.tsx` | Modal for naming saved playbook configurations | +| `PlaybookDeleteConfirmModal.tsx` | Confirmation modal for playbook deletion | +| `useBatchProcessor.ts` | Hook managing batch execution logic | ### Data Types ```typescript // Document entry in the batch run queue (supports duplicates) interface BatchDocumentEntry { - id: string; // Unique ID for drag-drop and duplicates - filename: string; // Document filename (without .md) - resetOnCompletion: boolean; // Uncheck all boxes when done - isDuplicate: boolean; // True if this is a duplicate entry + id: string; // Unique ID for drag-drop and duplicates + filename: string; // Document filename (without .md) + resetOnCompletion: boolean; // Uncheck all boxes when done + isDuplicate: boolean; // True if this is a duplicate entry } // Git worktree configuration for parallel work interface WorktreeConfig { - enabled: boolean; // Whether to use a worktree - path: string; // Absolute path for the worktree - branchName: string; // Branch name to use/create - createPROnCompletion: boolean; // Create PR when Auto Run finishes + enabled: boolean; // Whether to use a worktree + path: string; // Absolute path for the worktree + branchName: string; // Branch name to use/create + createPROnCompletion: boolean; // Create PR when Auto Run finishes } // Configuration for starting a batch run interface BatchRunConfig { - documents: BatchDocumentEntry[]; // Ordered list of docs to run - prompt: string; // Agent prompt template - loopEnabled: boolean; // Loop back to first doc when done - worktree?: WorktreeConfig; // Optional worktree configuration + documents: BatchDocumentEntry[]; // Ordered list of docs to run + prompt: string; // Agent prompt template + loopEnabled: boolean; // Loop back to first doc when done + worktree?: WorktreeConfig; // Optional worktree configuration } // Runtime batch processing state interface BatchRunState { - isRunning: boolean; - isStopping: boolean; - documents: string[]; // Document filenames in order - currentDocumentIndex: number; // Which document we're on (0-based) - currentDocTasksTotal: number; - currentDocTasksCompleted: number; - totalTasksAcrossAllDocs: number; - completedTasksAcrossAllDocs: number; - loopEnabled: boolean; - loopIteration: number; // How many times we've looped - folderPath: string; - worktreeActive: boolean; - worktreePath?: string; - worktreeBranch?: string; + isRunning: boolean; + isStopping: boolean; + documents: string[]; // Document filenames in order + currentDocumentIndex: number; // Which document we're on (0-based) + currentDocTasksTotal: number; + currentDocTasksCompleted: number; + totalTasksAcrossAllDocs: number; + completedTasksAcrossAllDocs: number; + loopEnabled: boolean; + loopIteration: number; // How many times we've looped + folderPath: string; + worktreeActive: boolean; + worktreePath?: string; + worktreeBranch?: string; } // Saved playbook configuration interface Playbook { - id: string; - name: string; - createdAt: number; - updatedAt: number; - documents: PlaybookDocumentEntry[]; - loopEnabled: boolean; - prompt: string; - worktreeSettings?: { - branchNameTemplate: string; - createPROnCompletion: boolean; - }; + id: string; + name: string; + createdAt: number; + updatedAt: number; + documents: PlaybookDocumentEntry[]; + loopEnabled: boolean; + prompt: string; + worktreeSettings?: { + branchNameTemplate: string; + createPROnCompletion: boolean; + }; } ``` @@ -885,11 +904,13 @@ When worktree is enabled, Auto Run operates in an isolated directory: ### Write Queue Integration Without worktree mode, Auto Run tasks queue through the existing execution queue: + - Auto Run tasks are marked as write operations (`readOnlyMode: false`) - Manual write messages queue behind Auto Run (sequential) - Read-only operations from other tabs can run in parallel With worktree mode: + - Auto Run operates in a separate directory - No queue conflicts with main workspace - True parallelization enabled @@ -902,38 +923,39 @@ Gamification system that rewards Auto Run usage with conductor-themed badges. ### Components -| Component | Purpose | -|-----------|---------| -| `conductorBadges.ts` | Badge definitions and progression levels | -| `useAchievements.ts` | Achievement tracking and unlock logic | -| `AchievementCard.tsx` | Individual badge display component | +| Component | Purpose | +| ---------------------------- | -------------------------------------------- | +| `conductorBadges.ts` | Badge definitions and progression levels | +| `useAchievements.ts` | Achievement tracking and unlock logic | +| `AchievementCard.tsx` | Individual badge display component | | `StandingOvationOverlay.tsx` | Celebration overlay with confetti animations | ### Badge Progression 15 conductor levels based on cumulative Auto Run time: -| Level | Badge | Time Required | -|-------|-------|---------------| -| 1 | Apprentice Conductor | 1 minute | -| 2 | Junior Conductor | 5 minutes | -| 3 | Assistant Conductor | 15 minutes | -| 4 | Associate Conductor | 30 minutes | -| 5 | Conductor | 1 hour | -| 6 | Senior Conductor | 2 hours | -| 7 | Principal Conductor | 4 hours | -| 8 | Master Conductor | 8 hours | -| 9 | Chief Conductor | 16 hours | -| 10 | Distinguished Conductor | 24 hours | -| 11 | Elite Conductor | 48 hours | -| 12 | Virtuoso Conductor | 72 hours | -| 13 | Legendary Conductor | 100 hours | -| 14 | Mythic Conductor | 150 hours | -| 15 | Transcendent Maestro | 200 hours | +| Level | Badge | Time Required | +| ----- | ----------------------- | ------------- | +| 1 | Apprentice Conductor | 1 minute | +| 2 | Junior Conductor | 5 minutes | +| 3 | Assistant Conductor | 15 minutes | +| 4 | Associate Conductor | 30 minutes | +| 5 | Conductor | 1 hour | +| 6 | Senior Conductor | 2 hours | +| 7 | Principal Conductor | 4 hours | +| 8 | Master Conductor | 8 hours | +| 9 | Chief Conductor | 16 hours | +| 10 | Distinguished Conductor | 24 hours | +| 11 | Elite Conductor | 48 hours | +| 12 | Virtuoso Conductor | 72 hours | +| 13 | Legendary Conductor | 100 hours | +| 14 | Mythic Conductor | 150 hours | +| 15 | Transcendent Maestro | 200 hours | ### Standing Ovation When a new badge is unlocked: + 1. `StandingOvationOverlay` displays with confetti animation 2. Badge details shown with celebration message 3. Share functionality available @@ -958,15 +980,15 @@ Multi-tab support within each agent, allowing parallel conversations with separa ```typescript interface AITab { - id: string; - name: string; - agentSessionId?: string; // Separate provider session per tab - aiLogs: LogEntry[]; // Tab-specific conversation history - isStarred: boolean; - readOnlyMode: boolean; - saveToHistory: boolean; - unreadCount: number; - createdAt: number; + id: string; + name: string; + agentSessionId?: string; // Separate provider session per tab + aiLogs: LogEntry[]; // Tab-specific conversation history + isStarred: boolean; + readOnlyMode: boolean; + saveToHistory: boolean; + unreadCount: number; + createdAt: number; } ``` @@ -980,13 +1002,13 @@ activeAITabId: string; // Currently active tab ID ### Shortcuts -| Shortcut | Action | -|----------|--------| -| `Cmd+T` | New tab | -| `Cmd+W` | Close current tab | -| `Alt+Cmd+T` | Open tab switcher | -| `Cmd+Shift+]` | Next tab | -| `Cmd+Shift+[` | Previous tab | +| Shortcut | Action | +| ------------- | ----------------- | +| `Cmd+T` | New tab | +| `Cmd+W` | Close current tab | +| `Alt+Cmd+T` | Open tab switcher | +| `Cmd+Shift+]` | Next tab | +| `Cmd+Shift+[` | Previous tab | --- @@ -1008,19 +1030,19 @@ In-tab file viewing that integrates file previews alongside AI conversation tabs ```typescript interface FilePreviewTab { - id: string; // Unique tab ID (UUID) - path: string; // Full file path - name: string; // Filename without extension (tab display name) - extension: string; // File extension with dot (e.g., '.md', '.ts') - content: string; // File content (loaded on open) - scrollTop: number; // Preserved scroll position - searchQuery: string; // Preserved search query - editMode: boolean; // Whether tab was in edit mode - editContent?: string; // Unsaved edit content (if pending changes) - createdAt: number; // Timestamp for ordering - lastModified: number; // File modification time (for refresh detection) - sshRemoteId?: string; // SSH remote ID for remote files - isLoading?: boolean; // True while content is being fetched + id: string; // Unique tab ID (UUID) + path: string; // Full file path + name: string; // Filename without extension (tab display name) + extension: string; // File extension with dot (e.g., '.md', '.ts') + content: string; // File content (loaded on open) + scrollTop: number; // Preserved scroll position + searchQuery: string; // Preserved search query + editMode: boolean; // Whether tab was in edit mode + editContent?: string; // Unsaved edit content (if pending changes) + createdAt: number; // Timestamp for ordering + lastModified: number; // File modification time (for refresh detection) + sshRemoteId?: string; // SSH remote ID for remote files + isLoading?: boolean; // True while content is being fetched } ``` @@ -1034,8 +1056,8 @@ type UnifiedTabRef = { type: 'ai' | 'file'; id: string }; // Discriminated union for rendering type UnifiedTab = - | { type: 'ai'; id: string; data: AITab } - | { type: 'file'; id: string; data: FilePreviewTab }; + | { type: 'ai'; id: string; data: AITab } + | { type: 'file'; id: string; data: FilePreviewTab }; ``` ### Session Fields @@ -1068,29 +1090,29 @@ The shared `buildUnifiedTabs(session)` function in `tabHelpers.ts` is the canoni File tabs display a colored badge based on file extension. Colors are theme-aware (light/dark) and support colorblind-safe palettes: -| Extensions | Light Theme | Dark Theme | Colorblind (Wong palette) | -|------------|-------------|------------|---------------------------| -| .ts, .tsx, .js, .jsx | Blue | Light Blue | #0077BB | -| .md, .mdx, .txt | Green | Light Green | #009988 | -| .json, .yaml, .toml | Amber | Yellow | #EE7733 | -| .css, .scss, .less | Purple | Light Purple | #AA4499 | -| .html, .xml, .svg | Orange | Light Orange | #CC3311 | -| .py | Teal | Cyan | #33BBEE | -| .rs | Rust | Light Rust | #EE3377 | -| .go | Cyan | Light Cyan | #44AA99 | -| .sh, .bash, .zsh | Gray | Light Gray | #BBBBBB | +| Extensions | Light Theme | Dark Theme | Colorblind (Wong palette) | +| -------------------- | ----------- | ------------ | ------------------------- | +| .ts, .tsx, .js, .jsx | Blue | Light Blue | #0077BB | +| .md, .mdx, .txt | Green | Light Green | #009988 | +| .json, .yaml, .toml | Amber | Yellow | #EE7733 | +| .css, .scss, .less | Purple | Light Purple | #AA4499 | +| .html, .xml, .svg | Orange | Light Orange | #CC3311 | +| .py | Teal | Cyan | #33BBEE | +| .rs | Rust | Light Rust | #EE3377 | +| .go | Cyan | Light Cyan | #44AA99 | +| .sh, .bash, .zsh | Gray | Light Gray | #BBBBBB | ### Key Files -| File | Purpose | -|------|---------| -| `TabBar.tsx` | Unified tab rendering with AI and file tabs | -| `FilePreview.tsx` | File content viewer with edit mode | -| `MainPanel.tsx` | Coordinates tab display and file loading | -| `tabHelpers.ts` | Shared tab utilities (`buildUnifiedTabs`, `ensureInUnifiedTabOrder`, `createTab`, `closeTab`, etc.) | -| `useTabHandlers.ts` | Tab operation hooks including `handleOpenFileTab` | -| `tabStore.ts` | Zustand selectors for tab state (`selectUnifiedTabs`, `selectActiveTab`) | -| `useDebouncedPersistence.ts` | Persists file tabs across sessions | +| File | Purpose | +| ---------------------------- | --------------------------------------------------------------------------------------------------- | +| `TabBar.tsx` | Unified tab rendering with AI and file tabs | +| `FilePreview.tsx` | File content viewer with edit mode | +| `MainPanel.tsx` | Coordinates tab display and file loading | +| `tabHelpers.ts` | Shared tab utilities (`buildUnifiedTabs`, `ensureInUnifiedTabOrder`, `createTab`, `closeTab`, etc.) | +| `useTabHandlers.ts` | Tab operation hooks including `handleOpenFileTab` | +| `tabStore.ts` | Zustand selectors for tab state (`selectUnifiedTabs`, `selectActiveTab`) | +| `useDebouncedPersistence.ts` | Persists file tabs across sessions | --- @@ -1100,22 +1122,22 @@ Sequential message processing system that prevents race conditions when multiple ### Components -| Component | Purpose | -|-----------|---------| -| `ExecutionQueueIndicator.tsx` | Shows queue status in tab bar | -| `ExecutionQueueBrowser.tsx` | Modal for viewing/managing queue | +| Component | Purpose | +| ----------------------------- | -------------------------------- | +| `ExecutionQueueIndicator.tsx` | Shows queue status in tab bar | +| `ExecutionQueueBrowser.tsx` | Modal for viewing/managing queue | ### Queue Item Types ```typescript interface QueuedItem { - id: string; - type: 'message' | 'command'; - content: string; - tabId: string; - readOnlyMode: boolean; - timestamp: number; - source: 'user' | 'autorun'; + id: string; + type: 'message' | 'command'; + content: string; + tabId: string; + readOnlyMode: boolean; + timestamp: number; + source: 'user' | 'autorun'; } ``` @@ -1147,9 +1169,9 @@ Back/forward navigation through agents and tabs, similar to browser history. ```typescript interface NavigationEntry { - sessionId: string; - tabId?: string; - timestamp: number; + sessionId: string; + tabId?: string; + timestamp: number; } ``` @@ -1161,9 +1183,9 @@ interface NavigationEntry { ### Shortcuts -| Shortcut | Action | -|----------|--------| -| `Cmd+Shift+,` | Navigate back | +| Shortcut | Action | +| ------------- | ---------------- | +| `Cmd+Shift+,` | Navigate back | | `Cmd+Shift+.` | Navigate forward | --- @@ -1245,13 +1267,13 @@ src/main/group-chat/ └── group-chat-agent.ts # Participant agent management ``` -| Component | Purpose | -|-----------|---------| -| `group-chat-storage.ts` | CRUD operations for group chats, participants, history entries | -| `group-chat-log.ts` | Append-only log of all messages (user, moderator, agents) | -| `group-chat-moderator.ts` | Spawns moderator AI, defines system prompts | -| `group-chat-router.ts` | Routes messages between user, moderator, and agents | -| `group-chat-agent.ts` | Adds/removes participant agents | +| Component | Purpose | +| ------------------------- | -------------------------------------------------------------- | +| `group-chat-storage.ts` | CRUD operations for group chats, participants, history entries | +| `group-chat-log.ts` | Append-only log of all messages (user, moderator, agents) | +| `group-chat-moderator.ts` | Spawns moderator AI, defines system prompts | +| `group-chat-router.ts` | Routes messages between user, moderator, and agents | +| `group-chat-agent.ts` | Adds/removes participant agents | ### Message Routing @@ -1259,25 +1281,25 @@ src/main/group-chat/ The router uses session ID patterns to identify message sources: -| Pattern | Source | -|---------|--------| -| `group-chat-{chatId}-moderator-{timestamp}` | Moderator process | +| Pattern | Source | +| ---------------------------------------------------- | ----------------- | +| `group-chat-{chatId}-moderator-{timestamp}` | Moderator process | | `group-chat-{chatId}-participant-{name}-{timestamp}` | Agent participant | #### Routing Functions ```typescript // User → Moderator -routeUserMessage(groupChatId, message, processManager, agentDetector, readOnly) +routeUserMessage(groupChatId, message, processManager, agentDetector, readOnly); // Moderator → Agents (extracts @mentions, spawns agent processes) -routeModeratorResponse(groupChatId, message, processManager, agentDetector, readOnly) +routeModeratorResponse(groupChatId, message, processManager, agentDetector, readOnly); // Agent → Back to moderator (triggers synthesis when all agents respond) -routeAgentResponse(groupChatId, participantName, message, processManager) +routeAgentResponse(groupChatId, participantName, message, processManager); // Synthesis round (after all agents respond) -spawnModeratorSynthesis(groupChatId, processManager, agentDetector) +spawnModeratorSynthesis(groupChatId, processManager, agentDetector); ``` ### The Synthesis Flow @@ -1297,12 +1319,12 @@ This is enforced by `routeModeratorResponse()` which checks for @mentions: // Extract mentions and route to agents const mentions = extractMentions(message, participants); if (mentions.length > 0) { - // Spawn agent processes, track pending responses - for (const participantName of mentions) { - // ... spawn batch process for agent - participantsToRespond.add(participantName); - } - pendingParticipantResponses.set(groupChatId, participantsToRespond); + // Spawn agent processes, track pending responses + for (const participantName of mentions) { + // ... spawn batch process for agent + participantsToRespond.add(participantName); + } + pendingParticipantResponses.set(groupChatId, participantsToRespond); } // If no mentions, message is logged but no agents are spawned // = conversation turn complete, ball is back with user @@ -1313,12 +1335,14 @@ if (mentions.length > 0) { Two key prompts control moderator behavior: **MODERATOR_SYSTEM_PROMPT** - Base instructions for all moderator interactions: + - Assist user directly for simple questions - Coordinate agents via @mentions when needed - Control conversation flow - Return to user only when answer is complete **MODERATOR_SYNTHESIS_PROMPT** - Used when reviewing agent responses: + - Synthesize if responses are complete (NO @mentions) - @mention agents if more info needed - Keep going until user's question is answered @@ -1386,11 +1410,11 @@ User: "How does @Maestro and @RunMaestro.ai relate?" ```typescript // Real-time updates to renderer -groupChatEmitters.emitMessage(chatId, message) // New message -groupChatEmitters.emitStateChange(chatId, state) // 'idle' | 'agent-working' -groupChatEmitters.emitParticipantsChanged(chatId, list) // Participant list updated -groupChatEmitters.emitHistoryEntry(chatId, entry) // New history entry -groupChatEmitters.emitModeratorUsage(chatId, usage) // Token usage stats +groupChatEmitters.emitMessage(chatId, message); // New message +groupChatEmitters.emitStateChange(chatId, state); // 'idle' | 'agent-working' +groupChatEmitters.emitParticipantsChanged(chatId, list); // Participant list updated +groupChatEmitters.emitHistoryEntry(chatId, entry); // New history entry +groupChatEmitters.emitModeratorUsage(chatId, usage); // Token usage stats ``` ### Storage Structure @@ -1426,35 +1450,35 @@ src/web/ ### Mobile Components (`src/web/mobile/`) -| Component | Purpose | -|-----------|---------| -| `App.tsx` | Main mobile app shell | -| `TabBar.tsx` | Bottom navigation bar | -| `SessionPillBar.tsx` | Horizontal session selector | -| `CommandInputBar.tsx` | Message input with voice support | -| `MessageHistory.tsx` | Conversation display | -| `ResponseViewer.tsx` | AI response viewer | -| `AutoRunIndicator.tsx` | Auto Run status display | -| `MobileHistoryPanel.tsx` | Command history browser | -| `QuickActionsMenu.tsx` | Quick action shortcuts | -| `SlashCommandAutocomplete.tsx` | Command autocomplete | -| `ConnectionStatusIndicator.tsx` | WebSocket connection status | -| `OfflineQueueBanner.tsx` | Offline message queue indicator | +| Component | Purpose | +| ------------------------------- | -------------------------------- | +| `App.tsx` | Main mobile app shell | +| `TabBar.tsx` | Bottom navigation bar | +| `SessionPillBar.tsx` | Horizontal session selector | +| `CommandInputBar.tsx` | Message input with voice support | +| `MessageHistory.tsx` | Conversation display | +| `ResponseViewer.tsx` | AI response viewer | +| `AutoRunIndicator.tsx` | Auto Run status display | +| `MobileHistoryPanel.tsx` | Command history browser | +| `QuickActionsMenu.tsx` | Quick action shortcuts | +| `SlashCommandAutocomplete.tsx` | Command autocomplete | +| `ConnectionStatusIndicator.tsx` | WebSocket connection status | +| `OfflineQueueBanner.tsx` | Offline message queue indicator | ### Web Hooks (`src/web/hooks/`) -| Hook | Purpose | -|------|---------| -| `useWebSocket.ts` | WebSocket connection management | -| `useSessions.ts` | Session state synchronization | -| `useCommandHistory.ts` | Command history management | -| `useSwipeGestures.ts` | Touch gesture handling | -| `usePullToRefresh.ts` | Pull-to-refresh functionality | -| `useOfflineQueue.ts` | Offline message queuing | -| `useNotifications.ts` | Push notification handling | -| `useDeviceColorScheme.ts` | System theme detection | -| `useUnreadBadge.ts` | Unread message badge | -| `useSwipeUp.ts` | Swipe-up gesture detection | +| Hook | Purpose | +| ------------------------- | ------------------------------- | +| `useWebSocket.ts` | WebSocket connection management | +| `useSessions.ts` | Session state synchronization | +| `useCommandHistory.ts` | Command history management | +| `useSwipeGestures.ts` | Touch gesture handling | +| `usePullToRefresh.ts` | Pull-to-refresh functionality | +| `useOfflineQueue.ts` | Offline message queuing | +| `useNotifications.ts` | Push notification handling | +| `useDeviceColorScheme.ts` | System theme detection | +| `useUnreadBadge.ts` | Unread message badge | +| `useSwipeUp.ts` | Swipe-up gesture detection | ### Communication @@ -1507,14 +1531,14 @@ src/cli/ ### Available Commands -| Command | Description | -|---------|-------------| -| `maestro list-agents` | List available AI agents | -| `maestro list-groups` | List session groups | -| `maestro list-playbooks` | List saved playbooks | -| `maestro show-agent ` | Show agent details | +| Command | Description | +| ---------------------------- | --------------------------- | +| `maestro list-agents` | List available AI agents | +| `maestro list-groups` | List session groups | +| `maestro list-playbooks` | List saved playbooks | +| `maestro show-agent ` | Show agent details | | `maestro show-playbook ` | Show playbook configuration | -| `maestro run-playbook ` | Execute a playbook | +| `maestro run-playbook ` | Execute a playbook | ### Output Formats @@ -1543,10 +1567,10 @@ src/shared/ ```typescript // Shared theme interface used by renderer, main (web server), and web client interface Theme { - id: ThemeId; - name: string; - mode: 'light' | 'dark' | 'vibe'; - colors: ThemeColors; + id: ThemeId; + name: string; + mode: 'light' | 'dark' | 'vibe'; + colors: ThemeColors; } ``` @@ -1556,12 +1580,36 @@ Utilities for processing template variables in Custom AI Commands: ```typescript // Available variables -{{date}} // YYYY-MM-DD -{{time}} // HH:MM:SS -{{datetime}} // Combined -{{cwd}} // Working directory -{{session}} // Session name -{{agent}} // Agent type +{ + { + date; + } +} // YYYY-MM-DD +{ + { + time; + } +} // HH:MM:SS +{ + { + datetime; + } +} // Combined +{ + { + cwd; + } +} // Working directory +{ + { + session; + } +} // Session name +{ + { + agent; + } +} // Agent type ``` --- @@ -1572,19 +1620,19 @@ Secure remote access to Maestro via Cloudflare Tunnels. ### Architecture -| Component | Purpose | -|-----------|---------| -| `tunnel-manager.ts` | Manages cloudflared process lifecycle | -| `utils/cliDetection.ts` | Detects cloudflared installation | -| `utils/networkUtils.ts` | Local IP detection for LAN access | +| Component | Purpose | +| ----------------------- | ------------------------------------- | +| `tunnel-manager.ts` | Manages cloudflared process lifecycle | +| `utils/cliDetection.ts` | Detects cloudflared installation | +| `utils/networkUtils.ts` | Local IP detection for LAN access | ### Tunnel Manager ```typescript interface TunnelStatus { - active: boolean; - url?: string; // Public tunnel URL - error?: string; + active: boolean; + url?: string; // Public tunnel URL + error?: string; } // IPC API @@ -1612,62 +1660,66 @@ window.maestro.tunnel.onStatusChange(callback); ### IPC Handlers (Main Process) **Pattern 1: Throw for critical failures** + ```typescript ipcMain.handle('process:spawn', async (_, config) => { - if (!processManager) throw new Error('Process manager not initialized'); - return processManager.spawn(config); + if (!processManager) throw new Error('Process manager not initialized'); + return processManager.spawn(config); }); ``` **Pattern 2: Try-catch with boolean return** + ```typescript ipcMain.handle('git:isRepo', async (_, cwd) => { - try { - const result = await execFileNoThrow('git', ['rev-parse', '--is-inside-work-tree'], cwd); - return result.exitCode === 0; - } catch { - return false; - } + try { + const result = await execFileNoThrow('git', ['rev-parse', '--is-inside-work-tree'], cwd); + return result.exitCode === 0; + } catch { + return false; + } }); ``` ### Services (Renderer) **Pattern: Never throw, return safe defaults** + ```typescript export const gitService = { - async isRepo(cwd: string): Promise { - try { - return await window.maestro.git.isRepo(cwd); - } catch (error) { - console.error('Git isRepo error:', error); - return false; - } - }, + async isRepo(cwd: string): Promise { + try { + return await window.maestro.git.isRepo(cwd); + } catch (error) { + console.error('Git isRepo error:', error); + return false; + } + }, }; ``` ### React Components **Pattern: Try-catch with user-friendly errors** + ```typescript const handleFileLoad = async (path: string) => { - try { - const content = await window.maestro.fs.readFile(path); - setFileContent(content); - } catch (error) { - console.error('Failed to load file:', error); - setError('Failed to load file'); - } + try { + const content = await window.maestro.fs.readFile(path); + setFileContent(content); + } catch (error) { + console.error('Failed to load file:', error); + setError('Failed to load file'); + } }; ``` ### Summary -| Layer | Pattern | -|-------|---------| -| IPC Handlers | Throw critical, catch optional | -| Services | Never throw, safe defaults | +| Layer | Pattern | +| -------------- | ----------------------------------------- | +| IPC Handlers | Throw critical, catch optional | +| Services | Never throw, safe defaults | | ProcessManager | Throw spawn failures, emit runtime events | -| Components | Try-catch async, show UI errors | -| Hooks | Internal catch, expose error state | +| Components | Try-catch async, show UI errors | +| Hooks | Internal catch, expose error state | diff --git a/BUILDING_WINDOWS.md b/BUILDING_WINDOWS.md index d58850ae3..04cff0447 100644 --- a/BUILDING_WINDOWS.md +++ b/BUILDING_WINDOWS.md @@ -6,26 +6,26 @@ This guide provides instructions for setting up your environment and running the Before you begin, ensure you have the following installed: -* **Node.js:** Version 22 or later. -* **Python:** Version 3 or later. +- **Node.js:** Version 22 or later. +- **Python:** Version 3 or later. Additionally, you will need the Visual Studio Build Tools to compile native Node.js modules used in this project. ### Installing Visual Studio Build Tools 1. **Download the Build Tools:** - * Go to the [Visual Studio Downloads page](https://my.visualstudio.com/Downloads?q=Visual%20Studio%202022). - * You may need to log in with a Microsoft account. - * Download the **Build Tools for Visual Studio 2022**. + - Go to the [Visual Studio Downloads page](https://my.visualstudio.com/Downloads?q=Visual%20Studio%202022). + - You may need to log in with a Microsoft account. + - Download the **Build Tools for Visual Studio 2022**. 2. **Run the Installer:** - * When the installer launches, you will be prompted to select workloads. - * Check the box for **Desktop development with C++**. - * Proceed with the installation. + - When the installer launches, you will be prompted to select workloads. + - Check the box for **Desktop development with C++**. + - Proceed with the installation. 3. **Verify the Setup:** - * After the installation is complete, open a PowerShell terminal in the project root. - * Run `npm ci` to install dependencies. If you have already run `npm install`, you can run `npx electron-rebuild` to rebuild the native modules. + - After the installation is complete, open a PowerShell terminal in the project root. + - Run `npm ci` to install dependencies. If you have already run `npm install`, you can run `npx electron-rebuild` to rebuild the native modules. ## Running the Application in Development Mode @@ -36,6 +36,7 @@ There are two ways to run the application in development mode on Windows: using The easiest way to start the development environment is to use the `dev:win` npm script. This script automates the entire process. Open a PowerShell terminal and run: + ```powershell npm run dev:win ``` @@ -47,11 +48,13 @@ This will handle all the necessary build steps and launch the application. If you encounter issues with the `dev:win` script or prefer to run the steps manually, follow this procedure. 1. **Build the application:** + ```powershell npm run build ``` 2. **Start the Vite renderer:** + ```powershell npm run dev:renderer ``` diff --git a/CLAUDE-AGENTS.md b/CLAUDE-AGENTS.md index 95f53f434..11e550431 100644 --- a/CLAUDE-AGENTS.md +++ b/CLAUDE-AGENTS.md @@ -4,36 +4,37 @@ Agent support documentation for the Maestro codebase. For the main guide, see [[ ## Supported Agents -| ID | Name | Status | Notes | -|----|------|--------|-------| -| `claude-code` | Claude Code | **Active** | Primary agent, `--print --verbose --output-format stream-json` | -| `codex` | OpenAI Codex | **Active** | Full support, `--json`, YOLO mode default | -| `opencode` | OpenCode | **Active** | Multi-provider support (75+ LLMs), stub provider session storage | -| `factory-droid` | Factory Droid | **Active** | Factory's AI coding assistant, `-o stream-json` | -| `terminal` | Terminal | Internal | Hidden from UI, used for shell sessions | +| ID | Name | Status | Notes | +| --------------- | ------------- | ---------- | ---------------------------------------------------------------- | +| `claude-code` | Claude Code | **Active** | Primary agent, `--print --verbose --output-format stream-json` | +| `codex` | OpenAI Codex | **Active** | Full support, `--json`, YOLO mode default | +| `opencode` | OpenCode | **Active** | Multi-provider support (75+ LLMs), stub provider session storage | +| `factory-droid` | Factory Droid | **Active** | Factory's AI coding assistant, `-o stream-json` | +| `terminal` | Terminal | Internal | Hidden from UI, used for shell sessions | ## Agent Capabilities Each agent declares capabilities that control UI feature availability. See `src/main/agent-capabilities.ts` for the full interface. -| Capability | Description | UI Feature Controlled | -|------------|-------------|----------------------| -| `supportsResume` | Can resume previous conversations | Resume button | -| `supportsReadOnlyMode` | Has plan/read-only mode | Read-only toggle | -| `supportsJsonOutput` | Emits structured JSON | Output parsing | -| `supportsSessionId` | Emits provider session ID | Session ID pill | -| `supportsImageInput` | Accepts image attachments | Attach image button | -| `supportsSlashCommands` | Has discoverable commands | Slash autocomplete | -| `supportsSessionStorage` | Persists browsable provider sessions | Sessions browser | -| `supportsCostTracking` | Reports token costs | Cost widget | -| `supportsUsageStats` | Reports token counts | Context window widget | -| `supportsBatchMode` | Runs per-message | Batch processing | -| `supportsStreaming` | Streams output | Real-time display | -| `supportsResultMessages` | Distinguishes final result | Message classification | +| Capability | Description | UI Feature Controlled | +| ------------------------ | ------------------------------------ | ---------------------- | +| `supportsResume` | Can resume previous conversations | Resume button | +| `supportsReadOnlyMode` | Has plan/read-only mode | Read-only toggle | +| `supportsJsonOutput` | Emits structured JSON | Output parsing | +| `supportsSessionId` | Emits provider session ID | Session ID pill | +| `supportsImageInput` | Accepts image attachments | Attach image button | +| `supportsSlashCommands` | Has discoverable commands | Slash autocomplete | +| `supportsSessionStorage` | Persists browsable provider sessions | Sessions browser | +| `supportsCostTracking` | Reports token costs | Cost widget | +| `supportsUsageStats` | Reports token counts | Context window widget | +| `supportsBatchMode` | Runs per-message | Batch processing | +| `supportsStreaming` | Streams output | Real-time display | +| `supportsResultMessages` | Distinguishes final result | Message classification | ## Agent-Specific Details ### Claude Code + - **Binary:** `claude` - **JSON Output:** `--output-format stream-json` - **Resume:** `--resume ` @@ -41,6 +42,7 @@ Each agent declares capabilities that control UI feature availability. See `src/ - **Session Storage:** `~/.claude/projects//` ### OpenAI Codex + - **Binary:** `codex` - **JSON Output:** `--json` - **Batch Mode:** `exec` subcommand @@ -50,6 +52,7 @@ Each agent declares capabilities that control UI feature availability. See `src/ - **Session Storage:** `~/.codex/sessions/YYYY/MM/DD/*.jsonl` ### OpenCode + - **Binary:** `opencode` - **JSON Output:** `--format json` - **Batch Mode:** `run` subcommand diff --git a/CLAUDE-FEATURES.md b/CLAUDE-FEATURES.md index 02be44cee..e0dd7e0d7 100644 --- a/CLAUDE-FEATURES.md +++ b/CLAUDE-FEATURES.md @@ -38,26 +38,29 @@ src/main/ ### Key Patterns **Real-time Updates:** + ```typescript // Backend broadcasts after each database write mainWindow?.webContents.send('stats:updated'); // Frontend subscribes with debouncing useEffect(() => { - const unsubscribe = window.maestro.stats.onStatsUpdated(() => { - debouncedRefresh(); - }); - return () => unsubscribe?.(); + const unsubscribe = window.maestro.stats.onStatsUpdated(() => { + debouncedRefresh(); + }); + return () => unsubscribe?.(); }, []); ``` **Colorblind-Friendly Palettes:** + ```typescript import { COLORBLIND_AGENT_PALETTE, getColorBlindAgentColor } from '../constants/colorblindPalettes'; // Wong-based palette with high contrast for accessibility ``` **Chart Error Boundaries:** + ```typescript @@ -68,10 +71,10 @@ import { COLORBLIND_AGENT_PALETTE, getColorBlindAgentColor } from '../constants/ ```typescript // In useSettings.ts -statsCollectionEnabled: boolean // Enable/disable stats collection (default: true) -defaultStatsTimeRange: 'day' | 'week' | 'month' | 'year' | 'all' // Default time filter -colorBlindMode: boolean // Use accessible color palettes -preventSleepEnabled: boolean // Prevent system sleep while agents are busy (default: false) +statsCollectionEnabled: boolean; // Enable/disable stats collection (default: true) +defaultStatsTimeRange: 'day' | 'week' | 'month' | 'year' | 'all'; // Default time filter +colorBlindMode: boolean; // Use accessible color palettes +preventSleepEnabled: boolean; // Prevent system sleep while agents are busy (default: false) ``` --- @@ -104,27 +107,33 @@ src/main/ipc/handlers/ ### Key Patterns **Building Graph Data:** + ```typescript import { buildGraphData } from './graphDataBuilder'; const { nodes, edges, stats } = await buildGraphData( - rootPath, - showExternalLinks, - maxNodes, - offset, - progressCallback + rootPath, + showExternalLinks, + maxNodes, + offset, + progressCallback ); ``` **Layout Algorithms:** + ```typescript -import { applyForceLayout, applyHierarchicalLayout, animateLayoutTransition } from './layoutAlgorithms'; -const positionedNodes = layoutMode === 'force' - ? applyForceLayout(nodes, edges) - : applyHierarchicalLayout(nodes, edges); +import { + applyForceLayout, + applyHierarchicalLayout, + animateLayoutTransition, +} from './layoutAlgorithms'; +const positionedNodes = + layoutMode === 'force' ? applyForceLayout(nodes, edges) : applyHierarchicalLayout(nodes, edges); animateLayoutTransition(currentNodes, positionedNodes, setNodes, savePositions); ``` **Node Animation (additions/removals):** + ```typescript import { diffNodes, animateNodesEntering, animateNodesExiting } from './layoutAlgorithms'; const { added, removed, stable } = diffNodes(previousNodes, newNodes); @@ -132,17 +141,19 @@ animateNodesExiting(removed, () => animateNodesEntering(added)); ``` **Real-time File Watching:** + ```typescript // Backend watches for .md file changes window.maestro.documentGraph.watchFolder(rootPath); window.maestro.documentGraph.onFilesChanged((changes) => { - debouncedRebuildGraph(); + debouncedRebuildGraph(); }); // Cleanup on modal close window.maestro.documentGraph.unwatchFolder(rootPath); ``` **Keyboard Navigation:** + ```typescript // Arrow keys navigate to connected nodes (spatial detection) // Enter opens selected node @@ -153,14 +164,16 @@ window.maestro.documentGraph.unwatchFolder(rootPath); ### Large File Handling Files over 1MB are truncated to first 100KB for link extraction to prevent UI blocking: + ```typescript -const LARGE_FILE_THRESHOLD = 1 * 1024 * 1024; // 1MB -const LARGE_FILE_PARSE_LIMIT = 100 * 1024; // 100KB +const LARGE_FILE_THRESHOLD = 1 * 1024 * 1024; // 1MB +const LARGE_FILE_PARSE_LIMIT = 100 * 1024; // 100KB ``` ### Pagination Default limit of 50 nodes with "Load more" for large directories: + ```typescript const DEFAULT_MAX_NODES = 50; const LOAD_MORE_INCREMENT = 25; @@ -170,6 +183,6 @@ const LOAD_MORE_INCREMENT = 25; ```typescript // In useSettings.ts -documentGraphShowExternalLinks: boolean // Show external link nodes (default: false) -documentGraphMaxNodes: number // Initial pagination limit (50-1000, default: 50) +documentGraphShowExternalLinks: boolean; // Show external link nodes (default: false) +documentGraphMaxNodes: number; // Initial pagination limit (50-1000, default: 50) ``` diff --git a/CLAUDE-PATTERNS.md b/CLAUDE-PATTERNS.md index 551587dd2..2b4d39977 100644 --- a/CLAUDE-PATTERNS.md +++ b/CLAUDE-PATTERNS.md @@ -5,18 +5,20 @@ Core implementation patterns for the Maestro codebase. For the main guide, see [ ## 1. Process Management Each agent runs **two processes** simultaneously: + - AI agent process (Claude Code, etc.) - spawned with `-ai` suffix - Terminal process (PTY shell) - spawned with `-terminal` suffix ```typescript // Agent stores both PIDs (code interface: Session object) -session.aiPid // AI agent process -session.terminalPid // Terminal process +session.aiPid; // AI agent process +session.terminalPid; // Terminal process ``` ## 2. Security Requirements **Always use `execFileNoThrow`** for external commands: + ```typescript import { execFileNoThrow } from './utils/execFile'; const result = await execFileNoThrow('git', ['status'], cwd); @@ -28,14 +30,15 @@ const result = await execFileNoThrow('git', ['status'], cwd); ## 3. Settings Persistence Add new settings in `useSettings.ts`: + ```typescript // 1. Add state with default value const [mySetting, setMySettingState] = useState(defaultValue); // 2. Add wrapper that persists const setMySetting = (value) => { - setMySettingState(value); - window.maestro.settings.set('mySetting', value); + setMySettingState(value); + window.maestro.settings.set('mySetting', value); }; // 3. Load from batch response in useEffect (settings use batch loading) @@ -60,20 +63,21 @@ const onCloseRef = useRef(onClose); onCloseRef.current = onClose; useEffect(() => { - if (isOpen) { - const id = registerLayer({ - type: 'modal', - priority: MODAL_PRIORITIES.YOUR_MODAL, - onEscape: () => onCloseRef.current(), - }); - return () => unregisterLayer(id); - } + if (isOpen) { + const id = registerLayer({ + type: 'modal', + priority: MODAL_PRIORITIES.YOUR_MODAL, + onEscape: () => onCloseRef.current(), + }); + return () => unregisterLayer(id); + } }, [isOpen, registerLayer, unregisterLayer]); ``` ## 5. Theme Colors Themes have 13 required colors. Use inline styles for theme colors: + ```typescript style={{ color: theme.colors.textMain }} // Correct className="text-gray-500" // Wrong for themed text @@ -100,34 +104,36 @@ session.activeFileTabId: string | null // Active file tab (null if AI tab ac ### When Adding Tabs Always update both the tab array AND `unifiedTabOrder`: + ```typescript // CORRECT — tab appears in TabBar return { - ...s, - aiTabs: [...s.aiTabs, newTab], - activeTabId: newTabId, - unifiedTabOrder: [...s.unifiedTabOrder, { type: 'ai', id: newTabId }], + ...s, + aiTabs: [...s.aiTabs, newTab], + activeTabId: newTabId, + unifiedTabOrder: [...s.unifiedTabOrder, { type: 'ai', id: newTabId }], }; // WRONG — tab content renders but no tab visible return { - ...s, - aiTabs: [...s.aiTabs, newTab], - activeTabId: newTabId, - // unifiedTabOrder not updated — ghost tab! + ...s, + aiTabs: [...s.aiTabs, newTab], + activeTabId: newTabId, + // unifiedTabOrder not updated — ghost tab! }; ``` ### When Activating Existing Tabs Use `ensureInUnifiedTabOrder()` to repair orphaned tabs defensively: + ```typescript import { ensureInUnifiedTabOrder } from '../utils/tabHelpers'; return { - ...s, - activeFileTabId: existingTab.id, - unifiedTabOrder: ensureInUnifiedTabOrder(s.unifiedTabOrder, 'file', existingTab.id), + ...s, + activeFileTabId: existingTab.id, + unifiedTabOrder: ensureInUnifiedTabOrder(s.unifiedTabOrder, 'file', existingTab.id), }; ``` @@ -139,12 +145,13 @@ return { ## 7. Execution Queue Messages are queued when the AI is busy: + ```typescript // Queue items for sequential execution interface QueuedItem { - type: 'message' | 'slashCommand'; - content: string; - timestamp: number; + type: 'message' | 'slashCommand'; + content: string; + timestamp: number; } // Add to queue instead of sending directly when busy @@ -154,6 +161,7 @@ session.executionQueue.push({ type: 'message', content, timestamp: Date.now() }) ## 8. Auto Run File-based document automation system: + ```typescript // Auto Run state on session session.autoRunFolderPath?: string; // Document folder path @@ -182,6 +190,7 @@ playbook-folder/ ``` Documents can reference assets using `{{AUTORUN_FOLDER}}/assets/filename`. The manifest lists assets explicitly: + ```json { "id": "example-playbook", @@ -195,6 +204,7 @@ Documents can reference assets using `{{AUTORUN_FOLDER}}/assets/filename`. The m AI conversation tabs display a hover overlay menu after a 400ms delay when hovering over tabs with an established provider session. The overlay includes tab management and context operations: **Menu Structure:** + ```typescript // Tab operations (always shown) - Copy Session ID (if provider session exists) @@ -215,6 +225,7 @@ AI conversation tabs display a hover overlay menu after a 400ms delay when hover ``` **Implementation Pattern:** + ```typescript const [overlayOpen, setOverlayOpen] = useState(false); const [overlayPosition, setOverlayPosition] = useState<{ top: number; left: number } | null>(null); @@ -241,6 +252,7 @@ const handleMouseEnter = () => { ``` **Key Features:** + - Appears after 400ms hover delay (only for tabs with `agentSessionId`) - Fixed positioning at tab bottom - Mouse can move from tab to overlay without closing @@ -277,11 +289,13 @@ const sshId = session.sshRemoteId || session.sessionSshRemoteConfig?.remoteId; ``` This applies to any operation that needs to run on the remote: + - `window.maestro.fs.readDir(path, sshId)` - `gitService.isRepo(path, sshId)` - Directory existence checks for `cd` command tracking Similarly for checking if an agent is remote: + ```typescript // WRONG const isRemote = !!session.sshRemoteId; @@ -306,6 +320,7 @@ When debugging visual issues (tooltips clipped, elements not visible, scroll beh 4. **Fixed Positioning:** Elements with `position: fixed` inside transformed parents won't position relative to viewport—check ancestor transforms **Common fixes:** + ```typescript // Tooltip/overlay escaping parent overflow import { createPortal } from 'react-dom'; diff --git a/CLAUDE-PERFORMANCE.md b/CLAUDE-PERFORMANCE.md index 3cdaeaa97..8493ec7bc 100644 --- a/CLAUDE-PERFORMANCE.md +++ b/CLAUDE-PERFORMANCE.md @@ -5,6 +5,7 @@ Performance best practices for the Maestro codebase. For the main guide, see [[C ## React Component Optimization **Use `React.memo` for list item components:** + ```typescript // Components rendered in arrays (tabs, agents, list items) should be memoized const Tab = memo(function Tab({ tab, isActive, ... }: TabProps) { @@ -22,6 +23,7 @@ const Tab = memo(function Tab({ tab, isActive, ... }: TabProps) { ``` **Consolidate chained `useMemo` calls:** + ```typescript // BAD: Multiple dependent useMemo calls create cascade re-computations const filtered = useMemo(() => agents.filter(...), [agents]); @@ -38,20 +40,22 @@ const { filtered, sorted, grouped } = useMemo(() => { ``` **Pre-compile regex patterns at module level:** + ```typescript // BAD: Regex compiled on every render const Component = () => { - const cleaned = text.replace(/^(\p{Emoji})+\s*/u, ''); + const cleaned = text.replace(/^(\p{Emoji})+\s*/u, ''); }; // GOOD: Compile once at module load const LEADING_EMOJI_REGEX = /^(\p{Emoji})+\s*/u; const Component = () => { - const cleaned = text.replace(LEADING_EMOJI_REGEX, ''); + const cleaned = text.replace(LEADING_EMOJI_REGEX, ''); }; ``` **Memoize helper function results used in render body:** + ```typescript // BAD: O(n) lookup on every keystroke (runs on every render) const activeTab = activeSession ? getActiveTab(activeSession) : undefined; @@ -59,8 +63,8 @@ const activeTab = activeSession ? getActiveTab(activeSession) : undefined; // GOOD: Memoize once, use everywhere const activeTab = useMemo( - () => activeSession ? getActiveTab(activeSession) : undefined, - [activeSession?.aiTabs, activeSession?.activeTabId] + () => (activeSession ? getActiveTab(activeSession) : undefined), + [activeSession?.aiTabs, activeSession?.activeTabId] ); // Use activeTab directly in JSX - no repeated lookups ``` @@ -68,6 +72,7 @@ const activeTab = useMemo( ## Data Structure Pre-computation **Build indices once, reuse in renders:** + ```typescript // BAD: O(n) tree traversal on every markdown render const result = remarkFileLinks({ fileTree, cwd }); @@ -80,6 +85,7 @@ const result = remarkFileLinks({ indices, cwd }); ## Main Process (Node.js) **Cache expensive lookups:** + ```typescript // BAD: Synchronous file check on every shell spawn fs.accessSync(shellPath, fs.constants.X_OK); @@ -93,6 +99,7 @@ shellPathCache.set(shell, resolved); ``` **Use async file operations:** + ```typescript // BAD: Blocking the main process fs.unlinkSync(tempFile); @@ -105,6 +112,7 @@ fsPromises.unlink(tempFile).catch(() => {}); ## Debouncing and Throttling **Use debouncing for user input and persistence:** + ```typescript // Agent persistence uses 2-second debounce to prevent excessive disk I/O // See: src/renderer/hooks/utils/useDebouncedPersistence.ts @@ -112,40 +120,45 @@ const { persist, isPending } = useDebouncedPersistence(session, 2000); // Always flush on visibility change and beforeunload to prevent data loss useEffect(() => { - const handleVisibilityChange = () => { - if (document.hidden) flushPending(); - }; - document.addEventListener('visibilitychange', handleVisibilityChange); - window.addEventListener('beforeunload', flushPending); - return () => { /* cleanup */ }; + const handleVisibilityChange = () => { + if (document.hidden) flushPending(); + }; + document.addEventListener('visibilitychange', handleVisibilityChange); + window.addEventListener('beforeunload', flushPending); + return () => { + /* cleanup */ + }; }, []); ``` **Debounce expensive search operations:** + ```typescript // BAD: Fuzzy matching all files on every keystroke const suggestions = useMemo(() => { - return getAtMentionSuggestions(atMentionFilter); // Runs 2000+ fuzzy matches per keystroke + return getAtMentionSuggestions(atMentionFilter); // Runs 2000+ fuzzy matches per keystroke }, [atMentionFilter]); // GOOD: Debounce the filter value first (100ms is imperceptible) const debouncedFilter = useDebouncedValue(atMentionFilter, 100); const suggestions = useMemo(() => { - return getAtMentionSuggestions(debouncedFilter); // Only runs after user stops typing + return getAtMentionSuggestions(debouncedFilter); // Only runs after user stops typing }, [debouncedFilter]); ``` **Use throttling for high-frequency events:** + ```typescript // Scroll handlers should be throttled to ~4ms (240fps max) const handleScroll = useThrottledCallback(() => { - // expensive scroll logic + // expensive scroll logic }, 4); ``` ## Update Batching **Batch rapid state updates during streaming:** + ```typescript // During AI streaming, IPC triggers 100+ updates/second // Without batching: 100+ React re-renders/second @@ -162,20 +175,22 @@ const handleScroll = useThrottledCallback(() => { ## Virtual Scrolling **Use virtual scrolling for large lists (100+ items):** + ```typescript // See: src/renderer/components/HistoryPanel.tsx import { useVirtualizer } from '@tanstack/react-virtual'; const virtualizer = useVirtualizer({ - count: items.length, - getScrollElement: () => scrollRef.current, - estimateSize: () => 40, // estimated row height + count: items.length, + getScrollElement: () => scrollRef.current, + estimateSize: () => 40, // estimated row height }); ``` ## IPC Parallelization **Parallelize independent async operations:** + ```typescript // BAD: Sequential awaits (4 × 50ms = 200ms) const branches = await git.branch(cwd); @@ -184,23 +199,24 @@ const status = await git.status(cwd); // GOOD: Parallel execution (max 50ms = 4x faster) const [branches, remotes, status] = await Promise.all([ - git.branch(cwd), - git.remote(cwd), - git.status(cwd), + git.branch(cwd), + git.remote(cwd), + git.status(cwd), ]); ``` ## Visibility-Aware Operations **Pause background operations when app is hidden:** + ```typescript // See: src/renderer/hooks/git/useGitStatusPolling.ts const handleVisibilityChange = () => { - if (document.hidden) { - stopPolling(); // Save battery/CPU when backgrounded - } else { - startPolling(); - } + if (document.hidden) { + stopPolling(); // Save battery/CPU when backgrounded + } else { + startPolling(); + } }; document.addEventListener('visibilitychange', handleVisibilityChange); ``` @@ -208,6 +224,7 @@ document.addEventListener('visibilitychange', handleVisibilityChange); ## Context Provider Memoization **Always memoize context values:** + ```typescript // BAD: New object on every render triggers all consumers to re-render return {children}; @@ -223,11 +240,14 @@ return {children}; ## Event Listener Cleanup **Always clean up event listeners:** + ```typescript useEffect(() => { - const handler = (e: Event) => { /* ... */ }; - document.addEventListener('click', handler); - return () => document.removeEventListener('click', handler); + const handler = (e: Event) => { + /* ... */ + }; + document.addEventListener('click', handler); + return () => document.removeEventListener('click', handler); }, []); ``` @@ -240,6 +260,7 @@ For React DevTools profiling workflow, see [[CONTRIBUTING.md#profiling]]. **Exporting DevTools Performance traces:** The Chrome DevTools Performance panel's "Save profile" button fails in Electron with: + ``` NotAllowedError: The request is not allowed by the user agent or the platform in the current context. ``` @@ -249,6 +270,7 @@ This occurs because Electron 28 doesn't fully support the File System Access API **Workarounds:** 1. **Launch with experimental flag** (enables FSAA): + ```bash # macOS /Applications/Maestro.app/Contents/MacOS/Maestro --enable-experimental-web-platform-features @@ -258,10 +280,11 @@ This occurs because Electron 28 doesn't fully support the File System Access API ``` 2. **Use Maestro's native save dialog** (copy trace JSON from DevTools, then in renderer console): + ```javascript - navigator.clipboard.readText().then(data => - window.maestro.dialog.saveFile({ defaultPath: 'trace.json', content: data }) - ); + navigator.clipboard + .readText() + .then((data) => window.maestro.dialog.saveFile({ defaultPath: 'trace.json', content: data })); ``` 3. **Right-click context menu** - Right-click on the flame graph and select "Save profile..." which may use a different code path. diff --git a/CLAUDE-PLATFORM.md b/CLAUDE-PLATFORM.md index 3af32983d..352c87963 100644 --- a/CLAUDE-PLATFORM.md +++ b/CLAUDE-PLATFORM.md @@ -6,15 +6,15 @@ Cross-platform and multi-environment considerations for the Maestro codebase. Fo ## Platform Compatibility Matrix -| Feature | macOS | Windows | Linux | SSH Remote | -|---------|-------|---------|-------|------------| -| Claude Code | Full | Full | Full | Full | -| OpenAI Codex | Full | Full | Full | Full | -| OpenCode | Full | Full | Full | Full | -| Factory Droid | Full | Full | Full | Full | -| File watching (chokidar) | Yes | Yes | Yes | **No** | -| Git worktrees | Yes | Yes | Yes | Yes | -| PTY terminal | Yes | Yes | Yes | N/A | +| Feature | macOS | Windows | Linux | SSH Remote | +| ------------------------ | ----- | ------- | ----- | ---------- | +| Claude Code | Full | Full | Full | Full | +| OpenAI Codex | Full | Full | Full | Full | +| OpenCode | Full | Full | Full | Full | +| Factory Droid | Full | Full | Full | Full | +| File watching (chokidar) | Yes | Yes | Yes | **No** | +| Git worktrees | Yes | Yes | Yes | Yes | +| PTY terminal | Yes | Yes | Yes | N/A | --- @@ -23,36 +23,41 @@ Cross-platform and multi-environment considerations for the Maestro codebase. Fo ### 1. Path Handling **Path separators differ:** + ```typescript // WRONG - hardcoded separator const fullPath = folder + '/' + filename; // CORRECT - use path.join or path.posix for SSH import * as path from 'path'; -const fullPath = path.join(folder, filename); // Local -const remotePath = path.posix.join(folder, filename); // SSH remote +const fullPath = path.join(folder, filename); // Local +const remotePath = path.posix.join(folder, filename); // SSH remote ``` **Path delimiters differ:** + ```typescript // Windows uses ';', Unix uses ':' -const delimiter = path.delimiter; // Use this, don't hardcode +const delimiter = path.delimiter; // Use this, don't hardcode ``` **Tilde expansion:** + ```typescript // Node.js fs does NOT expand ~ automatically import { expandTilde } from '../shared/pathUtils'; -const expanded = expandTilde('~/.config/file'); // Always use this +const expanded = expandTilde('~/.config/file'); // Always use this ``` **Minimum path lengths:** + ```typescript // Validation must account for platform differences -const minPathLength = process.platform === 'win32' ? 4 : 5; // C:\a vs /a/b +const minPathLength = process.platform === 'win32' ? 4 : 5; // C:\a vs /a/b ``` **Windows reserved names:** + ```typescript // CON, PRN, AUX, NUL, COM1-9, LPT1-9 are invalid on Windows const reservedNames = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i; @@ -61,26 +66,30 @@ const reservedNames = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i; ### 2. Shell Detection & Execution **Default shells differ:** + ```typescript // Windows: $SHELL doesn't exist; default to PowerShell const defaultShell = process.platform === 'win32' ? 'powershell.exe' : 'bash'; ``` **Command lookup differs:** + ```typescript // 'which' on Unix, 'where' on Windows const command = process.platform === 'win32' ? 'where' : 'which'; ``` **Executable permissions:** + ```typescript // Unix requires X_OK check; Windows does not if (process.platform !== 'win32') { - await fs.promises.access(filePath, fs.constants.X_OK); + await fs.promises.access(filePath, fs.constants.X_OK); } ``` **Windows shell execution:** + ```typescript // Some Windows commands need shell: true const useShell = isWindows && needsWindowsShell(command); @@ -89,6 +98,7 @@ const useShell = isWindows && needsWindowsShell(command); ### 3. SSH Remote Execution **Two SSH identifiers with different lifecycles:** + ```typescript // sshRemoteId: Set AFTER AI agent spawns (via onSshRemote callback) // sessionSshRemoteConfig.remoteId: Set BEFORE spawn (user configuration) @@ -101,34 +111,38 @@ const sshId = session.sshRemoteId || session.sessionSshRemoteConfig?.remoteId; ``` **File watching not available for SSH:** + ```typescript // chokidar can't watch remote directories if (sshRemoteId) { - // Use polling instead of file watching + // Use polling instead of file watching } ``` **Prompts must go via stdin for SSH:** + ```typescript // IMPORTANT: ALL agent prompts are passed via stdin passthrough for SSH. // This avoids shell escaping issues and command line length limits. if (isSSH) { - // Pass prompt via stdin, not as command line argument + // Pass prompt via stdin, not as command line argument } ``` **Path resolution on remote:** + ```typescript // Don't resolve paths locally when operating on remote // The remote may have different filesystem structure if (isRemote) { - // Use path as-is, normalize slashes only + // Use path as-is, normalize slashes only } ``` ### 4. Agent-Specific Differences **Provider session ID terminology:** + ```typescript // Claude Code: session_id // Codex: thread_id @@ -136,6 +150,7 @@ if (isRemote) { ``` **Storage locations differ per platform:** + ```typescript // Claude Code: ~/.claude/projects// // Codex: ~/.codex/sessions/YYYY/MM/DD/*.jsonl @@ -145,6 +160,7 @@ if (isRemote) { ``` **Resume flags differ:** + ```typescript // Claude Code: --resume // Codex: resume (subcommand, not flag) @@ -152,6 +168,7 @@ if (isRemote) { ``` **Read-only mode flags differ:** + ```typescript // Claude Code: --permission-mode plan // Codex: --sandbox read-only @@ -161,15 +178,17 @@ if (isRemote) { ### 5. Keyboard & Input **macOS Alt key produces special characters:** + ```typescript // Alt+L = '¬', Alt+P = 'π', Alt+U = 'ü' // Must use e.code for Alt key combos, not e.key if (e.altKey && process.platform === 'darwin') { - const key = e.code.replace('Key', '').toLowerCase(); // 'KeyL' -> 'l' + const key = e.code.replace('Key', '').toLowerCase(); // 'KeyL' -> 'l' } ``` **Windows command line length limit:** + ```typescript // cmd.exe has ~8KB command line limit // Use sendPromptViaStdin to bypass this for long prompts @@ -178,12 +197,14 @@ if (e.altKey && process.platform === 'darwin') { ### 6. Git Operations **Git stat format differs:** + ```typescript // GNU stat vs BSD stat have different format specifiers // Use git commands that work cross-platform ``` **Case sensitivity:** + ```typescript // macOS with case-insensitive filesystem: // Renaming "readme.md" to "README.md" may not trigger expected events @@ -208,14 +229,14 @@ When making changes that involve any of the above areas, verify: ## Key Files for Platform Logic -| Concern | Primary Files | -|---------|---------------| -| Path utilities | `src/shared/pathUtils.ts` | -| Shell detection | `src/main/utils/shellDetector.ts` | -| WSL detection | `src/main/utils/wslDetector.ts` | -| CLI detection | `src/main/utils/cliDetection.ts` | -| SSH spawn wrapper | `src/main/utils/ssh-spawn-wrapper.ts` | -| SSH command builder | `src/main/utils/ssh-command-builder.ts` | -| Agent path probing | `src/main/agents/path-prober.ts` | +| Concern | Primary Files | +| ------------------- | ---------------------------------------------------------- | +| Path utilities | `src/shared/pathUtils.ts` | +| Shell detection | `src/main/utils/shellDetector.ts` | +| WSL detection | `src/main/utils/wslDetector.ts` | +| CLI detection | `src/main/utils/cliDetection.ts` | +| SSH spawn wrapper | `src/main/utils/ssh-spawn-wrapper.ts` | +| SSH command builder | `src/main/utils/ssh-command-builder.ts` | +| Agent path probing | `src/main/agents/path-prober.ts` | | Windows diagnostics | `src/main/debug-package/collectors/windows-diagnostics.ts` | -| Safe exec | `src/main/utils/execFile.ts` | +| Safe exec | `src/main/utils/execFile.ts` | diff --git a/CLAUDE-SESSION.md b/CLAUDE-SESSION.md index 36b5bbf4c..630e6212c 100644 --- a/CLAUDE-SESSION.md +++ b/CLAUDE-SESSION.md @@ -10,98 +10,98 @@ Key fields on the Session object (abbreviated - see `src/renderer/types/index.ts ```typescript interface Session { - // Identity - id: string; - name: string; - groupId?: string; // Agent grouping - toolType: ToolType; // 'claude-code' | 'codex' | 'opencode' | 'terminal' - state: SessionState; // 'idle' | 'busy' | 'error' | 'connecting' - inputMode: 'ai' | 'terminal'; // Which process receives input - bookmarked?: boolean; // Pinned to top - - // Paths - cwd: string; // Current working directory (can change via cd) - projectRoot: string; // Initial directory (never changes, used for session storage) - fullPath: string; // Full resolved path - - // Processes - aiPid: number; // AI process ID - port: number; // Web server communication port - - // Multi-Tab Support (AI Tabs) - aiTabs: AITab[]; // Multiple conversation tabs - activeTabId: string; // Currently active AI tab - closedTabHistory: ClosedTab[]; // Undo stack for closed AI tabs - - // File Preview Tabs - filePreviewTabs: FilePreviewTab[]; // Open file preview tabs - activeFileTabId: string | null; // Active file tab (null if AI tab active) - unifiedTabOrder: UnifiedTabRef[]; // Visual order of all tabs (AI + file) - closedUnifiedTabHistory: ClosedUnifiedTab[]; // Unified undo stack for Cmd+Shift+T - - // Logs (per-tab) - shellLogs: LogEntry[]; // Terminal output history - - // Execution Queue - executionQueue: QueuedItem[]; // Sequential execution queue - - // Usage & Stats - usageStats?: UsageStats; // Token usage and cost - contextUsage: number; // Context window usage percentage - workLog: WorkLogItem[]; // Work tracking - - // Git Integration - isGitRepo: boolean; // Git features enabled - changedFiles: FileArtifact[]; // Git change tracking - gitBranches?: string[]; // Branch cache for completion - gitTags?: string[]; // Tag cache for completion - - // File Explorer - fileTree: any[]; // File tree structure - fileExplorerExpanded: string[]; // Expanded folder paths - fileExplorerScrollPos: number; // Scroll position - - // Web/Live Sessions - isLive: boolean; // Accessible via web interface - liveUrl?: string; // Live session URL - - // Auto Run - autoRunFolderPath?: string; // Auto Run document folder - autoRunSelectedFile?: string; // Selected document - autoRunMode?: 'edit' | 'preview'; // Current mode - - // Command History - aiCommandHistory?: string[]; // AI input history - shellCommandHistory?: string[]; // Terminal input history - - // Error Handling - agentError?: AgentError; // Current agent error (auth, tokens, rate limit, etc.) - agentErrorPaused?: boolean; // Input blocked while error modal shown + // Identity + id: string; + name: string; + groupId?: string; // Agent grouping + toolType: ToolType; // 'claude-code' | 'codex' | 'opencode' | 'terminal' + state: SessionState; // 'idle' | 'busy' | 'error' | 'connecting' + inputMode: 'ai' | 'terminal'; // Which process receives input + bookmarked?: boolean; // Pinned to top + + // Paths + cwd: string; // Current working directory (can change via cd) + projectRoot: string; // Initial directory (never changes, used for session storage) + fullPath: string; // Full resolved path + + // Processes + aiPid: number; // AI process ID + port: number; // Web server communication port + + // Multi-Tab Support (AI Tabs) + aiTabs: AITab[]; // Multiple conversation tabs + activeTabId: string; // Currently active AI tab + closedTabHistory: ClosedTab[]; // Undo stack for closed AI tabs + + // File Preview Tabs + filePreviewTabs: FilePreviewTab[]; // Open file preview tabs + activeFileTabId: string | null; // Active file tab (null if AI tab active) + unifiedTabOrder: UnifiedTabRef[]; // Visual order of all tabs (AI + file) + closedUnifiedTabHistory: ClosedUnifiedTab[]; // Unified undo stack for Cmd+Shift+T + + // Logs (per-tab) + shellLogs: LogEntry[]; // Terminal output history + + // Execution Queue + executionQueue: QueuedItem[]; // Sequential execution queue + + // Usage & Stats + usageStats?: UsageStats; // Token usage and cost + contextUsage: number; // Context window usage percentage + workLog: WorkLogItem[]; // Work tracking + + // Git Integration + isGitRepo: boolean; // Git features enabled + changedFiles: FileArtifact[]; // Git change tracking + gitBranches?: string[]; // Branch cache for completion + gitTags?: string[]; // Tag cache for completion + + // File Explorer + fileTree: any[]; // File tree structure + fileExplorerExpanded: string[]; // Expanded folder paths + fileExplorerScrollPos: number; // Scroll position + + // Web/Live Sessions + isLive: boolean; // Accessible via web interface + liveUrl?: string; // Live session URL + + // Auto Run + autoRunFolderPath?: string; // Auto Run document folder + autoRunSelectedFile?: string; // Selected document + autoRunMode?: 'edit' | 'preview'; // Current mode + + // Command History + aiCommandHistory?: string[]; // AI input history + shellCommandHistory?: string[]; // Terminal input history + + // Error Handling + agentError?: AgentError; // Current agent error (auth, tokens, rate limit, etc.) + agentErrorPaused?: boolean; // Input blocked while error modal shown } interface AITab { - id: string; - name: string; - logs: LogEntry[]; // Tab-specific conversation history - agentSessionId?: string; // Provider session ID for this tab - scrollTop?: number; - draftInput?: string; + id: string; + name: string; + logs: LogEntry[]; // Tab-specific conversation history + agentSessionId?: string; // Provider session ID for this tab + scrollTop?: number; + draftInput?: string; } interface FilePreviewTab { - id: string; // Unique tab ID (UUID) - path: string; // Full file path - name: string; // Filename without extension - extension: string; // File extension with dot (e.g., '.md') - content: string; // File content - scrollTop: number; // Preserved scroll position - searchQuery: string; // Preserved search query - editMode: boolean; // Whether tab was in edit mode - editContent?: string; // Unsaved edit content - createdAt: number; // Timestamp for ordering - lastModified: number; // File modification time - sshRemoteId?: string; // SSH remote ID for remote files - isLoading?: boolean; // True while loading remote content + id: string; // Unique tab ID (UUID) + path: string; // Full file path + name: string; // Filename without extension + extension: string; // File extension with dot (e.g., '.md') + content: string; // File content + scrollTop: number; // Preserved scroll position + searchQuery: string; // Preserved search query + editMode: boolean; // Whether tab was in edit mode + editContent?: string; // Unsaved edit content + createdAt: number; // Timestamp for ordering + lastModified: number; // File modification time + sshRemoteId?: string; // SSH remote ID for remote files + isLoading?: boolean; // True while loading remote content } // Unified tab references for ordering @@ -113,16 +113,19 @@ type UnifiedTabRef = { type: 'ai' | 'file'; id: string }; ## Code Conventions ### TypeScript + - Strict mode enabled - Interface definitions for all data structures - Types exported via `preload.ts` for renderer ### React Components + - Functional components with hooks - Tailwind for layout, inline styles for theme colors - `tabIndex={-1}` + `outline-none` for programmatic focus ### Commit Messages + ``` feat: new feature fix: bug fix diff --git a/CLAUDE-WIZARD.md b/CLAUDE-WIZARD.md index 4d74cc5bc..ec1b1c9c2 100644 --- a/CLAUDE-WIZARD.md +++ b/CLAUDE-WIZARD.md @@ -48,7 +48,7 @@ const { openWizard } = useWizard(); openWizard(); // Keyboard shortcut (default) -Cmd+Shift+N // Opens wizard +Cmd + Shift + N; // Opens wizard // Also available in: // - Command K menu: "New Agent Wizard" @@ -87,25 +87,29 @@ The Wizard maintains two types of state: - Cleared on completion or when user chooses "Just Quit" **State Save Triggers:** + - Auto-save: When `currentStep` changes (step > 1) - `WizardContext.tsx` useEffect with `saveResumeState()` - Manual save: User clicks "Save & Exit" - `MaestroWizard.tsx` `handleConfirmExit()` **State Clear Triggers:** + - Wizard completion: `App.tsx` wizard completion handler + `WizardContext.tsx` `COMPLETE_WIZARD` action - User quits: "Quit without saving" button - `MaestroWizard.tsx` `handleQuitWithoutSaving()` - User starts fresh: "Start Fresh" in resume modal - `App.tsx` resume handlers **Opening Wizard Logic:** The `openWizard()` function in `WizardContext.tsx` handles state initialization: + ```typescript // If previous wizard was completed, reset in-memory state first if (state.isComplete === true) { - dispatch({ type: 'RESET_WIZARD' }); // Clear stale state + dispatch({ type: 'RESET_WIZARD' }); // Clear stale state } -dispatch({ type: 'OPEN_WIZARD' }); // Show wizard UI +dispatch({ type: 'OPEN_WIZARD' }); // Show wizard UI ``` This ensures: + - **Fresh starts**: Completed wizards don't contaminate new runs - **Resume works**: Abandoned wizards (isComplete: false) preserve state - **No race conditions**: Persisted state is checked after wizard opens @@ -135,22 +139,22 @@ The tour highlights UI elements with spotlight cutouts: ### Customization Points -| What | Where | -|------|-------| -| Add wizard step | `WizardContext.tsx` (WIZARD_TOTAL_STEPS, WizardStep type, STEP_INDEX) | -| Modify wizard prompts | `src/prompts/wizard-*.md` (content), `services/wizardPrompts.ts` (logic) | -| Change confidence threshold | `READY_CONFIDENCE_THRESHOLD` in wizardPrompts.ts (default: 80) | -| Add tour step | `tour/tourSteps.ts` array | -| Modify Auto Run document format | `src/prompts/wizard-document-generation.md` | -| Change wizard keyboard shortcut | `shortcuts.ts` → `openWizard` | +| What | Where | +| ------------------------------- | ------------------------------------------------------------------------ | +| Add wizard step | `WizardContext.tsx` (WIZARD_TOTAL_STEPS, WizardStep type, STEP_INDEX) | +| Modify wizard prompts | `src/prompts/wizard-*.md` (content), `services/wizardPrompts.ts` (logic) | +| Change confidence threshold | `READY_CONFIDENCE_THRESHOLD` in wizardPrompts.ts (default: 80) | +| Add tour step | `tour/tourSteps.ts` array | +| Modify Auto Run document format | `src/prompts/wizard-document-generation.md` | +| Change wizard keyboard shortcut | `shortcuts.ts` → `openWizard` | ### Related Settings ```typescript // In useSettings.ts -wizardCompleted: boolean // First wizard completion -tourCompleted: boolean // First tour completion -firstAutoRunCompleted: boolean // Triggers celebration modal +wizardCompleted: boolean; // First wizard completion +tourCompleted: boolean; // First tour completion +firstAutoRunCompleted: boolean; // Triggers celebration modal ``` --- @@ -195,8 +199,8 @@ src/renderer/contexts/InlineWizardContext.tsx # State provider ### Customization Points -| What | Where | -|------|-------| -| Modify inline wizard prompts | `src/prompts/wizard-*.md` | -| Change confidence threshold | `READY_CONFIDENCE_THRESHOLD` in wizardPrompts.ts | -| Modify generation UI | `DocumentGenerationView.tsx`, `AustinFactsDisplay.tsx` | +| What | Where | +| ---------------------------- | ------------------------------------------------------ | +| Modify inline wizard prompts | `src/prompts/wizard-*.md` | +| Change confidence threshold | `READY_CONFIDENCE_THRESHOLD` in wizardPrompts.ts | +| Modify generation UI | `DocumentGenerationView.tsx`, `AustinFactsDisplay.tsx` | diff --git a/CLAUDE.md b/CLAUDE.md index 52f41a6c4..0c9c61127 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,17 +6,17 @@ Essential guidance for working with this codebase. For detailed architecture, se This guide has been split into focused sub-documents for progressive disclosure: -| Document | Description | -|----------|-------------| -| [[CLAUDE-PATTERNS.md]] | Core implementation patterns (process management, settings, modals, themes, Auto Run, SSH, Encore Features) | -| [[CLAUDE-IPC.md]] | IPC API surface (`window.maestro.*` namespaces) | -| [[CLAUDE-PERFORMANCE.md]] | Performance best practices (React optimization, debouncing, batching) | -| [[CLAUDE-WIZARD.md]] | Onboarding Wizard, Inline Wizard, and Tour System | -| [[CLAUDE-FEATURES.md]] | Usage Dashboard and Document Graph features | -| [[CLAUDE-AGENTS.md]] | Supported agents and capabilities | -| [[CLAUDE-SESSION.md]] | Session interface (agent data model) and code conventions | -| [[CLAUDE-PLATFORM.md]] | Cross-platform concerns (Windows, Linux, macOS, SSH remote) | -| [AGENT_SUPPORT.md](AGENT_SUPPORT.md) | Detailed agent integration guide | +| Document | Description | +| ------------------------------------ | ----------------------------------------------------------------------------------------------------------- | +| [[CLAUDE-PATTERNS.md]] | Core implementation patterns (process management, settings, modals, themes, Auto Run, SSH, Encore Features) | +| [[CLAUDE-IPC.md]] | IPC API surface (`window.maestro.*` namespaces) | +| [[CLAUDE-PERFORMANCE.md]] | Performance best practices (React optimization, debouncing, batching) | +| [[CLAUDE-WIZARD.md]] | Onboarding Wizard, Inline Wizard, and Tour System | +| [[CLAUDE-FEATURES.md]] | Usage Dashboard and Document Graph features | +| [[CLAUDE-AGENTS.md]] | Supported agents and capabilities | +| [[CLAUDE-SESSION.md]] | Session interface (agent data model) and code conventions | +| [[CLAUDE-PLATFORM.md]] | Cross-platform concerns (Windows, Linux, macOS, SSH remote) | +| [AGENT_SUPPORT.md](AGENT_SUPPORT.md) | Detailed agent integration guide | --- @@ -25,21 +25,27 @@ This guide has been split into focused sub-documents for progressive disclosure: Core behaviors for effective collaboration. Failures here cause the most rework. ### Surface Assumptions Early + Before implementing non-trivial work, explicitly state assumptions. Never silently fill in ambiguous requirements—the most common failure mode is guessing wrong and running with it. Format: "Assumptions: 1) X, 2) Y. Correct me now or I proceed." ### Manage Confusion Actively + When encountering inconsistencies, conflicting requirements, or unclear specs: **STOP**. Name the specific confusion, present the tradeoff, and wait for resolution. Bad: silently picking one interpretation. Good: "I see X in file A but Y in file B—which takes precedence?" ### Push Back When Warranted + Not a yes-machine. When an approach has clear problems: point out the issue directly, explain the concrete downside, propose an alternative, then accept the decision if overridden. Sycophancy ("Of course!") followed by implementing a bad idea helps no one. ### Enforce Simplicity + Natural tendency is to overcomplicate—actively resist. Before finishing: Can this be fewer lines? Are abstractions earning their complexity? Would a senior dev say "why didn't you just..."? Prefer the boring, obvious solution. ### Maintain Scope Discipline + Touch only what's asked. Do NOT: remove comments you don't understand, "clean up" orthogonal code, refactor adjacent systems as side effects, or delete seemingly-unused code without approval. Surgical precision, not unsolicited renovation. ### Dead Code Hygiene + After refactoring: identify now-unreachable code, list it explicitly, ask "Should I remove these now-unused elements: [list]?" Don't leave corpses. Don't delete without asking. --- @@ -58,6 +64,7 @@ In Maestro, the terms "agent" and "session" have distinct meanings: Use "agent" in user-facing language. Reserve "session" for provider-level conversation contexts or when documenting the code interface. ### UI Components + - **Left Bar** - Left sidebar with agent list and groups (`SessionList.tsx`) - **Right Bar** - Right sidebar with Files, History, Auto Run tabs (`RightPanel.tsx`) - **Main Window** - Center workspace (`MainPanel.tsx`) @@ -66,6 +73,7 @@ Use "agent" in user-facing language. Reserve "session" for provider-level conver - **System Log Viewer** - Special view for system logs (`LogViewer.tsx`) ### Agent States (color-coded) + - **Green** - Ready/idle - **Yellow** - Agent thinking/busy - **Red** - No connection/error @@ -85,13 +93,13 @@ Maestro is an Electron desktop app for managing multiple AI coding assistants si ### Supported Agents -| ID | Name | Status | -|----|------|--------| -| `claude-code` | Claude Code | **Active** | -| `codex` | OpenAI Codex | **Active** | -| `opencode` | OpenCode | **Active** | +| ID | Name | Status | +| --------------- | ------------- | ---------- | +| `claude-code` | Claude Code | **Active** | +| `codex` | OpenAI Codex | **Active** | +| `opencode` | OpenCode | **Active** | | `factory-droid` | Factory Droid | **Active** | -| `terminal` | Terminal | Internal | +| `terminal` | Terminal | Internal | See [[CLAUDE-AGENTS.md]] for capabilities and integration details. @@ -155,45 +163,45 @@ src/ ## Key Files for Common Tasks -| Task | Primary Files | -|------|---------------| -| Add IPC handler | `src/main/index.ts`, `src/main/preload.ts` | -| Add UI component | `src/renderer/components/` | -| Add web/mobile component | `src/web/components/`, `src/web/mobile/` | -| Add keyboard shortcut | `src/renderer/constants/shortcuts.ts`, `App.tsx` | -| Add theme | `src/renderer/constants/themes.ts` | -| Add modal | Component + `src/renderer/constants/modalPriorities.ts` | -| Add tab overlay menu | See Tab Hover Overlay Menu pattern in [[CLAUDE-PATTERNS.md]] | -| Add setting | `src/renderer/hooks/useSettings.ts`, `src/main/index.ts` | -| Add template variable | `src/shared/templateVariables.ts`, `src/renderer/utils/templateVariables.ts` | -| Modify system prompts | `src/prompts/*.md` (wizard, Auto Run, etc.) | -| Add Spec-Kit command | `src/prompts/speckit/`, `src/main/speckit-manager.ts` | -| Add OpenSpec command | `src/prompts/openspec/`, `src/main/openspec-manager.ts` | -| Add CLI command | `src/cli/commands/`, `src/cli/index.ts` | -| Configure agent | `src/main/agent-detector.ts`, `src/main/agent-capabilities.ts` | -| Add agent output parser | `src/main/parsers/`, `src/main/parsers/index.ts` | -| Add agent session storage | `src/main/storage/`, `src/main/agent-session-storage.ts` | -| Add agent error patterns | `src/main/parsers/error-patterns.ts` | -| Add playbook feature | `src/cli/services/playbooks.ts` | -| Add marketplace playbook | `src/main/ipc/handlers/marketplace.ts` (import from GitHub) | -| Playbook import/export | `src/main/ipc/handlers/playbooks.ts` (ZIP handling with assets) | -| Modify wizard flow | `src/renderer/components/Wizard/` (see [[CLAUDE-WIZARD.md]]) | -| Add tour step | `src/renderer/components/Wizard/tour/tourSteps.ts` | -| Modify file linking | `src/renderer/utils/remarkFileLinks.ts` (remark plugin for `[[wiki]]` and path links) | -| Add documentation page | `docs/*.md`, `docs/docs.json` (navigation) | -| Add documentation screenshot | `docs/screenshots/` (PNG, kebab-case naming) | -| MCP server integration | See [MCP Server docs](https://docs.runmaestro.ai/mcp-server) | -| Add stats/analytics feature | `src/main/stats-db.ts`, `src/main/ipc/handlers/stats.ts` | -| Add Usage Dashboard chart | `src/renderer/components/UsageDashboard/` | -| Add Document Graph feature | `src/renderer/components/DocumentGraph/`, `src/main/ipc/handlers/documentGraph.ts` | -| Add colorblind palette | `src/renderer/constants/colorblindPalettes.ts` | -| Add performance metrics | `src/shared/performance-metrics.ts` | -| Add power management | `src/main/power-manager.ts`, `src/main/ipc/handlers/system.ts` | -| Spawn agent with SSH support | `src/main/utils/ssh-spawn-wrapper.ts` (required for SSH remote execution) | -| Modify file preview tabs | `TabBar.tsx`, `FilePreview.tsx`, `MainPanel.tsx` (see ARCHITECTURE.md → File Preview Tab System) | -| Add Director's Notes feature | `src/renderer/components/DirectorNotes/`, `src/main/ipc/handlers/director-notes.ts` | -| Add Encore Feature | `src/renderer/types/index.ts` (flag), `useSettings.ts` (state), `SettingsModal.tsx` (toggle UI), gate in `App.tsx` + keyboard handler | -| Modify history components | `src/renderer/components/History/` | +| Task | Primary Files | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| Add IPC handler | `src/main/index.ts`, `src/main/preload.ts` | +| Add UI component | `src/renderer/components/` | +| Add web/mobile component | `src/web/components/`, `src/web/mobile/` | +| Add keyboard shortcut | `src/renderer/constants/shortcuts.ts`, `App.tsx` | +| Add theme | `src/renderer/constants/themes.ts` | +| Add modal | Component + `src/renderer/constants/modalPriorities.ts` | +| Add tab overlay menu | See Tab Hover Overlay Menu pattern in [[CLAUDE-PATTERNS.md]] | +| Add setting | `src/renderer/hooks/useSettings.ts`, `src/main/index.ts` | +| Add template variable | `src/shared/templateVariables.ts`, `src/renderer/utils/templateVariables.ts` | +| Modify system prompts | `src/prompts/*.md` (wizard, Auto Run, etc.) | +| Add Spec-Kit command | `src/prompts/speckit/`, `src/main/speckit-manager.ts` | +| Add OpenSpec command | `src/prompts/openspec/`, `src/main/openspec-manager.ts` | +| Add CLI command | `src/cli/commands/`, `src/cli/index.ts` | +| Configure agent | `src/main/agent-detector.ts`, `src/main/agent-capabilities.ts` | +| Add agent output parser | `src/main/parsers/`, `src/main/parsers/index.ts` | +| Add agent session storage | `src/main/storage/`, `src/main/agent-session-storage.ts` | +| Add agent error patterns | `src/main/parsers/error-patterns.ts` | +| Add playbook feature | `src/cli/services/playbooks.ts` | +| Add marketplace playbook | `src/main/ipc/handlers/marketplace.ts` (import from GitHub) | +| Playbook import/export | `src/main/ipc/handlers/playbooks.ts` (ZIP handling with assets) | +| Modify wizard flow | `src/renderer/components/Wizard/` (see [[CLAUDE-WIZARD.md]]) | +| Add tour step | `src/renderer/components/Wizard/tour/tourSteps.ts` | +| Modify file linking | `src/renderer/utils/remarkFileLinks.ts` (remark plugin for `[[wiki]]` and path links) | +| Add documentation page | `docs/*.md`, `docs/docs.json` (navigation) | +| Add documentation screenshot | `docs/screenshots/` (PNG, kebab-case naming) | +| MCP server integration | See [MCP Server docs](https://docs.runmaestro.ai/mcp-server) | +| Add stats/analytics feature | `src/main/stats-db.ts`, `src/main/ipc/handlers/stats.ts` | +| Add Usage Dashboard chart | `src/renderer/components/UsageDashboard/` | +| Add Document Graph feature | `src/renderer/components/DocumentGraph/`, `src/main/ipc/handlers/documentGraph.ts` | +| Add colorblind palette | `src/renderer/constants/colorblindPalettes.ts` | +| Add performance metrics | `src/shared/performance-metrics.ts` | +| Add power management | `src/main/power-manager.ts`, `src/main/ipc/handlers/system.ts` | +| Spawn agent with SSH support | `src/main/utils/ssh-spawn-wrapper.ts` (required for SSH remote execution) | +| Modify file preview tabs | `TabBar.tsx`, `FilePreview.tsx`, `MainPanel.tsx` (see ARCHITECTURE.md → File Preview Tab System) | +| Add Director's Notes feature | `src/renderer/components/DirectorNotes/`, `src/main/ipc/handlers/director-notes.ts` | +| Add Encore Feature | `src/renderer/types/index.ts` (flag), `useSettings.ts` (state), `SettingsModal.tsx` (toggle UI), gate in `App.tsx` + keyboard handler | +| Modify history components | `src/renderer/components/History/` | --- @@ -204,33 +212,36 @@ src/ Maestro uses Sentry for error tracking. Field data from production crashes is invaluable for improving code quality. **DO let exceptions bubble up:** + ```typescript // WRONG - silently swallowing errors hides bugs from Sentry try { - await riskyOperation(); + await riskyOperation(); } catch (e) { - console.error(e); // Lost to the void + console.error(e); // Lost to the void } // CORRECT - let unhandled exceptions reach Sentry -await riskyOperation(); // Crashes are reported automatically +await riskyOperation(); // Crashes are reported automatically ``` **DO handle expected/recoverable errors explicitly:** + ```typescript // CORRECT - known failure modes should be handled gracefully try { - await fetchUserData(); + await fetchUserData(); } catch (e) { - if (e.code === 'NETWORK_ERROR') { - showOfflineMessage(); // Expected, recoverable - } else { - throw e; // Unexpected - let Sentry capture it - } + if (e.code === 'NETWORK_ERROR') { + showOfflineMessage(); // Expected, recoverable + } else { + throw e; // Unexpected - let Sentry capture it + } } ``` **DO use Sentry utilities for explicit reporting:** + ```typescript import { captureException, captureMessage } from '../utils/sentry'; @@ -252,6 +263,7 @@ await captureMessage('Unusual state detected', 'warning', { state }); Agents can be configured to run on remote hosts via SSH. Without proper SSH wrapping, agents will always execute locally, breaking the user's expected behavior. **Required pattern:** + 1. Check if the session has `sshRemoteConfig` with `enabled: true` 2. Use `wrapSpawnWithSsh()` from `src/main/utils/ssh-spawn-wrapper.ts` to wrap the spawn config 3. Pass the SSH store (available via `createSshRemoteStoreAdapter(settingsStore)`) @@ -262,12 +274,13 @@ import { createSshRemoteStoreAdapter } from '../utils/ssh-remote-resolver'; // Before spawning, wrap the config with SSH if needed if (sshStore && session.sshRemoteConfig?.enabled) { - const sshWrapped = await wrapSpawnWithSsh(spawnConfig, session.sshRemoteConfig, sshStore); - // Use sshWrapped.command, sshWrapped.args, sshWrapped.cwd, etc. + const sshWrapped = await wrapSpawnWithSsh(spawnConfig, session.sshRemoteConfig, sshStore); + // Use sshWrapped.command, sshWrapped.args, sshWrapped.cwd, etc. } ``` **Also ensure:** + - The correct agent type is used (don't hardcode `claude-code`) - Custom agent configuration (customPath, customArgs, customEnvVars) is passed through - Agent's `binaryName` is used for remote execution (not local paths) @@ -288,20 +301,24 @@ Initial hypotheses are often wrong. Before implementing any fix: 4. **Feature not working:** Verify the code path is actually being executed (add temporary `console.log`, check output, then remove) **Historical patterns that wasted time:** + - Tab naming bug: Modal coordination was "fixed" when the actual issue was an unregistered IPC handler - Tooltip clipping: Attempted `overflow: visible` on element when parent container had `overflow: hidden` - Session validation: Fixed renderer calls when handler wasn't wired in main process ### Focus Not Working + 1. Add `tabIndex={0}` or `tabIndex={-1}` 2. Add `outline-none` class 3. Use `ref={(el) => el?.focus()}` for auto-focus ### Settings Not Persisting + 1. Check wrapper function calls `window.maestro.settings.set()` 2. Check loading code in `useSettings.ts` useEffect ### Modal Escape Not Working + 1. Register with layer stack (don't handle Escape locally) 2. Check priority is set correctly @@ -314,16 +331,18 @@ Maestro provides a hosted MCP (Model Context Protocol) server for AI application **Server URL:** `https://docs.runmaestro.ai/mcp` **Available Tools:** + - `SearchMaestro` - Search the Maestro knowledge base for documentation, code examples, API references, and guides **Connect from Claude Desktop/Code:** + ```json { - "mcpServers": { - "maestro": { - "url": "https://docs.runmaestro.ai/mcp" - } - } + "mcpServers": { + "maestro": { + "url": "https://docs.runmaestro.ai/mcp" + } + } } ``` diff --git a/CONSTITUTION.md b/CONSTITUTION.md index 80070bd92..63cdc96e5 100644 --- a/CONSTITUTION.md +++ b/CONSTITUTION.md @@ -25,11 +25,12 @@ The first two tenets address these modes directly. The remaining tenets apply to ## The Six Tenets -### 1. Unattended Excellence *(Solo Mode)* +### 1. Unattended Excellence _(Solo Mode)_ The measure of Maestro's success is how long agents run without intervention. **What this means in practice:** + - Auto Run is a first-class citizen, not an afterthought - Error recovery should be automatic where possible - Agents should be self-healing and self-continuing @@ -39,11 +40,12 @@ The measure of Maestro's success is how long agents run without intervention. **The Autonomy Principle:** Every feature we build should extend the runway of unattended operation. If a feature requires more babysitting, it's moving backwards. -### 2. The Conductor's Perspective *(Interactive Mode)* +### 2. The Conductor's Perspective _(Interactive Mode)_ You are the maestro. The agents are your orchestra. The interface is your podium. Fleet management is critical for optimal interactive experience. **What this means in practice:** + - Overview and control trump granular details - Point at an agent, give direction, move on - Status should be glanceable, not readable @@ -60,6 +62,7 @@ A conductor doesn't play every instrument—they ensure each section knows its p Your hands never leave the keyboard. Every action, every navigation, every command flows through deliberate keystrokes. **What this means in practice:** + - Focus must always be predictable and intentional - Escape always returns you to a known, useful state - Every modal, every panel, every input has a keyboard path @@ -74,6 +77,7 @@ When you press a key, you must know where focus will land. When you press Escape The interface must be faster than thought. Latency kills flow. **What this means in practice:** + - UI interactions respond in milliseconds, not seconds - Switching sessions is instantaneous - Keyboard navigation has zero perceptible delay @@ -81,13 +85,14 @@ The interface must be faster than thought. Latency kills flow. - Perceived performance matters as much as actual performance **The Speed Imperative:** -If you can fly through the interface with your keyboard, but the interface can't keep up, keyboard-first is a lie. Speed is what makes sovereignty *feel* sovereign. +If you can fly through the interface with your keyboard, but the interface can't keep up, keyboard-first is a lie. Speed is what makes sovereignty _feel_ sovereign. ### 5. Delightful Focus We solve fleet management brilliantly. We don't solve everything. **What this means in practice:** + - Say no to feature creep that dilutes the core experience - Polish what exists before adding what's new - Every interaction should feel responsive and intentional @@ -95,13 +100,14 @@ We solve fleet management brilliantly. We don't solve everything. - Latency is a bug **The Delight Standard:** -Users should smile when they use Maestro. Not because it's cute, but because it's *satisfying*—like a perfectly weighted keyboard or a door that closes with a solid click. +Users should smile when they use Maestro. Not because it's cute, but because it's _satisfying_—like a perfectly weighted keyboard or a door that closes with a solid click. ### 6. Transparent Complexity Power users deserve depth. New users deserve simplicity. Both get what they need. **What this means in practice:** + - Progressive disclosure of advanced features - Sensible defaults that work out of the box - Power features accessible but not intrusive @@ -174,4 +180,4 @@ You're conducting. --- -*This Constitution is a living document. It evolves as Maestro evolves, but its core principles remain: keyboard-first, unattended-focused, delightfully simple, and always in service of the conductor.* +_This Constitution is a living document. It evolves as Maestro evolves, but its core principles remain: keyboard-first, unattended-focused, delightfully simple, and always in service of the conductor._ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 88ce6c58f..b0687922f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -120,13 +120,14 @@ npm run package:linux # Package for Linux By default, `npm run dev` uses an isolated data directory (`~/Library/Application Support/maestro-dev/`) separate from production. This allows you to run both dev and production instances simultaneously—useful when using the production Maestro to work on the dev instance. -| Command | Data Directory | Can Run Alongside Production? | -|---------|---------------|-------------------------------| -| `npm run dev` | `maestro-dev/` | ✅ Yes | +| Command | Data Directory | Can Run Alongside Production? | +| ----------------------- | ----------------------- | ------------------------------ | +| `npm run dev` | `maestro-dev/` | ✅ Yes | | `npm run dev:prod-data` | `maestro/` (production) | ❌ No - close production first | -| `npm run dev:demo` | `/tmp/maestro-demo/` | ✅ Yes | +| `npm run dev:demo` | `/tmp/maestro-demo/` | ✅ Yes | **When to use each:** + - **`npm run dev`** — Default for most development. Start fresh or use dev-specific test data. - **`npm run dev:prod-data`** — Test with your real sessions and settings. Must close production app first to avoid database lock conflicts. - **`npm run dev:demo`** — Screenshots, demos, or testing with completely fresh state. @@ -190,6 +191,7 @@ Watch mode keeps Jest running and automatically re-runs tests when you save chan - Provides instant feedback during development **Interactive options in watch mode:** + - `a` - Run all tests - `f` - Run only failing tests - `p` - Filter by filename pattern @@ -216,6 +218,7 @@ src/__tests__/ This project uses [Husky](https://typicode.github.io/husky/) and [lint-staged](https://github.com/lint-staged/lint-staged) to automatically format and lint staged files before each commit. **How it works:** + 1. When you run `git commit`, Husky triggers the pre-commit hook 2. lint-staged runs Prettier and ESLint only on your staged files 3. If there are unfixable errors, the commit is blocked @@ -224,16 +227,19 @@ This project uses [Husky](https://typicode.github.io/husky/) and [lint-staged](h **Setup is automatic** — hooks are installed when you run `npm install` (via the `prepare` script). **Bypassing hooks (emergency only):** + ```bash git commit --no-verify -m "emergency fix" ``` **Running lint-staged manually:** + ```bash npx lint-staged ``` **Troubleshooting:** + - **Hooks not running** — Check if `.husky/pre-commit` has executable permissions: `chmod +x .husky/pre-commit` - **Wrong tool version** — Ensure `npx` is using local `node_modules`: delete `node_modules` and run `npm install` - **Hook fails in CI/Docker** — The `prepare` script uses `husky || true` to gracefully skip in environments without `.git` @@ -251,6 +257,7 @@ npm run lint:eslint -- --fix # Auto-fix ESLint issues where possible ### TypeScript Linting The TypeScript linter checks all three build configurations: + - `tsconfig.lint.json` - Renderer, web, and shared code - `tsconfig.main.json` - Main process code - `tsconfig.cli.json` - CLI tooling @@ -258,17 +265,20 @@ The TypeScript linter checks all three build configurations: ### ESLint ESLint is configured with TypeScript and React plugins (`eslint.config.mjs`): + - `react-hooks/rules-of-hooks` - Enforces React hooks rules - `react-hooks/exhaustive-deps` - Enforces correct hook dependencies - `@typescript-eslint/no-unused-vars` - Warns about unused variables - `prefer-const` - Suggests const for never-reassigned variables **When to run manual linting:** + - Pre-commit hooks handle staged files automatically - Run full lint after significant refactors: `npm run lint && npm run lint:eslint` - When CI fails with type errors **Common lint issues:** + - Unused imports or variables - Type mismatches in function calls - Missing required properties on interfaces @@ -302,6 +312,7 @@ ESLint is configured with TypeScript and React plugins (`eslint.config.mjs`): ### Adding Keyboard Shortcuts 1. Add definition in `src/renderer/constants/shortcuts.ts`: + ```typescript myShortcut: { id: 'myShortcut', label: 'My Action', keys: ['Meta', 'k'] }, ``` @@ -320,19 +331,22 @@ ESLint is configured with TypeScript and React plugins (`eslint.config.mjs`): ### Adding a New Setting 1. Add state in `useSettings.ts`: + ```typescript const [mySetting, setMySettingState] = useState(defaultValue); ``` 2. Create wrapper function: + ```typescript const setMySetting = (value) => { - setMySettingState(value); - window.maestro.settings.set('mySetting', value); + setMySettingState(value); + window.maestro.settings.set('mySetting', value); }; ``` 3. Load in useEffect: + ```typescript const saved = await window.maestro.settings.get('mySetting'); if (saved !== undefined) setMySettingState(saved); @@ -407,14 +421,16 @@ Then add the ID to `ThemeId` type in `src/shared/theme-types.ts` and to the `isV ### Adding an IPC Handler 1. Add handler in `src/main/index.ts`: + ```typescript ipcMain.handle('myNamespace:myAction', async (_, arg1, arg2) => { - // Implementation - return result; + // Implementation + return result; }); ``` 2. Expose in `src/main/preload.ts`: + ```typescript myNamespace: { myAction: (arg1, arg2) => ipcRenderer.invoke('myNamespace:myAction', arg1, arg2), @@ -445,8 +461,8 @@ Encore Features are managed through a single settings object: ```typescript // src/renderer/types/index.ts export interface EncoreFeatureFlags { - directorNotes: boolean; - // Add new features here + directorNotes: boolean; + // Add new features here } ``` @@ -455,18 +471,20 @@ The flags live in `useSettings.ts` and persist via `window.maestro.settings`. Th ### Adding a New Encore Feature 1. **Add the flag** to `EncoreFeatureFlags` in `src/renderer/types/index.ts`: + ```typescript export interface EncoreFeatureFlags { - directorNotes: boolean; - myFeature: boolean; // Add here + directorNotes: boolean; + myFeature: boolean; // Add here } ``` 2. **Set the default** in `useSettings.ts` — always default to `false`: + ```typescript const DEFAULT_ENCORE_FEATURES: EncoreFeatureFlags = { - directorNotes: false, - myFeature: false, + directorNotes: false, + myFeature: false, }; ``` @@ -482,8 +500,8 @@ The flags live in `useSettings.ts` and persist via `window.maestro.settings`. Th ### Existing Encore Features -| Feature | Flag | Description | -|---------|------|-------------| +| Feature | Flag | Description | +| ---------------- | --------------- | --------------------------------------------- | | Director's Notes | `directorNotes` | AI-generated synopsis of work across sessions | ## Adding a New AI Agent @@ -494,18 +512,18 @@ Maestro supports multiple AI coding agents. Each agent has different capabilitie Before implementing, investigate the agent's CLI to determine which capabilities it supports: -| Capability | Question to Answer | Example | -|------------|-------------------|---------| -| **Session Resume** | Can the provider resume a previous conversation? | `--resume `, `--session ` | -| **Read-Only Mode** | Is there a plan/analysis-only mode? | `--permission-mode plan`, `--agent plan` | -| **JSON Output** | Does it emit structured JSON? | `--output-format json`, `--format json` | -| **Session ID** | Does output include a session identifier? | `session_id`, `sessionID` in JSON | -| **Image Input** | Can you send images to the agent? | `--input-format stream-json`, `-f image.png` | -| **Slash Commands** | Are there discoverable commands? | Emitted in init message | -| **Session Storage** | Does the provider persist sessions to disk? | `~/.agent/sessions/` | -| **Cost Tracking** | Is it API-based with costs? | Cloud API vs local model | -| **Usage Stats** | Does it report token counts? | `tokens`, `usage` in output | -| **Batch Mode** | Does it run per-message or persistently? | `--print` vs interactive | +| Capability | Question to Answer | Example | +| ------------------- | ------------------------------------------------ | -------------------------------------------- | +| **Session Resume** | Can the provider resume a previous conversation? | `--resume `, `--session ` | +| **Read-Only Mode** | Is there a plan/analysis-only mode? | `--permission-mode plan`, `--agent plan` | +| **JSON Output** | Does it emit structured JSON? | `--output-format json`, `--format json` | +| **Session ID** | Does output include a session identifier? | `session_id`, `sessionID` in JSON | +| **Image Input** | Can you send images to the agent? | `--input-format stream-json`, `-f image.png` | +| **Slash Commands** | Are there discoverable commands? | Emitted in init message | +| **Session Storage** | Does the provider persist sessions to disk? | `~/.agent/sessions/` | +| **Cost Tracking** | Is it API-based with costs? | Cloud API vs local model | +| **Usage Stats** | Does it report token counts? | `tokens`, `usage` in output | +| **Batch Mode** | Does it run per-message or persistently? | `--print` vs interactive | ### Implementation Steps @@ -549,15 +567,15 @@ In `src/main/agent-output-parser.ts`, add a parser for the agent's JSON format: ```typescript class MyAgentOutputParser implements AgentOutputParser { - parseJsonLine(line: string): ParsedEvent { - const msg = JSON.parse(line); - return { - type: msg.type, - sessionId: msg.session_id, // Agent-specific field name - text: msg.content, // Agent-specific field name - tokens: msg.usage, // Agent-specific field name - }; - } + parseJsonLine(line: string): ParsedEvent { + const msg = JSON.parse(line); + return { + type: msg.type, + sessionId: msg.session_id, // Agent-specific field name + text: msg.content, // Agent-specific field name + tokens: msg.usage, // Agent-specific field name + }; + } } ``` @@ -579,13 +597,13 @@ If the agent persists sessions to disk: ```typescript class MyAgentSessionStorage implements AgentSessionStorage { - async listSessions(projectPath: string): Promise { - // Read from agent's session directory - } + async listSessions(projectPath: string): Promise { + // Read from agent's session directory + } - async readSession(projectPath: string, sessionId: string): Promise { - // Parse session file format - } + async readSession(projectPath: string, sessionId: string): Promise { + // Parse session file format + } } ``` @@ -613,26 +631,26 @@ npm run dev Based on capabilities, these UI features are automatically enabled/disabled: -| Feature | Required Capability | Component | -|---------|-------------------|-----------| -| Read-only toggle | `supportsReadOnlyMode` | InputArea | -| Image attachment | `supportsImageInput` | InputArea | -| Session browser | `supportsSessionStorage` | RightPanel | -| Resume button | `supportsResume` | AgentSessionsBrowser | -| Cost widget | `supportsCostTracking` | MainPanel | -| Token display | `supportsUsageStats` | MainPanel, TabBar | -| Session ID pill | `supportsSessionId` | MainPanel | -| Slash autocomplete | `supportsSlashCommands` | InputArea | +| Feature | Required Capability | Component | +| ------------------ | ------------------------ | -------------------- | +| Read-only toggle | `supportsReadOnlyMode` | InputArea | +| Image attachment | `supportsImageInput` | InputArea | +| Session browser | `supportsSessionStorage` | RightPanel | +| Resume button | `supportsResume` | AgentSessionsBrowser | +| Cost widget | `supportsCostTracking` | MainPanel | +| Token display | `supportsUsageStats` | MainPanel, TabBar | +| Session ID pill | `supportsSessionId` | MainPanel | +| Slash autocomplete | `supportsSlashCommands` | InputArea | ### Supported Agents Reference -| Agent | Resume | Read-Only | JSON | Images | Sessions | Cost | Status | -|-------|--------|-----------|------|--------|----------|------|--------| -| Claude Code | ✅ `--resume` | ✅ `--permission-mode plan` | ✅ | ✅ | ✅ `~/.claude/` | ✅ | ✅ Complete | -| Codex | ✅ `exec resume` | ✅ `--sandbox read-only` | ✅ | ✅ | ✅ `~/.codex/` | ❌ (tokens only) | ✅ Complete | -| OpenCode | ✅ `--session` | ✅ `--agent plan` | ✅ | ✅ | ✅ `~/.local/share/opencode/` | ✅ | ✅ Complete | -| Factory Droid | ✅ `-s, --session-id` | ✅ (default mode) | ✅ | ✅ | ✅ `~/.factory/` | ❌ (tokens only) | ✅ Complete | -| Gemini CLI | TBD | TBD | TBD | TBD | TBD | ✅ | 📋 Planned | +| Agent | Resume | Read-Only | JSON | Images | Sessions | Cost | Status | +| ------------- | --------------------- | --------------------------- | ---- | ------ | ----------------------------- | ---------------- | ----------- | +| Claude Code | ✅ `--resume` | ✅ `--permission-mode plan` | ✅ | ✅ | ✅ `~/.claude/` | ✅ | ✅ Complete | +| Codex | ✅ `exec resume` | ✅ `--sandbox read-only` | ✅ | ✅ | ✅ `~/.codex/` | ❌ (tokens only) | ✅ Complete | +| OpenCode | ✅ `--session` | ✅ `--agent plan` | ✅ | ✅ | ✅ `~/.local/share/opencode/` | ✅ | ✅ Complete | +| Factory Droid | ✅ `-s, --session-id` | ✅ (default mode) | ✅ | ✅ | ✅ `~/.factory/` | ❌ (tokens only) | ✅ Complete | +| Gemini CLI | TBD | TBD | TBD | TBD | TBD | ✅ | 📋 Planned | For detailed implementation guide, see [AGENT_SUPPORT.md](AGENT_SUPPORT.md). @@ -673,16 +691,16 @@ Maestro prioritizes a snappy interface and minimal battery consumption. Follow t ```typescript // Bad: O(n) lookup in every iteration -agents.filter(a => { - const group = groups.find(g => g.id === a.groupId); // O(n) per agent - return group && !group.collapsed; +agents.filter((a) => { + const group = groups.find((g) => g.id === a.groupId); // O(n) per agent + return group && !group.collapsed; }); // Good: O(1) lookup with memoized Map -const groupsById = useMemo(() => new Map(groups.map(g => [g.id, g])), [groups]); -agents.filter(a => { - const group = groupsById.get(a.groupId); // O(1) - return group && !group.collapsed; +const groupsById = useMemo(() => new Map(groups.map((g) => [g.id, g])), [groups]); +agents.filter((a) => { + const group = groupsById.get(a.groupId); // O(1) + return group && !group.collapsed; }); ``` @@ -726,10 +744,12 @@ npx react-devtools Then run `npm run dev` — the app auto-connects (connection script in `src/renderer/index.html`). **Tabs:** + - **Components** — Inspect React component tree, props, state, hooks - **Profiler** — Record and analyze render performance, identify unnecessary re-renders **Profiler workflow:** + 1. Click the record button (blue circle) 2. Interact with the app (navigate, type, scroll) 3. Stop recording @@ -739,6 +759,7 @@ Then run `npm run dev` — the app auto-connects (connection script in `src/rend - Why a component rendered (props/state/hooks changed) **Chrome DevTools Performance tab** (`Cmd+Option+I` → Performance): + 1. Record during the slow operation 2. Look for long tasks (>50ms) blocking the main thread 3. Identify expensive JavaScript execution or layout thrashing @@ -812,19 +833,19 @@ PRs are automatically reviewed by two AI-powered tools: - Leave **inline review comments** on potential issues - Provide a **sequence diagram** for complex changes -| Command | Effect | -|---------|--------| -| `@coderabbitai review` | Trigger a full review (useful for existing PRs) | -| `@coderabbitai summary` | Regenerate the PR summary | -| `@coderabbitai resolve` | Resolve all CodeRabbit review comments | -| `@coderabbitai configuration` | Show current repo settings | +| Command | Effect | +| ----------------------------- | ----------------------------------------------- | +| `@coderabbitai review` | Trigger a full review (useful for existing PRs) | +| `@coderabbitai summary` | Regenerate the PR summary | +| `@coderabbitai resolve` | Resolve all CodeRabbit review comments | +| `@coderabbitai configuration` | Show current repo settings | You can reply to any CodeRabbit comment to ask follow-up questions — it responds conversationally. **[Greptile](https://greptile.com)** — Codebase-aware review with deeper architectural context. Greptile indexes the full repo and reviews PRs with understanding of how changes relate to the broader codebase. -| Command | Effect | -|---------|--------| +| Command | Effect | +| ----------- | ------------------------------------------------------------- | | `@greptile` | Ask Greptile a question or request a review in any PR comment | Reply to Greptile comments the same way you would CodeRabbit. @@ -834,17 +855,20 @@ Reply to Greptile comments the same way you would CodeRabbit. All PRs must pass these checks before review: 1. **Linting passes** — Run both TypeScript and ESLint checks: + ```bash npm run lint # TypeScript type checking npm run lint:eslint # ESLint code quality ``` 2. **Tests pass** — Run the full test suite: + ```bash npm test ``` 3. **Manual testing** — Test affected features in the running app: + ```bash npm run dev ``` @@ -892,6 +916,7 @@ npm run refresh-openspec ``` These scripts fetch the latest prompts from their respective repositories: + - **Spec-Kit**: [github/spec-kit](https://github.com/github/spec-kit) → `src/prompts/speckit/` - **OpenSpec**: [Fission-AI/OpenSpec](https://github.com/Fission-AI/OpenSpec) → `src/prompts/openspec/` @@ -902,6 +927,7 @@ Review any changes with `git diff` before committing. ### 1. Prepare Icons Place icons in `build/` directory: + - `icon.icns` - macOS (512x512 or 1024x1024) - `icon.ico` - Windows (256x256) - `icon.png` - Linux (512x512) @@ -909,9 +935,10 @@ Place icons in `build/` directory: ### 2. Update Version Update in `package.json`: + ```json { - "version": "0.1.0" + "version": "0.1.0" } ``` @@ -958,17 +985,18 @@ docs/ Pages are organized by topic in `docs.json` under `navigation.dropdowns`: -| Group | Pages | Purpose | -|-------|-------|---------| -| **Overview** | index, about/overview, features, screenshots | Introduction and feature highlights | -| **Getting Started** | installation, getting-started | Onboarding new users | -| **Usage** | general-usage, history, context-management, autorun-playbooks, git-worktrees, group-chat, remote-access, slash-commands, speckit-commands, configuration | Feature documentation | -| **Providers & CLI** | provider-notes, cli | Provider configuration and command line docs | -| **Reference** | achievements, keyboard-shortcuts, troubleshooting | Quick reference guides | +| Group | Pages | Purpose | +| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | +| **Overview** | index, about/overview, features, screenshots | Introduction and feature highlights | +| **Getting Started** | installation, getting-started | Onboarding new users | +| **Usage** | general-usage, history, context-management, autorun-playbooks, git-worktrees, group-chat, remote-access, slash-commands, speckit-commands, configuration | Feature documentation | +| **Providers & CLI** | provider-notes, cli | Provider configuration and command line docs | +| **Reference** | achievements, keyboard-shortcuts, troubleshooting | Quick reference guides | ### Adding a New Documentation Page 1. **Create the markdown file** in `docs/`: + ```markdown --- title: My Feature @@ -980,13 +1008,11 @@ Pages are organized by topic in `docs.json` under `navigation.dropdowns`: ``` 2. **Add to navigation** in `docs/docs.json`: + ```json { - "group": "Usage", - "pages": [ - "existing-page", - "my-feature" - ] + "group": "Usage", + "pages": ["existing-page", "my-feature"] } ``` @@ -999,11 +1025,11 @@ Pages are organized by topic in `docs.json` under `navigation.dropdowns`: Every documentation page needs YAML frontmatter: -| Field | Required | Description | -|-------|----------|-------------| -| `title` | Yes | Page title (appears in navigation and browser tab) | -| `description` | Yes | Brief description for SEO and page previews | -| `icon` | No | [Mintlify icon](https://mintlify.com/docs/content/components/icons) for navigation | +| Field | Required | Description | +| ------------- | -------- | ---------------------------------------------------------------------------------- | +| `title` | Yes | Page title (appears in navigation and browser tab) | +| `description` | Yes | Brief description for SEO and page previews | +| `icon` | No | [Mintlify icon](https://mintlify.com/docs/content/components/icons) for navigation | ### Screenshots @@ -1012,11 +1038,13 @@ All screenshots are stored in `docs/screenshots/` and referenced with relative p **Adding a new screenshot:** 1. **Capture the screenshot** using Maestro's demo mode for clean, consistent visuals: + ```bash rm -rf /tmp/maestro-demo && npm run dev:demo ``` 2. **Save as PNG** in `docs/screenshots/` with a descriptive kebab-case name: + ``` docs/screenshots/my-feature-overview.png docs/screenshots/my-feature-settings.png @@ -1028,6 +1056,7 @@ All screenshots are stored in `docs/screenshots/` and referenced with relative p ``` **Screenshot guidelines:** + - Use **PNG format** for UI screenshots (better quality for text) - Capture at **standard resolution** (avoid Retina 2x for smaller file sizes, or use 2x for crisp details) - Use a **consistent theme** (Pedurple is used in most existing screenshots) @@ -1038,12 +1067,12 @@ All screenshots are stored in `docs/screenshots/` and referenced with relative p Static assets like logos and icons live in `docs/assets/`: -| File | Usage | -|------|-------| -| `icon.png` | Main logo (used in light and dark mode) | -| `icon.ico` | Favicon | -| `made-with-maestro.svg` | Badge for README | -| `maestro-app-icon.png` | High-res app icon | +| File | Usage | +| ----------------------- | --------------------------------------- | +| `icon.png` | Main logo (used in light and dark mode) | +| `icon.ico` | Favicon | +| `made-with-maestro.svg` | Badge for README | +| `maestro-app-icon.png` | High-res app icon | Reference assets with `/assets/` paths in `docs.json` configuration. @@ -1066,6 +1095,7 @@ This is a helpful tip. ``` **Embed videos:** + ```markdown