Skip to content

Conversation

@DealSegfault
Copy link

@DealSegfault DealSegfault commented Dec 27, 2025

Integrated support for multiple agent CLIs.

What changed

  • The setup wizard now detects the new agent for Cursor as well (and others) by checking common install paths.

  • Cursor provider: constrained to auto mode by default since it’s free. Additional models can be enabled later in the Cursor provider.

    • Default: cursor auto
  • OpenCode provider: added because it supports many free models, including GLM 4.6 and GLM 4.7. I set GLM 4.7 as the default since it’s free and performs well (roughly comparable to Opus 4.5 per community feedback). Note: it can be a bit slow, but still a solid free option.

    • Default: glm4.7
  • Codex provider: added with full OpenAI model support. The implementation is largely aligned with the existing OpenAI path; the main motivation was enabling Codex 5.2.

Why this is useful

With Cursor/OpenCode/Codex coverage, this unlocks cheaper workflows. As a user, my priority was to run Automaker without relying on Claude Code, which is expensive.

Also included improved context management based on the examples from Agent-Skills-for-Context-Engineering (linked above).

What changed

Integrated the context-engineering workflow into the system to produce a compressed context representation.

Added a critique/review pass to improve feedback quality during development (stronger “critic” behavior when iterating).

Reference

https://github.com/muratcankoylan/Agent-Skills-for-Context-Engineering/tree/main/examples

Follow-up idea

Claude Code can still be valuable for small tasks. It should be straightforward to route lightweight operations (title renaming, summaries, spec generation, etc.) to a cheaper/faster CLI while keeping the main agent configurable.

Summary by CodeRabbit

  • New Features

    • Multi-provider AI support added (Cursor, Codex, OpenCode) with provider-aware model selector and expanded model catalog.
    • New AI Provider settings UI to choose default provider and model.
    • Guided CLI setup & status panels (install/verify) for Cursor, Codex, and OpenCode.
  • Improvements

    • Frontend/backend default-provider sync on startup.
    • Simplified agent sending behavior for more immediate responses.
  • New Tools

    • LLM-based evaluation and rubric generation for assessments.

✏️ Tip: You can customize this high-level summary in your review settings.

leet added 9 commits December 27, 2025 02:06
- Add CodexProvider with streaming JSON execution support
- Add OpenCodeProvider with MCP server integration
- Add CursorProvider with authentication and model support
- Add CodexConfigManager for MCP server configuration
- Support for multiple model tiers (basic, standard, premium)
- CLI path resolution and authentication checking
- Add LlmJudge utilities for evaluating agent outputs
- Support direct scoring, pairwise comparison, and rubric generation
- Add SubprocessManager for JSONL streaming CLI execution
- Implement quality assessment and improvement suggestions
- Enable automated evaluation of agent responses and code quality
- Add Codex, OpenCode, and Cursor status routes
- Add Cursor installation and authentication verification
- Add default provider configuration route
- Add provider testing capabilities
- Enable CLI provider setup and monitoring via API endpoints
- Remove backlog-plan routes and common utilities
- Remove agent queue routes (add, clear, list, remove)
- Remove deprecated UI components (backlog-plan dialog, claude settings)
- Remove unused settings-helpers and os-detection hook
- Clean up codebase by removing outdated functionality
- Extend provider types to support Codex, OpenCode, and Cursor
- Update ProviderFactory to instantiate new CLI providers
- Add CLI-specific configuration options and model definitions
- Support for MCP servers, authentication, and streaming execution
- Enhance provider detection and installation status tracking
- Add CLI status components for Codex, OpenCode, and Cursor
- Add setup step components for CLI provider installation
- Add AI provider settings section with CLI provider support
- Enable real-time CLI status monitoring in settings UI
- Add provider authentication and configuration interfaces
- Integrate LLM judge for automated agent output evaluation
- Add auto-critique system to agent context parser
- Enhance auto-mode service with quality assessment
- Improve agent execution with feedback loops and corrections
- Add context-aware agent behavior with self-improvement capabilities
- Add comprehensive tests for Cursor provider functionality
- Update provider factory tests to include new CLI providers
- Update model resolver tests for new provider mappings
- Update agent service tests for auto-critique integration
- Remove tests for deprecated backlog-plan and queue features
- Add provider sync hook for real-time CLI provider status
- Enhance board view with improved model selection and constants
- Update settings view with CLI status monitoring and navigation
- Improve agent view with enhanced context parsing capabilities
- Update setup view with provider-specific installation steps
- Enhance sidebar with trash operations and file browser improvements
- Update themes and global styles for better CLI integration
- Improve electron integration with provider synchronization
- Add path input enhancements and keyboard shortcut improvements
- Update app store and setup store for CLI provider support
@coderabbitai
Copy link

coderabbitai bot commented Dec 27, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

Adds three new CLI-backed providers (Cursor, OpenCode, Codex), provider routing/defaults, subprocess JSONL utilities, LLM-judge, UI setup/settings for provider/model selection and CLI status/install/verify flows, model/type expansions, frontend-backend default-provider sync, and multiple server and UI integrations and tests.

Changes

Cohort / File(s) Summary
New Providers
apps/server/src/providers/codex-provider.ts, apps/server/src/providers/cursor-provider.ts, apps/server/src/providers/opencode-provider.ts
New provider classes implementing CLI execution, JSONL streaming, installation detection, model catalogs, feature/tool/event mapping, streaming-to-ProviderMessage translation, and robust error/stream handling.
Provider Factory & Config
apps/server/src/providers/provider-factory.ts, apps/server/src/providers/codex-config-manager.ts
Added provider routing, default-provider get/set, provider enumeration/get-by-name, model routing maps, and Codex MCP TOML config manager for CODEX_HOME/config.toml merging.
Subprocess Manager & Tests
apps/server/src/lib/subprocess-manager.ts, apps/server/tests/unit/lib/subprocess-manager.test.ts
New spawnProcess and spawnJSONLProcess with merged env, AbortController support, idle and startup timeouts, JSONL line parsing, exit handling and unit tests covering parsing and timeout behaviors.
LLM Judge
apps/server/src/lib/llm-judge.ts
New LLM evaluation framework (direct score, pairwise compare, rubric generation) integrating provider model resolution, streaming/collection utilities, JSON extraction/parsing, structured outputs and failures.
Server bootstrap & routes
apps/server/src/index.ts, apps/server/src/routes/setup/*, apps/server/src/routes/app-spec/*, apps/server/src/routes/models/*
Mounted new setup and backlog-plan routes, added graceful shutdown/cleanup interval handling, provider-aware spec/feature generation, new endpoints for Cursor/OpenCode/Codex status/install/verify, and extended models/providers endpoints.
SDK options & agent/auto-mode changes
apps/server/src/lib/sdk-options.ts, apps/server/src/services/agent-service.ts, apps/server/src/services/auto-mode-service.ts
Claude.md auto-load gated by model (getModelForUseCase), systemPrompt propagation, model-aware context filtering, subprocess startupTimeout support.
Model maps/types & resolver
libs/types/src/model.ts, libs/types/src/index.ts, libs/model-resolver/src/index.ts, libs/model-resolver/src/resolver.ts, libs/types/src/provider.ts
Added CURSOR/OPENCODE/CODEX model maps and alias types, expanded ModelAlias/ModelId/AgentModel, ModelProvider union widened, and ExecuteOptions gains optional timeoutMs; resolver allows pass-through of non-Claude IDs.
UI store, sync & API clients
apps/ui/src/store/app-store.ts, apps/ui/src/store/setup-store.ts, apps/ui/src/hooks/use-provider-sync.ts, apps/ui/src/lib/electron.ts, apps/ui/src/lib/http-api-client.ts
defaultProvider/defaultModel added to store with setters/persistence; hook to sync backend default provider on mount; Electron/HTTP API extended with getCursorStatus/getOpenCodeStatus/getCodexStatus, installCursor, verifyCursorAuth, and setDefaultProvider; mock updates.
UI Setup & Settings
apps/ui/src/components/views/setup-view*.tsx, apps/ui/src/components/views/settings-view*/**, apps/ui/src/components/views/settings-view/ai-provider/*
New Cursor/Codex/OpenCode setup steps, AIProvider settings UI (provider cards, model selector), CLI status components, and hooks to check all CLIs; settings view includes AI Provider pane.
Model selector & UI constants
apps/ui/src/components/views/board-view/shared/model-constants.ts, apps/ui/src/components/views/board-view/shared/model-selector.tsx, apps/ui/src/lib/agent-context-parser.ts
Added CURSOR/OPENCODE/CODEX model groups and ALL_MODELS, provider tabs in selector, revised formatModelName and DEFAULT_MODEL fallback.
Agent/queue & PathInput/FileBrowser
apps/ui/src/hooks/use-electron-agent.ts, apps/ui/src/components/ui/path-input.tsx, apps/ui/src/components/dialogs/file-browser-dialog.tsx
Removed server-side agent queue (migrated to client queue), simplified PathInput props (removed entries/onSelectEntry) and FileBrowserDialog keyboard hint, removed useOSDetection usage in UI.
Trash & Sidebar refactor
apps/ui/src/components/layout/sidebar.tsx, apps/ui/src/components/layout/sidebar/hooks/*, apps/ui/src/components/layout/sidebar/dialogs/trash-dialog.tsx
Replaced useTrashOperations with new useTrashDialog (moved dialog visibility state into hook), refactored TrashDialog internals and exports.
Log parser & utilities
apps/ui/src/lib/log-parser.ts, apps/server/src/lib/llm-judge.ts
Tool name normalization, new aliases and categories (List/Patch), improved summaries; llm-judge introduced evaluation helpers (see above).
Docs & prompts
docs/server/providers.md, docs/server/utilities.md, docs/tuto.md, libs/prompts/src/defaults.ts, libs/prompts/src/index.ts
Docs updated for Codex sandbox flags and CLI timeouts, subprocess-manager docs, new tutorial; DEFAULT_SUBTASK_GENERATION_PROMPT added to prompts (note: duplicate declaration present).
Tests & unit updates
apps/server/tests/unit/**/*, apps/ui/tests/unit/**/*, apps/server/tests/unit/providers/*
New/updated tests for providers, provider-factory, model-resolver, subprocess-manager, cursor-provider buffering, and minor test adjustments.
Various UI tweaks
many files under apps/ui/src/components/views/* and hooks
Replaced hardcoded default 'opus' with store defaultModel, added provider-aware behavior across feature creation, profile quick-select, and dialogs; UI components and icons adjusted to support multiple providers.

Sequence Diagram(s)

sequenceDiagram
    participant UI as Frontend
    participant Store as App Store
    participant Backend as Server API
    participant Factory as ProviderFactory
    participant Provider as CLI Provider
    participant CLI as Native CLI

    UI->>Store: setDefaultProvider('cursor')
    Store->>Backend: POST /api/setup/default-provider
    Backend->>Factory: ProviderFactory.setDefaultProvider('cursor')
    Factory-->>Backend: ack
    Backend-->>Store: success

    UI->>Backend: POST /api/app-spec/generate (model='auto')
    Backend->>Factory: getProviderForModel('auto')
    Factory-->>Backend: CursorProvider
    Backend->>Provider: executeQuery(options)
    Provider->>CLI: spawn cursor-agent (jsonl)
    CLI-->>Provider: stream JSONL events
    Provider-->>Backend: stream ProviderMessage events
    Backend-->>UI: stream result to frontend
Loading
sequenceDiagram
    actor User
    participant UI as Frontend Setup
    participant Backend as Server
    participant CLI as cursor-agent
    participant Auth as Auth parser

    User->>UI: Open Cursor Setup
    UI->>Backend: GET /api/setup/cursor-status
    Backend->>CLI: which/where + common-path checks
    CLI-->>Backend: {installed?, path, version}
    Backend-->>UI: status

    alt Not installed
        UI->>Backend: POST /api/setup/install-cursor
        Backend->>CLI: run installer shell
        CLI-->>Backend: install output
        Backend-->>UI: install result
    end

    UI->>Backend: POST /api/setup/verify-cursor-auth
    Backend->>CLI: spawn cursor-agent status (with env/key)
    CLI-->>Backend: status output
    Backend->>Auth: parse auth indicators
    Auth-->>Backend: {authenticated?, method}
    Backend-->>UI: auth result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested reviewers

  • Shironex

Poem

🐰
I hopped through code with careful paws,
Brought new providers, CLIs, and laws.
Streams that sing and models new,
Defaults synced between me and you.
A tiny cheer — the rabbit bows!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.05% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title "Multi cli integration" accurately describes the main feature of the PR, which adds support for multiple CLI providers (Cursor, OpenCode, Codex) alongside Claude.
✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @DealSegfault, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly expands the application's AI capabilities by integrating multiple external CLI agents, including Cursor, OpenCode, and Codex. It introduces an advanced 'LLM-as-a-Judge' system to improve the quality and efficiency of autonomous agent operations, particularly in plan generation and context summarization. Concurrently, several legacy features like backlog planning and agent queuing have been removed, and internal context management has been refactored for a cleaner architecture. These changes aim to provide users with more flexible and powerful AI-driven development workflows.

Highlights

  • Multi-CLI Integration: Introduced support for Cursor, OpenCode, and Codex CLIs, allowing users to leverage a wider range of AI models and tools. The setup wizard now detects these new agents, and the ProviderFactory dynamically selects the appropriate provider based on the chosen model. Cursor is set as the default provider, constrained to auto mode, while OpenCode defaults to GLM 4.7, and Codex integrates full OpenAI model support, including GPT-5.2 Codex.
  • LLM-as-a-Judge for Auto Mode: Implemented an 'LLM-as-a-Judge' system within the auto-mode service to enhance agent output quality. This includes capabilities for direct scoring, pairwise comparison, and rubric generation. The system can now revise agent plans based on evaluation and generate concise context summaries for continuation, leading to more robust and cost-effective workflows.
  • Refactored Context Management: The previous CLAUDE.md auto-loading mechanism and related settings-helpers.ts file have been removed. Context files are now handled directly as part of the system prompt, simplifying the SDK options and removing redundant logic across various routes and services.
  • Removed Features: The backlog planning feature and the agent queue management functionality have been removed from both the backend and frontend. This streamlines the application by focusing on core agent execution and feature implementation workflows.
  • UI/UX Enhancements: The setup flow has been updated to guide users through configuring the new CLI providers. The model selection UI now includes options for Cursor, OpenCode, and Codex models. The file browser's path input component has been simplified, and the trash dialog now uses native browser confirmation for delete operations.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces significant changes to the server's AI provider architecture, deprecating the backlog-plan feature and CLAUDE.md auto-loading in favor of a more flexible, multi-provider system. New providers for Cursor, OpenCode, and Codex CLIs are added, along with LLM-as-a-Judge utilities for evaluating AI outputs. The server's index.ts is refactored to remove SettingsService dependencies from several routes and services, and a new subprocess-manager.ts handles JSONL streaming from external CLIs. The UI is updated to reflect these changes, including new setup steps for Cursor, Codex, and OpenCode, a unified AI provider selection in settings, and removal of the backlog-plan dialog and agent queue functionality. Review comments highlighted a ReferenceError in subprocess-manager.ts due to an undeclared variable, a potential issue with claude-provider.ts discarding final results, and the need to replace console.log statements with a centralized logger in cursor-provider.ts for better log management.


child.stderr?.on('data', (chunk) => {
stderr += chunk.toString();
lastOutput = Date.now();
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

The variable lastOutput is used here but it's not declared within the scope of the spawnProcess function. This will cause a ReferenceError at runtime. It seems this line was mistakenly copied from spawnJSONLProcess, where lastOutput is used for timeout handling. Since spawnProcess does not implement a timeout, this line is unnecessary and should be removed.

Comment on lines 180 to 182
if (!responseText && typeof msg.result === 'string') {
responseText = msg.result;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The current logic for collecting provider text might discard the final, complete result message if any partial assistant text has already been received. The condition !responseText prevents the final result from overwriting the accumulated partial text. To ensure the definitive final result is always used when available, the check for !responseText should be removed.

Suggested change
if (!responseText && typeof msg.result === 'string') {
responseText = msg.result;
}
if (typeof msg.result === 'string') {
responseText = msg.result;
}

logger.debug(`[spawn] cursor-agent ${args.slice(0, 6).join(' ')}... [prompt truncated]`);

// Spawn the cursor-agent process (NOT 'cursor agent')
console.log('[CursorProvider] Spawning cursor-agent process...');
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This file contains numerous console.log statements for debugging. While helpful during development, they can create noise in production logs. It's recommended to replace them with the centralized logger utility, which allows for consistent formatting and control over log levels across different environments. For instance, this log could be changed to logger.info(...).

Suggested change
console.log('[CursorProvider] Spawning cursor-agent process...');
logger.info('[CursorProvider] Spawning cursor-agent process...');

@Shironex Shironex added Enhancement Improvements to existing functionality or UI. Testers-Requested Request for others to test an enhancement or bug fix/etc. Do Not Merge Use this label if something should not be merged. labels Dec 27, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 19

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
apps/ui/src/lib/agent-context-parser.ts (1)

49-54: The fallback logic at line 53 has a critical bug for O-series models.

Models from the Cursor and OpenCode providers are properly handled with explicit checks (lines 38, 48). However, Codex's O-series models (o1, o3) contain no hyphens and will fail the fallback logic:

'o1'.split('-').slice(1, 3).join(' ')  // Returns empty string ''

Add explicit handling for O-series models:

if (model.startsWith('o') && /^o\d/.test(model)) return `O${model.slice(1).toUpperCase()}`;

Also note: provider-factory.ts imports CURSOR_MODEL_MAP, OPENCODE_MODEL_MAP, and CODEX_MODEL_MAP from @automaker/types, but these are not defined or exported there—only CLAUDE_MODEL_MAP exists. This will cause a compilation error.

apps/ui/src/hooks/use-electron-agent.ts (1)

27-37: Busy-agent messages are dropped instead of queued; wire addToQueue or remove queue

Right now, if isProcessing is true, sendMessage just logs a warning and returns, while useMessageQueue and queuedMessages are never actually used to enqueue anything. That means user messages sent while the agent is busy are silently discarded instead of being queued for later processing.

Given the comment and presence of queue state, it looks like the intent is to queue messages when the agent is busy. Consider either:

  • Wiring sendMessage to addToQueue when isProcessing is true (and using sendMessageDirectly only from the queue), or
  • Removing the queue-related state entirely if you no longer want client-side queuing.

Example of the first option:

Proposed wiring to enqueue when busy
-  const { queuedMessages, isProcessingQueue, addToQueue, clearQueue, processNext } =
+  const { queuedMessages, isProcessingQueue, addToQueue, clearQueue, processNext } =
     useMessageQueue({
       onProcessNext: async (queuedMessage) => {
         await sendMessageDirectly(
           queuedMessage.content,
           queuedMessage.images,
           queuedMessage.textFiles
         );
       },
     });

  const sendMessage = useCallback(
    async (content: string, images?: ImageAttachment[], textFiles?: TextFileAttachment[]) => {
      const api = getElectronAPI();
      if (!api?.agent) {
        setError('API not available');
        return;
      }

      if (isProcessing) {
-        console.warn('[useElectronAgent] Already processing a message');
-        return;
+        // Queue the message instead of dropping it
+        addToQueue({
+          id: crypto.randomUUID ? crypto.randomUUID() : `${Date.now()}`,
+          content,
+          images,
+          textFiles,
+          timestamp: new Date(),
+        });
+        return;
      }

Also applies to: 136-147, 316-333

apps/ui/src/lib/electron.ts (1)

484-553: SetupAPI interface is missing getCodexStatus, causing type drift with implementations

ElectronAPI.setup and createMockSetupAPI both define a getCodexStatus method, but the SetupAPI interface does not. Because createMockSetupAPI(): SetupAPI returns an object literal with getCodexStatus, this will either:

  • Fail type-checking in strict TypeScript (excess property on object literal), or
  • Leave getCodexStatus effectively untyped wherever SetupAPI is used.

To keep the types and implementations aligned, add getCodexStatus to SetupAPI with the same shape used in ElectronAPI.setup and createMockSetupAPI.

Proposed `SetupAPI` interface update
 interface SetupAPI {
   getClaudeStatus: () => Promise<{
     success: boolean;
     status?: string;
     installed?: boolean;
     method?: string;
     version?: string;
     path?: string;
     auth?: {
       authenticated: boolean;
       method: string;
       hasCredentialsFile?: boolean;
       hasToken?: boolean;
       hasStoredOAuthToken?: boolean;
       hasStoredApiKey?: boolean;
       hasEnvApiKey?: boolean;
       hasEnvOAuthToken?: boolean;
       hasCliAuth?: boolean;
       hasRecentActivity?: boolean;
     };
     error?: string;
   }>;
+  getCursorStatus?: () => Promise<{
+    success: boolean;
+    status?: string;
+    installed?: boolean;
+    method?: string;
+    version?: string;
+    path?: string;
+    auth?: {
+      authenticated: boolean;
+      method: string;
+      hasApiKey?: boolean;
+      hasStoredApiKey?: boolean;
+      hasEnvApiKey?: boolean;
+      apiKeyValid?: boolean;
+    };
+    error?: string;
+  }>;
+  getOpenCodeStatus?: () => Promise<{
+    success: boolean;
+    status?: string;
+    installed?: boolean;
+    method?: string;
+    version?: string;
+    path?: string;
+    auth?: {
+      authenticated: boolean;
+      method: string;
+      hasApiKey?: boolean;
+      apiKeyValid?: boolean;
+      hasEnvApiKey?: boolean;
+    };
+    error?: string;
+  }>;
+  getCodexStatus?: () => Promise<{
+    success: boolean;
+    status?: string;
+    installed?: boolean;
+    method?: string;
+    version?: string;
+    path?: string;
+    auth?: {
+      authenticated: boolean;
+      method: string;
+      hasApiKey?: boolean;
+      apiKeyValid?: boolean;
+      hasEnvApiKey?: boolean;
+    };
+    error?: string;
+  }>;
-  getCursorStatus?: () => Promise<...>;
-  getOpenCodeStatus?: () => Promise<...>;

(You can reorder as you prefer; key point is to define getCodexStatus in SetupAPI.)

Also applies to: 1115-1170, 1234-1298

apps/server/src/services/auto-mode-service.ts (1)

1999-2018: auto_mode_progress is emitted twice for the same text chunks before spec generation

In runAgent, each text block currently triggers two progress events while specDetected is still false:

  • New emit near the top of the text-block handling:
    • this.emitAutoModeEvent('auto_mode_progress', { featureId, content: block.text })
  • Existing emit later in the same branch guarded by if (!specDetected) { ... }.

This will cause duplicate progress messages in the UI for all planning/implementation output up until [SPEC_GENERATED] is seen.

You can fix this by keeping a single emit site (the earlier one is fine) and removing the later if (!specDetected) block.

Proposed fix: remove redundant emit
          if (block.type === 'text') {
            // Add separator before new text if we already have content and it doesn't end with newlines
            if (responseText.length > 0 && !responseText.endsWith('\n\n')) {
              if (responseText.endsWith('\n')) {
                responseText += '\n';
              } else {
                responseText += '\n\n';
              }
            }
            responseText += block.text || '';

-            // Emit progress event for real-time UI updates
-            this.emitAutoModeEvent('auto_mode_progress', {
-              featureId,
-              content: block.text,
-            });
+            // Emit progress event for real-time UI updates
+            this.emitAutoModeEvent('auto_mode_progress', {
+              featureId,
+              content: block.text,
+            });

            // Check for authentication errors ...
            ...
-            // Only emit progress for non-marker text (marker was already handled above)
-            if (!specDetected) {
-              this.emitAutoModeEvent('auto_mode_progress', {
-                featureId,
-                content: block.text,
-              });
-            }

Also applies to: 2470-2476

apps/server/src/index.ts (1)

115-125: Guard agentService.initialize() against startup failures

agentService.initialize() is now awaited in an async IIFE without any error handling. If initialization throws (e.g., corrupt data dir, schema mismatch), you’ll get an unhandled rejection and a server that may continue running in a partially initialized state. Wrapping this in a try/catch and either logging and exiting, or clearly marking the service as unavailable, would make startup behavior more predictable and easier to debug.

🧹 Nitpick comments (40)
apps/ui/src/components/ui/path-input.tsx (1)

235-244: Consider using a button element for breadcrumb navigation.

Using href="#" with preventDefault is a common but technically suboptimal pattern. It can cause unwanted scroll-to-top behavior if preventDefault fails, and is semantically incorrect for non-link actions.

If BreadcrumbLink accepts an asChild prop or similar composition pattern (common in Radix-based UI libraries), consider wrapping a <button> instead:

<BreadcrumbLink asChild>
  <button
    type="button"
    onClick={() => handleBreadcrumbClick(crumb.path)}
    className="font-mono text-xs text-muted-foreground hover:text-foreground transition-colors truncate max-w-[150px]"
  >
    {crumb.name}
  </button>
</BreadcrumbLink>

This is a minor accessibility improvement and can be deferred if the component API doesn't support it easily.

apps/ui/src/lib/log-parser.ts (1)

267-304: LGTM with optional DRY improvement.

The normalization integration and new Patch/List cases are well-implemented. The file path extraction logic is consistent across cases.

Consider extracting the repeated file path resolution into a helper to reduce duplication:

🔎 Optional refactor to reduce repetition
// Helper at top of function or as separate utility
const getFilePath = (parsed: Record<string, unknown>): string | undefined =>
  (parsed.file_path as string | undefined) ||
  (parsed.filePath as string | undefined) ||
  (parsed.path as string | undefined);

This would simplify each case to:

case 'Read': {
  const filePath = getFilePath(parsed);
  return `Reading ${filePath?.split('/').pop() || 'file'}`;
}
apps/ui/src/components/dialogs/file-browser-dialog.tsx (1)

369-374: Consider replacing navigator.platform with the modern User-Agent Client Hints API.

navigator.platform is deprecated. Use navigator.userAgentData.platform instead:

navigator.userAgentData?.platform?.includes('Mac') ? '⌘' : 'Ctrl'

This is only a UI hint, so the current code remains functional. The keyboard event handler (lines 213-215) correctly uses e.metaKey || e.ctrlKey, which is the proper approach and not deprecated.

apps/ui/src/styles/global.css (1)

434-451: Optional: Reduce redundancy in retro theme scrollbar styling.

The retro theme is included in the :is(.dark, .retro) selector at lines 434-441, but immediately overridden by retro-specific rules at lines 444-450. While this works correctly due to CSS cascade, it creates redundant declarations that could confuse maintainers.

🔎 Suggested refactor

Option 1: Remove retro from the shared :is() block:

-:is(.dark, .retro) ::-webkit-scrollbar-thumb {
+.dark ::-webkit-scrollbar-thumb {
   background: oklch(0.3 0 0);
   border-radius: 4px;
 }

-:is(.dark, .retro) ::-webkit-scrollbar-thumb:hover {
+.dark ::-webkit-scrollbar-thumb:hover {
   background: oklch(0.4 0 0);
 }

 /* Retro Scrollbar override */
 .retro ::-webkit-scrollbar-thumb {
   background: var(--primary);
   border-radius: 0;
 }

Option 2: Add a comment explaining the intentional override pattern if this structure is preferred for organizational reasons.

apps/ui/src/components/views/settings-view/config/navigation.ts (1)

25-26: The navigation item uses provider-specific id but generic label — confirm intentional design choice.

The id: 'cursor' maps to a multi-provider settings section, as confirmed by the rendered content: AIProviderSection allows selecting Cursor, Codex, OpenCode, or Claude, with corresponding CLI status displays. While this naming mismatch doesn't break functionality, renaming the id to 'ai-provider' would improve clarity and better reflect the actual multi-provider purpose of this settings section.

Before making the change, verify whether 'cursor' as the primary entry point for AI provider selection is intentional (e.g., Cursor as the "hero" provider). If so, consider adding a brief comment to explain why a provider-specific id labels a generic multi-provider interface.

apps/ui/src/components/views/profiles-view/utils.ts (1)

10-21: Add explicit CLAUDE_MODELS validation for clarity and consistency.

The function currently defaults to 'claude' without an explicit check. While the profile form restricts model selection to Claude models, adding an explicit check improves clarity and aligns with the pattern used in model-selector.tsx:

 export function getProviderFromModel(model: AgentModel): ModelProvider {
   if (CODEX_MODELS.some((option) => option.id === model)) {
     return 'codex';
   }
   if (OPENCODE_MODELS.some((option) => option.id === model)) {
     return 'opencode';
   }
   if (CURSOR_MODELS.some((option) => option.id === model)) {
     return 'cursor';
   }
+  if (CLAUDE_MODELS.some((option) => option.id === model)) {
+    return 'claude';
+  }
-  return 'claude';
+  // Fallback for unknown models (should not happen with proper typing)
+  return 'claude';
 }

Model IDs are unique across all provider arrays, so no conflicts exist with the current order-dependent checks.

apps/ui/src/lib/agent-context-parser.ts (1)

39-47: Consider handling additional GPT variants.

The GPT parsing logic correctly extracts version and recognizes 'codex', 'max', and 'mini' suffixes. However, other common GPT variants like 'turbo', 'preview', or 'instruct' will be silently ignored in the display label.

🔎 Proposed enhancement to handle additional GPT suffixes
 if (model.startsWith('gpt-')) {
   const [, version, ...suffixes] = model.split('-');
   if (!version) return model;
   let label = `GPT-${version}`;
   if (suffixes.includes('codex')) label += ' Codex';
+  if (suffixes.includes('turbo')) label += ' Turbo';
+  if (suffixes.includes('preview')) label += ' Preview';
+  if (suffixes.includes('instruct')) label += ' Instruct';
   if (suffixes.includes('max')) label += ' Max';
   if (suffixes.includes('mini')) label += ' Mini';
   return label;
 }
apps/server/src/providers/codex-config-manager.ts (1)

20-68: Codex MCP config writer looks solid; only minor TOML robustness nits

The overall flow (read existing, append non-duplicate server blocks, and TOML-escape values) is sound and should work well with Codex’s CLI config expectations. Two small, optional robustness points:

  • content.includes(header) can theoretically match inside comments/strings; if you ever hit false positives, consider anchoring on line starts (e.g., regex over lines) rather than raw substring search.
  • Env keys are written as bare identifiers (KEY = "value"). That’s fine for typical ABC_123-style keys, but if you ever support keys with spaces or exotic chars, you may want to quote the key name as well.

No blockers from this file.

Also applies to: 94-145

apps/server/src/services/auto-mode-service.ts (1)

59-69: Judge/summarization integration is powerful but adds several extra LLM calls per run

The new judge-based workflows (maybeGenerateContextSummary, maybeRevisePlanWithJudge) give you nice capabilities:

  • Automatic context summaries for follow-ups (agent-summary.md + feature.summary).
  • Plan quality scoring and optional revisions before execution.

However, when AUTOMAKER_JUDGE_ENABLED and AUTOMAKER_CONTEXT_SUMMARY / AUTOMAKER_PLAN_JUDGE are left at their default “on” values, a single feature run can now invoke multiple extra model calls (e.g., rubric generation once per process, two runJudgeText calls, a pairwiseCompare, and a directScore, plus optional plan revision streams).

Given the cost/latency implications, it may be worth:

  • Documenting these env flags prominently so operators can disable them in resource‑constrained environments.
  • Optionally short‑circuiting judge/summary for obviously small runs (e.g., very short responseText, or planningMode === 'skip' + no spec detected), beyond the current SUMMARY_MIN_CHARS gate.

No correctness issue here—just a consideration for operational cost and performance.

Also applies to: 2611-2765, 2767-2890

apps/ui/src/app.tsx (1)

6-7: Startup provider sync hook is a reasonable place for default-provider alignment

Calling useProviderSync() once in App keeps the backend’s default provider in sync with the store on startup, without complicating router/layout components. Since the hook already guards on hasSynced and logs errors internally, this is a low‑risk integration.

Also applies to: 25-27

apps/server/src/routes/models/routes/providers.ts (1)

20-29: Consider API consistency for provider response structure.

The new provider entries have inconsistent structures:

  • anthropic and codex include both available and hasApiKey
  • cursor and opencode only include available

While this may align with each provider's authentication model (cursor/opencode supporting free tiers), the inconsistent API shape could complicate client-side handling. Consider either:

  1. Adding hasApiKey: false to cursor/opencode for consistency
  2. Documenting the optional nature of hasApiKey in the API contract
apps/ui/src/components/views/board-view/hooks/use-board-features.ts (1)

59-67: LGTM with a suggestion for validation.

The change correctly replaces the hardcoded 'opus' fallback with the dynamic defaultModel from the store, enabling multi-provider support. The backward compatibility approach is sound for features loaded from disk.

Consider adding validation to ensure the resolved model is valid:

Optional: Add model validation
        const featuresWithIds = result.features.map((f: any, index: number) => ({
          ...f,
          id: f.id || `feature-${index}-${Date.now()}`,
          status: f.status || 'backlog',
          startedAt: f.startedAt, // Preserve startedAt timestamp
          // Ensure model and thinkingLevel are set for backward compatibility
-         model: f.model || defaultModel,
+         model: (f.model && isValidModel(f.model)) ? f.model : defaultModel,
          thinkingLevel: f.thinkingLevel || 'none',
        }));

This would protect against corrupted or obsolete model values in persisted features.

apps/server/tests/unit/lib/model-resolver.test.ts (2)

54-60: Consider using CODEX_MODEL_MAP constants instead of hardcoded strings.

The test hardcodes specific Codex model strings like 'gpt-5.2' and 'gpt-5.1-codex'. For consistency with the Cursor tests (lines 73-79) that use Object.values(CURSOR_MODEL_MAP), consider using constants from CODEX_MODEL_MAP if these models are defined there.

Optional: Use map values for consistency
    it('should pass through Codex model strings', () => {
-     const models = ['gpt-5.2', 'gpt-5.1-codex', 'o1'];
+     const models = Object.values(CODEX_MODEL_MAP);
      models.forEach((model) => {
        const result = resolveModelString(model);
        expect(result).toBe(model);
      });
    });

73-87: Clarify distinction between "passthrough" and "resolve" tests.

Lines 73-79 test "pass through Cursor model aliases" and lines 81-87 test "resolve Codex model aliases", but both verify that resolveModelString(model) returns model unchanged. The distinction between "passthrough" and "resolve" is unclear—are these testing different behaviors, or is this duplication?

If both provider model maps should pass through unchanged, consider:

  1. Merging into a single parameterized test, or
  2. Clarifying the test names to explain the difference
apps/ui/src/components/views/settings-view/cli-status/codex-cli-status.tsx (1)

81-84: Authentication status icon inconsistency.

The CheckCircle2 icon is displayed regardless of whether the user is authenticated or not. When not authenticated, this could be misleading as a green checkmark typically implies success.

🔎 Proposed fix to show appropriate icon based on auth state
-            <div className="flex items-center gap-2 text-xs text-sky-400/70 ml-1">
-              <CheckCircle2 className="w-3.5 h-3.5" />
-              <span>{isAuthenticated ? 'Authenticated' : 'Run: codex login'}</span>
-            </div>
+            <div className={cn(
+              "flex items-center gap-2 text-xs ml-1",
+              isAuthenticated ? "text-sky-400/70" : "text-amber-400/70"
+            )}>
+              {isAuthenticated ? (
+                <CheckCircle2 className="w-3.5 h-3.5" />
+              ) : (
+                <AlertCircle className="w-3.5 h-3.5" />
+              )}
+              <span>{isAuthenticated ? 'Authenticated' : 'Run: codex login'}</span>
+            </div>
apps/server/src/routes/models/routes/available.ts (1)

21-157: Consider extracting model definitions to a shared constant or configuration.

The hardcoded model catalog will require code changes each time models are added/updated. For now this is acceptable, but as the model list grows, consider extracting to a configuration file or database-backed source for easier maintenance.

apps/ui/src/hooks/use-provider-sync.ts (2)

16-34: Dependency array includes defaultProvider but effect only runs once.

The useEffect depends on defaultProvider, but the hasSynced.current guard ensures only a single execution. While functionally correct (store changes sync separately), the dependency could be removed or replaced with an empty array to better reflect the "once on mount" intent.

🔎 Proposed clarification
   useEffect(() => {
     // Only sync once on mount
     if (hasSynced.current) return;
     hasSynced.current = true;

     const syncProvider = async () => {
       try {
         const api = getElectronAPI();
         if (api.setup && 'setDefaultProvider' in api.setup) {
           await (api.setup as any).setDefaultProvider(defaultProvider);
           console.log(`[ProviderSync] Synced default provider: ${defaultProvider}`);
         }
       } catch (error) {
         console.error('[ProviderSync] Failed to sync provider:', error);
       }
     };

     syncProvider();
-  }, [defaultProvider]);
+  // Note: defaultProvider is captured at mount time; subsequent changes sync via store
+  // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, []);

24-26: Type assertion bypasses type safety.

The cast to any on line 25 avoids type checking. If the API shape is dynamic, consider using a more precise type or type guard.

apps/server/tests/unit/providers/cursor-provider.test.ts (1)

130-170: Consider adding error scenario tests.

The existing tests cover happy-path buffering well. Consider adding tests for:

  • Error messages during buffering
  • Empty stream (no messages)
  • Process exit with non-zero code
apps/server/src/routes/setup/routes/default-provider.ts (1)

29-37: Consider adding type narrowing after validation.

After the validation check, provider is still typed as any from req.body. Adding explicit type narrowing improves type safety.

🔎 Proposed fix
+type ValidProvider = 'claude' | 'cursor' | 'opencode' | 'codex';
+const VALID_PROVIDERS: ValidProvider[] = ['claude', 'cursor', 'opencode', 'codex'];
+
 export function createSetDefaultProviderHandler() {
   return async (req: Request, res: Response): Promise<void> => {
     try {
       const { provider } = req.body || {};

-      if (!provider || !['claude', 'cursor', 'opencode', 'codex'].includes(provider)) {
+      if (!provider || !VALID_PROVIDERS.includes(provider as ValidProvider)) {
         res.status(400).json({
           success: false,
           error: 'Invalid provider. Must be "claude", "cursor", "opencode", or "codex".',
         });
         return;
       }

-      ProviderFactory.setDefaultProvider(provider);
+      ProviderFactory.setDefaultProvider(provider as ValidProvider);
apps/ui/src/components/views/settings-view/cli-status/cursor-cli-status.tsx (1)

107-115: Consider adding a security awareness note for installation commands.

The installation commands pipe remote scripts directly to shell interpreters (curl ... | bash and iwr ... | iex), which is a common but potentially risky pattern. While these are official Cursor installation commands, consider adding a brief note advising users to review the script source or link to Cursor's official documentation for transparency.

Optional: Add security context
               <div className="space-y-3">
-              <p className="text-xs font-medium text-foreground/80">Installation Commands:</p>
+              <p className="text-xs font-medium text-foreground/80">
+                Installation Commands:
+              </p>
+              <p className="text-[10px] text-muted-foreground/70 -mt-1">
+                These commands download and execute the official Cursor CLI installer.
+              </p>
               <div className="space-y-2">
apps/ui/src/components/views/settings-view/ai-provider/ai-provider-section.tsx (3)

34-41: Consider refactoring nested ternary for readability.

The nested ternary operator for model selection works correctly but can be difficult to read and maintain. A switch statement or object lookup would be more maintainable.

Refactor to object lookup for better readability
-  const models =
-    defaultProvider === 'cursor'
-      ? CURSOR_MODELS
-      : defaultProvider === 'codex'
-        ? CODEX_MODELS
-        : defaultProvider === 'opencode'
-          ? OPENCODE_MODELS
-          : CLAUDE_MODELS;
+  const MODEL_MAP = {
+    cursor: CURSOR_MODELS,
+    codex: CODEX_MODELS,
+    opencode: OPENCODE_MODELS,
+    claude: CLAUDE_MODELS,
+  } as const;
+
+  const models = MODEL_MAP[defaultProvider];

48-56: Simplify default model selection logic.

Similar to the model array selection, this nested ternary for default model selection could be more readable as an object lookup.

Refactor to object lookup
     // Set a sensible default model for the new provider
-    if (provider === 'cursor') {
-      onModelChange('auto');
-    } else if (provider === 'codex') {
-      onModelChange('gpt-5.2-codex');
-    } else if (provider === 'opencode') {
-      onModelChange('glm4.7');
-    } else {
-      onModelChange('sonnet');
-    }
+    const DEFAULT_MODELS: Record<ModelProvider, AgentModel> = {
+      cursor: 'auto',
+      codex: 'gpt-5.2-codex',
+      opencode: 'glm4.7',
+      claude: 'sonnet',
+    };
+    onModelChange(DEFAULT_MODELS[provider]);

319-341: Simplify info box conditional rendering.

The nested ternary for provider-specific info text could be more maintainable as an object lookup, similar to the earlier suggestions.

Refactor to object lookup
+  const PROVIDER_INFO: Record<ModelProvider, React.ReactNode> = {
+    cursor: (
+      <>
+        <strong className="text-purple-400">Cursor Auto Mode</strong> automatically selects
+        the best model for each task. You can also explicitly choose Claude Sonnet through
+        the Cursor platform.
+      </>
+    ),
+    codex: (
+      <>
+        <strong className="text-sky-400">Codex CLI</strong> runs locally with JSONL
+        streaming, tool use, and MCP server support using GPT-5.x Codex models.
+      </>
+    ),
+    opencode: (
+      <>
+        <strong className="text-emerald-400">OpenCode CLI</strong> uses your local
+        configuration to run the free GLM 4.7 model with tool support.
+      </>
+    ),
+    claude: (
+      <>
+        <strong className="text-amber-400">Claude SDK</strong> provides direct access to
+        Anthropic&apos;s models with full control over thinking levels and extended context
+        windows.
+      </>
+    ),
+  };
+
   <div className="p-4 rounded-xl bg-accent/20 border border-border/30">
     <p className="text-xs text-muted-foreground/90 leading-relaxed">
-      {defaultProvider === 'cursor' ? (
-        <>...</>
-      ) : defaultProvider === 'codex' ? (
-        <>...</>
-      ) : defaultProvider === 'opencode' ? (
-        <>...</>
-      ) : (
-        <>...</>
-      )}
+      {PROVIDER_INFO[defaultProvider]}
     </p>
   </div>
apps/server/src/routes/setup/routes/test-provider.ts (1)

44-51: Simplify nested ternaries with object lookups.

The provider label and model selection use nested ternaries that reduce readability. Consider using object lookups for cleaner code.

Refactor to object lookups
+    const PROVIDER_LABELS = {
+      cursor: 'Cursor CLI',
+      opencode: 'OpenCode CLI',
+      codex: 'Codex CLI',
+      claude: 'Claude SDK',
+    } as const;
+
+    const PROVIDER_MODELS = {
+      cursor: 'auto',
+      opencode: 'glm4.7',
+      codex: 'gpt-5.2-codex',
+      claude: 'claude-sonnet-4-20250514',
+    } as const;
+
-    const providerLabel =
-      providerName === 'cursor'
-        ? 'Cursor CLI'
-        : providerName === 'opencode'
-          ? 'OpenCode CLI'
-          : providerName === 'codex'
-            ? 'Codex CLI'
-            : 'Claude SDK';
+    const providerLabel = PROVIDER_LABELS[providerName as keyof typeof PROVIDER_LABELS] || 'Unknown Provider';

     const testPrompt = `Create a file at /tmp/hello.md with the following content:
     ...`;

-    const model =
-      providerName === 'cursor'
-        ? 'auto'
-        : providerName === 'opencode'
-          ? 'glm4.7'
-          : providerName === 'codex'
-            ? 'gpt-5.2-codex'
-            : 'claude-sonnet-4-20250514';
+    const model = PROVIDER_MODELS[providerName as keyof typeof PROVIDER_MODELS] || 'claude-sonnet-4-20250514';

Also applies to: 69-76

apps/ui/src/components/views/setup-view/steps/opencode-setup-step.tsx (1)

29-51: Consider handling the success: false case explicitly.

When status.success is falsy (lines 35-44), cliStatus remains at its previous value (or null). The user sees no feedback in the UI beyond the toast on exception. Consider setting an error state or clearing cliStatus when the API returns success: false.

         if (status.success) {
           setCliStatus({
             installed: !!status.installed,
             version: status.version,
             path: status.path,
             auth: status.auth
               ? { authenticated: status.auth.authenticated, method: status.auth.method }
               : undefined,
           });
+        } else {
+          setCliStatus(null);
         }
apps/ui/src/components/views/setup-view/steps/codex-setup-step.tsx (1)

25-57: Significant code duplication with OpenCodeSetupStep.

The checkStatus callback, state management, and useEffect pattern are nearly identical to OpenCodeSetupStep. Consider extracting a shared hook like useCliStatusCheck(apiMethod) to reduce duplication and ensure consistent behavior across setup steps.

Example shared hook
// useCliStatusCheck.ts
export function useCliStatusCheck<T>(
  getStatus: () => Promise<{ success: boolean; installed?: boolean; version?: string; path?: string; auth?: { authenticated: boolean; method?: string } }>
) {
  const [cliStatus, setCliStatus] = useState<T | null>(null);
  const [isChecking, setIsChecking] = useState(false);

  const checkStatus = useCallback(async () => {
    setIsChecking(true);
    try {
      const status = await getStatus();
      if (status.success) {
        setCliStatus(/* map status */);
      }
    } catch (error) {
      console.error('Failed to check CLI status:', error);
      toast.error('Failed to check CLI status');
    } finally {
      setIsChecking(false);
    }
  }, [getStatus]);

  useEffect(() => { checkStatus(); }, [checkStatus]);

  return { cliStatus, isChecking, checkStatus };
}
apps/server/src/routes/setup/routes/verify-cursor-auth.ts (1)

33-36: Unsafe type assertion on process.env.

process.env values can be undefined, but the assertion as Record<string, string> hides this. While this typically works, it's safer to filter out undefined values:

-      const env: Record<string, string> = { ...process.env } as Record<string, string>;
+      const env: Record<string, string | undefined> = { ...process.env };
       if (apiKey) {
         env.CURSOR_API_KEY = apiKey;
       }

Or, if you need strict Record<string, string>, filter explicitly:

const env = Object.fromEntries(
  Object.entries(process.env).filter(([, v]) => v !== undefined)
) as Record<string, string>;
apps/ui/src/components/views/board-view/shared/model-constants.ts (1)

129-135: ALL_MODELS ordering places Cursor first.

The order is: Cursor → Codex → OpenCode → Claude. If this affects UI presentation order (e.g., in dropdowns), ensure the ordering is intentional. Claude models were previously the only option, so users may expect them first.

apps/ui/src/components/views/settings-view/cli-status/opencode-cli-status.tsx (1)

1-127: Consider extracting a shared CLI status component.

This component follows the same pattern as likely exists for Codex and Cursor CLI status components. A generic CliStatusCard component accepting provider-specific props (icon, color, name, install commands) would reduce duplication across the settings view.

apps/server/src/lib/subprocess-manager.ts (2)

22-24: timeout parameter is destructured but never used in spawnProcess.

The timeout option is extracted from options but has no effect in this function. Either implement timeout logic (similar to spawnJSONLProcess) or remove it from the destructuring to avoid confusion.

🔎 Proposed fix
 export async function spawnProcess(options: SubprocessOptions): Promise<SubprocessResult> {
-  const { command, args, cwd, env, abortController } = options;
+  const { command, args, cwd, env, abortController, timeout } = options;
   const child = spawn(command, args, {

Then implement timeout logic, or if not needed:

 export async function spawnProcess(options: SubprocessOptions): Promise<SubprocessResult> {
-  const { command, args, cwd, env, abortController } = options;
+  const { command, args, cwd, env, abortController } = options; // timeout intentionally unused

54-64: Simplify duplicate return statements.

Lines 59-61 and 63 return the same object. The conditional check for aborted is unnecessary since both branches return identical values.

🔎 Proposed fix
   const exitCode = await new Promise<number | null>((resolve, reject) => {
     child.on('close', (code) => resolve(code ?? null));
     child.on('error', (err) => reject(err));
   });

-  if (aborted) {
-    return { stdout, stderr, exitCode };
-  }
-
   return { stdout, stderr, exitCode };
 }
apps/ui/src/components/views/setup-view/steps/cursor-setup-step.tsx (2)

196-222: Consider verifying API key immediately after saving.

Currently, users must save the API key and then separately click "Verify". Consider auto-verifying after successful save for better UX.

🔎 Proposed enhancement
         if (result.success) {
           setApiKeys({ ...apiKeys, cursor: apiKey } as any);
           setCursorAuthStatus({
             authenticated: true,
             method: 'api_key',
             hasApiKey: true,
             apiKeyValid: true,
           });
           toast.success('API key saved successfully!');
+          // Auto-verify after save
+          await verifyApiKeyAuth();
         } else {

205-205: Type safety: as any casts on apiKeys.

The as any casts indicate a type mismatch between the component's expectations and the store's apiKeys type. Consider updating the store type or using a proper interface.

Also applies to: 272-272

apps/ui/src/components/views/board-view/shared/model-selector.tsx (1)

163-165: Consider expanding shortName prefix stripping.

The shortName logic removes 'Claude ' and 'Cursor ' prefixes but not others like 'GPT-', 'GLM '. For consistency, consider extending this or using a more generic approach.

🔎 Proposed enhancement
-          const shortName = option.label.replace('Claude ', '').replace('Cursor ', '');
+          const shortName = option.label
+            .replace(/^(Claude|Cursor|GPT-|GLM)\s*/i, '');
apps/ui/src/store/app-store.ts (1)

483-486: Default provider/model wiring is consistent but could validate model on provider change

Adding defaultProvider/defaultModel into AppState, initializing them to 'cursor'/'auto', persisting them via partialize, and syncing defaultProvider to the backend through setDefaultProvider is coherent and matches the new multi-provider design. One improvement to consider is resetting or validating defaultModel whenever defaultProvider changes so you don’t end up with incompatible pairs (e.g., defaultProvider: 'claude' with defaultModel: 'auto'), which would simplify downstream resolver logic.

Also applies to: 760-763, 937-939, 1562-1578, 2725-2727

apps/server/src/providers/cursor-provider.ts (1)

41-43: Cursor CLI integration works but should honor provider config env and use logger consistently

The streaming/buffering logic and final result fallback look good. Two minor follow-ups to consider:

  • In the spawn env, you currently spread only process.env; if BaseProvider is configured with config.env or an API key, you may want to merge those in (and set CURSOR_API_KEY when provided) to keep behavior aligned with other providers.
  • There’s a mix of console.log and logger usage; standardizing on the logger (or gating the console logs behind a debug flag) would reduce noise in production logs.

Also applies to: 83-96, 133-179

apps/ui/src/lib/http-api-client.ts (1)

501-588: Setup API extensions look correct; consider reusing a shared provider type

The new setup methods for Cursor/OpenCode/Codex status, install/auth, and default provider wiring map cleanly onto the expected /api/setup/* routes and return shapes that match the setup-store usage. To avoid future drift, you might want to replace the repeated 'claude' | 'cursor' | 'opencode' | 'codex' unions here with the shared ModelProvider type from @automaker/types, so HTTP client, store, and backend stay in sync from a single source.

apps/server/src/providers/provider-factory.ts (1)

17-26: Model-to-provider routing and default provider behavior look correct

The use of shared model maps plus prefix/regex checks gives solid coverage for routing Cursor/OpenCode/Claude/Codex models, and the defaultProvider fallback ensures unknown IDs still resolve in a predictable way. Instantiating fresh providers in getProviderForModel/getAllProviders is fine for current usage; if this path ever becomes hot, you could cache instances and replace the console.log calls with a logger to reduce noise, but the current behavior is functionally sound.

Also applies to: 50-99, 105-111, 138-153, 163-173

apps/server/src/lib/llm-judge.ts (1)

258-381: Consider extracting scale parsing logic.

The scale parsing at line 264 (Number.parseInt(scale.split('-')[1], 10)) is safe given the RubricScale type constraint, but this pattern repeats at line 594. Consider extracting to a helper function for reusability and to make the parsing logic more explicit:

function parseRubricScale(scale: RubricScale): { min: number; max: number } {
  const [min, max] = scale.split('-').map(v => Number.parseInt(v, 10));
  return { min, max };
}

This would improve maintainability and make the intent clearer.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b60e8f0 and f2955d4.

📒 Files selected for processing (108)
  • apps/server/src/index.ts
  • apps/server/src/lib/llm-judge.ts
  • apps/server/src/lib/sdk-options.ts
  • apps/server/src/lib/secure-fs.ts
  • apps/server/src/lib/settings-helpers.ts
  • apps/server/src/lib/subprocess-manager.ts
  • apps/server/src/providers/claude-provider.ts
  • apps/server/src/providers/codex-config-manager.ts
  • apps/server/src/providers/codex-provider.ts
  • apps/server/src/providers/cursor-provider.ts
  • apps/server/src/providers/opencode-provider.ts
  • apps/server/src/providers/provider-factory.ts
  • apps/server/src/providers/types.ts
  • apps/server/src/routes/agent/index.ts
  • apps/server/src/routes/agent/routes/queue-add.ts
  • apps/server/src/routes/agent/routes/queue-clear.ts
  • apps/server/src/routes/agent/routes/queue-list.ts
  • apps/server/src/routes/agent/routes/queue-remove.ts
  • apps/server/src/routes/app-spec/generate-features-from-spec.ts
  • apps/server/src/routes/app-spec/generate-spec.ts
  • apps/server/src/routes/app-spec/index.ts
  • apps/server/src/routes/app-spec/routes/generate-features.ts
  • apps/server/src/routes/app-spec/routes/generate.ts
  • apps/server/src/routes/backlog-plan/common.ts
  • apps/server/src/routes/backlog-plan/generate-plan.ts
  • apps/server/src/routes/backlog-plan/index.ts
  • apps/server/src/routes/backlog-plan/routes/apply.ts
  • apps/server/src/routes/backlog-plan/routes/generate.ts
  • apps/server/src/routes/backlog-plan/routes/status.ts
  • apps/server/src/routes/backlog-plan/routes/stop.ts
  • apps/server/src/routes/context/index.ts
  • apps/server/src/routes/context/routes/describe-file.ts
  • apps/server/src/routes/context/routes/describe-image.ts
  • apps/server/src/routes/github/index.ts
  • apps/server/src/routes/github/routes/validate-issue.ts
  • apps/server/src/routes/models/routes/available.ts
  • apps/server/src/routes/models/routes/providers.ts
  • apps/server/src/routes/setup/get-cursor-status.ts
  • apps/server/src/routes/setup/index.ts
  • apps/server/src/routes/setup/routes/codex-status.ts
  • apps/server/src/routes/setup/routes/cursor-status.ts
  • apps/server/src/routes/setup/routes/default-provider.ts
  • apps/server/src/routes/setup/routes/install-cursor.ts
  • apps/server/src/routes/setup/routes/opencode-status.ts
  • apps/server/src/routes/setup/routes/test-provider.ts
  • apps/server/src/routes/setup/routes/verify-cursor-auth.ts
  • apps/server/src/routes/suggestions/generate-suggestions.ts
  • apps/server/src/routes/suggestions/index.ts
  • apps/server/src/routes/suggestions/routes/generate.ts
  • apps/server/src/services/agent-service.ts
  • apps/server/src/services/auto-mode-service.ts
  • apps/server/tests/unit/lib/model-resolver.test.ts
  • apps/server/tests/unit/providers/cursor-provider.test.ts
  • apps/server/tests/unit/providers/provider-factory.test.ts
  • apps/server/tests/unit/services/agent-service.test.ts
  • apps/ui/src/app.tsx
  • apps/ui/src/components/dialogs/file-browser-dialog.tsx
  • apps/ui/src/components/layout/sidebar.tsx
  • apps/ui/src/components/layout/sidebar/dialogs/trash-dialog.tsx
  • apps/ui/src/components/layout/sidebar/hooks/index.ts
  • apps/ui/src/components/layout/sidebar/hooks/use-trash-dialog.ts
  • apps/ui/src/components/layout/sidebar/hooks/use-trash-operations.ts
  • apps/ui/src/components/ui/path-input.tsx
  • apps/ui/src/components/views/agent-view.tsx
  • apps/ui/src/components/views/board-view.tsx
  • apps/ui/src/components/views/board-view/board-header.tsx
  • apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx
  • apps/ui/src/components/views/board-view/dialogs/agent-output-modal.tsx
  • apps/ui/src/components/views/board-view/dialogs/backlog-plan-dialog.tsx
  • apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx
  • apps/ui/src/components/views/board-view/dialogs/index.ts
  • apps/ui/src/components/views/board-view/hooks/use-board-features.ts
  • apps/ui/src/components/views/board-view/shared/model-constants.ts
  • apps/ui/src/components/views/board-view/shared/model-selector.tsx
  • apps/ui/src/components/views/profiles-view/components/profile-form.tsx
  • apps/ui/src/components/views/profiles-view/utils.ts
  • apps/ui/src/components/views/settings-view.tsx
  • apps/ui/src/components/views/settings-view/ai-provider/ai-provider-section.tsx
  • apps/ui/src/components/views/settings-view/ai-provider/index.ts
  • apps/ui/src/components/views/settings-view/claude/claude-md-settings.tsx
  • apps/ui/src/components/views/settings-view/cli-status/codex-cli-status.tsx
  • apps/ui/src/components/views/settings-view/cli-status/cursor-cli-status.tsx
  • apps/ui/src/components/views/settings-view/cli-status/index.ts
  • apps/ui/src/components/views/settings-view/cli-status/opencode-cli-status.tsx
  • apps/ui/src/components/views/settings-view/config/navigation.ts
  • apps/ui/src/components/views/settings-view/hooks/use-cli-status.ts
  • apps/ui/src/components/views/settings-view/hooks/use-settings-view.ts
  • apps/ui/src/components/views/settings-view/shared/types.ts
  • apps/ui/src/components/views/setup-view.tsx
  • apps/ui/src/components/views/setup-view/steps/codex-setup-step.tsx
  • apps/ui/src/components/views/setup-view/steps/cursor-setup-step.tsx
  • apps/ui/src/components/views/setup-view/steps/index.ts
  • apps/ui/src/components/views/setup-view/steps/opencode-setup-step.tsx
  • apps/ui/src/hooks/index.ts
  • apps/ui/src/hooks/use-electron-agent.ts
  • apps/ui/src/hooks/use-os-detection.ts
  • apps/ui/src/hooks/use-provider-sync.ts
  • apps/ui/src/hooks/use-settings-migration.ts
  • apps/ui/src/lib/agent-context-parser.ts
  • apps/ui/src/lib/electron.ts
  • apps/ui/src/lib/http-api-client.ts
  • apps/ui/src/lib/log-parser.ts
  • apps/ui/src/store/app-store.ts
  • apps/ui/src/store/setup-store.ts
  • apps/ui/src/styles/global.css
  • apps/ui/src/styles/themes/red.css
  • apps/ui/src/styles/themes/retro.css
  • apps/ui/src/types/electron.d.ts
💤 Files with no reviewable changes (22)
  • apps/ui/src/hooks/index.ts
  • apps/ui/src/styles/themes/retro.css
  • apps/ui/src/hooks/use-os-detection.ts
  • apps/server/src/providers/claude-provider.ts
  • apps/server/src/routes/backlog-plan/routes/generate.ts
  • apps/server/src/routes/agent/index.ts
  • apps/server/src/lib/secure-fs.ts
  • apps/ui/src/styles/themes/red.css
  • apps/server/src/routes/agent/routes/queue-remove.ts
  • apps/server/src/routes/agent/routes/queue-list.ts
  • apps/server/src/routes/agent/routes/queue-add.ts
  • apps/server/src/routes/backlog-plan/routes/apply.ts
  • apps/server/src/routes/backlog-plan/routes/status.ts
  • apps/ui/src/components/views/board-view/dialogs/backlog-plan-dialog.tsx
  • apps/server/src/routes/backlog-plan/routes/stop.ts
  • apps/server/src/routes/backlog-plan/index.ts
  • apps/server/src/routes/backlog-plan/generate-plan.ts
  • apps/server/src/lib/settings-helpers.ts
  • apps/ui/src/components/views/board-view/dialogs/index.ts
  • apps/server/src/routes/backlog-plan/common.ts
  • apps/ui/src/components/views/settings-view/claude/claude-md-settings.tsx
  • apps/server/src/routes/agent/routes/queue-clear.ts
🧰 Additional context used
🧬 Code graph analysis (37)
apps/ui/src/hooks/use-provider-sync.ts (1)
apps/ui/src/lib/electron.ts (1)
  • getElectronAPI (769-778)
apps/ui/src/components/views/settings-view/cli-status/codex-cli-status.tsx (2)
apps/ui/src/lib/utils.ts (1)
  • cn (5-7)
apps/ui/src/components/ui/button.tsx (1)
  • Button (108-108)
apps/server/src/routes/setup/routes/verify-cursor-auth.ts (1)
apps/server/src/routes/setup/common.ts (1)
  • getApiKey (18-20)
apps/ui/src/components/views/setup-view/steps/opencode-setup-step.tsx (1)
apps/ui/src/lib/electron.ts (1)
  • getElectronAPI (769-778)
apps/server/src/routes/setup/routes/cursor-status.ts (1)
apps/server/src/routes/setup/get-cursor-status.ts (1)
  • getCursorStatus (33-143)
apps/ui/src/components/layout/sidebar/hooks/use-trash-dialog.ts (3)
apps/ui/src/lib/electron.ts (1)
  • TrashedProject (2972-2975)
apps/ui/src/components/layout/sidebar/hooks/index.ts (2)
  • useTrashDialog (11-11)
  • useTrashOperations (5-5)
apps/ui/src/components/layout/sidebar/hooks/use-trash-operations.ts (1)
  • useTrashOperations (12-92)
apps/ui/src/app.tsx (1)
apps/ui/src/hooks/use-provider-sync.ts (1)
  • useProviderSync (12-35)
apps/server/src/routes/setup/routes/test-provider.ts (1)
apps/server/src/providers/provider-factory.ts (1)
  • ProviderFactory (28-174)
apps/ui/src/components/views/board-view/board-header.tsx (1)
init.mjs (1)
  • isWindows (33-33)
apps/server/src/routes/setup/routes/default-provider.ts (1)
apps/server/src/providers/provider-factory.ts (1)
  • ProviderFactory (28-174)
apps/server/src/routes/suggestions/routes/generate.ts (3)
apps/server/src/routes/app-spec/routes/generate.ts (1)
  • createGenerateHandler (19-92)
apps/server/src/lib/events.ts (1)
  • EventEmitter (10-13)
apps/server/src/routes/suggestions/generate-suggestions.ts (1)
  • generateSuggestions (124-254)
apps/ui/src/components/views/settings-view.tsx (6)
apps/ui/src/components/views/settings-view/shared/types.ts (1)
  • Theme (3-3)
apps/ui/src/store/app-store.ts (2)
  • ThemeMode (18-18)
  • ThemeMode (34-51)
libs/types/src/index.ts (1)
  • ThemeMode (50-50)
libs/types/src/settings.ts (1)
  • ThemeMode (24-59)
apps/server/src/providers/provider-factory.ts (1)
  • setDefaultProvider (32-35)
apps/ui/src/components/views/settings-view/cli-status/index.ts (3)
  • CursorCliStatus (3-3)
  • CodexCliStatus (2-2)
  • OpenCodeCliStatus (4-4)
apps/server/src/routes/github/index.ts (2)
apps/server/src/lib/events.ts (1)
  • EventEmitter (10-13)
apps/server/src/routes/github/routes/validate-issue.ts (1)
  • createValidateIssueHandler (193-287)
apps/ui/src/components/views/agent-view.tsx (4)
apps/server/src/services/agent-service.ts (1)
  • sendMessage (113-380)
apps/ui/src/components/views/board-view/shared/model-constants.ts (1)
  • CLAUDE_MODELS (12-34)
libs/types/src/index.ts (1)
  • CLAUDE_MODELS (79-79)
apps/ui/src/lib/utils.ts (1)
  • cn (5-7)
apps/server/src/providers/cursor-provider.ts (1)
apps/ui/scripts/update-version.mjs (1)
  • version (15-15)
apps/ui/src/components/views/settings-view/cli-status/opencode-cli-status.tsx (2)
apps/ui/src/lib/utils.ts (1)
  • cn (5-7)
apps/ui/src/components/ui/button.tsx (1)
  • Button (108-108)
apps/ui/src/components/views/setup-view/steps/cursor-setup-step.tsx (4)
apps/ui/src/store/app-store.ts (1)
  • useAppStore (968-2753)
apps/ui/src/lib/electron.ts (1)
  • getElectronAPI (769-778)
apps/ui/src/components/ui/card.tsx (5)
  • Card (84-84)
  • CardHeader (84-84)
  • CardTitle (84-84)
  • CardDescription (84-84)
  • CardContent (84-84)
apps/ui/src/components/views/setup-view/components/terminal-output.tsx (1)
  • TerminalOutput (5-18)
apps/ui/src/components/layout/sidebar/hooks/use-trash-operations.ts (2)
apps/ui/src/lib/electron.ts (1)
  • TrashedProject (2972-2975)
apps/ui/src/components/layout/sidebar/hooks/index.ts (1)
  • useTrashOperations (5-5)
apps/ui/src/components/views/board-view/shared/model-selector.tsx (3)
apps/ui/src/store/app-store.ts (2)
  • AgentModel (15-15)
  • ModelProvider (15-15)
apps/ui/src/components/views/board-view/shared/model-constants.ts (5)
  • CODEX_MODELS (63-127)
  • OPENCODE_MODELS (53-61)
  • CURSOR_MODELS (36-51)
  • CLAUDE_MODELS (12-34)
  • ModelOption (4-10)
apps/ui/src/lib/utils.ts (1)
  • cn (5-7)
apps/server/src/lib/llm-judge.ts (1)
apps/server/src/providers/provider-factory.ts (1)
  • ProviderFactory (28-174)
apps/ui/src/store/app-store.ts (4)
libs/types/src/index.ts (2)
  • ModelProvider (54-54)
  • AgentModel (36-36)
libs/types/src/settings.ts (2)
  • ModelProvider (71-71)
  • AgentModel (12-12)
libs/types/src/model.ts (1)
  • AgentModel (23-23)
apps/ui/src/lib/http-api-client.ts (1)
  • getHttpApiClient (1141-1146)
apps/ui/src/components/views/settings-view/hooks/use-cli-status.ts (2)
apps/ui/src/store/setup-store.ts (1)
  • useSetupStore (165-246)
apps/ui/src/lib/electron.ts (1)
  • getElectronAPI (769-778)
apps/ui/src/components/views/profiles-view/utils.ts (2)
apps/ui/src/store/app-store.ts (2)
  • AgentModel (15-15)
  • ModelProvider (15-15)
apps/ui/src/components/views/board-view/shared/model-constants.ts (3)
  • CODEX_MODELS (63-127)
  • OPENCODE_MODELS (53-61)
  • CURSOR_MODELS (36-51)
apps/server/src/routes/app-spec/generate-spec.ts (1)
apps/server/src/routes/app-spec/generate-features-from-spec.ts (1)
  • generateFeaturesFromSpec (18-162)
apps/server/src/routes/app-spec/routes/generate-features.ts (2)
apps/server/src/lib/events.ts (1)
  • EventEmitter (10-13)
apps/server/src/routes/app-spec/generate-features-from-spec.ts (1)
  • generateFeaturesFromSpec (18-162)
apps/server/src/routes/setup/routes/install-cursor.ts (1)
init.mjs (1)
  • isWindows (33-33)
apps/server/src/services/agent-service.ts (2)
apps/server/src/lib/events.ts (1)
  • EventEmitter (10-13)
libs/utils/src/context-loader.ts (1)
  • loadContextFiles (148-202)
apps/server/src/routes/app-spec/index.ts (1)
apps/server/src/lib/events.ts (1)
  • EventEmitter (10-13)
apps/ui/src/store/setup-store.ts (2)
apps/ui/src/components/views/settings-view/shared/types.ts (1)
  • CliStatus (5-27)
apps/ui/src/lib/http-api-client.ts (1)
  • get (174-178)
apps/server/src/providers/provider-factory.ts (4)
apps/server/src/providers/cursor-provider.ts (1)
  • CursorProvider (29-647)
apps/server/src/providers/opencode-provider.ts (1)
  • OpenCodeProvider (26-862)
apps/server/src/providers/claude-provider.ts (1)
  • ClaudeProvider (17-178)
apps/server/src/providers/codex-provider.ts (1)
  • CodexProvider (28-787)
apps/server/src/services/auto-mode-service.ts (4)
apps/server/src/providers/provider-factory.ts (1)
  • ProviderFactory (28-174)
apps/server/src/lib/llm-judge.ts (3)
  • GenerateRubricOutput (114-139)
  • generateRubric (588-700)
  • runJudgeText (250-256)
libs/utils/src/context-loader.ts (1)
  • loadContextFiles (148-202)
libs/types/src/provider.ts (1)
  • ExecuteOptions (34-46)
apps/server/src/index.ts (2)
apps/server/src/routes/github/index.ts (1)
  • createGitHubRoutes (20-52)
apps/server/src/routes/context/index.ts (1)
  • createContextRoutes (17-24)
apps/ui/src/components/views/board-view.tsx (1)
apps/ui/src/store/app-store.ts (1)
  • useAppStore (968-2753)
apps/server/src/routes/suggestions/index.ts (3)
apps/server/src/lib/events.ts (1)
  • EventEmitter (10-13)
apps/server/src/middleware/validate-paths.ts (1)
  • validatePathParams (22-69)
apps/server/src/routes/suggestions/routes/generate.ts (1)
  • createGenerateHandler (13-58)
apps/server/src/routes/setup/get-cursor-status.ts (2)
init.mjs (1)
  • isWindows (33-33)
apps/server/src/routes/setup/common.ts (1)
  • getApiKey (18-20)
apps/server/src/routes/setup/routes/codex-status.ts (1)
apps/server/src/providers/codex-provider.ts (1)
  • CodexProvider (28-787)
apps/server/src/routes/app-spec/routes/generate.ts (2)
apps/server/src/routes/suggestions/routes/generate.ts (1)
  • createGenerateHandler (13-58)
apps/server/src/lib/events.ts (1)
  • EventEmitter (10-13)

Comment on lines 22 to 28
auth: {
authenticated: status.authenticated ?? false,
method: status.hasApiKey ? 'api_key' : status.authenticated ? 'cli' : 'none',
hasApiKey: status.hasApiKey ?? false,
apiKeyValid: status.hasApiKey ?? false,
hasEnvApiKey: !!process.env.OPENAI_API_KEY,
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

apiKeyValid incorrectly equated to hasApiKey.

Line 26 sets apiKeyValid: status.hasApiKey ?? false, which conflates the presence of an API key with its validity. The existence of a key doesn't guarantee it's valid or authorized. Based on the CodexProvider.detectInstallation implementation (apps/server/src/providers/codex-provider.ts:27-786), the provider only checks for key presence, not validity.

This could mislead downstream consumers (UI/clients) into thinking authentication is properly configured when the key might be invalid or expired.

🔎 Recommended approach

Either:

  1. Rename the field to accurately reflect what's being checked (e.g., hasApiKey), or
  2. Implement actual validation logic that attempts to verify the key with the OpenAI API
         auth: {
           authenticated: status.authenticated ?? false,
           method: status.hasApiKey ? 'api_key' : status.authenticated ? 'cli' : 'none',
           hasApiKey: status.hasApiKey ?? false,
-          apiKeyValid: status.hasApiKey ?? false,
+          apiKeyValid: false, // TODO: Implement actual key validation
           hasEnvApiKey: !!process.env.OPENAI_API_KEY,
         },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
auth: {
authenticated: status.authenticated ?? false,
method: status.hasApiKey ? 'api_key' : status.authenticated ? 'cli' : 'none',
hasApiKey: status.hasApiKey ?? false,
apiKeyValid: status.hasApiKey ?? false,
hasEnvApiKey: !!process.env.OPENAI_API_KEY,
},
auth: {
authenticated: status.authenticated ?? false,
method: status.hasApiKey ? 'api_key' : status.authenticated ? 'cli' : 'none',
hasApiKey: status.hasApiKey ?? false,
apiKeyValid: false, // TODO: Implement actual key validation
hasEnvApiKey: !!process.env.OPENAI_API_KEY,
},
🤖 Prompt for AI Agents
In apps/server/src/routes/setup/routes/codex-status.ts around lines 22 to 28,
the apiKeyValid field is incorrectly set to status.hasApiKey (presence ≠
validity); either replace this assignment with real validation (call the
Codex/OpenAI validation endpoint or make a lightweight auth request in
detectInstallation and set apiKeyValid based on the success/failure of that
request) or, if you cannot perform validation here, rename the field to
apiKeyPresent/hasApiKeyPresent and return the boolean from status to avoid
implying the key is valid. Ensure downstream consumers get the new field name or
the actual validation result consistently.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/ui/src/lib/agent-context-parser.ts (1)

63-63: Improve fallback to handle models without hyphens.

The current fallback model.split('-').slice(1, 3).join(' ') returns an empty string for model names that don't contain hyphens (e.g., a single-word model name like 'unknownmodel'). This could result in blank labels in the UI.

🔎 Proposed fix to return original model string as fallback
-  return model.split('-').slice(1, 3).join(' ');
+  const parts = model.split('-').slice(1, 3).join(' ');
+  return parts || model;

This ensures that if the split-based fallback produces an empty string, the original model string is displayed instead.

♻️ Duplicate comments (3)
apps/ui/src/components/layout/sidebar/hooks/use-trash-operations.ts (2)

21-36: Error handling now properly implemented.

The try/catch block with user-facing toast notifications addresses the previously flagged concern. Users will now receive appropriate feedback on both success and failure scenarios.


73-95: Well-implemented guards and error handling for empty trash.

  • Early return when trashedProjects.length === 0 prevents unnecessary operations
  • Confirmation dialog clearly explains the action scope
  • Error handling with catch block addresses the previously flagged concern
  • Using trashedProjects.length in the dependency array is an appropriate optimization to avoid re-creating the callback when only array contents change
apps/server/src/lib/subprocess-manager.ts (1)

37-39: Critical: lastOutput is not declared in spawnProcess scope.

Line 39 references lastOutput, but this variable is only declared in spawnJSONLProcess (line 77). This will cause a ReferenceError at runtime when stderr receives data. This line appears to have been mistakenly copied from spawnJSONLProcess.

🔎 Proposed fix

Since spawnProcess doesn't implement timeout tracking, remove the erroneous assignment:

  child.stderr?.on('data', (chunk) => {
    stderr += chunk.toString();
-   lastOutput = Date.now();
  });

If timeout support was intended for spawnProcess, you would need to declare and initialize lastOutput, implement the timeout checking logic, and add cleanup similar to spawnJSONLProcess.

🧹 Nitpick comments (15)
apps/server/src/lib/subprocess-manager.ts (3)

7-14: Note: timeout option only applies to spawnJSONLProcess.

The timeout field is declared in SubprocessOptions but is only implemented in spawnJSONLProcess (line 66). The spawnProcess function ignores this option. Consider either:

  • Documenting that timeout only applies to JSONL processes
  • Implementing timeout support in spawnProcess for consistency
  • Moving timeout to a separate options interface for JSONL-specific options

58-62: Remove redundant conditional.

Both branches return the identical result. The abort status is already captured in exitCode and the collected output, so this check serves no purpose.

🔎 Proposed fix
- if (aborted) {
-   return { stdout, stderr, exitCode };
- }
-
  return { stdout, stderr, exitCode };

26-26: Consider security implications of environment variable merging.

Both functions merge process.env with custom environment variables using the spread operator ({ ...process.env, ...env }). While this is a common pattern, be aware that all parent process environment variables (including potentially sensitive ones like API keys, tokens, or credentials) are passed to spawned subprocesses unless explicitly filtered.

If the spawned commands are user-controllable or from untrusted sources, consider allowlisting only necessary environment variables instead of merging all of process.env.

Also applies to: 69-69

apps/ui/src/components/dialogs/file-browser-dialog.tsx (1)

78-85: Avoid code duplication: use the existing isMac helper.

The inline Mac detection logic duplicates functionality already available in apps/ui/src/lib/utils.ts (lines 59-64). The existing helper is more robust as it also checks process.platform for SSR scenarios.

🔎 Proposed fix: import and use the existing helper
+import { isMac } from '@/lib/utils';
+
 export function FileBrowserDialog({
   open,
   onOpenChange,
   onSelect,
   title = 'Select Project Directory',
   description = 'Navigate to your project folder or paste a path directly',
   initialPath,
 }: FileBrowserDialogProps) {
   const [currentPath, setCurrentPath] = useState<string>('');
   const [parentPath, setParentPath] = useState<string | null>(null);
   const [directories, setDirectories] = useState<DirectoryEntry[]>([]);
   const [drives, setDrives] = useState<string[]>([]);
   const [loading, setLoading] = useState(false);
   const [error, setError] = useState('');
   const [warning, setWarning] = useState('');
   const [recentFolders, setRecentFolders] = useState<string[]>([]);
-  const isMac =
-    typeof navigator !== 'undefined' &&
-    (
-      (navigator as Navigator & { userAgentData?: { platform?: string } }).userAgentData
-        ?.platform ??
-      navigator.platform ??
-      ''
-    ).includes('Mac');
libs/types/src/model.ts (1)

13-24: Consider documenting the identity mapping pattern.

Unlike CLAUDE_MODEL_MAP which maps aliases to full model strings (e.g., haikuclaude-haiku-4-5-20251001), these new provider maps use identity mappings. This works for pass-through but offers no aliasing convenience. Consider either:

  1. Adding a brief comment explaining this is intentional for non-Claude providers
  2. Adding short aliases if the CLI tools support them (e.g., glmglm4.7)

Also, OPENCODE_MODEL_MAP has two entries (glm4.7 and glm-4.7) that appear to be alternate spellings of the same model. Verify this is intentional for flexibility.

apps/server/src/providers/cursor-provider.ts (2)

41-42: Hardcoded model override may confuse users.

effectiveModel is always set to 'auto' regardless of what the user requests. While line 77-79 logs this override, silently ignoring the user's model selection could be confusing. Consider either:

  1. Throwing an error if an unsupported model is requested
  2. Documenting this limitation in getAvailableModels() (which only returns auto)

94-97: Remove unnecessary stdin handling.

When stdio[0] is 'ignore', the child process's stdin is set to /dev/null and childProcess.stdin will be null. The optional chaining on line 96 safely handles this, but the call is a no-op. Consider removing line 96 along with its comment.

     stdio: ['ignore', 'pipe', 'pipe'],
   });
   logger.info('[CursorProvider] Process spawned, PID:', childProcess.pid);
-  // Explicitly end stdin in case the process expects EOF to begin execution
-  childProcess.stdin?.end();
apps/ui/src/components/views/board-view/hooks/use-board-features.ts (1)

60-69: Consider extracting validModelIds to module scope.

The validModelIds Set is recreated on every loadFeatures call. Since ALL_MODELS is static, you could define validModelIds once at module level to avoid repeated Set construction.

🔎 Proposed refactor

At the top of the file, after imports:

+const VALID_MODEL_IDS = new Set(ALL_MODELS.map((modelOption) => modelOption.id));
+
 interface UseBoardFeaturesProps {
   currentProject: { path: string; id: string } | null;
 }

Then in the function:

-        const { defaultModel } = useAppStore.getState();
-        const validModelIds = new Set(ALL_MODELS.map((modelOption) => modelOption.id));
+        const { defaultModel } = useAppStore.getState();
         const featuresWithIds = result.features.map((f: any, index: number) => ({
           ...f,
           id: f.id || `feature-${index}-${Date.now()}`,
           status: f.status || 'backlog',
           startedAt: f.startedAt,
-          model: typeof f.model === 'string' && validModelIds.has(f.model) ? f.model : defaultModel,
+          model: typeof f.model === 'string' && VALID_MODEL_IDS.has(f.model) ? f.model : defaultModel,
           thinkingLevel: f.thinkingLevel || 'none',
         }));
libs/model-resolver/src/resolver.ts (1)

34-41: Consider optimizing pass-through lookups.

The pass-through check uses Object.values().includes() for each model map, which performs linear searches. For frequently called resolution paths, consider creating a combined Set of all valid non-Claude model IDs at module scope.

🔎 Proposed optimization
+// Create a combined set of all valid non-Claude model IDs for O(1) lookups
+const NON_CLAUDE_MODEL_IDS = new Set([
+  ...Object.values(CURSOR_MODEL_MAP),
+  ...Object.values(OPENCODE_MODEL_MAP),
+  ...Object.values(CODEX_MODEL_MAP),
+]);
+
 export function resolveModelString(
   modelKey?: string,
   defaultModel: string = DEFAULT_MODELS.claude
 ): string {
   if (!modelKey) {
     return defaultModel;
   }
 
   // Pass through known non-Claude model identifiers
-  if (
-    Object.values(CURSOR_MODEL_MAP).includes(modelKey) ||
-    Object.values(OPENCODE_MODEL_MAP).includes(modelKey) ||
-    Object.values(CODEX_MODEL_MAP).includes(modelKey)
-  ) {
+  if (NON_CLAUDE_MODEL_IDS.has(modelKey)) {
     return modelKey;
   }
apps/ui/src/hooks/use-provider-sync.ts (1)

12-34: Potential stale value on initial sync.

The hook captures defaultProvider value at mount time but the hasSynced flag prevents re-syncing if the value changes before the async syncProvider() completes. If defaultProvider updates during the initial render cycle, the synced value may be stale.

Also note that setDefaultProvider in app-store.ts already performs backend sync (per the relevant code snippets), so this hook may duplicate the sync on startup.

🔎 Suggested fix using a ref to capture current value
 export function useProviderSync() {
   const { defaultProvider } = useAppStore();
   const hasSynced = useRef(false);
+  const providerRef = useRef(defaultProvider);
+  providerRef.current = defaultProvider;

   useEffect(() => {
     // Only sync once on mount
     if (hasSynced.current) return;
     hasSynced.current = true;

     const syncProvider = async () => {
       try {
         const api = getElectronAPI();
         if (api.setup?.setDefaultProvider) {
-          await api.setup.setDefaultProvider(defaultProvider);
-          console.log(`[ProviderSync] Synced default provider: ${defaultProvider}`);
+          await api.setup.setDefaultProvider(providerRef.current);
+          console.log(`[ProviderSync] Synced default provider: ${providerRef.current}`);
         }
       } catch (error) {
         console.error('[ProviderSync] Failed to sync provider:', error);
       }
     };

     syncProvider();
-  }, [defaultProvider]);
+  }, []); // Empty deps since we only sync once on mount
 }
apps/ui/src/components/views/settings-view.tsx (1)

87-97: Type casting may hide runtime issues.

The direct cast newTheme as ThemeMode bypasses TypeScript's type checking. If Theme includes values not present in ThemeMode, the cast will succeed at compile time but could cause unexpected behavior at runtime.

Consider using a type guard or validation:

🔎 Suggested safer approach
+ const isValidThemeMode = (theme: Theme): theme is ThemeMode => {
+   const validModes: ThemeMode[] = ['light', 'dark', 'system']; // adjust as needed
+   return validModes.includes(theme as ThemeMode);
+ };
+
   const handleSetTheme = (newTheme: Theme) => {
-    // Convert Theme to ThemeMode (ThemeMode is a subset of Theme)
-    // If the theme is not in ThemeMode, we'll use it as-is and let the store handle it
-    const themeMode = newTheme as ThemeMode;
+    const themeMode = isValidThemeMode(newTheme) ? newTheme : 'system';
     // Always update global theme so user's preference persists across all projects
     setTheme(themeMode);
apps/ui/src/components/views/settings-view/ai-provider/provider-card.tsx (1)

56-85: Well-structured component with good type safety.

The ProviderCard component is clean and follows React best practices:

  • Type-safe color theming via ProviderColor union
  • Proper button semantics with type="button"
  • Consistent styling pattern via COLOR_CLASSES record

Consider adding accessibility attributes for screen readers:

     <button
       type="button"
       onClick={onClick}
+      aria-pressed={isSelected}
+      aria-label={`Select ${label} provider`}
       className={cn(
apps/server/src/services/agent-service.ts (1)

213-224: Preliminary options created twice.

The code creates preliminaryOptions solely to determine the provider, then creates the final sdkOptions again with nearly identical parameters (plus systemPrompt and autoLoadClaudeMd). Consider refactoring to avoid duplicate option construction.

🔎 Suggested optimization
-      // Resolve provider before building CLAUDE.md options
-      const preliminaryOptions = createChatOptions({
-        cwd: effectiveWorkDir,
-        model: model,
-        sessionModel: session.model,
-        abortController: session.abortController!,
-      });
-      const preliminaryModel = preliminaryOptions.model!;
-      const preliminaryProvider = ProviderFactory.getProviderForModel(preliminaryModel);
+      // Determine effective model for provider resolution
+      const effectiveModel = model || session.model;
+      const preliminaryProvider = ProviderFactory.getProviderForModel(
+        effectiveModel || 'claude-sonnet-4-20250514' // default fallback
+      );
       const autoLoadClaudeMd =
         autoLoadClaudeMdSetting && preliminaryProvider.getName() === 'claude';
apps/server/src/routes/models/routes/available.ts (1)

22-120: Model specifications are duplicated across available.ts and provider implementations.

The hardcoded model definitions here duplicate specs in provider files:

  • Cursor Auto (contextWindow: 200000, maxOutputTokens: 16000) defined in both cursor-provider.ts and available.ts
  • GPT-5 variants (contextWindow: 256000, maxOutputTokens: 32000/16000) defined in both codex-provider.ts and available.ts
  • GLM 4.7 (contextWindow: 128000, maxOutputTokens: 4096) defined in both opencode-provider.ts and available.ts

This creates a maintenance burden where spec changes must be synchronized across multiple files. However, since this pattern is consistent with existing Claude model definitions, consolidation is a low-priority refactor.

apps/server/src/services/auto-mode-service.ts (1)

6-6: Update outdated comment to reflect multi-provider support.

The comment still mentions "Claude or Cursor" specifically, but line 2 correctly generalizes to "AI providers". Update this comment to match.

🔎 Suggested fix
- * - Feature execution with Claude or Cursor
+ * - Feature execution with AI providers (Claude, Cursor, OpenCode, Codex)
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f2955d4 and e0e9baf.

📒 Files selected for processing (53)
  • apps/server/src/index.ts
  • apps/server/src/lib/llm-judge.ts
  • apps/server/src/lib/sdk-options.ts
  • apps/server/src/lib/subprocess-manager.ts
  • apps/server/src/providers/claude-provider.ts
  • apps/server/src/providers/codex-config-manager.ts
  • apps/server/src/providers/cursor-provider.ts
  • apps/server/src/providers/provider-factory.ts
  • apps/server/src/providers/types.ts
  • apps/server/src/routes/app-spec/generate-features-from-spec.ts
  • apps/server/src/routes/app-spec/generate-spec.ts
  • apps/server/src/routes/app-spec/index.ts
  • apps/server/src/routes/app-spec/routes/create.ts
  • apps/server/src/routes/context/index.ts
  • apps/server/src/routes/context/routes/describe-file.ts
  • apps/server/src/routes/context/routes/describe-image.ts
  • apps/server/src/routes/github/index.ts
  • apps/server/src/routes/github/routes/validate-issue.ts
  • apps/server/src/routes/models/routes/available.ts
  • apps/server/src/routes/models/routes/providers.ts
  • apps/server/src/routes/setup/get-cursor-status.ts
  • apps/server/src/routes/setup/get-opencode-status.ts
  • apps/server/src/routes/setup/routes/codex-status.ts
  • apps/server/src/routes/setup/routes/install-cursor.ts
  • apps/server/src/routes/setup/routes/opencode-status.ts
  • apps/server/src/routes/setup/routes/test-provider.ts
  • apps/server/src/services/agent-service.ts
  • apps/server/src/services/auto-mode-service.ts
  • apps/ui/src/components/dialogs/file-browser-dialog.tsx
  • apps/ui/src/components/layout/sidebar/hooks/use-trash-operations.ts
  • apps/ui/src/components/ui/path-input.tsx
  • apps/ui/src/components/views/board-view.tsx
  • apps/ui/src/components/views/board-view/board-header.tsx
  • apps/ui/src/components/views/board-view/hooks/use-board-features.ts
  • apps/ui/src/components/views/profiles-view/utils.ts
  • apps/ui/src/components/views/settings-view.tsx
  • apps/ui/src/components/views/settings-view/ai-provider/ai-provider-section.tsx
  • apps/ui/src/components/views/settings-view/ai-provider/provider-card.tsx
  • apps/ui/src/components/views/settings-view/config/navigation.ts
  • apps/ui/src/components/views/settings-view/hooks/use-settings-view.ts
  • apps/ui/src/hooks/use-electron-agent.ts
  • apps/ui/src/hooks/use-provider-sync.ts
  • apps/ui/src/hooks/use-settings-migration.ts
  • apps/ui/src/lib/agent-context-parser.ts
  • apps/ui/src/lib/electron.ts
  • apps/ui/src/lib/http-api-client.ts
  • apps/ui/src/lib/log-parser.ts
  • apps/ui/src/store/app-store.ts
  • apps/ui/src/styles/global.css
  • libs/model-resolver/src/index.ts
  • libs/model-resolver/src/resolver.ts
  • libs/types/src/index.ts
  • libs/types/src/model.ts
💤 Files with no reviewable changes (3)
  • apps/server/src/routes/context/routes/describe-file.ts
  • apps/server/src/routes/context/index.ts
  • apps/server/src/routes/context/routes/describe-image.ts
✅ Files skipped from review due to trivial changes (1)
  • apps/ui/src/lib/log-parser.ts
🚧 Files skipped from review as they are similar to previous changes (9)
  • apps/server/src/routes/app-spec/generate-features-from-spec.ts
  • apps/ui/src/components/views/board-view/board-header.tsx
  • apps/ui/src/components/views/settings-view/ai-provider/ai-provider-section.tsx
  • apps/server/src/routes/setup/routes/test-provider.ts
  • apps/ui/src/components/views/board-view.tsx
  • apps/server/src/routes/setup/routes/install-cursor.ts
  • apps/server/src/routes/github/index.ts
  • apps/server/src/index.ts
  • apps/ui/src/components/views/settings-view/hooks/use-settings-view.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Always import from shared packages (@automaker/*), never from old relative paths

Files:

  • apps/ui/src/components/views/board-view/hooks/use-board-features.ts
  • apps/ui/src/hooks/use-provider-sync.ts
  • apps/ui/src/components/views/settings-view/config/navigation.ts
  • apps/server/src/routes/setup/get-opencode-status.ts
  • apps/ui/src/components/layout/sidebar/hooks/use-trash-operations.ts
  • apps/server/src/routes/app-spec/routes/create.ts
  • apps/ui/src/hooks/use-settings-migration.ts
  • apps/server/src/routes/setup/routes/codex-status.ts
  • apps/server/src/lib/subprocess-manager.ts
  • libs/model-resolver/src/index.ts
  • apps/server/src/routes/models/routes/providers.ts
  • apps/server/src/routes/setup/get-cursor-status.ts
  • apps/server/src/routes/app-spec/generate-spec.ts
  • apps/ui/src/components/ui/path-input.tsx
  • apps/ui/src/components/views/settings-view.tsx
  • apps/server/src/providers/claude-provider.ts
  • apps/server/src/routes/github/routes/validate-issue.ts
  • apps/server/src/providers/cursor-provider.ts
  • apps/server/src/routes/app-spec/index.ts
  • apps/ui/src/hooks/use-electron-agent.ts
  • apps/server/src/providers/codex-config-manager.ts
  • apps/ui/src/store/app-store.ts
  • apps/server/src/routes/setup/routes/opencode-status.ts
  • apps/ui/src/lib/electron.ts
  • apps/ui/src/components/dialogs/file-browser-dialog.tsx
  • apps/server/src/lib/sdk-options.ts
  • libs/types/src/model.ts
  • apps/ui/src/components/views/settings-view/ai-provider/provider-card.tsx
  • apps/server/src/lib/llm-judge.ts
  • apps/ui/src/lib/http-api-client.ts
  • apps/server/src/services/auto-mode-service.ts
  • apps/server/src/services/agent-service.ts
  • apps/server/src/providers/provider-factory.ts
  • apps/server/src/providers/types.ts
  • libs/types/src/index.ts
  • apps/server/src/routes/models/routes/available.ts
  • apps/ui/src/lib/agent-context-parser.ts
  • libs/model-resolver/src/resolver.ts
  • apps/ui/src/components/views/profiles-view/utils.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use resolveModelString() from @automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Files:

  • apps/ui/src/components/views/board-view/hooks/use-board-features.ts
  • apps/ui/src/hooks/use-provider-sync.ts
  • apps/ui/src/components/views/settings-view/config/navigation.ts
  • apps/server/src/routes/setup/get-opencode-status.ts
  • apps/ui/src/components/layout/sidebar/hooks/use-trash-operations.ts
  • apps/server/src/routes/app-spec/routes/create.ts
  • apps/ui/src/hooks/use-settings-migration.ts
  • apps/server/src/routes/setup/routes/codex-status.ts
  • apps/server/src/lib/subprocess-manager.ts
  • libs/model-resolver/src/index.ts
  • apps/server/src/routes/models/routes/providers.ts
  • apps/server/src/routes/setup/get-cursor-status.ts
  • apps/server/src/routes/app-spec/generate-spec.ts
  • apps/ui/src/components/ui/path-input.tsx
  • apps/ui/src/components/views/settings-view.tsx
  • apps/server/src/providers/claude-provider.ts
  • apps/server/src/routes/github/routes/validate-issue.ts
  • apps/server/src/providers/cursor-provider.ts
  • apps/server/src/routes/app-spec/index.ts
  • apps/ui/src/hooks/use-electron-agent.ts
  • apps/server/src/providers/codex-config-manager.ts
  • apps/ui/src/store/app-store.ts
  • apps/server/src/routes/setup/routes/opencode-status.ts
  • apps/ui/src/lib/electron.ts
  • apps/ui/src/components/dialogs/file-browser-dialog.tsx
  • apps/server/src/lib/sdk-options.ts
  • libs/types/src/model.ts
  • apps/ui/src/components/views/settings-view/ai-provider/provider-card.tsx
  • apps/server/src/lib/llm-judge.ts
  • apps/ui/src/lib/http-api-client.ts
  • apps/server/src/services/auto-mode-service.ts
  • apps/server/src/services/agent-service.ts
  • apps/server/src/providers/provider-factory.ts
  • apps/server/src/providers/types.ts
  • libs/types/src/index.ts
  • apps/server/src/routes/models/routes/available.ts
  • apps/ui/src/lib/agent-context-parser.ts
  • libs/model-resolver/src/resolver.ts
  • apps/ui/src/components/views/profiles-view/utils.ts
apps/server/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use createEventEmitter() from lib/events.ts for all server operations to emit events that stream to frontend via WebSocket

Files:

  • apps/server/src/routes/setup/get-opencode-status.ts
  • apps/server/src/routes/app-spec/routes/create.ts
  • apps/server/src/routes/setup/routes/codex-status.ts
  • apps/server/src/lib/subprocess-manager.ts
  • apps/server/src/routes/models/routes/providers.ts
  • apps/server/src/routes/setup/get-cursor-status.ts
  • apps/server/src/routes/app-spec/generate-spec.ts
  • apps/server/src/providers/claude-provider.ts
  • apps/server/src/routes/github/routes/validate-issue.ts
  • apps/server/src/providers/cursor-provider.ts
  • apps/server/src/routes/app-spec/index.ts
  • apps/server/src/providers/codex-config-manager.ts
  • apps/server/src/routes/setup/routes/opencode-status.ts
  • apps/server/src/lib/sdk-options.ts
  • apps/server/src/lib/llm-judge.ts
  • apps/server/src/services/auto-mode-service.ts
  • apps/server/src/services/agent-service.ts
  • apps/server/src/providers/provider-factory.ts
  • apps/server/src/providers/types.ts
  • apps/server/src/routes/models/routes/available.ts
🧠 Learnings (4)
📚 Learning: 2025-12-28T05:07:48.138Z
Learnt from: CR
Repo: AutoMaker-Org/automaker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T05:07:48.138Z
Learning: Applies to **/*.{ts,tsx} : Use `resolveModelString()` from automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Applied to files:

  • libs/model-resolver/src/index.ts
  • apps/server/src/lib/sdk-options.ts
  • libs/types/src/model.ts
  • apps/server/src/lib/llm-judge.ts
  • apps/server/src/services/auto-mode-service.ts
  • libs/types/src/index.ts
  • apps/ui/src/lib/agent-context-parser.ts
  • libs/model-resolver/src/resolver.ts
  • apps/ui/src/components/views/profiles-view/utils.ts
📚 Learning: 2025-12-28T05:07:48.138Z
Learnt from: CR
Repo: AutoMaker-Org/automaker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T05:07:48.138Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Always import from shared packages (automaker/*), never from old relative paths

Applied to files:

  • libs/model-resolver/src/index.ts
📚 Learning: 2025-12-28T05:07:48.138Z
Learnt from: CR
Repo: AutoMaker-Org/automaker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T05:07:48.138Z
Learning: Each feature executes in an isolated git worktree created via automaker/git-utils to protect the main branch during AI agent execution

Applied to files:

  • apps/server/src/services/auto-mode-service.ts
📚 Learning: 2025-12-28T05:07:48.138Z
Learnt from: CR
Repo: AutoMaker-Org/automaker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T05:07:48.138Z
Learning: Store project-specific rules in `.automaker/context/` and load them into agent prompts via `loadContextFiles()` from automaker/utils

Applied to files:

  • apps/server/src/services/auto-mode-service.ts
  • apps/server/src/services/agent-service.ts
🧬 Code graph analysis (17)
apps/ui/src/components/views/board-view/hooks/use-board-features.ts (2)
apps/ui/src/store/app-store.ts (1)
  • useAppStore (975-2768)
apps/ui/src/components/views/board-view/shared/model-constants.ts (1)
  • ALL_MODELS (130-135)
apps/ui/src/hooks/use-provider-sync.ts (1)
apps/ui/src/lib/electron.ts (1)
  • getElectronAPI (785-794)
apps/ui/src/components/layout/sidebar/hooks/use-trash-operations.ts (2)
apps/ui/src/lib/electron.ts (1)
  • TrashedProject (3017-3020)
apps/ui/src/components/layout/sidebar/hooks/index.ts (1)
  • useTrashOperations (5-5)
apps/server/src/routes/app-spec/routes/create.ts (2)
apps/server/src/lib/events.ts (1)
  • EventEmitter (10-13)
apps/server/src/services/settings-service.ts (1)
  • SettingsService (104-583)
apps/server/src/lib/subprocess-manager.ts (1)
init.mjs (3)
  • exitCode (294-301)
  • rl (188-191)
  • parsed (102-102)
apps/server/src/routes/setup/get-cursor-status.ts (3)
apps/ui/scripts/update-version.mjs (1)
  • version (15-15)
init.mjs (1)
  • isWindows (33-33)
apps/server/src/routes/setup/common.ts (1)
  • getApiKey (18-20)
apps/server/src/routes/app-spec/index.ts (1)
apps/server/src/routes/app-spec/routes/create.ts (1)
  • createCreateHandler (20-95)
apps/ui/src/hooks/use-electron-agent.ts (1)
apps/server/src/services/agent-service.ts (1)
  • addToQueue (632-659)
apps/ui/src/store/app-store.ts (5)
libs/types/src/index.ts (2)
  • ModelProvider (66-66)
  • AgentModel (47-47)
libs/types/src/settings.ts (2)
  • ModelProvider (71-71)
  • AgentModel (12-12)
apps/server/src/types/settings.ts (2)
  • ModelProvider (14-14)
  • AgentModel (11-11)
libs/types/src/model.ts (1)
  • AgentModel (65-65)
apps/ui/src/lib/http-api-client.ts (1)
  • getHttpApiClient (1180-1185)
apps/server/src/routes/setup/routes/opencode-status.ts (1)
apps/server/src/routes/setup/get-opencode-status.ts (1)
  • getOpenCodeStatus (26-65)
apps/ui/src/lib/electron.ts (1)
libs/types/src/index.ts (2)
  • BacklogPlanResult (115-115)
  • BacklogPlanEvent (116-116)
apps/ui/src/components/dialogs/file-browser-dialog.tsx (1)
apps/ui/src/lib/utils.ts (1)
  • isMac (60-65)
apps/server/src/services/auto-mode-service.ts (8)
apps/server/src/routes/worktree/common.ts (1)
  • execAsync (13-13)
apps/server/src/routes/github/routes/common.ts (1)
  • execAsync (8-8)
apps/server/src/lib/llm-judge.ts (5)
  • GenerateRubricOutput (114-139)
  • generateRubric (588-700)
  • runJudgeText (250-256)
  • pairwiseCompare (459-586)
  • directScore (258-381)
apps/server/src/lib/settings-helpers.ts (2)
  • getAutoLoadClaudeMdSetting (17-46)
  • filterClaudeMdFromContext (60-99)
libs/utils/src/context-loader.ts (1)
  • loadContextFiles (148-202)
apps/server/src/lib/sdk-options.ts (1)
  • validateWorkingDirectory (37-49)
apps/server/src/providers/types.ts (1)
  • ExecuteOptions (34-46)
libs/types/src/provider.ts (1)
  • ExecuteOptions (34-46)
apps/server/src/services/agent-service.ts (5)
apps/server/src/lib/settings-helpers.ts (1)
  • getAutoLoadClaudeMdSetting (17-46)
apps/server/src/lib/sdk-options.ts (1)
  • createChatOptions (307-328)
apps/server/src/providers/provider-factory.ts (1)
  • ProviderFactory (28-174)
libs/utils/src/index.ts (1)
  • loadContextFiles (50-50)
apps/server/src/lib/secure-fs.ts (1)
  • secureFs (8-23)
apps/server/src/providers/provider-factory.ts (4)
apps/server/src/providers/cursor-provider.ts (1)
  • CursorProvider (29-647)
apps/server/src/providers/opencode-provider.ts (1)
  • OpenCodeProvider (26-862)
apps/server/src/providers/claude-provider.ts (1)
  • ClaudeProvider (17-181)
apps/server/src/providers/codex-provider.ts (1)
  • CodexProvider (28-787)
libs/model-resolver/src/resolver.ts (2)
libs/types/src/index.ts (3)
  • CURSOR_MODEL_MAP (38-38)
  • OPENCODE_MODEL_MAP (39-39)
  • CODEX_MODEL_MAP (40-40)
libs/types/src/model.ts (3)
  • CURSOR_MODEL_MAP (13-16)
  • OPENCODE_MODEL_MAP (21-24)
  • CODEX_MODEL_MAP (29-42)
apps/ui/src/components/views/profiles-view/utils.ts (2)
apps/ui/src/store/app-store.ts (2)
  • AgentModel (15-15)
  • ModelProvider (15-15)
apps/ui/src/components/views/board-view/shared/model-constants.ts (4)
  • CODEX_MODELS (63-127)
  • OPENCODE_MODELS (53-61)
  • CURSOR_MODELS (36-51)
  • CLAUDE_MODELS (12-34)
🪛 ast-grep (0.40.3)
apps/server/src/providers/codex-config-manager.ts

[warning] 154-154: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(^\\[mcp_servers\\.${escaped}\\]$, 'm')
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

🔇 Additional comments (69)
apps/ui/src/components/layout/sidebar/hooks/use-trash-operations.ts (2)

5-10: LGTM! Clean interface extension for trash operations.

The addition of trashedProjects prop is well-typed and correctly destructured in the hook. This enables the empty-state guard and aligns with the TrashedProject type defined in @/lib/electron.

Also applies to: 12-17


38-71: Good addition of user confirmation before destructive disk operation.

The confirmation dialog properly warns users before sending the project folder to system Trash. The async flow, error handling, and activeTrashId state management are all correct.

apps/ui/src/components/views/settings-view/config/navigation.ts (1)

12-12: Navigation configuration for multi-provider support is properly configured.

The 'ai-provider' navigation item is correctly implemented:

  • SettingsViewId type includes the 'ai-provider' value
  • Corresponding component exists at apps/ui/src/components/views/settings-view/ai-provider/
  • Icon choices are appropriate: Wand2 for AI provider selection, Terminal for Claude SDK settings

The changes properly separate general provider settings from Claude-specific SDK configuration, supporting the PR's multi-provider architecture goals.

apps/server/src/lib/subprocess-manager.ts (1)

65-166: LGTM! Solid JSONL streaming implementation.

The async generator correctly:

  • Spawns and manages the child process lifecycle
  • Implements timeout tracking with lastOutput updates (lines 77, 88, 133, 95)
  • Parses JSONL line-by-line with proper error handling for malformed JSON (line 138)
  • Cleans up resources (readline interface and timer) in the finally block (lines 142-145)
  • Throws appropriate errors for spawn failures, aborts, timeouts, and non-zero exits (lines 150-165)

The timeout interval calculation on line 100 (Math.min(timeout, 1000)) appropriately balances responsiveness with overhead.

apps/ui/src/styles/global.css (1)

434-471: Excellent fix! Previous critical issue resolved.

All 14 themes now have complete scrollbar thumb styling, addressing the critical gap flagged in the previous review. The implementation correctly uses a consolidated :is() selector for 13 themes with shared styling, while the red theme maintains its dedicated block with theme-specific colors.

apps/ui/src/components/dialogs/file-browser-dialog.tsx (1)

243-243: Good accessibility improvement!

Removing focus-visible:outline-none restores the default focus indicator, which improves keyboard navigation accessibility.

apps/server/src/routes/app-spec/index.ts (1)

20-20: LGTM! Consistency improvement.

This change aligns the /create route with /generate and /generate-features routes (lines 21-22) that already pass settingsService. Good catch fixing this omission.

apps/server/src/routes/app-spec/routes/create.ts (1)

16-16: LGTM! Proper type import and signature update.

The type-only import and optional parameter addition properly extend the handler to accept SettingsService, enabling spec generation to utilize server-side settings.

Also applies to: 20-20

apps/server/src/providers/codex-config-manager.ts (5)

5-7: LGTM: Standard library imports are appropriate.

The use of Node.js standard library modules (fs/promises, os, path) is appropriate for file system operations and path resolution.


48-67: LGTM: Config merging logic is correct.

The method properly validates entries, checks for existing blocks, and appends new configuration blocks without duplication.


69-126: LGTM: TOML block rendering is well-structured.

The method properly validates required fields (command or url), handles optional fields, and supports both snake_case and camelCase naming conventions for compatibility.


146-151: LGTM: Key formatting logic is correct.

The method appropriately quotes keys that contain special characters while allowing simple alphanumeric keys to remain unquoted.


153-157: LGTM: Static analysis ReDoS warning is a false positive.

The regex is constructed from user input, but Line 154 properly escapes all regex special characters before interpolation. This converts the dynamic input into a literal pattern, effectively preventing Regular Expression Denial of Service (ReDoS) attacks. The static analysis tool cannot detect this sanitization.

apps/ui/src/lib/agent-context-parser.ts (4)

51-53: GLM parsing improvements look great—past concerns resolved!

The new regex-based GLM parsing correctly extracts version numbers from model strings (e.g., 'glm-4.7' → 'GLM 4.7', 'glm-4.6' → 'GLM 4.6', 'opencode/glm-4.7' → 'GLM 4.7'). This addresses the concerns raised in previous review comments about hardcoding 'GLM 4.7' for all GLM models. The implementation now correctly displays different GLM versions and includes a sensible fallback to 'GLM' for generic references.


37-50: GPT model parsing logic is well-structured.

The implementation correctly handles GPT model strings by:

  • Extracting the version number
  • Guarding against empty versions (line 41)
  • Appending appropriate suffixes (Codex, Turbo, Preview, Instruct, Max, Mini)

This provides clear, user-friendly display labels for the various GPT model variants.


54-58: O-series model parsing handles expected patterns correctly.

The regex ^o(\d+)(?:-(.+))?$ properly matches O-series models like 'o2', 'o3-preview', etc., and formats them appropriately with or without suffixes.


27-31: No issues found. The DEFAULT_MODEL = 'sonnet' constant is correctly used only for display purposes in formatModelName() calls, which explicitly handles the 'sonnet' alias (line 61) and returns 'Sonnet 4.5'. The comment accurately describes it as a "display fallback only," and the actual default for model initialization comes from app-store.defaultModel as noted. The coding guideline about resolveModelString() applies to model initialization for API calls, not display formatting.

apps/server/src/routes/app-spec/generate-spec.ts (1)

89-93: LGTM!

The key change from '[SpecRegeneration]' to '[SpecGeneration]' aligns the configuration namespace with the function name, improving clarity and consistency with other generation flows in the codebase.

apps/server/src/lib/sdk-options.ts (3)

140-145: LGTM! Clarify the default behavior for undefined model.

The helper correctly identifies Claude models. However, returning true when model is undefined (line 142-143) assumes Claude is the default provider. This is reasonable given the current architecture, but consider adding a brief comment explaining this assumption for future maintainers.


158-184: LGTM!

The updated logic correctly:

  1. Falls back to sessionModel when model is not provided
  2. Gates CLAUDE.md auto-loading on both the setting flag and Claude model detection
  3. Safely handles both string and object systemPrompt types with the type guard at line 178

200-213: LGTM!

The type widening of systemPrompt to string | SystemPromptConfig properly supports both simple text prompts and structured preset configurations. The updated JSDoc comment clarifies the auto-loading behavior.

apps/server/src/routes/github/routes/validate-issue.ts (1)

83-87: LGTM!

The key change from '[ValidateIssue]' to '[IssueValidation]' maintains consistency with the naming convention used across other Claude MD auto-loading call sites.

libs/types/src/model.ts (2)

47-52: LGTM!

The DEFAULT_MODELS extension properly provides fallback model strings for each new provider, enabling consistent behavior when no explicit model is specified.


54-65: LGTM!

The type aliases are correctly derived from the model maps, and the union type ModelAlias properly encompasses all provider aliases. The AgentModel alias maintains backward compatibility.

apps/server/src/providers/cursor-provider.ts (2)

316-336: LGTM!

The readline-based stream parsing is robust—using crlfDelay: Infinity handles cross-platform line endings, and the dynamic import avoids top-level module issues.


536-617: LGTM with minor note on platform detection.

The installation detection logic properly checks PATH first, then falls back to common installation locations. The os.platform() === 'win32' check is consistent with Node.js conventions.

Note: The external context snippet mentions process.platform is used elsewhere—both approaches are equivalent, but os.platform() is slightly more idiomatic when os is already imported.

apps/server/src/lib/llm-judge.ts (3)

141-158: LGTM!

The getDefaultJudgeModel function correctly selects a model based on the configured default provider, with appropriate fallbacks using the newly defined model maps and DEFAULT_MODELS. Based on learnings, resolveModelString() is properly used to convert aliases to full model names.


169-189: LGTM! Result handling is correct.

The current implementation at lines 179-182 unconditionally assigns msg.result to responseText when a success result is received. This correctly prioritizes the final, authoritative result over partial assistant text, addressing the concern from the previous review.


228-248: LGTM!

The runJudgePrompt function properly uses resolveModelString with a fallback to getDefaultJudgeModel(), integrates with ProviderFactory for model-agnostic execution, and configures appropriate options including limited tool access (['Read']).

apps/ui/src/hooks/use-settings-migration.ts (1)

225-226: LGTM!

The addition of defaultProvider and defaultModel to the settings sync payload correctly persists the new multi-provider preferences to file-based storage. This aligns with the broader PR changes introducing Cursor, OpenCode, and Codex provider support.

apps/server/src/providers/claude-provider.ts (2)

36-37: LGTM!

Destructuring settingSources from options improves code clarity.


59-61: LGTM!

The conditional spread correctly forwards settingSources to the Claude SDK only when provided, enabling CLAUDE.md auto-loading based on the configuration computed in buildClaudeMdOptions.

apps/ui/src/components/views/profiles-view/utils.ts (1)

10-24: LGTM!

The provider resolution logic correctly checks model membership across all provider catalogs (Codex, OpenCode, Cursor, Claude) and returns the appropriate provider. The implementation is clear and maintains proper fallback behavior.

apps/server/src/routes/setup/routes/opencode-status.ts (1)

9-23: LGTM!

The handler follows the established pattern for status endpoints: delegates to a dedicated status getter (getOpenCodeStatus), returns the result with proper error handling, and uses consistent logging utilities.

apps/server/src/providers/types.ts (2)

14-21: LGTM!

The SystemPromptConfig interface provides good type safety with literal types for type and preset, making the Claude SDK preset mode explicit and extensible. The optional append field allows for customization while maintaining the preset structure.


38-38: LGTM!

The union type string | SystemPromptConfig for systemPrompt provides flexibility while maintaining type safety, allowing both simple string prompts and structured preset configurations.

libs/model-resolver/src/index.ts (1)

7-14: LGTM!

The expanded re-exports appropriately surface the new provider model maps (Cursor, OpenCode, Codex) alongside existing Claude maps, providing a centralized public API for model resolution across all supported providers.

apps/server/src/routes/models/routes/providers.ts (1)

20-31: LGTM!

The new provider entries (cursor, opencode, codex) follow the established pattern with consistent structure for available and hasApiKey fields. The environment variable mappings are appropriate for each provider.

apps/server/src/routes/setup/routes/codex-status.ts (1)

9-35: LGTM with acknowledged TODO.

The handler follows the established pattern for CLI status endpoints. The previous concern about apiKeyValid being incorrectly set to hasApiKey has been addressed by explicitly setting it to false with a TODO comment (line 26), which is honest about the current limitation until proper key validation is implemented.

apps/ui/src/components/views/settings-view.tsx (1)

124-149: New AI provider section looks well-structured.

The ai-provider case properly renders the AIProviderSection with provider/model state and change handlers, followed by CLI status components for each provider. The pattern is consistent with the existing claude case structure.

apps/server/src/services/agent-service.ts (3)

293-295: Good addition of stream state tracking flags.

The sawError and emittedComplete flags properly track stream state to ensure the complete event is emitted exactly once, even when the stream ends without an explicit result message.


343-356: Robust handling of result messages.

The code now correctly handles cases where msg.result is defined (not just truthy), and ensures an assistant message is created if one doesn't exist. This prevents silent failures when the stream ends with a result but no prior assistant message.


372-379: Good fallback for complete event emission.

The fallback ensures a complete event is always emitted when the stream ends normally, preventing the UI from getting stuck in a "processing" state.

apps/ui/src/lib/http-api-client.ts (1)

501-588: New provider status and configuration APIs are well-structured.

The new setup methods follow established patterns:

  • Status endpoints use GET with consistent response shapes
  • Action endpoints (install, verify, setDefaultProvider) use POST
  • Provider type is constrained to valid values via union literal
  • Auth object structures are consistent across providers
libs/types/src/index.ts (1)

36-48: Exports correctly expanded for multi-provider support.

The export structure properly distinguishes between value exports (model maps) and type exports (alias types). This enables consumers to use resolveModelString() from @automaker/model-resolver with the new provider-specific model maps. Based on learnings, this aligns with the pattern of using model resolver for alias conversion.

apps/server/src/routes/setup/get-cursor-status.ts (3)

17-31: LGTM: Well-structured status interface.

The CursorStatus interface provides comprehensive auth state tracking with multiple authentication sources, which aligns well with the priority-based auth detection logic implemented below.


33-88: LGTM: Robust installation detection with platform-aware fallbacks.

The two-stage detection strategy (PATH lookup followed by common locations) provides good coverage across different installation methods and platforms. Version discovery gracefully degrades if unavailable.


116-135: Fix confirmed: Auth priority now correct.

The guard at line 118 (if (!auth.authenticated)) correctly prevents the config file check from overwriting higher-priority authentication methods (environment variable and stored API key). This resolves the issue flagged in the previous review.

apps/ui/src/store/app-store.ts (4)

10-15: LGTM: Proper use of shared types.

Importing from @automaker/types follows the coding guideline to use shared packages, and re-exporting for convenience is a good pattern.

As per coding guidelines, using @automaker/* packages.


483-485: LGTM: Clear state additions for provider configuration.

The new fields integrate well with the existing state structure and use appropriate types from the shared package.


1569-1585: LGTM: Backend sync with appropriate error handling.

The fire-and-forget pattern for setDefaultProvider is appropriate here—UI updates immediately while backend sync happens asynchronously. Error logging provides visibility without blocking the user flow. The dynamic import prevents circular dependencies.


2739-2740: LGTM: Consistent persistence configuration.

Adding these fields to the persisted state ensures user preferences survive app restarts, following the established pattern for other settings.

apps/server/src/providers/provider-factory.ts (3)

17-42: LGTM: Well-designed default provider mechanism.

The module-level state with typed getter/setter provides a clean API for managing the default provider. Case-insensitive model ID sets will help avoid routing issues. The console logging aids debugging without being excessive.


50-100: LGTM: Comprehensive routing with sensible fallback.

The routing logic covers multiple detection strategies (model ID sets, prefixes, literals, regex) and falls back to the configured default provider for unknown models. The regex /^o\d/ correctly matches OpenAI's o-series models (o1, o3, etc.).


105-158: LGTM: Provider discovery with alias support.

getAllProviders provides complete provider enumeration, and getProviderByName handles common aliases gracefully. Returning null for unknown names is a safe default.

apps/ui/src/lib/electron.ts (3)

475-488: LGTM: Consistent API design.

The backlogPlan API follows the established pattern for async operations with streaming (generate/stop/status/onEvent), maintaining consistency with suggestions and specRegeneration APIs.


520-568: LGTM: Consistent provider status API.

The optional status methods provide a uniform interface for checking provider installation and authentication, with provider-specific auth fields appropriately tailored to each provider's authentication mechanism.


1291-1421: LGTM: Appropriate mock implementations.

The mock implementations provide safe defaults (not installed, not authenticated) for development and testing, with helpful console logging. Error messages appropriately direct users to manual installation when mocking.

apps/server/src/services/auto-mode-service.ts (11)

61-88: LGTM! Environment-driven feature flags and thresholds are well-implemented.

The judge and summary features are controlled by environment variables with sensible defaults. The helper functions include proper validation and fallback handling.


30-46: LGTM! Provider-aware default model selection is correctly implemented.

The function correctly routes to provider-specific defaults using ProviderFactory.getDefaultProvider() and DEFAULT_MODELS from @automaker/model-resolver, aligning with the coding guidelines.


1586-1631: LGTM! Summary and artifact helpers support context-engineering workflow.

The new helper methods (getFeatureSummaryPath, loadFeatureSummary, updateFeatureSummary, writeFeatureArtifact) provide a clean abstraction for managing compressed context summaries, which aligns with the PR's context-engineering integration objectives.


618-627: LGTM! Context loading follows guidelines and respects autoLoadClaudeMd setting.

The code correctly uses loadContextFiles() from @automaker/utils and respects the autoLoadClaudeMd setting via getAutoLoadClaudeMdSetting() and filterClaudeMdFromContext() helpers. Based on learnings, this aligns with the pattern of loading project-specific rules from .automaker/context/.

Also applies to: 833-843


655-661: LGTM! Model resolution follows guidelines and provides clear provider context.

The code correctly uses resolveModelString() from @automaker/model-resolver to handle model aliases, and consistently logs both the model and provider for debugging. Based on learnings, this is the correct approach for model resolution.

Also applies to: 888-894, 1192-1198


1164-1167: LGTM! Working directory validation is correctly applied.

The validateWorkingDirectory() call is a critical security boundary check. The comment clearly explains why this validation is necessary in analyzeProject() since it builds ExecuteOptions directly without using the factory function.


1773-1773: LGTM! Conciseness instructions improve agent output quality.

The added instructions to keep outputs concise and avoid restating prompts will reduce verbosity and improve token efficiency. This aligns with best practices for agent prompting.

Also applies to: 1801-1801, 2982-2982


2515-2530: LGTM! Tool result handling and event emission improve observability.

The new tool_result block handling provides complete instrumentation of tool execution, emitting auto_mode_tool_result events with the result and tool use ID. This enhances real-time progress tracking.


2088-2106: LGTM! Judge integration for plan revision and context summaries is well-implemented.

The judge integration follows a clean pattern:

  • maybeRevisePlanWithJudge scores plans and triggers revision if below threshold
  • maybeGenerateContextSummary generates and scores summaries with pairwise comparison
  • Both are optional via environment flags and fail gracefully
  • Proper abort signal handling and error recovery

The implementation aligns with the PR's goal of integrating "auto-critique/review pass to improve feedback quality" mentioned in the objectives.

Also applies to: 2632-2911


2551-2561: LGTM! Error handling ensures summary generation doesn't break core functionality.

The try-catch wrapper with a warning log ensures that context summary generation failures don't impact the main feature execution flow, which is appropriate for this non-critical enhancement.


1966-1977: LGTM! System prompt handling includes helpful debug logging.

The debug logging distinguishes between string and preset-based system prompts, providing useful trace information for debugging without being overly verbose.

Comment on lines +21 to +23
async configureMcpServers(
_cwd: string,
mcpServers: Record<string, unknown>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

# Search for interface definitions that might define configureMcpServers
echo "=== Searching for interfaces defining configureMcpServers ==="
rg -n "configureMcpServers" --type ts --type tsx -B5 -A5

echo -e "\n=== Searching for class implementing this method ==="
rg -n "class.*CodexConfigManager|async configureMcpServers" --type ts --type tsx -B3

echo -e "\n=== Searching for interface definitions with configureMcpServers ==="
ast-grep --pattern 'interface $_ {
  $$$
  configureMcpServers($$$) 
  $$$
}'

Repository: AutoMaker-Org/automaker

Length of output: 325


🏁 Script executed:

#!/bin/bash

# View the target file
echo "=== Viewing codex-config-manager.ts ==="
cat -n apps/server/src/providers/codex-config-manager.ts

echo -e "\n=== Search for configureMcpServers references ==="
rg -n "configureMcpServers" --max-count=20

echo -e "\n=== Search for interface definitions ==="
rg -n "interface.*ConfigManager|export.*class.*ConfigManager" -A10

Repository: AutoMaker-Org/automaker

Length of output: 6902


🏁 Script executed:

#!/bin/bash

# View the calling context
cat -n apps/server/src/providers/codex-provider.ts | sed -n '45,70p'

# Check if there are any other references to _cwd or cwd in the context
echo -e "\n=== Checking broader context of codex-provider.ts ==="
cat -n apps/server/src/providers/codex-provider.ts | sed -n '1,100p'

Repository: AutoMaker-Org/automaker

Length of output: 4810


Remove the unused _cwd parameter or implement directory-scoped configuration.

The _cwd parameter is passed from the caller but never used in the method. The implementation ignores it and always uses process.env.CODEX_HOME with a fallback to the user's home directory, regardless of the cwd passed in. Either remove the parameter and update the call site, or use it to support directory-scoped configuration.

🤖 Prompt for AI Agents
In apps/server/src/providers/codex-config-manager.ts around lines 21–23, the
_cwd parameter is declared but never used; either remove it from the method
signature and update all call sites (and types/tests) to stop passing cwd, or
wire it into the configuration resolution so directory-scoped configuration
works: prefer the passed _cwd when computing CODEX_HOME (e.g. resolve/config
path based on _cwd) before falling back to process.env.CODEX_HOME or the user
home, update the function signature/type declarations accordingly, and adjust
any callers and tests to reflect the new behavior.

Comment on lines +128 to +144
private formatValue(value: unknown): string {
if (Array.isArray(value)) {
const items = value.map((item) => this.formatValue(item));
return `[${items.join(', ')}]`;
}
if (typeof value === 'number' && Number.isFinite(value)) {
return String(value);
}
if (typeof value === 'boolean') {
return value ? 'true' : 'false';
}
if (typeof value === 'string') {
const escaped = value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
return `"${escaped}"`;
}
return '""';
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Strengthen TOML string escaping and type handling.

Two concerns:

  1. Line 140: String escaping only handles backslashes and double quotes. TOML strings may also contain newlines (\n), tabs (\t), and other control characters that need proper escaping.

  2. Line 143: The default case returns '""' for unhandled types (null, undefined, objects, etc.), which could silently produce invalid configurations.

🔎 Proposed fix to improve type handling and escaping
 private formatValue(value: unknown): string {
   if (Array.isArray(value)) {
     const items = value.map((item) => this.formatValue(item));
     return `[${items.join(', ')}]`;
   }
   if (typeof value === 'number' && Number.isFinite(value)) {
     return String(value);
   }
   if (typeof value === 'boolean') {
     return value ? 'true' : 'false';
   }
   if (typeof value === 'string') {
-    const escaped = value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
+    const escaped = value
+      .replace(/\\/g, '\\\\')
+      .replace(/"/g, '\\"')
+      .replace(/\n/g, '\\n')
+      .replace(/\r/g, '\\r')
+      .replace(/\t/g, '\\t');
     return `"${escaped}"`;
   }
+  // Warn about unhandled types
+  console.warn(`Unhandled value type in TOML formatting: ${typeof value}`);
   return '""';
 }

Alternatively, consider using a dedicated TOML library (e.g., @iarna/toml or smol-toml) to ensure proper escaping and validation.

🤖 Prompt for AI Agents
In apps/server/src/providers/codex-config-manager.ts around lines 128 to 144,
the formatValue implementation only escapes backslashes and double quotes and
returns '""' for all other unhandled types; update it to fully escape TOML
string control characters (at minimum replace backslash, double quote, newline
(\n), carriage return (\r), tab (\t) and other common control chars with their
backslash-escaped equivalents) when handling strings, and replace the final
fallback behavior for null/undefined/objects by either (a) serializing
objects/arrays explicitly (or delegating to formatValue recursively for arrays)
and throwing a descriptive error for unsupported types, or (b) using a TOML
serializer library (e.g., @iarna/toml or smol-toml) to produce safe, validated
output; ensure any change preserves existing array/number/boolean handling and
surface a clear error instead of silently returning an empty string for
unsupported types.

Comment on lines +102 to +112
// Handle abort signal
if (abortController) {
abortController.signal.addEventListener('abort', () => {
abortRequested = true;
childProcess.kill('SIGTERM');
});
if (abortController.signal.aborted) {
abortRequested = true;
childProcess.kill('SIGTERM');
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Abort listener is never removed, risking memory leaks.

The abort event listener added at line 104 is never removed. If the AbortController is reused or the generator is abandoned before completion, this listener remains attached. Consider using { once: true } option or removing the listener in a finally block.

   if (abortController) {
-    abortController.signal.addEventListener('abort', () => {
+    abortController.signal.addEventListener('abort', () => {
       abortRequested = true;
       childProcess.kill('SIGTERM');
-    });
+    }, { once: true });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Handle abort signal
if (abortController) {
abortController.signal.addEventListener('abort', () => {
abortRequested = true;
childProcess.kill('SIGTERM');
});
if (abortController.signal.aborted) {
abortRequested = true;
childProcess.kill('SIGTERM');
}
}
// Handle abort signal
if (abortController) {
abortController.signal.addEventListener('abort', () => {
abortRequested = true;
childProcess.kill('SIGTERM');
}, { once: true });
if (abortController.signal.aborted) {
abortRequested = true;
childProcess.kill('SIGTERM');
}
}
🤖 Prompt for AI Agents
In apps/server/src/providers/cursor-provider.ts around lines 102 to 112, the
abort event listener added to abortController.signal is never removed which can
leak memory if the controller is reused or the operation is abandoned; modify
the code to either register the listener with { once: true } or store the
listener function and explicitly remove it in a finally/cleanup block after the
child process exits, and keep the existing immediate aborted check (or call the
listener directly) so the abort path still terminates the child process as
intended.

Comment on lines +30 to +55
const auth = {
authenticated: false,
method: 'none',
hasApiKey: false,
hasStoredApiKey: !!getApiKey('opencode'),
hasEnvApiKey: !!process.env.OPENCODE_API_KEY,
apiKeyValid: false,
};

if (auth.hasEnvApiKey) {
auth.authenticated = true;
auth.hasApiKey = true;
auth.method = 'api_key_env';
}

if (!auth.authenticated && auth.hasStoredApiKey) {
auth.authenticated = true;
auth.hasApiKey = true;
auth.method = 'api_key';
}

if (!auth.authenticated && installStatus.authenticated) {
auth.authenticated = true;
auth.hasApiKey = installStatus.hasApiKey ?? false;
auth.method = 'cli';
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check other status files for apiKeyValid handling
echo "=== get-cursor-status.ts ==="
rg -n 'apiKeyValid' apps/server/src/routes/setup/get-cursor-status.ts -A2 -B2

echo -e "\n=== get-codex-status.ts ==="
rg -n 'apiKeyValid' apps/server/src/routes/setup/get-codex-status.ts -A2 -B2

echo -e "\n=== get-opencode-status.ts ==="
rg -n 'apiKeyValid' apps/server/src/routes/setup/get-opencode-status.ts -A2 -B2

Repository: AutoMaker-Org/automaker

Length of output: 1046


🏁 Script executed:

# Get full content of get-opencode-status.ts to understand the context
cat -n apps/server/src/routes/setup/get-opencode-status.ts

Repository: AutoMaker-Org/automaker

Length of output: 2161


🏁 Script executed:

# Check if there are any other status files and their patterns
fd -e ts 'get-.*-status' apps/server/src/routes/setup/

Repository: AutoMaker-Org/automaker

Length of output: 218


🏁 Script executed:

rg -n 'apiKeyValid' apps/server/src/routes/setup/get-claude-status.ts -A2 -B2

Repository: AutoMaker-Org/automaker

Length of output: 901


Set apiKeyValid to true when API key authentication succeeds.

Currently, auth.apiKeyValid remains false even when authentication succeeds via API key (env or stored), whereas other provider status files (get-cursor-status.ts, get-claude-status.ts) correctly set this field to true. Additionally, the CLI authentication branch should set apiKeyValid based on installStatus.hasApiKey.

Suggested fix
   if (auth.hasEnvApiKey) {
     auth.authenticated = true;
     auth.hasApiKey = true;
     auth.method = 'api_key_env';
+    auth.apiKeyValid = true;
   }

   if (!auth.authenticated && auth.hasStoredApiKey) {
     auth.authenticated = true;
     auth.hasApiKey = true;
     auth.method = 'api_key';
+    auth.apiKeyValid = true;
   }

   if (!auth.authenticated && installStatus.authenticated) {
     auth.authenticated = true;
     auth.hasApiKey = installStatus.hasApiKey ?? false;
     auth.method = 'cli';
+    auth.apiKeyValid = installStatus.hasApiKey ?? false;
   }
🤖 Prompt for AI Agents
In apps/server/src/routes/setup/get-opencode-status.ts around lines 30 to 55,
auth.apiKeyValid is never set to true when authentication succeeds via env or
stored API key and the CLI branch ignores installStatus.hasApiKey; update the
three authentication branches so that when auth.hasEnvApiKey or
auth.hasStoredApiKey is used set auth.apiKeyValid = true, and in the CLI branch
set auth.apiKeyValid = !!installStatus.hasApiKey (or assign
installStatus.hasApiKey ?? false) so apiKeyValid correctly reflects whether a
usable API key is present.

Comment on lines +392 to +471
/* Custom scrollbar for dark themes */
:is(
.dark,
.retro,
.dracula,
.nord,
.monokai,
.tokyonight,
.solarized,
.gruvbox,
.catppuccin,
.onedark,
.synthwave,
.red,
.sunset,
.gray
)
::-webkit-scrollbar {
width: 8px;
height: 8px;
}

::-webkit-scrollbar-track {
:is(
.dark,
.retro,
.dracula,
.nord,
.monokai,
.tokyonight,
.solarized,
.gruvbox,
.catppuccin,
.onedark,
.synthwave,
.red,
.sunset,
.gray
)
::-webkit-scrollbar-track {
background: var(--muted);
}

::-webkit-scrollbar-thumb {
background: var(--muted-foreground);
:is(
.dark,
.retro,
.dracula,
.nord,
.monokai,
.tokyonight,
.solarized,
.gruvbox,
.catppuccin,
.onedark,
.synthwave,
.sunset,
.gray
)
::-webkit-scrollbar-thumb {
background: oklch(0.3 0 0);
border-radius: 4px;
}

:is(
.dark,
.retro,
.dracula,
.nord,
.monokai,
.tokyonight,
.solarized,
.gruvbox,
.catppuccin,
.onedark,
.synthwave,
.sunset,
.gray
)
::-webkit-scrollbar-thumb:hover {
background: oklch(0.4 0 0);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add scrollbar styling for .forest and .ocean themes.

The .forest and .ocean dark themes are defined as custom variants (lines 19-20) but are missing from all scrollbar selectors. Users selecting these themes will see default browser scrollbars instead of the styled versions provided to other dark themes.

🔎 Proposed fix: Include forest and ocean in scrollbar selectors

Add .forest and .ocean to each :is() selector:

 :is(
     .dark,
     .retro,
     .dracula,
     .nord,
     .monokai,
     .tokyonight,
     .solarized,
     .gruvbox,
     .catppuccin,
     .onedark,
     .synthwave,
     .red,
     .sunset,
-    .gray
+    .gray,
+    .forest,
+    .ocean
   )
   ::-webkit-scrollbar {
   width: 8px;
   height: 8px;
 }

 :is(
     .dark,
     .retro,
     .dracula,
     .nord,
     .monokai,
     .tokyonight,
     .solarized,
     .gruvbox,
     .catppuccin,
     .onedark,
     .synthwave,
     .red,
     .sunset,
-    .gray
+    .gray,
+    .forest,
+    .ocean
   )
   ::-webkit-scrollbar-track {
   background: var(--muted);
 }

 :is(
     .dark,
     .retro,
     .dracula,
     .nord,
     .monokai,
     .tokyonight,
     .solarized,
     .gruvbox,
     .catppuccin,
     .onedark,
     .synthwave,
     .sunset,
-    .gray
+    .gray,
+    .forest,
+    .ocean
   )
   ::-webkit-scrollbar-thumb {
   background: oklch(0.3 0 0);
   border-radius: 4px;
 }

 :is(
     .dark,
     .retro,
     .dracula,
     .nord,
     .monokai,
     .tokyonight,
     .solarized,
     .gruvbox,
     .catppuccin,
     .onedark,
     .synthwave,
     .sunset,
-    .gray
+    .gray,
+    .forest,
+    .ocean
   )
   ::-webkit-scrollbar-thumb:hover {
   background: oklch(0.4 0 0);
 }
🤖 Prompt for AI Agents
In apps/ui/src/styles/global.css around lines 392 to 471, the custom scrollbar
selectors using :is(...) are missing the .forest and .ocean theme classes; add
.forest and .ocean to every :is(...) list (all four scrollbar selectors:
::-webkit-scrollbar, ::-webkit-scrollbar-track, ::-webkit-scrollbar-thumb, and
::-webkit-scrollbar-thumb:hover) so those themes receive the same dark scrollbar
styling as the other themes.

…egfault/automaker into multi_cli_integration
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/server/src/services/auto-mode-service.ts (1)

54-62: Duplicate imports detected.

getAutoLoadClaudeMdSetting and filterClaudeMdFromContext are imported twice - once at line 54 and again at lines 59-62. This will cause a compilation error.

🔎 Proposed fix

Remove the duplicate import block:

 import { createAutoModeOptions, validateWorkingDirectory } from '../lib/sdk-options.js';
-import { filterClaudeMdFromContext, getAutoLoadClaudeMdSetting } from '../lib/settings-helpers.js';
 import { FeatureLoader } from './feature-loader.js';
 import type { SettingsService } from './settings-service.js';
 import { pipelineService, PipelineService } from './pipeline-service.js';
 import {
   getAutoLoadClaudeMdSetting,
   getEnableSandboxModeSetting,
   filterClaudeMdFromContext,
 } from '../lib/settings-helpers.js';
🧹 Nitpick comments (8)
apps/ui/src/components/views/agent-view.tsx (2)

52-52: Import CLAUDE_MODELS from the shared package.

Per coding guidelines, import from shared packages (@automaker/*) rather than internal paths. CLAUDE_MODELS is exported from @automaker/types.

🔎 Proposed fix
-import { CLAUDE_MODELS } from '@/components/views/board-view/shared/model-constants';
+import { CLAUDE_MODELS } from '@automaker/types';

As per coding guidelines, always import from shared packages.


66-66: Use resolveModelString() to convert model aliases.

Per coding guidelines, model aliases ('haiku', 'sonnet', 'opus') should be resolved to full model names using resolveModelString() from @automaker/model-resolver before passing to the agent hook.

🔎 Proposed fix

Add import:

+import { resolveModelString } from '@automaker/model-resolver';

Update the hook call to resolve the model:

   const {
     messages,
     isProcessing,
     isConnected,
     sendMessage,
     clearHistory,
     stopExecution,
     error: agentError,
   } = useElectronAgent({
     sessionId: currentSessionId || '',
     workingDirectory: currentProject?.path,
-    model: selectedModel,
+    model: resolveModelString(selectedModel),
     onToolUse: (toolName) => {
       setCurrentTool(toolName);
       setTimeout(() => setCurrentTool(null), 2000);
     },
   });

As per coding guidelines, use resolveModelString() to convert model aliases.

Also applies to: 93-93

apps/server/src/index.ts (1)

136-142: Consider clearing interval on shutdown.

The stale validation cleanup interval is never cleared during graceful shutdown. While process.exit() will terminate the interval anyway, for consistency with the cleanup pattern used for terminalService.cleanup(), consider storing the interval reference and clearing it.

🔎 Proposed refactor
+const validationCleanupInterval = setInterval(() => {
-setInterval(() => {
   const cleaned = cleanupStaleValidations();
   if (cleaned > 0) {
     console.log(`[Server] Cleaned up ${cleaned} stale validation entries`);
   }
 }, VALIDATION_CLEANUP_INTERVAL_MS);

 // In shutdown handlers:
 process.on('SIGTERM', () => {
   console.log('SIGTERM received, shutting down...');
+  clearInterval(validationCleanupInterval);
   terminalService.cleanup();
   // ...
 });
apps/server/src/services/auto-mode-service.ts (2)

29-46: Function defined between import statements.

getDefaultModelForProvider() is defined between import statements (lines 29-45), with more imports following at line 46. This violates typical TypeScript/ESLint conventions where all imports should be grouped at the top of the file.

🔎 Proposed refactor

Move all imports to the top of the file before any function definitions:

 import {
   directScore,
   pairwiseCompare,
   generateRubric,
   runJudgeText,
   type GenerateRubricOutput,
 } from '../lib/llm-judge.js';
-
-/**
- * Get the default model based on the current default provider setting
- */
-function getDefaultModelForProvider(): string {
-  // ... function body
-}
 import { resolveDependencies, areDependenciesSatisfied } from '@automaker/dependency-resolver';
 // ... rest of imports ...

+/**
+ * Get the default model based on the current default provider setting
+ */
+function getDefaultModelForProvider(): string {
+  // ... function body
+}

2856-2857: Consider parallel summary generation for performance.

The short and detailed summaries are generated sequentially using runJudgeText. Since they're independent operations, running them in parallel with Promise.all could reduce latency.

🔎 Proposed refactor
-    const shortSummary = (await runJudgeText(shortPrompt, systemPrompt, judgeConfig)).trim();
-    const detailedSummary = (await runJudgeText(detailedPrompt, systemPrompt, judgeConfig)).trim();
+    const [shortSummary, detailedSummary] = await Promise.all([
+      runJudgeText(shortPrompt, systemPrompt, judgeConfig).then(s => s.trim()),
+      runJudgeText(detailedPrompt, systemPrompt, judgeConfig).then(s => s.trim()),
+    ]);
apps/server/src/providers/claude-provider.ts (1)

36-37: Minor inconsistency in variable usage.

settingSources is destructured from options at line 36, but line 56 uses options.settingSources instead of the destructured variable. While functionally equivalent, using the destructured variable would be more consistent.

🔎 Proposed refactor
       // Forward settingSources for CLAUDE.md file loading
-      ...(options.settingSources && { settingSources: options.settingSources }),
+      ...(settingSources && { settingSources }),
apps/server/src/services/agent-service.ts (1)

225-235: Consider caching preliminary options to avoid duplicate work.

The code creates preliminaryOptions via createChatOptions to determine the provider, then creates another set of options at line 253. This duplicates some work. Consider extracting the model resolution logic or caching the preliminary result.

However, the current approach is clear and the overhead is minimal. The separation makes the intent explicit: first determine provider eligibility, then build final options with the appropriate settings.

apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx (1)

202-203: Consider removing unused defaultProvider from dependencies.

defaultProvider is included in the dependency array but is not referenced in the effect body (lines 164-206). Including unused dependencies can cause unnecessary re-renders when defaultProvider changes.

Proposed fix to remove unused dependency
  }, [
    open,
    defaultSkipTests,
    defaultBranch,
    defaultPlanningMode,
    defaultRequirePlanApproval,
    defaultAIProfileId,
    aiProfiles,
    defaultModel,
-   defaultProvider,
    parentFeature,
    allFeatures,
  ]);
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e0e9baf and 3514998.

📒 Files selected for processing (19)
  • apps/server/src/index.ts
  • apps/server/src/lib/sdk-options.ts
  • apps/server/src/providers/claude-provider.ts
  • apps/server/src/providers/types.ts
  • apps/server/src/routes/github/index.ts
  • apps/server/src/routes/github/routes/validate-issue.ts
  • apps/server/src/services/agent-service.ts
  • apps/server/src/services/auto-mode-service.ts
  • apps/ui/src/components/views/agent-view.tsx
  • apps/ui/src/components/views/board-view.tsx
  • apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx
  • apps/ui/src/components/views/board-view/hooks/use-board-features.ts
  • apps/ui/src/components/views/settings-view.tsx
  • apps/ui/src/hooks/use-settings-migration.ts
  • apps/ui/src/lib/electron.ts
  • apps/ui/src/lib/http-api-client.ts
  • apps/ui/src/store/app-store.ts
  • apps/ui/src/types/electron.d.ts
  • libs/types/src/index.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • apps/ui/src/store/app-store.ts
  • apps/ui/src/components/views/board-view.tsx
  • apps/ui/src/hooks/use-settings-migration.ts
  • apps/ui/src/components/views/board-view/hooks/use-board-features.ts
  • apps/server/src/routes/github/routes/validate-issue.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Always import from shared packages (@automaker/*), never from old relative paths

Files:

  • apps/ui/src/types/electron.d.ts
  • libs/types/src/index.ts
  • apps/ui/src/components/views/agent-view.tsx
  • apps/ui/src/lib/http-api-client.ts
  • apps/server/src/services/agent-service.ts
  • apps/ui/src/components/views/settings-view.tsx
  • apps/server/src/lib/sdk-options.ts
  • apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx
  • apps/server/src/providers/claude-provider.ts
  • apps/server/src/providers/types.ts
  • apps/ui/src/lib/electron.ts
  • apps/server/src/index.ts
  • apps/server/src/routes/github/index.ts
  • apps/server/src/services/auto-mode-service.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use resolveModelString() from @automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Files:

  • apps/ui/src/types/electron.d.ts
  • libs/types/src/index.ts
  • apps/ui/src/components/views/agent-view.tsx
  • apps/ui/src/lib/http-api-client.ts
  • apps/server/src/services/agent-service.ts
  • apps/ui/src/components/views/settings-view.tsx
  • apps/server/src/lib/sdk-options.ts
  • apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx
  • apps/server/src/providers/claude-provider.ts
  • apps/server/src/providers/types.ts
  • apps/ui/src/lib/electron.ts
  • apps/server/src/index.ts
  • apps/server/src/routes/github/index.ts
  • apps/server/src/services/auto-mode-service.ts
apps/server/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use createEventEmitter() from lib/events.ts for all server operations to emit events that stream to frontend via WebSocket

Files:

  • apps/server/src/services/agent-service.ts
  • apps/server/src/lib/sdk-options.ts
  • apps/server/src/providers/claude-provider.ts
  • apps/server/src/providers/types.ts
  • apps/server/src/index.ts
  • apps/server/src/routes/github/index.ts
  • apps/server/src/services/auto-mode-service.ts
🧠 Learnings (4)
📚 Learning: 2025-12-28T05:07:48.138Z
Learnt from: CR
Repo: AutoMaker-Org/automaker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T05:07:48.138Z
Learning: Applies to **/*.{ts,tsx} : Use `resolveModelString()` from automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Applied to files:

  • libs/types/src/index.ts
  • apps/server/src/lib/sdk-options.ts
  • apps/server/src/services/auto-mode-service.ts
📚 Learning: 2025-12-28T05:07:48.138Z
Learnt from: CR
Repo: AutoMaker-Org/automaker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T05:07:48.138Z
Learning: Store project-specific rules in `.automaker/context/` and load them into agent prompts via `loadContextFiles()` from automaker/utils

Applied to files:

  • apps/server/src/services/agent-service.ts
  • apps/server/src/services/auto-mode-service.ts
📚 Learning: 2025-12-28T05:07:48.138Z
Learnt from: CR
Repo: AutoMaker-Org/automaker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T05:07:48.138Z
Learning: Applies to apps/server/src/**/*.{ts,tsx} : Use `createEventEmitter()` from `lib/events.ts` for all server operations to emit events that stream to frontend via WebSocket

Applied to files:

  • apps/server/src/routes/github/index.ts
📚 Learning: 2025-12-28T05:07:48.138Z
Learnt from: CR
Repo: AutoMaker-Org/automaker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T05:07:48.138Z
Learning: Each feature executes in an isolated git worktree created via automaker/git-utils to protect the main branch during AI agent execution

Applied to files:

  • apps/server/src/services/auto-mode-service.ts
🧬 Code graph analysis (7)
apps/ui/src/components/views/agent-view.tsx (4)
apps/server/src/services/agent-service.ts (1)
  • sendMessage (134-433)
libs/types/src/index.ts (1)
  • CLAUDE_MODELS (91-91)
apps/ui/src/components/views/board-view/shared/model-constants.ts (1)
  • CLAUDE_MODELS (12-34)
apps/ui/src/lib/utils.ts (1)
  • cn (5-7)
apps/server/src/services/agent-service.ts (4)
apps/server/src/lib/settings-helpers.ts (1)
  • getAutoLoadClaudeMdSetting (17-46)
apps/server/src/lib/sdk-options.ts (1)
  • createChatOptions (311-334)
apps/server/src/providers/provider-factory.ts (1)
  • ProviderFactory (28-174)
libs/utils/src/context-loader.ts (1)
  • loadContextFiles (148-202)
apps/ui/src/components/views/settings-view.tsx (6)
apps/ui/src/store/app-store.ts (2)
  • ThemeMode (19-19)
  • ThemeMode (35-52)
libs/types/src/settings.ts (1)
  • ThemeMode (24-59)
apps/ui/src/components/views/settings-view/hooks/use-cli-status.ts (1)
  • useCliStatus (32-234)
apps/ui/src/components/views/settings-view/ai-provider/ai-provider-section.tsx (1)
  • AIProviderSection (30-251)
apps/server/src/providers/provider-factory.ts (1)
  • setDefaultProvider (32-35)
apps/ui/src/components/views/settings-view/cli-status/index.ts (2)
  • CursorCliStatus (3-3)
  • CodexCliStatus (2-2)
apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx (1)
apps/ui/src/store/app-store.ts (1)
  • useAppStore (1004-2913)
apps/ui/src/lib/electron.ts (1)
libs/types/src/index.ts (2)
  • BacklogPlanResult (121-121)
  • BacklogPlanEvent (122-122)
apps/server/src/index.ts (1)
apps/server/src/routes/backlog-plan/index.ts (1)
  • createBacklogPlanRoutes (14-30)
apps/server/src/services/auto-mode-service.ts (7)
apps/server/src/providers/provider-factory.ts (1)
  • ProviderFactory (28-174)
apps/server/src/lib/llm-judge.ts (5)
  • GenerateRubricOutput (114-139)
  • generateRubric (588-700)
  • runJudgeText (250-256)
  • pairwiseCompare (459-586)
  • directScore (258-381)
apps/server/src/lib/settings-helpers.ts (2)
  • getAutoLoadClaudeMdSetting (17-46)
  • filterClaudeMdFromContext (88-127)
libs/utils/src/context-loader.ts (1)
  • loadContextFiles (148-202)
libs/utils/src/index.ts (1)
  • loadContextFiles (50-50)
apps/server/src/lib/sdk-options.ts (1)
  • validateWorkingDirectory (37-49)
apps/server/src/providers/types.ts (1)
  • ExecuteOptions (34-47)
🔇 Additional comments (34)
libs/types/src/index.ts (1)

36-48: LGTM! All exports verified from model.ts.

The expanded export statement follows the established naming pattern and is backward-compatible. The new model maps (CURSOR_MODEL_MAP, OPENCODE_MODEL_MAP, CODEX_MODEL_MAP) and their corresponding type aliases align well with the existing CLAUDE_MODEL_MAP pattern. All referenced constants and types are properly exported from model.ts.

apps/ui/src/components/views/agent-view.tsx (4)

138-154: LGTM: Simplified send logic with proper guards.

The removal of queueing behavior in favor of immediate message sending is correctly implemented. The isProcessing check at line 140 properly prevents duplicate sends, and the message content/attachments are cleared before sending.


551-584: LGTM: Clean model selector implementation.

The model selector dropdown is well-implemented with:

  • Proper disabled state during processing
  • Clear visual feedback for the selected model
  • Accessible dropdown menu structure
  • Fallback display value

932-958: LGTM: Correct conditional rendering for Send/Stop button.

The button logic properly toggles between Send and Stop based on processing state, with appropriate disabled conditions:

  • Stop button shown during processing, disabled only when disconnected
  • Send button shown otherwise, disabled when no content or disconnected

775-775: LGTM: Comprehensive disabled state handling.

All file attachment and input controls are properly disabled during processing or when disconnected, providing consistent UX and preventing state mutations during agent execution.

Also applies to: 793-793, 829-829, 859-859, 890-890, 920-920

apps/ui/src/types/electron.d.ts (2)

185-192: LGTM! Consistent event variant structure.

The new auto_mode_tool_result variant follows the established pattern for AutoModeEvent types and appropriately supports the multi-provider tooling workflow.


983-983: LGTM! Provider support appropriately expanded.

The ModelDefinition provider field correctly extends to support Cursor, OpenCode, and Codex providers as specified in the PR objectives.

apps/ui/src/lib/http-api-client.ts (3)

501-554: LGTM! Consistent status API implementation.

The new status getter methods (getCursorStatus, getOpenCodeStatus, getCodexStatus) follow the established pattern from getClaudeStatus and correctly delegate to backend endpoints.


556-571: LGTM! Installation and auth methods properly structured.

The installCursor and verifyCursorAuth methods correctly implement the backend API calls with appropriate parameter passing and return types.


573-587: LGTM! Default provider management correctly implemented.

The getDefaultProvider and setDefaultProvider methods properly handle provider selection with the correct union types matching the expanded provider support.

apps/ui/src/lib/electron.ts (7)

535-583: LGTM! Multi-provider status methods properly defined.

The getCursorStatus, getOpenCodeStatus, and getCodexStatus methods follow the established pattern and correctly define the provider status API surface for the Electron interface.


625-637: LGTM! Cursor-specific installation and auth methods correctly defined.

The installCursor and verifyCursorAuth optional methods properly extend the setup API surface to support Cursor provider management.


1168-1216: LGTM! SetupAPI interface correctly extended.

The status getter methods in the SetupAPI interface match the ElectronAPI definitions, maintaining consistency across the API surface.


1255-1273: LGTM! Installation, auth, and provider selection methods properly typed.

The SetupAPI interface additions (installCursor, verifyCursorAuth, setDefaultProvider) are correctly defined with appropriate parameter and return types.


1306-1351: LGTM! Mock status implementations suitable for testing.

The mock implementations for getCursorStatus, getOpenCodeStatus, and getCodexStatus provide appropriate test data with not_installed states and unauthenticated auth objects.


1363-1370: LGTM! Mock installCursor appropriately returns unavailable.

The mock implementation correctly indicates that CLI installation is not supported in the mock environment with a clear error message.


1422-1436: LGTM! Mock auth and provider selection implementations functional.

The mock implementations for verifyCursorAuth and setDefaultProvider provide appropriate testing behavior, with auth verification returning unauthenticated and provider selection succeeding.

apps/ui/src/components/views/settings-view.tsx (3)

2-2: LGTM: Imports follow established patterns.

The new imports for ThemeMode and the CLI status components are consistent with existing import patterns in the file and appropriate for app-internal UI components.

Also applies to: 13-15, 18-18


53-56: LGTM: Store integration follows established patterns.

The destructuring of provider and model state/setters from the app store is consistent with the existing approach for other settings.


102-115: LGTM: CLI status hook integration is correct.

The expansion of useCliStatus() to include Cursor, Codex, and OpenCode statuses follows the established pattern for Claude and correctly destructures the values documented in the hook's implementation.

apps/server/src/routes/github/index.ts (1)

22-36: LGTM!

The SettingsService parameter is cleanly integrated as an optional dependency and properly forwarded to createValidateIssueHandler. This aligns with the pattern used in other route factories throughout the codebase.

apps/server/src/index.ts (1)

126-132: LGTM!

Wrapping the agent service initialization in a try/catch with process.exit(1) on failure is a good practice. This ensures the server doesn't continue running in a broken state if a critical service fails to initialize.

apps/server/src/services/auto-mode-service.ts (3)

77-93: LGTM with note on validation logic.

The environment variable parsing helpers are well-implemented. Note that readIntEnv rejects values ≤ 0 (line 83), which is appropriate for the current use cases (thresholds and counts). If zero becomes a valid value for any future config, a separate helper would be needed.


664-666: Good observability improvement.

Logging the provider name alongside the model enhances debugging and operational visibility when multiple providers are in use.


2688-2703: LGTM!

The tool_result handling block properly emits events for real-time UI updates and persists tool results to the output file, maintaining consistency with the existing tool_use handling pattern.

apps/server/src/providers/types.ts (2)

14-21: LGTM!

The SystemPromptConfig interface cleanly defines the structure for Claude SDK preset mode configuration. The type supports the CLAUDE.md auto-loading feature while maintaining type safety.


38-38: LGTM!

The union type string | SystemPromptConfig for systemPrompt provides flexibility for both simple string prompts and the new preset-based configuration.

apps/server/src/services/agent-service.ts (3)

303-304: LGTM!

The sawError and emittedComplete flags provide robust tracking to ensure the completion event is emitted exactly once, even in edge cases where the stream ends without an explicit result message.


353-365: LGTM!

The result handling now properly creates an assistant message if one doesn't exist, preventing cases where a successful result might not be captured in the conversation history.


381-388: LGTM!

The fallback completion event emission ensures clients always receive a completion notification, even if the stream ends without an explicit result message and no error occurred.

apps/server/src/lib/sdk-options.ts (3)

140-145: LGTM!

The isClaudeModelKey helper correctly identifies Claude-compatible models by checking for the claude- prefix or presence in CLAUDE_MODEL_MAP. Returning true for undefined models is the right default behavior.


154-184: LGTM!

The buildClaudeMdOptions function now correctly:

  1. Checks if the model is Claude-compatible before enabling auto-loading
  2. Includes both 'user' and 'project' in settingSources for comprehensive CLAUDE.md discovery
  3. Only appends string-based systemPrompts to the preset configuration

This provider-aware approach prevents attempting to use Claude-specific features with non-Claude providers.


200-200: LGTM!

The type signature update to string | SystemPromptConfig aligns with the imported type from providers/types.ts.

apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx (1)

121-131: LGTM - Store-based default model initialization.

The migration from hardcoded 'opus' to store-based defaultModel is well implemented. The initial value is set from the store on mount, and the effect at line 164 ensures proper synchronization when the dialog opens.

app.use('/api/agent', createAgentRoutes(agentService, events));
app.use('/api/sessions', createSessionsRoutes(agentService));
app.use('/api/features', createFeaturesRoutes(featureLoader));
app.use('/api/backlog-plan', createBacklogPlanRoutes(events, settingsService));
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Duplicate route mounting detected.

The /api/backlog-plan route is mounted twice - once at line 154 and again at line 171. This will cause the second mount to shadow the first, which could lead to unexpected behavior if the route handlers differ or cause routing conflicts.

🔎 Proposed fix

Remove one of the duplicate mounts. Keep only line 154 since it appears earlier in the logical grouping:

 app.use('/api/context', createContextRoutes(settingsService));
-app.use('/api/backlog-plan', createBacklogPlanRoutes(events, settingsService));
 app.use('/api/pipeline', createPipelineRoutes(pipelineService));

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/server/src/index.ts around lines 154 to 171, the /api/backlog-plan route
is mounted twice (once at line 154 and again at line 171); remove the duplicate
mounting so the route is only registered once (keep the earlier mount at line
154 as suggested) and delete or comment out the later
app.use('/api/backlog-plan', ...) call to prevent routing conflicts.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
apps/server/src/index.ts (1)

161-161: Duplicate route mounting detected.

The /api/backlog-plan route is mounted twice—once at Line 161 and again at Line 178. The second mount will shadow the first, potentially causing routing conflicts or unexpected behavior.

🔎 Proposed fix

Remove the duplicate mount at Line 178:

 app.use('/api/context', createContextRoutes(settingsService));
-app.use('/api/backlog-plan', createBacklogPlanRoutes(events, settingsService));
 app.use('/api/mcp', createMCPRoutes(mcpTestService));

Also applies to: 178-178

🧹 Nitpick comments (3)
apps/server/src/lib/sdk-options.ts (1)

224-228: Consider handling SystemPromptConfig passthrough.

When config.systemPrompt is already a SystemPromptConfig object (not a string), it's silently ignored in favor of the preset. If the caller explicitly provides a SystemPromptConfig, consider either merging it or passing it through.

🔎 Proposed enhancement
   if (config.systemPrompt) {
     if (typeof config.systemPrompt === 'string') {
       result.systemPrompt.append = config.systemPrompt;
+    } else if (config.systemPrompt.type === 'preset' && config.systemPrompt.append) {
+      // Preserve user's append if they passed a SystemPromptConfig
+      result.systemPrompt.append = config.systemPrompt.append;
     }
   }
apps/server/src/routes/app-spec/generate-features-from-spec.ts (1)

145-150: Non-Claude providers silently drop SystemPromptConfig objects.

When options.systemPrompt is a SystemPromptConfig object (from buildClaudeMdOptions), non-Claude providers receive nothing. Consider extracting the append text or logging a warning.

🔎 Proposed fix
   if (providerName === 'claude') {
     streamOptions.systemPrompt = options.systemPrompt;
     streamOptions.settingSources = options.settingSources;
   } else if (typeof options.systemPrompt === 'string') {
     streamOptions.systemPrompt = options.systemPrompt;
+  } else if (options.systemPrompt && typeof options.systemPrompt === 'object' && 'append' in options.systemPrompt) {
+    // Extract append text for CLI-based providers
+    streamOptions.systemPrompt = options.systemPrompt.append;
   }
apps/server/src/providers/codex-provider.ts (1)

793-795: WebSearch and WebFetch classified as write tools.

These are read-only network operations but are included in writeTools. If this is intentional (e.g., treating external network access as elevated privilege), consider adding a comment explaining the rationale.

🔎 Suggested clarification
   private needsWriteAccess(allowedTools?: string[]): boolean {
     if (!allowedTools) {
       return true;
     }
     if (allowedTools.length === 0) {
       return false;
     }

+    // WebSearch/WebFetch included because external network access requires elevated sandbox permissions
     const writeTools = new Set(['Write', 'Edit', 'Bash', 'WebSearch', 'WebFetch']);
     return allowedTools.some((tool) => writeTools.has(tool));
   }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3514998 and 69a4a44.

📒 Files selected for processing (15)
  • apps/server/src/index.ts
  • apps/server/src/lib/sdk-options.ts
  • apps/server/src/lib/subprocess-manager.ts
  • apps/server/src/providers/claude-provider.ts
  • apps/server/src/providers/codex-provider.ts
  • apps/server/src/routes/app-spec/generate-features-from-spec.ts
  • apps/ui/src/components/views/settings-view.tsx
  • apps/ui/src/components/views/settings-view/hooks/use-settings-view.ts
  • apps/ui/src/hooks/use-settings-migration.ts
  • apps/ui/src/lib/http-api-client.ts
  • apps/ui/src/store/app-store.ts
  • docs/server/providers.md
  • docs/server/utilities.md
  • libs/types/src/index.ts
  • libs/types/src/provider.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • apps/ui/src/components/views/settings-view/hooks/use-settings-view.ts
  • apps/server/src/lib/subprocess-manager.ts
  • apps/ui/src/lib/http-api-client.ts
  • apps/server/src/providers/claude-provider.ts
  • apps/ui/src/components/views/settings-view.tsx
  • apps/ui/src/store/app-store.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Always import from shared packages (@automaker/*), never from old relative paths

Files:

  • libs/types/src/provider.ts
  • apps/server/src/lib/sdk-options.ts
  • apps/ui/src/hooks/use-settings-migration.ts
  • apps/server/src/index.ts
  • libs/types/src/index.ts
  • apps/server/src/providers/codex-provider.ts
  • apps/server/src/routes/app-spec/generate-features-from-spec.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use resolveModelString() from @automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Files:

  • libs/types/src/provider.ts
  • apps/server/src/lib/sdk-options.ts
  • apps/ui/src/hooks/use-settings-migration.ts
  • apps/server/src/index.ts
  • libs/types/src/index.ts
  • apps/server/src/providers/codex-provider.ts
  • apps/server/src/routes/app-spec/generate-features-from-spec.ts
apps/server/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use createEventEmitter() from lib/events.ts for all server operations to emit events that stream to frontend via WebSocket

Files:

  • apps/server/src/lib/sdk-options.ts
  • apps/server/src/index.ts
  • apps/server/src/providers/codex-provider.ts
  • apps/server/src/routes/app-spec/generate-features-from-spec.ts
🧠 Learnings (2)
📚 Learning: 2025-12-28T05:07:48.138Z
Learnt from: CR
Repo: AutoMaker-Org/automaker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T05:07:48.138Z
Learning: Applies to **/*.{ts,tsx} : Use `resolveModelString()` from automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Applied to files:

  • apps/server/src/lib/sdk-options.ts
  • libs/types/src/index.ts
  • apps/server/src/routes/app-spec/generate-features-from-spec.ts
📚 Learning: 2025-12-28T05:07:48.138Z
Learnt from: CR
Repo: AutoMaker-Org/automaker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T05:07:48.138Z
Learning: Applies to apps/server/src/**/*.{ts,tsx} : Use `createEventEmitter()` from `lib/events.ts` for all server operations to emit events that stream to frontend via WebSocket

Applied to files:

  • apps/server/src/index.ts
🧬 Code graph analysis (3)
apps/server/src/lib/sdk-options.ts (1)
libs/types/src/index.ts (1)
  • CLAUDE_MODEL_MAP (41-41)
apps/server/src/index.ts (1)
apps/server/src/routes/backlog-plan/index.ts (1)
  • createBacklogPlanRoutes (14-30)
apps/server/src/providers/codex-provider.ts (3)
apps/server/src/providers/types.ts (6)
  • ExecuteOptions (11-11)
  • ProviderMessage (34-45)
  • InstallationStatus (50-58)
  • ModelDefinition (72-84)
  • ContentBlock (21-29)
  • ConversationMessage (10-10)
apps/server/src/providers/codex-config-manager.ts (1)
  • CodexConfigManager (20-158)
apps/server/src/lib/subprocess-manager.ts (1)
  • spawnJSONLProcess (66-167)
🔇 Additional comments (13)
apps/server/src/index.ts (3)

34-34: LGTM!

The import is correctly structured for an internal route module.


133-139: LGTM!

The explicit error handling prevents the server from continuing in a broken state and provides clear failure signaling.


142-149: LGTM!

The periodic cleanup mechanism is a good practice for preventing memory leaks in long-running processes.

docs/server/utilities.md (1)

584-594: LGTM!

The updated example accurately documents the new sandboxed Codex CLI invocation pattern with --sandbox workspace-write and --ask-for-approval never flags, which aligns with the CodexProvider implementation.

apps/server/src/lib/sdk-options.ts (1)

140-145: LGTM!

The isClaudeModelKey helper correctly identifies Claude models by checking for the 'claude-' prefix or presence in CLAUDE_MODEL_MAP. Returning true for undefined/empty models maintains backward compatibility with existing Claude-defaulting behavior.

libs/types/src/provider.ts (1)

77-77: LGTM!

The timeoutMs optional property cleanly extends ExecuteOptions to support per-call timeout configuration, which integrates well with the new provider execution paths.

docs/server/providers.md (1)

234-234: LGTM!

The documentation accurately reflects the updated Codex CLI invocation pattern with sandboxed execution and automatic approval bypass.

libs/types/src/index.ts (1)

40-52: LGTM!

The expanded exports cleanly extend the public API surface to support the new multi-provider model maps and alias types, following the existing export patterns in the file.

apps/server/src/routes/app-spec/generate-features-from-spec.ts (1)

167-211: Good error recovery pattern.

The streamError capture with partial output continuation is a solid resilience pattern—allowing feature parsing to proceed with accumulated text even if the stream terminates early.

apps/server/src/providers/codex-provider.ts (4)

50-59: LGTM!

Clean CLI path resolution with proper error yielding before early return, preventing undefined behavior downstream.


95-102: LGTM!

The bidirectional API key synchronization (OPENAI_API_KEYCODEX_API_KEY) ensures compatibility with different authentication configurations.


369-561: LGTM!

The toProviderMessages method provides comprehensive JSONL event translation with graceful fallback handling for unknown event types. The defensive programming approach with null checks throughout is appropriate for parsing external CLI output.


85-87: LGTM!

Passing the prompt as a positional argument to spawnJSONLProcess (which uses spawn with an args array) is safe from shell injection. The subprocess manager handles this correctly.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

♻️ Duplicate comments (3)
apps/server/src/index.ts (1)

161-161: Duplicate route mounting detected.

The /api/backlog-plan route is mounted twice—once at line 161 and again at line 178. The second mount will shadow the first, potentially causing unexpected routing behavior.

🔎 Proposed fix

Remove the duplicate mount at line 178:

 app.use('/api/context', createContextRoutes(settingsService));
-app.use('/api/backlog-plan', createBacklogPlanRoutes(events, settingsService));
 app.use('/api/mcp', createMCPRoutes(mcpTestService));

Also applies to: 178-178

apps/ui/src/lib/electron.ts (1)

4-18: Backlog plan type wiring is now consistent and import-safe

Importing and re‑exporting BacklogPlanResult and BacklogPlanEvent from @automaker/types aligns this file with the shared types and resolves the earlier missing‑import issue noted in past reviews.

Also applies to: 21-35

apps/ui/src/components/views/settings-view.tsx (1)

127-152: [Unresolved from previous review] Import and resolve model aliases using resolveModelString().

This issue was flagged in a previous review but has not been addressed. Per the coding guidelines for TypeScript files, model aliases (haiku, sonnet, opus, auto, glm4.7) must be resolved to full model names using resolveModelString() from @automaker/model-resolver.

Currently, line 134 passes setDefaultModel directly without resolution, meaning aliases flow into the store unresolved.

Add the import and wrap the callback:

import { resolveModelString } from '@automaker/model-resolver';

Then update line 134:

onModelChange={(model) => {
  const resolved = resolveModelString(model);
  setDefaultModel(resolved ?? model);
}}

Based on learnings, this is required for all TypeScript files handling model strings.

🧹 Nitpick comments (7)
apps/ui/src/components/views/board-view/shared/model-selector.tsx (2)

32-59: Consider memoizing the provider inference.

inferProvider is redefined and invoked on every render. For a simple UI component this is likely fine, but if the parent re-renders frequently, wrapping the derivation in useMemo would prevent redundant computation.

🔎 Proposed optimization
+ import { useMemo } from 'react';

  // inside the component:
- const inferProvider = (): ModelProvider => {
-   // ...inference logic
- };
- const resolvedProvider = selectedProvider ?? inferProvider();

+ const resolvedProvider = useMemo(() => {
+   if (selectedProvider) return selectedProvider;
+   if (CODEX_MODELS.some((model) => resolveModelString(model.id) === normalizedSelectedModel)) {
+     return 'codex';
+   }
+   if (OPENCODE_MODELS.some((model) => resolveModelString(model.id) === normalizedSelectedModel)) {
+     return 'opencode';
+   }
+   if (CURSOR_MODELS.some((model) => resolveModelString(model.id) === normalizedSelectedModel)) {
+     return 'cursor';
+   }
+   if (CLAUDE_MODELS.some((model) => resolveModelString(model.id) === normalizedSelectedModel)) {
+     return 'claude';
+   }
+   const lowerModel = normalizedSelectedModel.toLowerCase();
+   if (lowerModel.startsWith('gpt-') || /^o\d/.test(lowerModel)) {
+     return 'codex';
+   }
+   if (lowerModel.startsWith('glm') || lowerModel === 'opencode') {
+     return 'opencode';
+   }
+   if (lowerModel === 'auto' || lowerModel.startsWith('cursor-')) {
+     return 'cursor';
+   }
+   return 'claude';
+ }, [selectedProvider, normalizedSelectedModel]);

83-151: Consider DRYing up the provider buttons.

The four provider buttons share nearly identical structure. Extracting this into a loop over a config array would reduce duplication and make adding/removing providers easier.

🔎 Proposed refactor
const providerDefaults: Record<ModelProvider, string> = {
  cursor: 'auto',
  codex: 'gpt-5.2-codex',
  opencode: 'glm4.7',
  claude: 'opus',
};

// Then in JSX:
{(['cursor', 'codex', 'opencode', 'claude'] as ModelProvider[]).map((provider) => {
  const { icon: Icon, label } = providerMeta[provider];
  const shortLabel = label.split(' ')[0]; // e.g., "Cursor"
  return (
    <button
      key={provider}
      type="button"
      onClick={() => {
        onProviderSelect?.(provider);
        onModelSelect(resolveModelString(providerDefaults[provider]) as AgentModel);
      }}
      className={cn(
        'flex-1 flex items-center justify-center gap-2 px-3 py-2 rounded-md border text-sm font-medium transition-colors',
        resolvedProvider === provider
          ? 'bg-primary text-primary-foreground border-primary'
          : 'bg-background hover:bg-accent border-input'
      )}
      data-testid={`${testIdPrefix}-provider-${provider}`}
    >
      <Icon className="w-4 h-4" />
      {shortLabel}
    </button>
  );
})}
docs/tuto.md (3)

10-16: Brief CLI setup section lacks motivation and detail.

The instructions install four tools (Opencode, Codex, Claude, Cursor) without explaining why each is needed, which constraint each satisfies, or how they fit together in the workflow. Given the PR adds multi-provider support, this section should clarify the role of each provider (e.g., "Cursor for X, Codex for Y, OpenCode for Z") and any licensing/installation prerequisites.

Consider expanding this section or cross-referencing setup documentation with provider roles. For example:

  • Opencode for GLM models (free)
  • Codex CLI for OpenAI models
  • Claude for critique/review (see context-engineering workflow below)
  • Cursor for lightweight, fast tasks

421-421: Use more concise phrasing.

Replace "A lot of things and exemple are not required" with a more direct statement, e.g., "Remove features and examples unrelated to your project spec."


23-410: Spec template is comprehensive but disconnected from tutorial flow.

The template (lines 23–410) is detailed and well-structured; however, it is inserted mid-tutorial (between CLI setup and usage steps) without a transition sentence. Consider:

  1. Explicitly state that this is a reference template to be used in step 7 (spec generation).
  2. Add a brief preamble explaining when and how to apply the template.
  3. Consider moving the template to an appendix or separate file to improve readability and modularity.
apps/ui/src/store/app-store.ts (1)

1615-1641: Consider making backend sync await consistent.

The setDefaultProvider action performs two async operations (backend API call and settings sync) in fire-and-forget style, while setDefaultModel only syncs settings. This creates a potential race condition:

  1. User changes provider → backend API call fires
  2. User immediately changes model → settings sync fires
  3. If the provider API call completes after the model sync, the model might be overwritten with stale data

Consider one of these approaches:

Option 1: Await backend sync before updating state

- setDefaultProvider: (provider) => {
-   set({ defaultProvider: provider });
+ setDefaultProvider: async (provider) => {
+   try {
+     const { getHttpApiClient } = await import('@/lib/http-api-client');
+     await getHttpApiClient().setup.setDefaultProvider(provider);
+     set({ defaultProvider: provider });
+     const { syncSettingsToServer } = await import('@/hooks/use-settings-migration');
+     await syncSettingsToServer();
+   } catch (err) {
+     console.error('[AppStore] Failed to sync provider:', err);
+     // Consider reverting state or showing error
+   }
+ },

Option 2: Queue updates
Use a mutation queue to ensure operations complete in order.

Based on learnings, fire-and-forget patterns are acceptable for UI responsiveness, but consider testing the race condition scenario where users rapidly switch provider then model.

apps/ui/src/components/views/board-view/shared/profile-quick-select.tsx (1)

69-69: Consider displaying the normalized model for consistency.

The UI displays the raw profile.model value, while the selection logic operates on the normalized model string. If these values differ significantly (e.g., "haiku" vs. "claude-3-haiku-20240307"), users might find it confusing.

🔎 Optional: Display normalized model value
                <p className="text-[10px] text-muted-foreground truncate">
-                  {profile.model}
+                  {normalizedProfileModel}
                  {profile.thinkingLevel !== 'none' && ` + ${profile.thinkingLevel}`}
                </p>

Alternatively, if you prefer to preserve the raw model name for user reference, consider adding a tooltip or secondary text showing the resolved model.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 69a4a44 and 3fd50c8.

📒 Files selected for processing (17)
  • apps/server/src/index.ts
  • apps/server/src/providers/claude-provider.ts
  • apps/server/src/routes/app-spec/generate-features-from-spec.ts
  • apps/server/src/services/auto-mode-service.ts
  • apps/server/src/services/settings-service.ts
  • apps/ui/src/components/views/agent-view.tsx
  • apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx
  • apps/ui/src/components/views/board-view/shared/model-selector.tsx
  • apps/ui/src/components/views/board-view/shared/profile-quick-select.tsx
  • apps/ui/src/components/views/settings-view.tsx
  • apps/ui/src/components/views/settings-view/ai-provider/ai-provider-section.tsx
  • apps/ui/src/components/views/settings-view/config/navigation.ts
  • apps/ui/src/lib/electron.ts
  • apps/ui/src/store/app-store.ts
  • docs/tuto.md
  • libs/types/src/model.ts
  • libs/types/src/settings.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx
  • apps/ui/src/components/views/agent-view.tsx
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Always import from shared packages (@automaker/*), never from old relative paths

Files:

  • apps/server/src/services/auto-mode-service.ts
  • apps/ui/src/components/views/settings-view/ai-provider/ai-provider-section.tsx
  • libs/types/src/settings.ts
  • apps/server/src/index.ts
  • apps/ui/src/components/views/board-view/shared/profile-quick-select.tsx
  • apps/server/src/services/settings-service.ts
  • apps/ui/src/store/app-store.ts
  • apps/server/src/providers/claude-provider.ts
  • libs/types/src/model.ts
  • apps/ui/src/components/views/settings-view/config/navigation.ts
  • apps/ui/src/lib/electron.ts
  • apps/ui/src/components/views/board-view/shared/model-selector.tsx
  • apps/server/src/routes/app-spec/generate-features-from-spec.ts
  • apps/ui/src/components/views/settings-view.tsx
apps/server/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use createEventEmitter() from lib/events.ts for all server operations to emit events that stream to frontend via WebSocket

Files:

  • apps/server/src/services/auto-mode-service.ts
  • apps/server/src/index.ts
  • apps/server/src/services/settings-service.ts
  • apps/server/src/providers/claude-provider.ts
  • apps/server/src/routes/app-spec/generate-features-from-spec.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use resolveModelString() from @automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Files:

  • apps/server/src/services/auto-mode-service.ts
  • apps/ui/src/components/views/settings-view/ai-provider/ai-provider-section.tsx
  • libs/types/src/settings.ts
  • apps/server/src/index.ts
  • apps/ui/src/components/views/board-view/shared/profile-quick-select.tsx
  • apps/server/src/services/settings-service.ts
  • apps/ui/src/store/app-store.ts
  • apps/server/src/providers/claude-provider.ts
  • libs/types/src/model.ts
  • apps/ui/src/components/views/settings-view/config/navigation.ts
  • apps/ui/src/lib/electron.ts
  • apps/ui/src/components/views/board-view/shared/model-selector.tsx
  • apps/server/src/routes/app-spec/generate-features-from-spec.ts
  • apps/ui/src/components/views/settings-view.tsx
🧠 Learnings (4)
📚 Learning: 2025-12-28T05:07:48.147Z
Learnt from: CR
Repo: AutoMaker-Org/automaker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T05:07:48.147Z
Learning: Applies to **/*.{ts,tsx} : Use `resolveModelString()` from automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Applied to files:

  • apps/server/src/services/auto-mode-service.ts
  • apps/ui/src/components/views/board-view/shared/profile-quick-select.tsx
  • libs/types/src/model.ts
  • apps/ui/src/components/views/board-view/shared/model-selector.tsx
  • apps/ui/src/components/views/settings-view.tsx
📚 Learning: 2025-12-28T05:07:48.147Z
Learnt from: CR
Repo: AutoMaker-Org/automaker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T05:07:48.147Z
Learning: Each feature executes in an isolated git worktree created via automaker/git-utils to protect the main branch during AI agent execution

Applied to files:

  • apps/server/src/services/auto-mode-service.ts
📚 Learning: 2025-12-30T01:02:07.114Z
Learnt from: illia1f
Repo: AutoMaker-Org/automaker PR: 324
File: apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx:122-131
Timestamp: 2025-12-30T01:02:07.114Z
Learning: Tailwind CSS v4 uses postfix syntax for the important modifier: append ! at the end of the utility class (e.g., backdrop-blur-[0px]! or hover:bg-red-500!). The older v3 style used a prefix (!) at the start (e.g., !backdrop-blur-[0px]); prefer the postfix form for consistency across TSX files.

Applied to files:

  • apps/ui/src/components/views/settings-view/ai-provider/ai-provider-section.tsx
  • apps/ui/src/components/views/board-view/shared/profile-quick-select.tsx
  • apps/ui/src/components/views/board-view/shared/model-selector.tsx
  • apps/ui/src/components/views/settings-view.tsx
📚 Learning: 2025-12-28T05:07:48.147Z
Learnt from: CR
Repo: AutoMaker-Org/automaker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T05:07:48.147Z
Learning: Store project-specific rules in `.automaker/context/` and load them into agent prompts via `loadContextFiles()` from automaker/utils

Applied to files:

  • docs/tuto.md
🧬 Code graph analysis (8)
apps/server/src/services/auto-mode-service.ts (3)
apps/server/src/providers/provider-factory.ts (1)
  • ProviderFactory (28-174)
libs/types/src/model.ts (1)
  • DEFAULT_MODELS (47-52)
libs/types/src/index.ts (1)
  • DEFAULT_MODELS (45-45)
apps/ui/src/components/views/settings-view/ai-provider/ai-provider-section.tsx (6)
libs/types/src/settings.ts (2)
  • ModelProvider (71-71)
  • AgentModel (12-12)
libs/types/src/model.ts (1)
  • AgentModel (71-71)
apps/ui/src/components/views/board-view/shared/model-constants.ts (3)
  • CURSOR_MODELS (36-51)
  • CODEX_MODELS (63-127)
  • OPENCODE_MODELS (53-61)
apps/ui/src/lib/electron.ts (1)
  • getElectronAPI (804-813)
apps/ui/src/lib/utils.ts (1)
  • cn (5-7)
apps/ui/src/components/views/settings-view/ai-provider/provider-card.tsx (1)
  • ProviderCard (46-86)
apps/server/src/index.ts (1)
apps/server/src/routes/backlog-plan/index.ts (1)
  • createBacklogPlanRoutes (14-30)
apps/ui/src/components/views/board-view/shared/profile-quick-select.tsx (1)
libs/types/src/model.ts (1)
  • AgentModel (71-71)
apps/server/src/services/settings-service.ts (2)
libs/types/src/settings.ts (1)
  • GlobalSettings (272-367)
apps/server/src/types/settings.ts (1)
  • GlobalSettings (20-20)
apps/ui/src/store/app-store.ts (6)
libs/types/src/settings.ts (2)
  • ModelProvider (71-71)
  • AgentModel (12-12)
libs/types/src/index.ts (2)
  • ModelProvider (70-70)
  • AgentModel (51-51)
apps/server/src/types/settings.ts (2)
  • ModelProvider (14-14)
  • AgentModel (11-11)
libs/types/src/model.ts (1)
  • AgentModel (71-71)
apps/ui/src/lib/http-api-client.ts (1)
  • getHttpApiClient (1328-1333)
apps/ui/src/hooks/use-settings-migration.ts (1)
  • syncSettingsToServer (196-249)
apps/ui/src/lib/electron.ts (1)
libs/types/src/index.ts (2)
  • BacklogPlanResult (127-127)
  • BacklogPlanEvent (128-128)
apps/server/src/routes/app-spec/generate-features-from-spec.ts (3)
apps/server/src/lib/sdk-options.ts (1)
  • getModelForUseCase (95-129)
libs/types/src/model.ts (1)
  • DEFAULT_MODELS (47-52)
libs/types/src/provider.ts (1)
  • ExecuteOptions (66-82)
🪛 LanguageTool
docs/tuto.md

[grammar] ~414-~414: Ensure spelling is correct
Context: ...ate in context tab copy response of the promps for specs in a md file called OBJECTIVE...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[style] ~421-~421: Consider using a synonym to be more concise.
Context: ...ECTIVES.md and @.automaker/app_spec.txt A lot of things and exemple are not required cle...

(A_LOT_OF)


[grammar] ~422-~422: Ensure spelling is correct
Context: ...omaker/app_spec.txt A lot of things and exemple are not required clean repo and update ...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🔇 Additional comments (18)
apps/server/src/index.ts (2)

133-139: LGTM! Good defensive practice.

Wrapping the agent service initialization in a try-catch with an explicit exit ensures the server won't run in a broken state if initialization fails.


144-144: LGTM! Proper resource cleanup.

The named interval variable enables proper cleanup during graceful shutdown, preventing resource leaks when the server terminates.

Also applies to: 504-504, 514-514

apps/server/src/providers/claude-provider.ts (1)

25-38: Forwarding settingSources via local destructuring looks correct

Using a local settingSources from ExecuteOptions and conditionally spreading it into sdkOptions preserves the prior behavior while keeping the SDK options shape explicit. No further changes needed here.

Also applies to: 55-78

apps/ui/src/lib/electron.ts (2)

494-507: ElectronAPI.backlogPlan surface looks coherent with shared types

The new backlogPlan API uses BacklogPlanResult for apply and BacklogPlanEvent for onEvent, with a simple generate/stop/status trio matching other APIs in this file. Marking the whole backlogPlan block as optional keeps the web/mock case safe.


539-587: Setup API extensions for Cursor/OpenCode/Codex and default provider are consistent

The added setup methods (getCursorStatus/getOpenCodeStatus/getCodexStatus, installCursor, verifyCursorAuth, setDefaultProvider) have matching shapes between ElectronAPI.setup, the SetupAPI interface, and createMockSetupAPI. Auth sub-objects are coherent and safely optional, and the setDefaultProvider union matches the provider identifiers used elsewhere.

Also applies to: 629-641, 1172-1220, 1259-1277, 1310-1355, 1367-1374, 1426-1440

apps/ui/src/components/views/settings-view/config/navigation.ts (1)

3-14: AI Provider navigation entry is wired cleanly

Importing Wand2 and adding the 'ai-provider' item into NAV_ITEMS is consistent with existing patterns; assuming SettingsViewId includes 'ai-provider', this should integrate the new settings section without issues.

Also applies to: 24-36

apps/server/src/services/settings-service.ts (1)

410-445: Migration of defaultProvider and defaultModel looks safe and backward‑compatible

Adding defaultProvider (fallback 'claude') and defaultModel (fallback 'sonnet') into the migrated globalSettings ensures these new fields are populated even for legacy localStorage state, and they’ll still be merged with DEFAULT_GLOBAL_SETTINGS via updateGlobalSettings. No additional migration handling seems necessary.

apps/server/src/services/auto-mode-service.ts (1)

614-616: Centralized default model resolution aligns feature runs with selected provider

Routing all feature and pipeline executions through resolveModelString(feature.model, this.getDefaultFeatureModel()), with getDefaultFeatureModel() deriving its fallback from ProviderFactory.getDefaultProvider() and DEFAULT_MODELS, makes the default model/provider behavior consistent across:

  • initial feature runs,
  • post‑implementation pipeline steps, and
  • follow‑up runs.

This keeps analysis flows explicitly on the Claude default while letting auto‑mode respect the globally selected provider. The helper is small, typed correctly against DEFAULT_MODELS, and avoids scattering provider‑specific defaults.

Also applies to: 754-755, 990-993, 1759-1765

apps/ui/src/components/views/settings-view/ai-provider/ai-provider-section.tsx (1)

1-253: LGTM! Well-structured provider selection UI.

The component correctly:

  • Uses resolveModelString() from @automaker/model-resolver consistently throughout (lines 37, 90, 92, 94, 96, 196, 203, 216) as required by coding guidelines
  • Imports from shared packages (@automaker/*) rather than relative paths
  • Handles backend synchronization with appropriate error feedback via toast
  • Implements proper type safety with ModelProvider and AgentModel types
  • Uses the extracted ProviderCard component to avoid duplication (per previous review feedback)

The provider selection logic (lines 89-97) correctly sets sensible defaults for each provider, and the backend sync (lines 100-119) now includes user-facing error toast as suggested in previous reviews.

libs/types/src/settings.ts (3)

71-71: LGTM! ModelProvider type correctly expanded.

The ModelProvider union type now properly includes all four providers: 'claude' | 'cursor' | 'opencode' | 'codex'. This aligns with the multi-provider architecture introduced in this PR.


311-314: LGTM! Clear documentation for new provider settings.

The new defaultProvider and defaultModel fields are well-documented and have appropriate types. The comments clearly indicate their purpose for routing feature execution to the selected AI provider.


512-513: LGTM! Sensible defaults for backward compatibility.

Defaulting to 'claude' provider and 'sonnet' model maintains backward compatibility with existing installations while enabling the new multi-provider functionality.

apps/server/src/routes/app-spec/generate-features-from-spec.ts (2)

180-214: LGTM! Excellent defensive coding and error handling.

The stream handling demonstrates several best practices:

  • Optional chaining on msg.message?.content (line 180) and block.text (line 182) prevents runtime errors
  • Captures stream errors separately without immediately throwing (lines 170, 196-200, 203-205)
  • Allows partial output to continue after errors (lines 207-214): "if there's some output, use it; otherwise rethrow"

This graceful degradation is particularly valuable for long-running feature generation where partial results can still be useful.


105-114: The provider fallback logic is correctly typed and safe.

The concern about getDefaultProvider() returning an invalid provider key is unfounded. The return type of getDefaultProvider() is strictly 'claude' | 'cursor' | 'opencode' | 'codex', which exactly matches the keys in DEFAULT_MODELS. TypeScript's type system prevents the alleged edge case.

Additionally, the logic correctly handles the case when defaultProvider === 'claude'—it intentionally does not update featureModel in this scenario, leaving it as getModelForUseCase('features'). This is the intended behavior.

libs/types/src/model.ts (2)

13-42: LGTM! Model maps follow consistent pattern.

The new model maps (CURSOR_MODEL_MAP, OPENCODE_MODEL_MAP, CODEX_MODEL_MAP) follow the same structure as the existing CLAUDE_MODEL_MAP:

  • Use Record<string, string> with as const for type inference
  • Provide meaningful alias-to-ID mappings
  • Include relevant models for each provider

Note: Some maps like OPENCODE_MODEL_MAP have identity mappings (e.g., 'glm4.7': 'glm4.7'), which is appropriate when the model ID itself serves as the canonical identifier.


54-71: LGTM! Type definitions correctly unify all providers.

The type hierarchy properly captures the multi-provider model system:

  • Individual alias types per provider (ClaudeModelAlias, CursorModelAlias, etc.)
  • ModelAlias union encompasses all provider aliases
  • ModelId union includes all concrete model strings from the maps
  • AgentModel = ModelAlias | ModelId allows both aliases and full IDs

This structure enables type-safe model selection across all providers while maintaining backward compatibility.

apps/ui/src/components/views/board-view/shared/profile-quick-select.tsx (2)

6-6: LGTM! Correct usage of shared package import.

The import correctly follows the coding guideline to use shared packages (@automaker/*) for model resolution utilities.


30-30: LGTM! Proper model normalization before comparison.

The code correctly applies resolveModelString() to normalize both the selected model and profile models before comparison, ensuring that model aliases are consistently resolved to full model names. This aligns with the coding guidelines and retrieved learnings.

Also applies to: 46-49

{models.map((option) => {
const resolvedOptionId = resolveModelString(option.id);
const isSelected = normalizedSelectedModel === resolvedOptionId;
const shortName = option.label.replace('Claude ', '').replace('Cursor ', '');
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Incomplete prefix stripping for short names.

Only 'Claude ' and 'Cursor ' are stripped from model labels. If OpenCode or Codex model labels include their provider name as a prefix, they won't be shortened consistently.

🔎 Proposed fix
- const shortName = option.label.replace('Claude ', '').replace('Cursor ', '');
+ const shortName = option.label
+   .replace('Claude ', '')
+   .replace('Cursor ', '')
+   .replace('OpenCode ', '')
+   .replace('Codex ', '');

Alternatively, use a regex for a more generic approach:

const shortName = option.label.replace(/^(Claude|Cursor|OpenCode|Codex)\s+/i, '');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const shortName = option.label.replace('Claude ', '').replace('Cursor ', '');
const shortName = option.label
.replace('Claude ', '')
.replace('Cursor ', '')
.replace('OpenCode ', '')
.replace('Codex ', '');
🤖 Prompt for AI Agents
In apps/ui/src/components/views/board-view/shared/model-selector.tsx around line
168, the shortName computation only strips 'Claude ' and 'Cursor ' prefixes so
other provider prefixes (e.g., OpenCode, Codex) remain; replace the current
chained replace calls with a single, case-insensitive regex that removes any of
the known provider prefixes (e.g., Claude, Cursor, OpenCode, Codex) followed by
whitespace so labels are shortened consistently.

key={profile.id}
type="button"
onClick={() => onSelect(profile.model, profile.thinkingLevel)}
onClick={() => onSelect(normalizedProfileModel as AgentModel, profile.thinkingLevel)}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Find resolveModelString implementation in @automaker/model-resolver
fd model-resolver -t d

Repository: AutoMaker-Org/automaker

Length of output: 87


🏁 Script executed:

# Search for resolveModelString definition across the codebase
rg -n "resolveModelString" --type=ts --type=tsx -B 2 -A 5

Repository: AutoMaker-Org/automaker

Length of output: 94


🏁 Script executed:

# Search for AgentModel type definition
rg -n "AgentModel" --type=ts --type=tsx -B 2 -A 3

Repository: AutoMaker-Org/automaker

Length of output: 94


🏁 Script executed:

# Examine the specific file around line 54
cat -n apps/ui/src/components/views/board-view/shared/profile-quick-select.tsx | sed -n '40,65p'

Repository: AutoMaker-Org/automaker

Length of output: 1489


🏁 Script executed:

# Search for resolveModelString using rg without tsx type
rg -n "resolveModelString" --type=ts -B 2 -A 5

Repository: AutoMaker-Org/automaker

Length of output: 50379


🏁 Script executed:

# Check the model-resolver library exports and index
ls -la libs/model-resolver/

Repository: AutoMaker-Org/automaker

Length of output: 536


🏁 Script executed:

# Search for AgentModel without type filter
rg -n "type AgentModel\|interface AgentModel\|export.*AgentModel" -A 3

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Find AgentModel type definition in app-store
find . -name "app-store*" -type f

Repository: AutoMaker-Org/automaker

Length of output: 99


🏁 Script executed:

# Search for AgentModel in store files
rg "type AgentModel|interface AgentModel|export.*AgentModel" --type=ts -B 2 -A 5

Repository: AutoMaker-Org/automaker

Length of output: 5683


🏁 Script executed:

# Find ModelAlias and ModelId definitions
rg "type ModelAlias|type ModelId|export type ModelAlias|export type ModelId" --type=ts -B 2 -A 3

Repository: AutoMaker-Org/automaker

Length of output: 1202


🏁 Script executed:

# Find the definitions of ClaudeModelAlias, CursorModelAlias, OpenCodeModelAlias
rg "type ClaudeModelAlias|type CursorModelAlias|type OpenCodeModelAlias" --type=ts -B 2 -A 3

Repository: AutoMaker-Org/automaker

Length of output: 994


🏁 Script executed:

# Also check what CLAUDE_MODEL_MAP, CURSOR_MODEL_MAP, OPENCODE_MODEL_MAP, CODEX_MODEL_MAP contain
rg "CLAUDE_MODEL_MAP|CURSOR_MODEL_MAP|OPENCODE_MODEL_MAP|CODEX_MODEL_MAP" --type=ts -B 1 -A 5 | head -100

Repository: AutoMaker-Org/automaker

Length of output: 5761


🏁 Script executed:

# Check the complete resolver.ts implementation to understand what it returns
cat -n libs/model-resolver/src/resolver.ts | sed -n '25,70p'

Repository: AutoMaker-Org/automaker

Length of output: 1812


Add type-safe guarantees for resolveModelString() return value or remove the unsafe type assertion.

The type assertion as AgentModel bypasses TypeScript's type checking. While resolveModelString() is designed to return valid model strings, its signature returns string rather than AgentModel. The function can return any string containing 'claude-' (line 44 of resolver.ts), which is broader than the AgentModel union type and could include arbitrary model identifiers outside the known set.

Consider either:

  1. Updating resolveModelString() to return a properly typed AgentModel with type guards
  2. Adding runtime validation before the assertion to ensure the result is a valid AgentModel
  3. Removing the assertion and handling the string result directly if type conversion is unnecessary
🤖 Prompt for AI Agents
In apps/ui/src/components/views/board-view/shared/profile-quick-select.tsx
around line 54, the code unsafely asserts normalizedProfileModel as AgentModel
when calling onSelect; update this to be type-safe by either (a) changing
resolveModelString() signature to return AgentModel (with narrowing/type guards
in resolver.ts so only valid union members are returned), or (b) perform a
runtime validation here against the AgentModel union (e.g., a small
isAgentModel(value): value is AgentModel guard) and only call onSelect with the
asserted type when the guard passes, otherwise handle the invalid case (fallback
or error); remove the direct "as AgentModel" assertion if you implement
validation or change the resolver signature so the compiler guarantees
correctness.


5. Create new project in automaker use starter pack

6. Create in context tab copy response of the promps for specs in a md file called OBJECTIVES.md
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix spelling error: "promps" → "prompts".

🧰 Tools
🪛 LanguageTool

[grammar] ~414-~414: Ensure spelling is correct
Context: ...ate in context tab copy response of the promps for specs in a md file called OBJECTIVE...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🤖 Prompt for AI Agents
In docs/tuto.md at line 414, fix the spelling error by replacing "promps" with
"prompts" so the line reads: "6. Create in context tab copy response of the
prompts for specs in a md file called OBJECTIVES.md".

Comment on lines +418 to +424
8. Open cursor, use composer ask it following prompt.

Clean what's not required for the project:
Everything is explained in @.automaker/context/OBJECTIVES.md and @.automaker/app_spec.txt
A lot of things and exemple are not required clean repo and update tasks in to build the project properly that my autocode will do.
Remove uselss feature for the project specs and update the others one. @.automaker/features

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's check the file exists and get the relevant section with more context
wc -l docs/tuto.md

Repository: AutoMaker-Org/automaker

Length of output: 83


🏁 Script executed:

# Read lines around 418-424 to understand the context better
sed -n '400,430p' docs/tuto.md

Repository: AutoMaker-Org/automaker

Length of output: 1170


🏁 Script executed:

# Search for references to @.automaker pattern in the codebase to understand the notation
rg "@\.automaker" --type md -B 2 -A 2

Repository: AutoMaker-Org/automaker

Length of output: 533


🏁 Script executed:

# Look for documentation about the @ notation or .automaker directory references
rg "@\." docs/ --type md -B 1 -A 1 | head -50

Repository: AutoMaker-Org/automaker

Length of output: 489


🏁 Script executed:

# Check if there's a .automaker directory or documentation about it
fd -t d "\.automaker"

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Look for the entire steps 5-9 section to understand the workflow context
sed -n '380,440p' docs/tuto.md

Repository: AutoMaker-Org/automaker

Length of output: 1522


🏁 Script executed:

# Look for README or main documentation files that might explain notation
cat README.md 2>/dev/null | head -100

Repository: AutoMaker-Org/automaker

Length of output: 5147


🏁 Script executed:

# Search for any references to Cursor or Composer in docs
rg -i "cursor|composer" docs/ --type md

Repository: AutoMaker-Org/automaker

Length of output: 1558


🏁 Script executed:

# Search for explanation of @ notation in documentation
rg "@\.|context/OBJECTIVES" --type md

Repository: AutoMaker-Org/automaker

Length of output: 274


🏁 Script executed:

# Check if there's a reference to how to use @. notation
rg -i "notation|file reference|path" docs/tuto.md

Repository: AutoMaker-Org/automaker

Length of output: 98


Clarify context file reference syntax and document the post-generation workflow steps.

The @.automaker/... syntax (lines 421, 423) is non-standard and unexplained—it appears throughout the prompt without definition. Clarify whether @. is a special notation for context-file paths within Cursor/the IDE, or if these should be standard relative paths (e.g., ./.automaker/context/OBJECTIVES.md).

Additionally, steps 5–9 form a post-generation cleanup workflow (using Cursor IDE and its Composer feature for AI-driven feature refinement). This should be explicitly labeled as a "post-generation refinement phase" to distinguish it from the core spec generation workflow (steps 1–4 in section 7). Cursor's role in step 8 is clear (AI-powered code/feature refinement), but adding a one-line note ("Cursor is used here for AI-driven refinement of generated features") would improve clarity.

🧰 Tools
🪛 LanguageTool

[style] ~421-~421: Consider using a synonym to be more concise.
Context: ...ECTIVES.md and @.automaker/app_spec.txt A lot of things and exemple are not required cle...

(A_LOT_OF)


[grammar] ~422-~422: Ensure spelling is correct
Context: ...omaker/app_spec.txt A lot of things and exemple are not required clean repo and update ...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


Clean what's not required for the project:
Everything is explained in @.automaker/context/OBJECTIVES.md and @.automaker/app_spec.txt
A lot of things and exemple are not required clean repo and update tasks in to build the project properly that my autocode will do.
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix spelling error: "exemple" → "example".

🧰 Tools
🪛 LanguageTool

[grammar] ~422-~422: Ensure spelling is correct
Context: ...omaker/app_spec.txt A lot of things and exemple are not required clean repo and update ...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🤖 Prompt for AI Agents
In docs/tuto.md at line 422, the word "exemple" is misspelled; replace "exemple"
with "example" so the sentence reads correctly (ensure only that token is
changed and preserve surrounding punctuation and spacing).

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/server/providers.md (1)

239-239: Clarify relationship between "30s timeout" and timeout environment variables.

Line 239 mentions "Timeout detection (30s no output)" as a feature, but the troubleshooting section (line 771) references CODEX_CLI_TIMEOUT_MS without clarifying that this 30-second default corresponds to that variable. Make the connection explicit so operators understand which variable controls the "30s" behavior mentioned earlier.

🔎 Suggested clarification

Revise line 239 to:

- ✅ Timeout detection (30s default idle timeout, configurable via `CODEX_CLI_TIMEOUT_MS`)

Or add a note in the timeout environment variables section:

Default idle timeout is 30 seconds (`CODEX_CLI_TIMEOUT_MS`). This can be increased for complex operations.

Also applies to: 771-771

♻️ Duplicate comments (1)
apps/server/src/providers/codex-provider.ts (1)

218-224: Command injection risk in version detection.

This issue was previously flagged: cliPath is passed to execAsync with shell execution, creating command injection risk if the path comes from untrusted sources (config or environment variables). Use execFile instead of shell execution.

🧹 Nitpick comments (5)
apps/server/src/lib/subprocess-manager.ts (4)

23-24: timeout and startupTimeout options are accepted but ignored.

spawnProcess destructures options but doesn't extract or use timeout/startupTimeout. If timeout support isn't intended for this function, consider documenting this or using a narrower options type to avoid confusion.

🔎 Option A: Extract a base options type
+export interface BaseSubprocessOptions {
+  command: string;
+  args: string[];
+  cwd: string;
+  env?: Record<string, string>;
+  abortController?: AbortController;
+}
+
+export interface SubprocessOptions extends BaseSubprocessOptions {
+  timeout?: number;
+  startupTimeout?: number;
+}

 export async function spawnProcess(options: SubprocessOptions): Promise<SubprocessResult> {
-  const { command, args, cwd, env, abortController } = options;
+  const { command, args, cwd, env, abortController } = options; // timeout intentionally unused

42-52: Abort listener is never removed, potential minor memory leak.

If the AbortController is long-lived and reused, the listener registered here will persist after the process completes. Consider using { once: true } or removing the listener in cleanup.

🔎 Proposed fix
   if (abortController) {
-    abortController.signal.addEventListener('abort', () => {
+    abortController.signal.addEventListener('abort', () => {
       aborted = true;
       child.kill('SIGTERM');
-    });
+    }, { once: true });

59-63: Redundant conditional return.

Both branches return identical { stdout, stderr, exitCode }. The if (aborted) check can be removed unless different handling is planned.

🔎 Proposed simplification
-  if (aborted) {
-    return { stdout, stderr, exitCode };
-  }
-
   return { stdout, stderr, exitCode };

136-140: Consider static import for readline.

The dynamic await import('readline') is evaluated on every call. Since readline is a Node.js built-in, a static import at the top of the file would be more efficient and conventional.

🔎 Proposed change
 import { spawn } from 'child_process';
+import { createInterface } from 'readline';

 // ... later in spawnJSONLProcess ...
-  const readline = await import('readline');
-  const rl = readline.createInterface({
+  const rl = createInterface({
     input: child.stdout as NodeJS.ReadableStream,
     crlfDelay: Infinity,
   });
apps/server/src/providers/codex-provider.ts (1)

394-400: Consider expanding model alias mappings.

The model mapper currently only handles "codex""gpt-5.2-codex". Consider adding more user-friendly aliases (e.g., "mini""gpt-5.1-codex-mini", "max""gpt-5.1-codex-max") to improve developer experience.

💡 Suggested expansion
   private mapModelToCodexFormat(model: string): string {
     const modelMap: Record<string, string> = {
       codex: 'gpt-5.2-codex',
+      mini: 'gpt-5.1-codex-mini',
+      max: 'gpt-5.1-codex-max',
+      'codex-mini': 'gpt-5.1-codex-mini',
+      'codex-max': 'gpt-5.1-codex-max',
     };

     return modelMap[model.toLowerCase()] || model;
   }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3fd50c8 and a56d3da.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (27)
  • apps/server/src/index.ts
  • apps/server/src/lib/sdk-options.ts
  • apps/server/src/lib/subprocess-manager.ts
  • apps/server/src/providers/claude-provider.ts
  • apps/server/src/providers/codex-provider.ts
  • apps/server/src/routes/context/routes/describe-image.ts
  • apps/server/src/services/agent-service.ts
  • apps/server/src/services/auto-mode-service.ts
  • apps/server/src/services/settings-service.ts
  • apps/server/tests/unit/lib/subprocess-manager.test.ts
  • apps/server/tests/unit/services/agent-service.test.ts
  • apps/ui/src/components/dialogs/file-browser-dialog.tsx
  • apps/ui/src/components/views/board-view.tsx
  • apps/ui/src/components/views/board-view/dialogs/agent-output-modal.tsx
  • apps/ui/src/components/views/settings-view.tsx
  • apps/ui/src/components/views/settings-view/config/navigation.ts
  • apps/ui/src/components/views/settings-view/hooks/use-settings-view.ts
  • apps/ui/src/hooks/use-settings-migration.ts
  • apps/ui/src/lib/electron.ts
  • apps/ui/src/lib/http-api-client.ts
  • apps/ui/src/store/app-store.ts
  • apps/ui/src/store/setup-store.ts
  • apps/ui/src/types/electron.d.ts
  • docs/server/providers.md
  • docs/server/utilities.md
  • libs/types/src/index.ts
  • libs/types/src/settings.ts
💤 Files with no reviewable changes (1)
  • apps/server/src/routes/context/routes/describe-image.ts
🚧 Files skipped from review as they are similar to previous changes (8)
  • apps/ui/src/components/views/settings-view/hooks/use-settings-view.ts
  • apps/ui/src/components/views/board-view.tsx
  • apps/ui/src/components/views/board-view/dialogs/agent-output-modal.tsx
  • apps/server/src/index.ts
  • docs/server/utilities.md
  • apps/ui/src/store/setup-store.ts
  • apps/server/src/services/settings-service.ts
  • apps/ui/src/lib/http-api-client.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Always import from shared packages (@automaker/*), never from old relative paths

Files:

  • apps/ui/src/hooks/use-settings-migration.ts
  • apps/server/src/providers/claude-provider.ts
  • apps/server/tests/unit/lib/subprocess-manager.test.ts
  • apps/server/tests/unit/services/agent-service.test.ts
  • apps/ui/src/store/app-store.ts
  • apps/ui/src/types/electron.d.ts
  • apps/server/src/lib/subprocess-manager.ts
  • libs/types/src/index.ts
  • apps/server/src/services/agent-service.ts
  • apps/ui/src/components/views/settings-view.tsx
  • apps/ui/src/components/dialogs/file-browser-dialog.tsx
  • apps/server/src/services/auto-mode-service.ts
  • libs/types/src/settings.ts
  • apps/ui/src/lib/electron.ts
  • apps/server/src/lib/sdk-options.ts
  • apps/server/src/providers/codex-provider.ts
  • apps/ui/src/components/views/settings-view/config/navigation.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use resolveModelString() from @automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Files:

  • apps/ui/src/hooks/use-settings-migration.ts
  • apps/server/src/providers/claude-provider.ts
  • apps/server/tests/unit/lib/subprocess-manager.test.ts
  • apps/server/tests/unit/services/agent-service.test.ts
  • apps/ui/src/store/app-store.ts
  • apps/ui/src/types/electron.d.ts
  • apps/server/src/lib/subprocess-manager.ts
  • libs/types/src/index.ts
  • apps/server/src/services/agent-service.ts
  • apps/ui/src/components/views/settings-view.tsx
  • apps/ui/src/components/dialogs/file-browser-dialog.tsx
  • apps/server/src/services/auto-mode-service.ts
  • libs/types/src/settings.ts
  • apps/ui/src/lib/electron.ts
  • apps/server/src/lib/sdk-options.ts
  • apps/server/src/providers/codex-provider.ts
  • apps/ui/src/components/views/settings-view/config/navigation.ts
apps/server/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use createEventEmitter() from lib/events.ts for all server operations to emit events that stream to frontend via WebSocket

Files:

  • apps/server/src/providers/claude-provider.ts
  • apps/server/src/lib/subprocess-manager.ts
  • apps/server/src/services/agent-service.ts
  • apps/server/src/services/auto-mode-service.ts
  • apps/server/src/lib/sdk-options.ts
  • apps/server/src/providers/codex-provider.ts
🧠 Learnings (4)
📚 Learning: 2025-12-28T05:07:48.147Z
Learnt from: CR
Repo: AutoMaker-Org/automaker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T05:07:48.147Z
Learning: Applies to **/*.{ts,tsx} : Use `resolveModelString()` from automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Applied to files:

  • libs/types/src/index.ts
  • apps/server/src/services/agent-service.ts
  • apps/ui/src/components/views/settings-view.tsx
  • apps/server/src/services/auto-mode-service.ts
  • apps/server/src/lib/sdk-options.ts
📚 Learning: 2025-12-28T05:07:48.147Z
Learnt from: CR
Repo: AutoMaker-Org/automaker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T05:07:48.147Z
Learning: Store project-specific rules in `.automaker/context/` and load them into agent prompts via `loadContextFiles()` from automaker/utils

Applied to files:

  • apps/server/src/services/agent-service.ts
  • apps/server/src/services/auto-mode-service.ts
📚 Learning: 2025-12-30T01:02:07.114Z
Learnt from: illia1f
Repo: AutoMaker-Org/automaker PR: 324
File: apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx:122-131
Timestamp: 2025-12-30T01:02:07.114Z
Learning: Tailwind CSS v4 uses postfix syntax for the important modifier: append ! at the end of the utility class (e.g., backdrop-blur-[0px]! or hover:bg-red-500!). The older v3 style used a prefix (!) at the start (e.g., !backdrop-blur-[0px]); prefer the postfix form for consistency across TSX files.

Applied to files:

  • apps/ui/src/components/views/settings-view.tsx
  • apps/ui/src/components/dialogs/file-browser-dialog.tsx
📚 Learning: 2025-12-28T05:07:48.147Z
Learnt from: CR
Repo: AutoMaker-Org/automaker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T05:07:48.147Z
Learning: Each feature executes in an isolated git worktree created via automaker/git-utils to protect the main branch during AI agent execution

Applied to files:

  • apps/server/src/services/auto-mode-service.ts
🧬 Code graph analysis (7)
apps/ui/src/store/app-store.ts (4)
libs/types/src/settings.ts (2)
  • ModelProvider (72-72)
  • AgentModel (13-13)
libs/types/src/model.ts (1)
  • AgentModel (71-71)
apps/ui/src/lib/http-api-client.ts (1)
  • getHttpApiClient (1826-1831)
apps/ui/src/hooks/use-settings-migration.ts (1)
  • syncSettingsToServer (198-251)
apps/server/src/services/agent-service.ts (2)
apps/server/src/lib/sdk-options.ts (1)
  • getModelForUseCase (218-252)
apps/server/src/lib/settings-helpers.ts (1)
  • filterClaudeMdFromContext (98-137)
apps/ui/src/components/dialogs/file-browser-dialog.tsx (1)
apps/ui/src/lib/utils.ts (1)
  • isMac (60-65)
apps/server/src/services/auto-mode-service.ts (4)
apps/server/src/lib/settings-helpers.ts (1)
  • filterClaudeMdFromContext (98-137)
apps/server/src/providers/provider-factory.ts (1)
  • ProviderFactory (28-174)
libs/types/src/model.ts (1)
  • DEFAULT_MODELS (47-52)
libs/types/src/index.ts (1)
  • DEFAULT_MODELS (45-45)
libs/types/src/settings.ts (2)
libs/types/src/index.ts (2)
  • ModelProvider (85-85)
  • AgentModel (51-51)
libs/types/src/model.ts (1)
  • AgentModel (71-71)
apps/ui/src/lib/electron.ts (1)
libs/types/src/index.ts (2)
  • BacklogPlanResult (142-142)
  • BacklogPlanEvent (143-143)
apps/server/src/providers/codex-provider.ts (2)
apps/server/src/providers/types.ts (5)
  • ProviderMessage (34-45)
  • InstallationStatus (50-58)
  • ModelDefinition (72-84)
  • ContentBlock (21-29)
  • ConversationMessage (10-10)
apps/server/src/lib/subprocess-manager.ts (1)
  • spawnJSONLProcess (66-183)
🔇 Additional comments (31)
docs/server/providers.md (1)

234-234: The --sandbox <mode> documentation is correct and already includes an example.

The docs/server/utilities.md file already demonstrates a concrete sandbox mode value (workspace-write). The valid modes supported by the Codex provider are determined by the code and are: read-only, workspace-write, and danger-full-access. The suggested modes (auto, manual, disabled) are not valid for this implementation.

apps/ui/src/components/views/settings-view/config/navigation.ts (2)

13-13: LGTM!

The Wand2 icon import is consistent with the existing icon imports and is appropriately used for the AI Provider navigation item.


27-27: Navigation item structure is correct and properly integrated.

The navigation item follows the correct pattern: the SettingsViewId type includes 'ai-provider' as a valid value, and the corresponding AIProviderSection view component is properly exported from apps/ui/src/components/views/settings-view/ai-provider/. The icon import and positioning are also correct.

apps/server/tests/unit/lib/subprocess-manager.test.ts (1)

1-99: LGTM!

The test suite provides good coverage for spawnJSONLProcess:

  • Basic JSONL parsing functionality
  • stderr activity preventing false idle timeouts
  • Startup timeout vs idle timeout distinction
  • Timeout enforcement when no activity occurs

Test structure is clean, uses appropriate timing values, and assertions match expected behavior from the implementation.

apps/server/src/lib/subprocess-manager.ts (1)

179-182: Process killed by external signal (not abort/timeout) may not throw.

If the process is killed externally (e.g., SIGKILL from system), code may be null with a non-null signal. The condition code && code !== 0 won't catch this since null && ... is falsy. Consider whether silent completion is intended for this edge case.

🔎 Optional: Handle signal-based termination
-  if (code && code !== 0) {
+  if (code !== 0) {
     const suffix = signal ? ` (signal: ${signal})` : '';
-    throw new Error(stderr || `Process exited with code ${code}${suffix}`);
+    throw new Error(stderr || `Process exited with code ${code ?? 'null'}${suffix}`);
   }
apps/ui/src/components/dialogs/file-browser-dialog.tsx (2)

235-235: LGTM - Restoring default focus behavior.

Removing focus-visible:outline-none restores the default focus indicators, which improves keyboard navigation accessibility.


369-372: LGTM - Simplified keyboard hint implementation.

The inline kbd element with conditional platform rendering is a clean, straightforward solution. The implementation correctly displays ⌘ on Mac and Ctrl on other platforms.

apps/server/src/providers/claude-provider.ts (1)

63-63: LGTM!

The refactor to extract settingSources into a local variable is clean and reduces redundant property access while preserving identical behavior.

Also applies to: 102-102

apps/server/src/services/auto-mode-service.ts (4)

1753-1759: Good centralization of default model logic.

The new getDefaultFeatureModel() helper consolidates model resolution across the service, making it easier to maintain and align with the multi-provider architecture introduced in this PR.


505-519: Well-designed model-aware context handling.

Computing the model early (lines 505-506) and using it to conditionally filter Claude.md (lines 518-519) ensures that Claude-specific context is only removed when using Claude models. This correctly prevents filtering for the newly added providers (Cursor, OpenCode, Codex).

As per coding guidelines: correctly uses resolveModelString() from @automaker/model-resolver.


665-667: Consistent model resolution pattern across execution paths.

The same model-aware context filtering logic is correctly applied in executePipelineSteps() and followUpFeature(), ensuring consistent behavior throughout the service.

Also applies to: 889-900


2023-2023: Correct systemPrompt propagation with fallback.

The updated logic properly forwards systemPrompt through the execution chain, with a sensible fallback pattern at line 2067 that prioritizes sdkOptions.systemPrompt while preserving any explicitly provided options?.systemPrompt.

Also applies to: 2067-2067

apps/server/src/lib/sdk-options.ts (2)

319-354: Conditional Claude.md loading supports multi-provider architecture.

The updated buildClaudeMdOptions() now correctly checks model compatibility before enabling Claude.md auto-loading (lines 329-330), preventing Claude-specific settings from being applied to Cursor, OpenCode, or Codex providers. The simplified append logic (lines 348-350) properly handles custom system prompts.


263-268: Verify the undefined model fallback behavior in isClaudeModelKey().

The function returns true when model is undefined (lines 264-266), meaning if both config.model and config.sessionModel are undefined, Claude.md settings will be applied. This is a defensive fallback that assumes Claude as the default provider.

While this may be intentional, it creates implicit behavior where non-Claude providers receive Claude settings when no model is explicitly specified. Consider documenting this assumption or requiring explicit model configuration to prevent unexpected behavior in integrations with other providers.

apps/server/src/services/agent-service.ts (1)

253-255: Consistent model-aware filtering applied in agent service.

The agent service correctly adopts the same pattern as auto-mode-service: computing the effective model via getModelForUseCase() (line 253) and conditionally filtering Claude.md only for Claude models (lines 254-255). This ensures consistent multi-provider behavior across both services.

As per coding guidelines: uses model resolution helpers from @automaker/model-resolver.

apps/server/tests/unit/services/agent-service.test.ts (1)

126-128: Test correctly updated for reduced file reads.

The test assertion now expects 2 file reads (session and metadata) instead of 3, with a clear comment explaining the behavior. This aligns with the updated session initialization logic.

apps/ui/src/types/electron.d.ts (2)

185-192: LGTM! New event variant follows existing pattern.

The auto_mode_tool_result variant is well-structured and consistent with other AutoModeEvent types. It properly includes the necessary context fields (featureId, projectId, projectPath) and the result/toolUseId payload.


985-985: LGTM! Provider expansion aligns with multi-CLI integration.

The expansion of the provider field to include 'cursor', 'opencode', and 'codex' alongside 'claude' is consistent with the PR's objective of supporting multiple agent CLIs.

libs/types/src/index.ts (1)

40-52: LGTM! Public API expansion follows existing patterns.

The new model map exports (CURSOR_MODEL_MAP, OPENCODE_MODEL_MAP, CODEX_MODEL_MAP) and their corresponding alias types follow the same pattern as the existing CLAUDE_MODEL_MAP export. This provides a consistent public API surface for consumers.

apps/ui/src/lib/electron.ts (4)

16-17: LGTM! Previously missing imports are now present.

The BacklogPlanResult and BacklogPlanEvent imports and re-exports address the past review comment. The types are now properly available for use in the backlogPlan API definitions.

Also applies to: 33-34


498-511: LGTM! Backlog plan API follows existing patterns.

The backlogPlan API interface is well-structured with generate, stop, status, apply, and onEvent methods. The method signatures are consistent with other async operation APIs in the codebase (e.g., autoMode, specRegeneration).


543-591: LGTM! Provider status APIs support multi-CLI integration.

The getCursorStatus, getOpenCodeStatus, and getCodexStatus methods, along with installCursor and verifyCursorAuth, provide a consistent interface for managing multiple AI provider CLIs. The structure mirrors the existing Claude status/setup flow, which promotes maintainability.

Also applies to: 633-645, 1176-1224, 1263-1281


1314-1359: LGTM! Mock implementations provide consistent development experience.

The mock implementations for the new provider status and installation methods follow the same patterns as existing mocks, returning appropriate not_installed/not_authenticated states. This enables UI development without requiring actual CLI installations.

Also applies to: 1371-1378, 1430-1444

libs/types/src/settings.ts (1)

72-72: LGTM! Multi-provider settings support properly implemented.

The changes successfully address the past review comment about missing defaultProvider and defaultModel fields:

  1. ModelProvider expansion (line 72) - Now supports 'claude', 'cursor', 'opencode', and 'codex'
  2. AIProfile.provider documentation (line 159) - Updated to reflect generic provider usage (no longer Claude-specific)
  3. GlobalSettings fields (lines 312-315) - defaultProvider and defaultModel properly added with clear documentation
  4. Default values (lines 526-527) - Reasonable defaults (claude/sonnet) that maintain backward compatibility

The implementation is consistent with the multi-CLI integration objective and provides proper type safety for the settings sync in use-settings-migration.ts.

Also applies to: 159-160, 312-315, 526-527

apps/ui/src/store/app-store.ts (3)

17-18: LGTM!

Re-exporting types from @automaker/types for convenience is appropriate and follows the coding guidelines to import from shared packages.


487-490: LGTM!

The new state fields defaultProvider and defaultModel are well-typed and appropriately placed in the AppState interface.


780-783: LGTM!

Action signatures are correctly typed and follow the established pattern in the store.

apps/ui/src/components/views/settings-view.tsx (4)

1-29: LGTM!

Import statements correctly use path aliases for internal components and shared packages. The distinction between internal app imports (@/ and ./) and cross-package shared imports (@automaker/*) is appropriate.


32-70: LGTM!

Store hook usage correctly destructures the new provider and model state fields and their setters.


103-113: LGTM!

The theme handling logic is correct. The updated comment accurately explains that Theme is a strict subset of ThemeMode and the cast is a safe widening operation. The past review concern has been properly addressed.


140-165: Model alias resolution is already correctly implemented in AIProviderSection.

The concern raised in this review comment is incorrect. AIProviderSection already imports resolveModelString from @automaker/model-resolver and resolves model aliases before passing the value to onModelChange. This is verified in multiple locations within the component:

  • The default model is normalized: const normalizedDefaultModel = resolveModelString(defaultModel);
  • When the provider changes, the selected model is resolved: onModelChange(resolveModelString('auto') as AgentModel);
  • When a user selects from the dropdown, the selection is resolved: onValueChange={(v: string) => onModelChange(resolveModelString(v) as AgentModel)}

The resolution occurs at the component level before calling onModelChange, ensuring that setDefaultModel in the store receives fully resolved model names. This is a valid and consistent pattern used throughout the codebase (agent-view.tsx, add-feature-dialog.tsx, model-selector.tsx, etc.). No changes are needed.

Likely an incorrect or invalid review comment.

Comment on lines +939 to +949
const isWindows = os.platform() === 'win32';
try {
const findCommand = isWindows ? 'where codex' : 'which codex';
const { stdout } = await execAsync(findCommand);
const cliPath = stdout.trim().split(/\r?\n/)[0];
if (cliPath) {
return cliPath;
}
} catch {
// Fall back to common locations
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Prefer execFile over shell execution for CLI detection.

The which/where commands are executed via shell using execAsync. While the command string is hardcoded, using execFile is safer and more explicit about not needing shell features.

🔎 Proposed refactor using execFile
+import { execFile } from 'child_process';
+const execFileAsync = promisify(execFile);

     const isWindows = os.platform() === 'win32';
     try {
-      const findCommand = isWindows ? 'where codex' : 'which codex';
-      const { stdout } = await execAsync(findCommand);
+      const findCommand = isWindows ? 'where' : 'which';
+      const { stdout } = await execFileAsync(findCommand, ['codex']);
       const cliPath = stdout.trim().split(/\r?\n/)[0];
       if (cliPath) {
         return cliPath;
       }
     } catch {
       // Fall back to common locations
     }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +79 to +86
const isMac =
typeof navigator !== 'undefined' &&
(
(navigator as Navigator & { userAgentData?: { platform?: string } }).userAgentData
?.platform ??
navigator.platform ??
''
).includes('Mac');
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Import isMac from the existing utility module.

This code duplicates the platform detection logic already available in @/lib/utils.ts (lines 59-64). The existing utility is more comprehensive (checks process.platform first) and is likely used elsewhere in the codebase.

🔎 Proposed fix
+import { isMac } from '@/lib/utils';
+
 export function FileBrowserDialog({
   open,
   onOpenChange,
   onSelect,
   title = 'Select Project Directory',
   description = 'Navigate to your project folder or paste a path directly',
   initialPath,
 }: FileBrowserDialogProps) {
   const [currentPath, setCurrentPath] = useState<string>('');
   const [parentPath, setParentPath] = useState<string | null>(null);
   const [directories, setDirectories] = useState<DirectoryEntry[]>([]);
   const [drives, setDrives] = useState<string[]>([]);
   const [loading, setLoading] = useState(false);
   const [error, setError] = useState('');
   const [warning, setWarning] = useState('');
   const [recentFolders, setRecentFolders] = useState<string[]>([]);
-  const isMac =
-    typeof navigator !== 'undefined' &&
-    (
-      (navigator as Navigator & { userAgentData?: { platform?: string } }).userAgentData
-        ?.platform ??
-      navigator.platform ??
-      ''
-    ).includes('Mac');

Based on the relevant code snippet from apps/ui/src/lib/utils.ts.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const isMac =
typeof navigator !== 'undefined' &&
(
(navigator as Navigator & { userAgentData?: { platform?: string } }).userAgentData
?.platform ??
navigator.platform ??
''
).includes('Mac');
const [recentFolders, setRecentFolders] = useState<string[]>([]);
🤖 Prompt for AI Agents
In apps/ui/src/components/dialogs/file-browser-dialog.tsx around lines 79-86,
the file contains duplicated platform detection logic for isMac; replace this
block by importing the existing isMac utility from '@/lib/utils' and use that
value instead (add an import at the top of the file: import { isMac } from
'@/lib/utils'), remove the local navigator/platform detection code, and ensure
any TypeScript types remain correct and no other references to the removed local
variable remain.

Comment on lines +226 to +227
defaultProvider: state.defaultProvider,
defaultModel: state.defaultModel,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check server-side settings update handling and resolveModelString usage

echo "=== Looking for updateGlobal or settings endpoints on server ==="
git ls-files | grep -E "server|service" | grep -E "\.ts$"

echo ""
echo "=== Check if resolveModelString is used in the codebase ==="
rg "resolveModelString" --type=ts --type=tsx

echo ""
echo "=== Check DEFAULT_GLOBAL_SETTINGS and defaultModel usage ==="
rg "DEFAULT_GLOBAL_SETTINGS|defaultModel" --type=ts --type=tsx -B 2 -A 2

echo ""
echo "=== Check if GlobalSettings interface expects resolved or aliased models ==="
rg "interface GlobalSettings" --type=ts -A 15

echo ""
echo "=== Check updateSettings or sync implementation ==="
rg "updateGlobal|syncSettings" --type=ts -A 10 | head -100

Repository: AutoMaker-Org/automaker

Length of output: 23852


🏁 Script executed:

#!/bin/bash

echo "=== Full GlobalSettings interface ==="
cat libs/types/src/settings.ts | grep -A 100 "export interface GlobalSettings"

echo ""
echo "=== Check where DEFAULT_GLOBAL_SETTINGS is defined ==="
rg "DEFAULT_GLOBAL_SETTINGS\s*=" --type=ts -A 30

echo ""
echo "=== Check update-global route implementation ==="
cat apps/server/src/routes/settings/routes/update-global.ts

echo ""
echo "=== Check settings-service updateGlobalSettings method ==="
cat apps/server/src/services/settings-service.ts | head -200

Repository: AutoMaker-Org/automaker

Length of output: 11495


🏁 Script executed:

#!/bin/bash

echo "=== Check if @automaker/model-resolver exists ==="
find . -name "model-resolver" -o -name "model-resolver.ts"

echo ""
echo "=== Check AgentModel type definition ==="
rg "type AgentModel|interface AgentModel" --type=ts -A 5

echo ""
echo "=== Check DEFAULT_GLOBAL_SETTINGS initialization ==="
rg "export const DEFAULT_GLOBAL_SETTINGS|DEFAULT_GLOBAL_SETTINGS\s*=" --type=ts -B 2 -A 40

echo ""
echo "=== Check resolveModelString in package.json or exports ==="
find . -name "package.json" -type f -path "*/model-resolver/*" -o -name "index.ts" -path "*/model-resolver/*"

echo ""
echo "=== Look for model resolution logic anywhere ==="
rg "haiku|sonnet|opus" --type=ts | head -20

Repository: AutoMaker-Org/automaker

Length of output: 6319


🏁 Script executed:

#!/bin/bash

echo "=== Check model-resolver exports ==="
cat libs/model-resolver/src/index.ts

echo ""
echo "=== Check use-settings-migration.ts for any model resolution ==="
cat apps/ui/src/hooks/use-settings-migration.ts

echo ""
echo "=== Check if resolveModelString is used elsewhere in settings context ==="
rg "resolveModelString" --type=ts -B 3 -A 3

Repository: AutoMaker-Org/automaker

Length of output: 50379


Resolve model aliases before syncing to server.

The syncSettingsToServer() function in lines 226-227 sends defaultModel to the server without resolving aliases, violating the coding guideline: "Use resolveModelString() from @automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names."

While the type system (AgentModel = ModelAlias | ModelId) allows both aliases and full IDs, and other UI components correctly use resolveModelString() (e.g., ai-provider-section.tsx), the sync path should be consistent.

Import resolveModelString from @automaker/model-resolver and resolve the model values before sending in the updates object:

export async function syncSettingsToServer(): Promise<boolean> {
  try {
    const api = getHttpApiClient();
+   const { resolveModelString } = await import('@automaker/model-resolver');
    const automakerStorage = getItem('automaker-storage');

    if (!automakerStorage) {
      return false;
    }

    const parsed = JSON.parse(automakerStorage);
    const state = parsed.state || parsed;

    // Extract settings to sync
    const updates = {
      // ... other fields ...
-     defaultModel: state.defaultModel,
+     defaultModel: resolveModelString(state.defaultModel) as AgentModel,
-     enhancementModel: state.enhancementModel,
+     enhancementModel: resolveModelString(state.enhancementModel) as AgentModel,
-     validationModel: state.validationModel,
+     validationModel: resolveModelString(state.validationModel) as AgentModel,
      // ... rest of updates ...
    };

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/ui/src/hooks/use-settings-migration.ts around lines 226-227, the
syncSettingsToServer call is sending state.defaultModel without resolving
possible model aliases; import resolveModelString from @automaker/model-resolver
and use it to convert state.defaultModel to a full model string before including
it in the updates object (e.g., const resolved =
resolveModelString(state.defaultModel) and send defaultModel: resolved),
ensuring you handle the import at top and preserve types.

Comment on lines +1626 to +1653
// Default Provider actions
setDefaultProvider: (provider) => {
set({ defaultProvider: provider });
// Sync with backend (fire and forget)
import('@/lib/http-api-client').then(({ getHttpApiClient }) => {
getHttpApiClient()
.setup.setDefaultProvider(provider)
.catch((err: Error) => {
console.error('[AppStore] Failed to sync provider with backend:', err);
});
});
import('@/hooks/use-settings-migration').then(({ syncSettingsToServer }) => {
syncSettingsToServer().catch((err) => {
console.error('[AppStore] Failed to persist provider settings:', err);
});
});
},
setDefaultModel: (model) => {
set({ defaultModel: model });
// Log the change (backend uses defaultModel based on defaultProvider)
console.log('[AppStore] Default model updated:', model);
import('@/hooks/use-settings-migration').then(({ syncSettingsToServer }) => {
syncSettingsToServer().catch((err) => {
console.error('[AppStore] Failed to persist model settings:', err);
});
});
},

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

echo "=== Checking current implementation of setDefaultModel in app-store.ts ==="
sed -n '1643,1652p' apps/ui/src/store/app-store.ts

echo -e "\n=== Checking imports at top of app-store.ts ==="
head -20 apps/ui/src/store/app-store.ts

echo -e "\n=== Checking if resolveModelString is imported ==="
rg "resolveModelString" apps/ui/src/store/app-store.ts

echo -e "\n=== Checking all callsites of setDefaultModel ==="
rg "setDefaultModel\(" apps/ui/src/ -B 2 -A 2

echo -e "\n=== Checking AIProviderSection for model resolution ==="
rg -l "AIProviderSection\|settings-view" apps/ui/src/components/ | head -5

Repository: AutoMaker-Org/automaker

Length of output: 1325


🏁 Script executed:

#!/bin/bash

echo "=== Check if resolveModelString is used in AIProviderSection ==="
find apps/ui/src/components -name "*ai-provider*" -o -name "*AIProvider*" | head -10

echo -e "\n=== Look for settings-view structure ==="
find apps/ui/src/components -type f -name "*.tsx" | grep -i settings | head -10

Repository: AutoMaker-Org/automaker

Length of output: 1064


🏁 Script executed:

#!/bin/bash

echo "=== Examine the AIProviderSection component ==="
rg -A 20 "const AIProviderSection|export.*AIProviderSection" apps/ui/src/components/views/settings-view/

echo -e "\n=== Check how model is handled in AIProviderSection ==="
rg "resolveModelString|setDefaultModel|onModelChange" apps/ui/src/components/views/settings-view/ -B 3 -A 3

Repository: AutoMaker-Org/automaker

Length of output: 12042


🏁 Script executed:

#!/bin/bash

echo "=== Get the full setDefaultModel implementation ==="
rg -A 15 "setDefaultModel: \(model\)" apps/ui/src/store/app-store.ts

echo -e "\n=== Check resolveModelString usage patterns elsewhere ==="
rg "resolveModelString" apps/ui/src/ -B 2 -A 2 | head -40

Repository: AutoMaker-Org/automaker

Length of output: 5217


Resolve model aliases in setters to ensure consistency across all call sites.

The setDefaultModel and setValidationModel setters store models directly without resolving aliases, violating the coding guideline requiring resolveModelString() from @automaker/model-resolver for TypeScript files. While AIProviderSection resolves before calling setDefaultModel, other components like feature-defaults-section call setValidationModel without resolution, creating inconsistent data handling.

🔎 Proposed fix

Import the resolver at the top of the file:

 import { create } from 'zustand';
 import { persist } from 'zustand/middleware';
+import { resolveModelString } from '@automaker/model-resolver';
 import type { Project, TrashedProject } from '@/lib/electron';

Then resolve models in both setters:

       setDefaultModel: (model) => {
+        const resolved = resolveModelString(model);
-        set({ defaultModel: model });
+        set({ defaultModel: resolved });
         // Log the change (backend uses defaultModel based on defaultProvider)
-        console.log('[AppStore] Default model updated:', model);
+        console.log('[AppStore] Default model updated:', model, '→', resolved);
         import('@/hooks/use-settings-migration').then(({ syncSettingsToServer }) => {
           syncSettingsToServer().catch((err) => {
             console.error('[AppStore] Failed to persist model settings:', err);
           });
         });
       },

       // Validation Model actions
       setValidationModel: (model) => {
+        const resolved = resolveModelString(model);
-        set({ validationModel: model })
+        set({ validationModel: resolved })
       },

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/ui/src/store/app-store.ts around lines 1626 to 1653, the setters store
model strings directly which can retain unresolved aliases; import
resolveModelString from '@automaker/model-resolver' at the top of the file and
update setDefaultModel (and the analogous setValidationModel setter) to call
const resolved = resolveModelString(model) and then set({ defaultModel: resolved
}) (or set validationModel to resolved), use the resolved value for logging and
any backend sync calls, and leave the rest of the fire-and-forget persistence
logic unchanged.

Comment on lines +731 to +732
CODEX_CLI_TIMEOUT_MS=300000
CODEX_CLI_STARTUP_TIMEOUT_MS=600000
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Document timeout defaults and guidance for Codex CLI timeouts.

The new CODEX_CLI_TIMEOUT_MS and CODEX_CLI_STARTUP_TIMEOUT_MS environment variables lack:

  • Default values (if any exist)
  • Recommended ranges
  • When to adjust each one (e.g., idle time vs. slow network startup)

Consider adding a brief table or explanatory text clarifying the purpose and typical values for each timeout.

🔎 Suggested documentation addition

Add to the "Codex Provider" section or the environment variables section:

| Variable | Purpose | Typical Value | Default |
| --- | --- | --- | --- |
| `CODEX_CLI_TIMEOUT_MS` | Idle timeout (no output) | 300000 (5 min) | 30000 (30s) |
| `CODEX_CLI_STARTUP_TIMEOUT_MS` | Time to first output | 600000 (10 min) | 60000 (60s) |

Or include prose explanation:

  • CODEX_CLI_TIMEOUT_MS: Maximum time (in ms) to wait for any output from the CLI before timing out. Default is 30 seconds. Increase this if the CLI processes large files slowly.
  • CODEX_CLI_STARTUP_TIMEOUT_MS: Maximum time (in ms) to wait for the CLI to produce its first output. Default is 60 seconds. Increase this if the CLI startup is slow on your system.
🤖 Prompt for AI Agents
In docs/server/providers.md around lines 731 to 732, the two new env vars
CODEX_CLI_TIMEOUT_MS and CODEX_CLI_STARTUP_TIMEOUT_MS are listed without
defaults, guidance or examples; update the documentation to state each
variable's purpose, provide a sensible default value and a typical recommended
range, and give brief guidance on when to increase them (e.g., idle processing
vs slow startup or network); include either a small table showing Variable |
Purpose | Typical Value | Default or a short prose block: CODEX_CLI_TIMEOUT_MS —
idle/output timeout (default 30000 ms, typical 30000–300000 ms; raise for
long-running operations), CODEX_CLI_STARTUP_TIMEOUT_MS — time to first
output/startup (default 60000 ms, typical 60000–600000 ms; raise for slow
environment or heavy initialization).

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
apps/server/src/providers/codex-provider.ts (2)

940-948: Refactor: Prefer execFile over shell execution for CLI detection.

The which/where commands are executed via shell using execAsync. While the command string is hardcoded and not a security risk, using execFile is safer and more explicit about not needing shell features.

🔎 Proposed refactor using execFile
+import { execFile } from 'child_process';
+const execFileAsync = promisify(execFile);

     const isWindows = os.platform() === 'win32';
     try {
-      const findCommand = isWindows ? 'where codex' : 'which codex';
-      const { stdout } = await execAsync(findCommand);
+      const findCommand = isWindows ? 'where' : 'which';
+      const { stdout } = await execFileAsync(findCommand, ['codex']);
       const cliPath = stdout.trim().split(/\r?\n/)[0];
       if (cliPath) {
         return cliPath;
       }
     } catch {
       // Fall back to common locations
     }

219-225: Critical: Command injection risk remains despite past review.

Although marked as addressed in previous commits, line 221 still executes cliPath via shell using string interpolation: `"${cliPath}" --version`. If cliPath originates from user input (via config.cliPath or CODEX_CLI_PATH environment variable), this creates a command injection vulnerability.

🔎 Recommended fix using execFile
+import { execFile } from 'child_process';
+const execFileAsync = promisify(execFile);

   async detectInstallation(): Promise<InstallationStatus> {
     const cliPath = await this.resolveCliPath();
     // ...
     let version = '';
     try {
-      const { stdout } = await execAsync(`"${cliPath}" --version`);
+      const { stdout } = await execFileAsync(cliPath, ['--version']);
       version = stdout.trim().split('\n')[0];
     } catch {
       // Version command might not be available
     }
🧹 Nitpick comments (2)
apps/server/src/providers/codex-provider.ts (2)

52-52: Consider using resolveModelString() for model alias resolution.

The coding guidelines specify using resolveModelString() from @automaker/model-resolver to convert model aliases. Currently, the code uses a local mapModelToCodexFormat() method which may not align with the shared model resolution system.

As per coding guidelines, providers should use the shared model resolver. Please verify whether the Codex models are registered in @automaker/model-resolver and can be resolved through resolveModelString().


395-401: Consider documenting the model mapping strategy.

The modelMap contains only one alias (codexgpt-5.2-codex). While this is functional, consider documenting whether additional common aliases (e.g., opus, sonnet) should be supported here or handled by the shared @automaker/model-resolver.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a56d3da and 837cb56.

📒 Files selected for processing (1)
  • apps/server/src/providers/codex-provider.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Always import from shared packages (@automaker/*), never from old relative paths

Files:

  • apps/server/src/providers/codex-provider.ts
apps/server/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use createEventEmitter() from lib/events.ts for all server operations to emit events that stream to frontend via WebSocket

Files:

  • apps/server/src/providers/codex-provider.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use resolveModelString() from @automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Files:

  • apps/server/src/providers/codex-provider.ts
🔇 Additional comments (9)
apps/server/src/providers/codex-provider.ts (9)

1-33: LGTM: Provider setup follows standard patterns.

The imports and class initialization are appropriate for a CLI-based provider implementation.


120-149: LGTM: The this binding issue is correctly resolved.

The captured provider reference (line 120) properly preserves the class context for method calls within the runWithArgs generator function, addressing the critical issue from previous reviews.


151-182: LGTM: Retry logic for --ask-for-approval is well-implemented.

The graceful fallback when the flag is unsupported, with proper retry prevention via didRetry, demonstrates good defensive programming.


242-354: LGTM: Model definitions are comprehensive and well-structured.

The catalog includes appropriate metadata for all Codex models with clear tier differentiation and capability flags.


424-826: LGTM: Event handling is robust and comprehensive.

The toProviderMessages implementation includes extensive fallback logic and type guards to handle the diverse JSONL event formats from the Codex CLI, demonstrating good defensive programming.


828-850: LGTM: Sandbox mode resolution is secure by default.

The logic correctly defaults to requiring write access when allowedTools is undefined (line 842), which is a safe conservative choice. The three-tier sandbox model (read-only, workspace-write, danger-full-access) provides appropriate security boundaries.


880-904: LGTM: Authentication check is comprehensive.

The implementation correctly checks multiple API key sources and the local auth.json file, providing complete authentication status information.


906-930: LGTM: Timeout configuration is properly validated.

The environment variable parsing includes appropriate validation for edge cases (non-finite, negative values) with sensible defaults.


965-982: LGTM: Common installation paths are comprehensive.

The platform-specific path lists cover standard installation locations for both Windows and Unix-like systems.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 837cb56 and 64cf974.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (7)
  • apps/server/package.json
  • apps/server/src/routes/app-spec/generate-spec.ts
  • apps/server/src/services/auto-mode-service.ts
  • apps/ui/src/components/views/board-view/dialogs/pipeline-settings-dialog.tsx
  • libs/prompts/src/defaults.ts
  • libs/prompts/src/index.ts
  • libs/types/src/pipeline.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/server/src/routes/app-spec/generate-spec.ts
  • apps/server/src/services/auto-mode-service.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Always import from shared packages (@automaker/*), never from old relative paths

Files:

  • libs/prompts/src/index.ts
  • libs/types/src/pipeline.ts
  • libs/prompts/src/defaults.ts
  • apps/ui/src/components/views/board-view/dialogs/pipeline-settings-dialog.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use resolveModelString() from @automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Files:

  • libs/prompts/src/index.ts
  • libs/types/src/pipeline.ts
  • libs/prompts/src/defaults.ts
  • apps/ui/src/components/views/board-view/dialogs/pipeline-settings-dialog.tsx
🧠 Learnings (2)
📚 Learning: 2025-12-28T05:07:48.147Z
Learnt from: CR
Repo: AutoMaker-Org/automaker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T05:07:48.147Z
Learning: Packages can only depend on packages above them in the dependency chain: types → (utils|prompts|platform|model-resolver|dependency-resolver) → git-utils → (server|ui)

Applied to files:

  • apps/server/package.json
📚 Learning: 2025-12-30T01:02:07.114Z
Learnt from: illia1f
Repo: AutoMaker-Org/automaker PR: 324
File: apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx:122-131
Timestamp: 2025-12-30T01:02:07.114Z
Learning: Tailwind CSS v4 uses postfix syntax for the important modifier: append ! at the end of the utility class (e.g., backdrop-blur-[0px]! or hover:bg-red-500!). The older v3 style used a prefix (!) at the start (e.g., !backdrop-blur-[0px]); prefer the postfix form for consistency across TSX files.

Applied to files:

  • apps/ui/src/components/views/board-view/dialogs/pipeline-settings-dialog.tsx
🧬 Code graph analysis (1)
libs/prompts/src/defaults.ts (1)
libs/prompts/src/index.ts (1)
  • DEFAULT_SUBTASK_GENERATION_PROMPT (36-36)
🔇 Additional comments (8)
libs/types/src/pipeline.ts (1)

11-11: LGTM!

The optional enableSubtaskGeneration property is a clean addition to the PipelineStep interface. The property name is descriptive and follows TypeScript conventions.

libs/prompts/src/index.ts (1)

36-36: LGTM!

The re-export of DEFAULT_SUBTASK_GENERATION_PROMPT properly exposes the new subtask generation prompt to consumers of the @automaker/prompts package.

apps/ui/src/components/views/board-view/dialogs/pipeline-settings-dialog.tsx (5)

243-243: LGTM!

The enableSubtaskGeneration property is properly added to the EditingStep interface and aligns with the type definition in @automaker/types.


285-286: LGTM!

The initialization of enableSubtaskGeneration: false for new steps provides a sensible default behavior.


296-297: LGTM!

Properly copies the enableSubtaskGeneration flag from the existing step, with a safe fallback to false.


718-737: LGTM!

The checkbox UI for enabling subtask generation is well-implemented with:

  • Clear labeling and help text
  • Proper state binding to editingStep.enableSubtaskGeneration
  • Accessible checkbox with appropriate styling

391-391: LGTM!

The enableSubtaskGeneration flag is correctly propagated through all save paths:

  • Creating new steps (Line 391, 461)
  • Updating existing steps (Line 430)
  • Handling missing steps (Line 445)

This ensures the feature works consistently across all edit scenarios.

Also applies to: 430-430, 445-445, 461-461

libs/prompts/src/defaults.ts (1)

242-265: No duplicate declarations found.

The search confirms only a single declaration of DEFAULT_SUBTASK_GENERATION_PROMPT exists at line 242 in libs/prompts/src/defaults.ts. There is no duplicate, and the code will compile without redeclaration errors.

Likely an incorrect or invalid review comment.

"@automaker/model-resolver": "1.0.0",
"@automaker/platform": "1.0.0",
"@automaker/prompts": "1.0.0",
"@automaker/prompts": "^1.0.0",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Inconsistent dependency versioning for internal monorepo package.

All other @automaker/* dependencies use exact version pins ("1.0.0"), but @automaker/prompts now uses a caret range ("^1.0.0"). In a monorepo with internal packages, exact version pins (or workspace protocol) are recommended to:

  • Keep all packages synchronized
  • Prevent version drift between packages
  • Avoid unexpected behavior from automatic minor/patch updates
🔎 Proposed fix to align with other internal dependencies
-    "@automaker/prompts": "^1.0.0",
+    "@automaker/prompts": "1.0.0",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"@automaker/prompts": "^1.0.0",
"@automaker/prompts": "1.0.0",
🤖 Prompt for AI Agents
In apps/server/package.json around line 32, the @automaker/prompts dependency
uses a caret range "^1.0.0" which is inconsistent with other internal
@automaker/* packages that use exact pins; update the dependency to the exact
version "1.0.0" (or switch to the workspace: protocol if the repo uses workspace
protocol everywhere) so it matches the rest of the monorepo and prevents version
drift.

name: editingStep.name,
instructions: editingStep.instructions,
colorClass: editingStep.colorClass,
colorClass: editingStep.colorClass,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Remove duplicate colorClass assignment.

Line 428 duplicates the colorClass property that already exists on Line 427. This creates a redundant assignment where the second value silently overwrites the first.

🔎 Proposed fix
                    colorClass: editingStep.colorClass,
-                    colorClass: editingStep.colorClass,
                    updatedAt: now,

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Biome (2.1.2)

[error] 427-428: This property is later overwritten by an object member with the same name.

Overwritten with this property.

If an object property with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the object and previous definitions are ignored.
Unsafe fix: Remove this property.

(lint/suspicious/noDuplicateObjectKeys)

🤖 Prompt for AI Agents
In apps/ui/src/components/views/board-view/dialogs/pipeline-settings-dialog.tsx
around line 428, there is a duplicate colorClass property assignment (it is
already set on the previous line); remove the redundant second colorClass entry
so the object only defines colorClass once, leaving the original intended value
intact.

)
}
rows={6}
rows={6}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove duplicate rows attribute.

The rows={6} attribute is specified twice on the same Textarea component. React will use the last value, but this redundancy should be removed for code clarity.

🔎 Proposed fix
                  }
                  rows={6}
-                  rows={6}
                  className="font-mono text-sm"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
rows={6}
}
rows={6}
className="font-mono text-sm"
🧰 Tools
🪛 Biome (2.1.2)

[error] 712-713: This JSX property is assigned multiple times.

This attribute is assigned again here.

(lint/suspicious/noDuplicateJsxProps)

🤖 Prompt for AI Agents
In apps/ui/src/components/views/board-view/dialogs/pipeline-settings-dialog.tsx
around line 713 the Textarea component has a duplicate rows={6} attribute;
remove the redundant rows prop so only a single rows={6} remains on that
Textarea to eliminate the duplicate and keep behavior unchanged.

@webdevcody
Copy link
Collaborator

closing, too many conflicts, talk to core contributors on discord if you want to help

@webdevcody webdevcody closed this Jan 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Do Not Merge Use this label if something should not be merged. Enhancement Improvements to existing functionality or UI. Testers-Requested Request for others to test an enhancement or bug fix/etc.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants