Skip to content

Conversation

@yumesha
Copy link
Contributor

@yumesha yumesha commented Jan 9, 2026

Enhanced worktree management and spec generation with better UX and
comprehensive test coverage:

Spec Generation Improvements:

  • Renamed "Target Branch" to "Worktree Branch" for clarity
  • Changed from always-required input to optional checkbox pattern
  • Auto-detects current git branch when "Use worktree branch" is unchecked
  • Added /api/worktree/current-branch endpoint for branch detection
  • Each project now uses its actual current branch (main/master/develop)

Delete Worktree Enhancements:

  • Added "Delete backlog of this worktree (X features)" checkbox option
  • Fixed bug: features now properly reassigned to main branch when not deleted
  • Reduced race condition errors by removing features from state before deletion
  • Fixed cancellation error toast when user cancels feature implementation

Test Coverage:

  • Added comprehensive E2E tests for delete worktree functionality (5 tests)
    • Move features to main when deleting worktree
    • Delete features with checkbox checked
    • Verify feature count display
    • Delete/keep git branch based on checkbox state
  • Added E2E tests for spec regeneration with worktrees (2 tests)
  • Updated test helpers to work with worktree panel UI
  • Fixed addFeature helper to detect current branch before selecting "Other"

Technical Changes:

  • Added getCurrentBranch to WorktreeAPI interface
  • Added data-testid to branch-switch-dropdown for testing
  • Clean up existing features for branch before regenerating spec
  • Remove features from local state before deletion to prevent updates

Summary by CodeRabbit

  • New Features

    • Target a specific Git branch/worktree when creating or regenerating specs; UI adds “Use worktree branch” controls and an API to read the current branch.
  • Improvements

    • Custom feature-count option; features record branch assignment. Deleting a worktree now lets you delete or reassign its backlog. Improved branch-name validation and suppressed error toasts for user cancellations.
  • Tests

    • New end-to-end tests covering worktree create/regenerate/delete and backlog behaviors.

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

  Enhanced worktree management and spec generation with better UX and
  comprehensive test coverage:

  **Spec Generation Improvements:**
  - Renamed "Target Branch" to "Worktree Branch" for clarity
  - Changed from always-required input to optional checkbox pattern
  - Auto-detects current git branch when "Use worktree branch" is unchecked
  - Added /api/worktree/current-branch endpoint for branch detection
  - Each project now uses its actual current branch (main/master/develop)

  **Delete Worktree Enhancements:**
  - Added "Delete backlog of this worktree (X features)" checkbox option
  - Fixed bug: features now properly reassigned to main branch when not deleted
  - Reduced race condition errors by removing features from state before deletion
  - Fixed cancellation error toast when user cancels feature implementation

  **Test Coverage:**
  - Added comprehensive E2E tests for delete worktree functionality (5 tests)
    - Move features to main when deleting worktree
    - Delete features with checkbox checked
    - Verify feature count display
    - Delete/keep git branch based on checkbox state
  - Added E2E tests for spec regeneration with worktrees (2 tests)
  - Updated test helpers to work with worktree panel UI
  - Fixed addFeature helper to detect current branch before selecting "Other"

  **Technical Changes:**
  - Added getCurrentBranch to WorktreeAPI interface
  - Added data-testid to branch-switch-dropdown for testing
  - Clean up existing features for branch before regenerating spec
  - Remove features from local state before deletion to prevent updates
@coderabbitai
Copy link

coderabbitai bot commented Jan 9, 2026

📝 Walkthrough

Walkthrough

Adds optional targetBranch/worktree support end-to-end: UI can request spec generation on a specified branch; server validates/creates git worktrees, propagates targetBranch into feature creation, and events include worktreePath/targetBranch. New worktree route and branch-name validation utilities added; tests updated/added.

Changes

Cohort / File(s) Summary of changes / attention points
Server: spec generation & feature creation
apps/server/src/routes/app-spec/generate-spec.ts, apps/server/src/routes/app-spec/generate-features-from-spec.ts, apps/server/src/routes/app-spec/parse-and-create-features.ts
Added optional targetBranch parameter across flows. generate-spec validates branch names and may create/configure a git worktree for targetBranch. Feature creation pre-cleans features for that branch, tags new features with branchName, and emits worktreePath/targetBranch. Review git worktree creation, error-handling (non-fatal warnings), and branch validation usage.
Server: request handlers
apps/server/src/routes/app-spec/routes/create.ts, apps/server/src/routes/app-spec/routes/generate.ts
Request bodies accept targetBranch?: string; handlers log and forward it to generateSpec. Check API contract and client compatibility.
Server: worktree route
apps/server/src/routes/worktree/index.ts, apps/server/src/routes/worktree/routes/get-current-branch.ts
New POST /api/worktree/current-branch route and createGetCurrentBranchHandler() that returns the repository's current branch with fallbacks and proper error responses. Validate path input handling and logging.
Libs: git-utils
libs/git-utils/src/branch-validation.ts, libs/git-utils/src/index.ts
New branch-safety utilities isSafeBranchName and validateBranchName exported. Review validation rules and edge cases for allowed/disallowed characters and patterns.
UI: API surface & types
apps/ui/src/lib/electron.ts, apps/ui/src/lib/http-api-client.ts, apps/ui/src/types/electron.d.ts
Spec regeneration API/events extended to accept/carry targetBranch and worktreePath. Added worktree.getCurrentBranch() API + mock. Update clients to pass targetBranch where applicable and verify typing consistency.
UI: spec view, dialogs & hooks
apps/ui/src/components/views/spec-view.tsx, apps/ui/src/components/views/spec-view/constants.ts, apps/ui/src/components/views/spec-view/dialogs/create-spec-dialog.tsx, apps/ui/src/components/views/spec-view/dialogs/regenerate-spec-dialog.tsx, apps/ui/src/components/views/spec-view/hooks/use-spec-generation.ts, apps/ui/src/components/views/spec-view/types.ts
New state/props to enable "Use worktree branch" on create/regenerate flows and support custom feature counts. Hooks call worktree.getCurrentBranch when not explicitly set and pass targetBranch to API. Many prop/signature changes—verify consumers and validation/disable logic.
UI: board & worktree deletion UI
apps/ui/src/components/views/board-view.tsx, apps/ui/src/components/views/board-view/dialogs/delete-worktree-dialog.tsx, apps/ui/src/components/views/board-view/hooks/use-board-features.ts, apps/ui/src/components/views/board-view/worktree-panel/components/branch-switch-dropdown.tsx
Delete-worktree dialog gains "Delete backlog" checkbox; onDeleted callback adds deleteFeatures param. BoardView will either delete feature files or reassign them to the main branch when a worktree is removed. Also includes cancellation-aware error handling and a data-testid on branch button. Review filesystem side effects and UI state resets.
Tests: E2E suites & helpers
apps/ui/tests/git/delete-worktree-with-features.spec.ts, apps/ui/tests/projects/regenerate-spec-with-worktree.spec.ts, apps/ui/tests/utils/views/board.ts, apps/ui/tests/utils/views/spec-editor.ts
New/updated Playwright tests for worktree-spec regen and deleting worktrees with feature backlogs. Helpers adjusted for branch selection behavior and editor key sequences. Tests interact with git and filesystem; expect longer runtime and environment sensitivity.
Misc / small
apps/ui/src/lib/electron.ts (mock), apps/ui/src/components/views/spec-view/constants.ts
Minor type/mocking updates (FeatureCount widened, mock getCurrentBranch implemented, FEATURE_COUNT_OPTIONS extended).

Sequence Diagram(s)

sequenceDiagram
  participant UI as Client UI
  participant API as HttpApiClient
  participant Server as SpecRegeneration Endpoint
  participant Git as Git (worktree/branch)
  participant FS as File system (features)

  UI->>API: request generate/create spec (project, options, targetBranch?)
  API->>Server: forward request (includes targetBranch)
  Server->>Git: validateBranchName(targetBranch) (if provided)
  alt targetBranch provided and not main/master
    Server->>Git: ensure .worktrees exists, add worktree (existing or new from HEAD)
  end
  Server->>Server: generate spec -> responseText
  Server->>Server: parseAndCreateFeatures(responseText, targetBranch)
  Server->>FS: remove existing features with branchName == targetBranch (pre-clean)
  Server->>FS: write new feature files with branchName = targetBranch
  Server->>API: emit spec_regeneration_complete (includes targetBranch, worktreePath)
  API->>UI: deliver event
  UI->>Git: optionally switch UI to generated worktree using worktreePath
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested labels

Enhancement

Suggested reviewers

  • Shironex

"I hopped through branches, soft and spry,
I validated names and planted a try.
Worktrees grew where features play,
I nibbled bugs and hopped away.
Hooray — the rabbit cheered the day! 🐇"

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ 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 'feat: improve worktree and spec generation workflows' directly corresponds to the main objective and scope of the PR, which focuses on improving worktree and spec generation workflows with multiple enhancements.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 @yumesha, 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 refines the user experience and underlying logic for managing Git worktrees and generating application specifications. It introduces more intuitive controls for creating and deleting worktrees, ensuring features are correctly handled during worktree operations, and provides greater flexibility in spec generation. The changes are backed by new API endpoints and comprehensive end-to-end tests to ensure stability and correctness.

Highlights

  • Enhanced Spec Generation Workflow: The spec generation process now offers improved user experience by renaming 'Target Branch' to 'Worktree Branch', making it an optional checkbox, and automatically detecting the current Git branch when a worktree branch isn't specified. A new API endpoint (/api/worktree/current-branch) has been added to facilitate this branch detection.
  • Improved Worktree Deletion: Deleting worktrees is more robust with a new option to 'Delete backlog of this worktree (X features)'. Bugs related to feature reassignment to the main branch have been fixed, and race conditions are reduced by removing features from the application state before deletion. A cancellation error toast bug during feature implementation has also been resolved.
  • Comprehensive Test Coverage: Extensive E2E tests have been added for both the delete worktree functionality (5 tests covering feature movement/deletion and branch handling) and spec regeneration with worktrees (2 tests). Test helpers were also updated to support the new worktree panel UI and current branch detection.
  • Dynamic Feature Count Input: The UI for specifying the number of features to generate now includes a 'Custom' option, allowing users to input an arbitrary number of features instead of being limited to predefined choices.

🧠 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 significantly enhances worktree management and spec generation workflows, introducing a better user experience with more granular options and comprehensive test coverage. The changes include renaming UI elements for clarity, adding optional worktree branch creation, auto-detecting the current git branch, and improving the worktree deletion process. While the feature enhancements and added E2E tests are excellent, I've identified a critical command injection vulnerability in the new server-side worktree creation logic. The targetBranch parameter is used directly in shell commands without sanitization. I have provided a detailed comment and a code suggestion to address this security risk. Other than this critical issue, the pull request is well-structured and the new features are thoughtfully implemented.


// Create worktree for target branch if specified and doesn't exist
if (targetBranch && targetBranch !== 'main' && targetBranch !== 'master') {
try {
Copy link
Contributor

Choose a reason for hiding this comment

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

security-critical critical

The targetBranch variable, which comes from the request body, is used directly in shell commands via execAsync. This creates a critical command injection vulnerability. A malicious user could provide a crafted branch name like my-branch; rm -rf / to execute arbitrary commands on the server.

To fix this, you must validate the targetBranch name to ensure it only contains characters safe for a branch name before using it in any shell commands. I've suggested adding a validation function that checks for invalid characters and path traversal attempts.

    const isValidGitBranchName = (branchName: string): boolean => {
      // Prevents command injection and path traversal.
      if (branchName.includes('..')) {
        return false;
      }
      // A simplified regex for valid git branch names. It disallows characters
      // often used in command injection attacks (;, &, |, $, `, etc.).
      return /^[a-zA-Z0-9_./-]+$/.test(branchName);
    };

    if (!isValidGitBranchName(targetBranch)) {
      const errorMessage = `Invalid target branch name provided: ${targetBranch}`;
      logger.error(errorMessage);
      // Throwing an error is important to stop execution and alert the caller.
      throw new Error(errorMessage);
    }

    try {

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: 9

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/components/views/board-view/dialogs/delete-worktree-dialog.tsx (1)

48-85: Stale deleteFeatures / deleteBranch state across dialog opens can cause unintended data loss.
If a user checks “Delete backlog…”, cancels, then opens the dialog for another worktree, the previous selection persists. Reset state when the dialog opens/closes and disable the checkboxes while isLoading.

Proposed fix
@@
-import { useState } from 'react';
+import { useEffect, useState } from 'react';
@@
   const [deleteBranch, setDeleteBranch] = useState(false);
   const [deleteFeatures, setDeleteFeatures] = useState(false);
   const [isLoading, setIsLoading] = useState(false);
+
+  useEffect(() => {
+    if (!open) {
+      setDeleteBranch(false);
+      setDeleteFeatures(false);
+    }
+  }, [open]);
@@
             <Checkbox
               id="delete-branch"
               checked={deleteBranch}
               onCheckedChange={(checked) => setDeleteBranch(checked === true)}
+              disabled={isLoading}
             />
@@
               <Checkbox
                 id="delete-features"
                 checked={deleteFeatures}
                 onCheckedChange={(checked) => setDeleteFeatures(checked === true)}
+                disabled={isLoading}
               />

Also applies to: 127-152

🤖 Fix all issues with AI agents
In @apps/server/src/routes/app-spec/generate-spec.ts:
- Around line 40-42: The code is injecting unvalidated targetBranch into shell
commands and filesystem paths (see parameter targetBranch, execAsync and
path.join(projectPath, '.worktrees', targetBranch)), enabling command injection
and path traversal; validate and sanitize targetBranch (allow only safe git-ref
characters, e.g. /^[A-Za-z0-9._\/-]+$/ but explicitly forbid sequences like
'..', null bytes, leading '/', and shell metacharacters) and reject invalid
values, then stop using interpolated shell strings—use
child_process.execFile/spawn with args or the existing git/worktree utility
functions instead of execAsync with template literals; when creating filesystem
paths use safe operations (e.g. path.basename or a whitelist mapping for
worktree names and path.resolve to ensure the result remains under projectPath)
so inputs cannot traverse out of the project, and apply the same validation/fix
for all usages of targetBranch in the file (including the block around lines
365–428).
- Around line 365-367: Replace the hardcoded check (targetBranch !== 'main' &&
targetBranch !== 'master') with a reusable primary-branch check using a
configurable list: create a helper (e.g., isPrimaryBranch(branch: string)) that
reads PRIMARY_BRANCHES from config/ENV (falling back to ['main','master']) and
returns PRIMARY_BRANCHES.includes(branch); then use if (targetBranch &&
!isPrimaryBranch(targetBranch)) when creating worktrees in generate-spec.ts (the
targetBranch conditional), parse-and-create-features.ts, and delete.ts so
additional primary branches like 'develop' are respected and the list can be
changed without code edits.

In @apps/server/src/routes/app-spec/parse-and-create-features.ts:
- Around line 68-69: TypeScript can't narrow secureFs.readFile's return from
string | Buffer to string, so explicitly assert the result as string: when
reading featureJsonPath use an assertion like (await
secureFs.readFile(featureJsonPath, 'utf-8')) as string and then pass that
asserted featureContent into JSON.parse to produce featureData; update the
reference to featureContent and ensure the assertion is applied to the
secureFs.readFile call (not to JSON.parse).

In @apps/ui/src/components/views/board-view.tsx:
- Around line 86-87: The onDeleted callback is causing race conditions and
double-deletions: compute mainBranch once (retrieve via
getCurrentWorktree/mainBranch) before processing, replace the forEach with
Promise.all over an array produced by map, and for each item await
handleDeleteFeature(...) (do not call removeFeature() separately since
handleDeleteFeature already removes it); likewise await
persistFeatureUpdate(...) in the reassign branch. After awaiting Promise.all for
all deletions/updates, then call setWorktreeRefreshKey to trigger refresh.

In @apps/ui/src/components/views/spec-view/dialogs/create-spec-dialog.tsx:
- Around line 199-217: The onChange for the custom feature Input validates only
>0; update it to enforce the 1–200 range: parse the value in the handler used by
setCustomFeatureCount and only call onFeatureCountChange(num) when num is a
valid integer between 1 and 200 (inclusive). Keep updating the input state
(setCustomFeatureCount) for intermediate/empty values but avoid invoking
onFeatureCountChange with out-of-range numbers; reference the existing
setCustomFeatureCount, onFeatureCountChange, customFeatureCount and the Input
onChange handler to implement this check.

In @apps/ui/src/components/views/spec-view/dialogs/regenerate-spec-dialog.tsx:
- Around line 38-43: The component can leave featureCount unchanged when the
user selects "Custom" with an empty input; to fix, initialize customFeatureCount
from the current featureCount when toggling setIsCustom(true) (use
setCustomFeatureCount(String(featureCount)) and fall back to min/max bounds from
FEATURE_COUNT_OPTIONS if featureCount is undefined), ensure any parseInt of
customFeatureCount is clamped to the allowed min/max before calling the
emitter/updater, and when saving/closing use the clamped numeric value to set
featureCount; apply the same initialization/clamping logic wherever custom
feature input is handled (including the other block referenced around lines
164-217).

In @apps/ui/tests/git/delete-worktree-with-features.spec.ts:
- Around line 29-82: The keyboard shortcut used in createWorktree is
platform-unsafe: replace the hardcoded 'Control+a' in the createWorktree
function with the cross-platform 'ControlOrMeta+a' (so the select-all works on
macOS runners); ensure any other uses of 'Control+a' in this file are similarly
updated to 'ControlOrMeta+a' to avoid flakes.

In @apps/ui/tests/projects/regenerate-spec-with-worktree.spec.ts:
- Around line 180-203: The test currently skips verification if the features
directory doesn't exist; change it to assert its existence and that at least one
feature was found before inspecting feature.json files. Replace the conditional
checks around featuresDir and featureDirs with hard assertions (use
expect(fs.existsSync(featuresDir)).toBeTruthy() and
expect(featureDirs.length).toBeGreaterThan(0)) and then iterate featureDirs to
read each feature.json and assert featureJson has branchName and (if
worktreePath exists) that featureJson.branchName === targetBranch; keep the same
symbols: featuresDir, featureDirs, featureJsonPath, featureJson, worktreePath,
and targetBranch to locate the code to modify.
🧹 Nitpick comments (13)
apps/server/src/routes/worktree/routes/get-current-branch.ts (2)

1-3: Comment states GET but endpoint is POST.

The comment on line 2 says "GET /current-branch endpoint" but based on the route registration in index.ts, this is a POST endpoint.

📝 Suggested fix
 /**
- * GET /current-branch endpoint - Get the current branch of a project
+ * POST /current-branch endpoint - Get the current branch of a project
  */

28-57: Consider adding a timeout to prevent indefinite hangs.

The execAsync calls have no timeout configured. If the git command hangs (e.g., due to a corrupted repo or network issues with remote operations), this could block indefinitely.

Additionally, the inner catch block (lines 53-57) silently returns 'main' as a fallback, which could mask legitimate git errors and return incorrect data to the caller.

⏱️ Suggested improvement
       try {
-        const { stdout } = await execAsync('git branch --show-current', { cwd: projectPath });
+        const { stdout } = await execAsync('git branch --show-current', { 
+          cwd: projectPath,
+          timeout: 10000 // 10 second timeout
+        });
         const branch = stdout.trim();

         if (!branch) {
           // Might be in detached HEAD state, try to get branch from symbolic-ref
           try {
             const { stdout: refStdout } = await execAsync('git symbolic-ref --short HEAD', {
               cwd: projectPath,
+              timeout: 10000
             });
apps/ui/tests/utils/views/board.ts (2)

134-161: Replace waitForTimeout with explicit wait conditions to reduce test flakiness.

Using fixed waitForTimeout delays (lines 137, 146, 153, 160) is discouraged in Playwright as it can cause flaky tests if the UI is slower or faster than expected. Consider using waitFor conditions or waiting for specific elements/states instead.

⏱️ Example improvement for one occurrence
     if (useCurrentBranch) {
       // Select "Use current selected branch" radio option
       await currentBranchRadio.click();
-      await page.waitForTimeout(200);
+      // Wait for radio to be checked
+      await expect(currentBranchRadio).toBeChecked();
     } else {

126-132: Partial branch name matching could cause false positives.

Using labelText?.includes(options.branch) on line 129 could match unintended branches. For example, if options.branch is "main", it would match labels containing "main-feature" or "maintain".

Consider using a more precise match:

🔍 Suggested fix
     if (currentBranchExists) {
       const labelText = await currentBranchLabel.textContent();
       // Check if the label contains our desired branch name
-      if (labelText?.includes(options.branch)) {
+      // Use word boundary or exact match to avoid partial matches
+      const branchPattern = new RegExp(`\\b${options.branch}\\b`);
+      if (labelText && branchPattern.test(labelText)) {
         useCurrentBranch = true;
       }
     }
apps/ui/tests/projects/regenerate-spec-with-worktree.spec.ts (1)

206-216: Silent error handling may mask test failures.

Catching git command errors and only logging (lines 213-216) means the test will pass even if git operations fail. Consider failing the test or at minimum making the skip conditional on a specific known limitation.

💡 Suggested approach
     // Verify git branch was created
     try {
       const { stdout: branchesOutput } = await execAsync('git branch', { cwd: projectPath });
       const branches = branchesOutput.split('\n').map((b) => b.trim().replace('* ', ''));

       if (fs.existsSync(worktreePath)) {
         expect(branches).toContain(targetBranch);
       }
     } catch (error) {
-      // Git commands might fail if project isn't properly initialized
-      console.log('Git branch verification skipped:', error);
+      // If worktree exists but git fails, that's unexpected - fail the test
+      if (fs.existsSync(worktreePath)) {
+        throw new Error(`Git verification failed but worktree exists: ${error}`);
+      }
+      // Otherwise, project may not be initialized - acceptable to skip
+      console.log('Git branch verification skipped (project may not be initialized):', error);
     }
apps/ui/src/components/views/spec-view/hooks/use-spec-generation.ts (1)

432-443: Consider adding debug logging for branch detection failures.

The empty catch block at line 440 silently swallows errors. While falling back to 'main' is reasonable, logging the error would aid debugging if branch detection unexpectedly fails.

📝 Suggested improvement
       if (!useWorktreeBranchOnCreate) {
         // Detect current branch
         try {
           const branchResult = await api.worktree?.getCurrentBranch?.(currentProject.path);
           effectiveBranch = branchResult?.success ? branchResult.branch : 'main';
-        } catch {
+        } catch (err) {
+          logger.debug('Failed to detect current branch, defaulting to main:', err);
           effectiveBranch = 'main';
         }
       }
apps/ui/src/components/views/board-view/hooks/use-board-features.ts (1)

225-236: Good cancellation detection pattern! Consider case-insensitive matching.

The cancellation detection logic correctly avoids showing error toasts for user-initiated operations. However, the string matching is case-sensitive. Depending on error sources, you may encounter variations like "Cancelled" or "ABORTED".

🔧 Suggested improvement
         // Check if this is a user cancellation (not a real error)
+        const errorLower = event.error?.toLowerCase() || '';
         const isCancellation =
-          event.error &&
-          (event.error.includes('cancelled') ||
-            event.error.includes('canceled') ||
-            event.error.includes('aborted'));
+          errorLower.includes('cancelled') ||
+          errorLower.includes('canceled') ||
+          errorLower.includes('aborted');
apps/server/src/routes/app-spec/routes/create.ts (1)

72-82: Refactor generateSpec function signature to use an options object pattern.

The generateSpec call passes undefined as a positional argument for settingsService (line 80) only to provide targetBranch. This indicates the function has too many optional parameters. Refactor the function signature to accept an options object instead, making the API clearer and avoiding undefined placeholders.

Current call (lines 72-82)
      generateSpec(
        projectPath,
        projectOverview,
        events,
        abortController,
        generateFeatures,
        analyzeProject,
        maxFeatures,
        undefined,
        targetBranch
      )
apps/server/src/routes/app-spec/parse-and-create-features.ts (1)

58-88: Consider extracting the deletion logic into a helper function.

The feature deletion logic is correct but adds significant complexity (31 lines) to this function. Consider extracting it into a separate helper function like deleteExistingFeaturesForBranch(featuresDir, targetBranch, logger) to improve readability and testability.

♻️ Proposed refactor

Extract to a new helper function:

+async function deleteExistingFeaturesForBranch(
+  featuresDir: string,
+  targetBranch: string,
+  logger: ReturnType<typeof createLogger>
+): Promise<number> {
+  logger.info(`Deleting existing features for branch '${targetBranch}'...`);
+  try {
+    const existingFeatures = await secureFs.readdir(featuresDir);
+    let deletedCount = 0;
+
+    for (const featureId of existingFeatures) {
+      const featureJsonPath = path.join(featuresDir, featureId, 'feature.json');
+      try {
+        const featureContent = await secureFs.readFile(featureJsonPath, 'utf-8') as string;
+        const featureData = JSON.parse(featureContent);
+
+        if (featureData.branchName === targetBranch) {
+          logger.info(`Deleting feature ${featureId} from branch ${targetBranch}`);
+          await secureFs.rm(path.join(featuresDir, featureId), { recursive: true });
+          deletedCount++;
+        }
+      } catch (err) {
+        logger.debug(`Skipping ${featureId}: ${err}`);
+      }
+    }
+
+    logger.info(`✓ Deleted ${deletedCount} existing features for branch '${targetBranch}'`);
+    return deletedCount;
+  } catch (err) {
+    logger.warn('Failed to delete existing features:', err);
+    return 0;
+  }
+}

Then simplify the main function:

-    // Delete existing features for this branch before creating new ones
     if (targetBranch) {
-      logger.info(`Deleting existing features for branch '${targetBranch}'...`);
-      try {
-        const existingFeatures = await secureFs.readdir(featuresDir);
-        let deletedCount = 0;
-
-        for (const featureId of existingFeatures) {
-          const featureJsonPath = path.join(featuresDir, featureId, 'feature.json');
-          try {
-            const featureContent = await secureFs.readFile(featureJsonPath, 'utf-8');
-            const featureData = JSON.parse(featureContent);
-
-            // Delete if it matches the target branch
-            if (featureData.branchName === targetBranch) {
-              logger.info(`Deleting feature ${featureId} from branch ${targetBranch}`);
-              await secureFs.rm(path.join(featuresDir, featureId), { recursive: true });
-              deletedCount++;
-            }
-          } catch (err) {
-            // Skip if feature.json doesn't exist or can't be read
-            logger.debug(`Skipping ${featureId}: ${err}`);
-          }
-        }
-
-        logger.info(`✓ Deleted ${deletedCount} existing features for branch '${targetBranch}'`);
-      } catch (err) {
-        logger.warn('Failed to delete existing features:', err);
-        // Continue anyway - not critical
-      }
+      await deleteExistingFeaturesForBranch(featuresDir, targetBranch, logger);
     }
apps/ui/src/components/views/spec-view/dialogs/regenerate-spec-dialog.tsx (1)

81-114: Add minimal client-side validation for worktreeBranch (and avoid accidental whitespace-only values).
You already gate the action button on .trim(), which is good. I’d also validate disallowed inputs early (e.g., .., leading -) and normalize to trim() before calling onWorktreeBranchChange.

Also applies to: 235-239

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

1311-1314: Type the new getCurrentBranch response to prevent accidental contract drift.
Right now it’s inferred from WorktreeAPI; being explicit here helps catch server/client mismatches earlier.

Proposed fix
@@
   worktree: WorktreeAPI = {
     getCurrentBranch: (projectPath: string) =>
-      this.post('/api/worktree/current-branch', { projectPath }),
+      this.post<{ success: boolean; branch?: string; error?: string }>(
+        '/api/worktree/current-branch',
+        { projectPath }
+      ),
apps/ui/src/lib/electron.ts (1)

411-418: Type updates look consistent; consider updating mock specRegeneration methods to accept/log targetBranch.
Not required for runtime, but it helps keep dev/test behavior aligned with production flows.

Also applies to: 420-436

apps/ui/tests/git/delete-worktree-with-features.spec.ts (1)

62-83: Many waitForTimeout(...) calls—prefer deterministic waits to reduce CI flakes.
E.g., after create/delete, wait for the branch tab to appear/disappear, or for the toast to appear, instead of fixed sleeps.

Also applies to: 133-220, 222-323

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 271749a and 7cbc45c.

📒 Files selected for processing (23)
  • 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/parse-and-create-features.ts
  • apps/server/src/routes/app-spec/routes/create.ts
  • apps/server/src/routes/app-spec/routes/generate.ts
  • apps/server/src/routes/worktree/index.ts
  • apps/server/src/routes/worktree/routes/get-current-branch.ts
  • apps/ui/src/components/views/board-view.tsx
  • apps/ui/src/components/views/board-view/dialogs/delete-worktree-dialog.tsx
  • apps/ui/src/components/views/board-view/hooks/use-board-features.ts
  • apps/ui/src/components/views/board-view/worktree-panel/components/branch-switch-dropdown.tsx
  • apps/ui/src/components/views/spec-view.tsx
  • apps/ui/src/components/views/spec-view/constants.ts
  • apps/ui/src/components/views/spec-view/dialogs/create-spec-dialog.tsx
  • apps/ui/src/components/views/spec-view/dialogs/regenerate-spec-dialog.tsx
  • apps/ui/src/components/views/spec-view/hooks/use-spec-generation.ts
  • apps/ui/src/components/views/spec-view/types.ts
  • apps/ui/src/lib/electron.ts
  • apps/ui/src/lib/http-api-client.ts
  • apps/ui/src/types/electron.d.ts
  • apps/ui/tests/git/delete-worktree-with-features.spec.ts
  • apps/ui/tests/projects/regenerate-spec-with-worktree.spec.ts
  • apps/ui/tests/utils/views/board.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/routes/worktree/routes/get-current-branch.ts
  • apps/server/src/routes/app-spec/generate-features-from-spec.ts
  • apps/server/src/routes/worktree/index.ts
  • apps/ui/tests/projects/regenerate-spec-with-worktree.spec.ts
  • apps/ui/src/components/views/board-view/dialogs/delete-worktree-dialog.tsx
  • apps/ui/src/components/views/board-view/hooks/use-board-features.ts
  • apps/ui/src/components/views/board-view/worktree-panel/components/branch-switch-dropdown.tsx
  • apps/ui/src/components/views/spec-view/dialogs/regenerate-spec-dialog.tsx
  • apps/ui/src/types/electron.d.ts
  • apps/ui/src/lib/http-api-client.ts
  • apps/ui/src/components/views/spec-view/dialogs/create-spec-dialog.tsx
  • apps/ui/src/components/views/spec-view/constants.ts
  • apps/ui/src/components/views/spec-view/hooks/use-spec-generation.ts
  • apps/server/src/routes/app-spec/parse-and-create-features.ts
  • apps/server/src/routes/app-spec/routes/generate.ts
  • apps/ui/src/components/views/spec-view/types.ts
  • apps/server/src/routes/app-spec/routes/create.ts
  • apps/ui/tests/utils/views/board.ts
  • apps/server/src/routes/app-spec/generate-spec.ts
  • apps/ui/tests/git/delete-worktree-with-features.spec.ts
  • apps/ui/src/components/views/board-view.tsx
  • apps/ui/src/components/views/spec-view.tsx
  • apps/ui/src/lib/electron.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/worktree/routes/get-current-branch.ts
  • apps/server/src/routes/app-spec/generate-features-from-spec.ts
  • apps/server/src/routes/worktree/index.ts
  • apps/server/src/routes/app-spec/parse-and-create-features.ts
  • apps/server/src/routes/app-spec/routes/generate.ts
  • apps/server/src/routes/app-spec/routes/create.ts
  • apps/server/src/routes/app-spec/generate-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/routes/worktree/routes/get-current-branch.ts
  • apps/server/src/routes/app-spec/generate-features-from-spec.ts
  • apps/server/src/routes/worktree/index.ts
  • apps/ui/tests/projects/regenerate-spec-with-worktree.spec.ts
  • apps/ui/src/components/views/board-view/dialogs/delete-worktree-dialog.tsx
  • apps/ui/src/components/views/board-view/hooks/use-board-features.ts
  • apps/ui/src/components/views/board-view/worktree-panel/components/branch-switch-dropdown.tsx
  • apps/ui/src/components/views/spec-view/dialogs/regenerate-spec-dialog.tsx
  • apps/ui/src/types/electron.d.ts
  • apps/ui/src/lib/http-api-client.ts
  • apps/ui/src/components/views/spec-view/dialogs/create-spec-dialog.tsx
  • apps/ui/src/components/views/spec-view/constants.ts
  • apps/ui/src/components/views/spec-view/hooks/use-spec-generation.ts
  • apps/server/src/routes/app-spec/parse-and-create-features.ts
  • apps/server/src/routes/app-spec/routes/generate.ts
  • apps/ui/src/components/views/spec-view/types.ts
  • apps/server/src/routes/app-spec/routes/create.ts
  • apps/ui/tests/utils/views/board.ts
  • apps/server/src/routes/app-spec/generate-spec.ts
  • apps/ui/tests/git/delete-worktree-with-features.spec.ts
  • apps/ui/src/components/views/board-view.tsx
  • apps/ui/src/components/views/spec-view.tsx
  • apps/ui/src/lib/electron.ts
🧠 Learnings (3)
📓 Common learnings
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
📚 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/routes/app-spec/generate-features-from-spec.ts
  • apps/ui/tests/projects/regenerate-spec-with-worktree.spec.ts
  • apps/ui/src/components/views/spec-view/hooks/use-spec-generation.ts
  • apps/server/src/routes/app-spec/parse-and-create-features.ts
  • apps/server/src/routes/app-spec/generate-spec.ts
  • apps/ui/tests/git/delete-worktree-with-features.spec.ts
  • apps/ui/src/components/views/board-view.tsx
📚 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/delete-worktree-dialog.tsx
  • apps/ui/src/components/views/board-view/worktree-panel/components/branch-switch-dropdown.tsx
  • apps/ui/src/components/views/spec-view/dialogs/regenerate-spec-dialog.tsx
  • apps/ui/src/components/views/spec-view/dialogs/create-spec-dialog.tsx
  • apps/ui/src/components/views/board-view.tsx
  • apps/ui/src/components/views/spec-view.tsx
🧬 Code graph analysis (9)
apps/server/src/routes/app-spec/generate-features-from-spec.ts (1)
apps/server/src/routes/app-spec/parse-and-create-features.ts (1)
  • parseAndCreateFeatures (14-145)
apps/ui/tests/projects/regenerate-spec-with-worktree.spec.ts (3)
apps/ui/tests/utils/git/worktree.ts (2)
  • createTempDirPath (52-55)
  • cleanupTempDir (157-161)
apps/ui/tests/utils/project/setup.ts (1)
  • setupWelcomeView (39-86)
apps/ui/tests/utils/api/client.ts (1)
  • authenticateForTests (357-361)
apps/ui/src/components/views/board-view/dialogs/delete-worktree-dialog.tsx (2)
apps/ui/src/lib/electron.ts (1)
  • WorktreeInfo (191-191)
apps/ui/src/types/electron.d.ts (1)
  • WorktreeInfo (601-606)
apps/ui/src/components/views/board-view/hooks/use-board-features.ts (1)
apps/server/src/routes/pipeline/common.ts (1)
  • logger (11-11)
apps/ui/src/components/views/spec-view/dialogs/regenerate-spec-dialog.tsx (3)
apps/ui/src/components/views/spec-view/types.ts (1)
  • RegenerateSpecDialogProps (39-58)
apps/ui/src/components/views/spec-view/constants.ts (1)
  • FEATURE_COUNT_OPTIONS (10-19)
apps/ui/src/lib/utils.ts (1)
  • cn (5-7)
apps/ui/src/components/views/spec-view/dialogs/create-spec-dialog.tsx (3)
apps/ui/src/components/views/spec-view/types.ts (1)
  • CreateSpecDialogProps (15-36)
apps/ui/src/components/views/spec-view/constants.ts (1)
  • FEATURE_COUNT_OPTIONS (10-19)
apps/ui/src/lib/utils.ts (1)
  • cn (5-7)
apps/server/src/routes/app-spec/parse-and-create-features.ts (2)
apps/server/src/lib/events.ts (1)
  • EventEmitter (13-16)
apps/server/src/lib/secure-fs.ts (1)
  • secureFs (8-39)
apps/server/src/routes/app-spec/generate-spec.ts (2)
apps/server/src/routes/worktree/common.ts (1)
  • execAsync (13-13)
apps/server/tests/integration/helpers/git-test-repo.ts (2)
  • worktreeExists (140-143)
  • branchExists (132-135)
apps/ui/tests/git/delete-worktree-with-features.spec.ts (5)
apps/ui/tests/utils/git/worktree.ts (4)
  • createTempDirPath (52-55)
  • waitForBoardView (494-512)
  • cleanupTempDir (157-161)
  • setupProjectWithPath (333-377)
apps/ui/tests/utils/api/client.ts (1)
  • authenticateForTests (357-361)
apps/ui/tests/utils/core/interactions.ts (1)
  • handleLoginScreenIfPresent (65-115)
apps/ui/tests/utils/core/waiting.ts (1)
  • waitForNetworkIdle (9-11)
apps/ui/tests/utils/views/board.ts (2)
  • addFeature (190-198)
  • getKanbanColumn (13-15)
🪛 GitHub Actions: E2E Tests
apps/server/src/routes/app-spec/parse-and-create-features.ts

[error] 69-69: TS2345: Argument of type 'string | Buffer' is not assignable to parameter of type 'string'.

🪛 GitHub Actions: PR Build Check
apps/server/src/routes/app-spec/parse-and-create-features.ts

[error] 69-69: TypeScript error TS2345: Argument of type 'string | Buffer' is not assignable to parameter of type 'string'.

🔇 Additional comments (21)
apps/ui/src/components/views/board-view/worktree-panel/components/branch-switch-dropdown.tsx (1)

53-53: LGTM! Test automation support added correctly.

The data-testid attribute enables E2E tests to reliably target specific worktree dropdown triggers, supporting the new test coverage mentioned in the PR objectives.

apps/ui/src/components/views/spec-view/hooks/use-spec-generation.ts (1)

354-364: LGTM! Worktree switching on completion is well-implemented.

The pattern of using useAppStore.getState() inside the event handler to get fresh state is correct for async callbacks. The conditional switching based on event.targetBranch and event.worktreePath properly integrates with the worktree workflow.

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

33-45: LGTM! Route registration follows established patterns.

The new /current-branch route is properly registered with validatePathParams('projectPath') middleware, consistent with similar routes like /info and /status. The import follows the ESM convention with .js extension.

apps/ui/src/components/views/spec-view/constants.ts (1)

10-19: LGTM! Custom feature count option added correctly.

The type widening from FeatureCount to number | 'custom' and the new 'Custom' option align well with the PR objectives to support custom feature counts in spec generation workflows.

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

26-48: LGTM! Correct parameter extraction and logging.

The targetBranch parameter is properly extracted from the request body and logged for debugging. The optional typing matches the API contract defined in the type definitions.


72-82: LGTM! Parameter propagation is correct.

The targetBranch parameter is properly threaded through to the generateSpec function, maintaining consistency with the expanded function signature.

apps/server/src/routes/app-spec/parse-and-create-features.ts (3)

14-22: LGTM! Parameter addition is correct.

The targetBranch parameter is properly added to the function signature and logged for debugging.


127-133: LGTM! Event emission includes new branch context.

The event correctly includes targetBranch and worktreePath to notify the UI about the branch-specific regeneration context. This aligns with the type definitions in electron.d.ts.


121-125: Worktree exclusion for main/master is intentional design.

The exclusion of 'main' and 'master' branches from worktreePath calculation is confirmed to be intentional, validated by the test should not create worktree for main/master branch in apps/ui/tests/projects/regenerate-spec-with-worktree.spec.ts. This pattern is consistently applied across generate-spec.ts and delete.ts.

However, note that the current implementation treats both 'main' and 'master' as primary branches requiring no worktrees, regardless of which is actually configured as the default in a repository. If your project uses a different convention for the primary branch name, this code won't recognize it—features created on the actual default branch will still allocate a worktreePath. Consider making the primary branch name configurable if this is a concern.

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

26-38: LGTM! Parameter threading is correct.

The targetBranch parameter is properly added to the function signature, logged, and will be passed downstream to parseAndCreateFeatures.


238-238: LGTM! Consistent parameter propagation.

The targetBranch is correctly passed to parseAndCreateFeatures, completing the parameter flow from the API endpoint through to feature creation.

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

319-320: LGTM! Event type correctly extended.

The spec_regeneration_complete event now includes optional targetBranch and worktreePath fields, matching the server-side event emission in parse-and-create-features.ts.


328-351: LGTM! API signatures correctly extended.

Both create and generate methods now accept an optional targetBranch parameter, maintaining backward compatibility while enabling the new worktree-based workflow.


639-644: LGTM! New API method for current branch detection.

The getCurrentBranch method provides the functionality mentioned in the PR objectives for auto-detecting the current git branch when "Use worktree branch" is unchecked.

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

1375-1378: Mock getCurrentBranch() defaulting to main is fine—ensure real endpoint returns the same shape.
This expects { success: true, branch: string }.

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

1393-1424: Field naming is correct—no changes needed. Both /api/spec-regeneration/create and /api/spec-regeneration/generate endpoints accept targetBranch as expected. The /api/worktree/current-branch endpoint returns { success: true, branch: string }. The client code aligns correctly with the server implementation.

apps/ui/src/components/views/spec-view/dialogs/create-spec-dialog.tsx (2)

81-114: Worktree branch UI implementation looks good.

The checkbox pattern with conditional input provides clear UX. The explanatory text effectively communicates when the current branch is used versus when a worktree is created. The validation logic correctly requires a branch name when the checkbox is enabled.


164-197: Custom feature count button logic is well-implemented.

The state management cleanly handles the transition between preset and custom values. The button selection state (lines 165-166) correctly reflects both numeric matches and the custom mode, and the click handler appropriately toggles isCustom and applies existing custom values when available.

apps/ui/src/components/views/spec-view/types.ts (2)

2-2: LGTM! Type widening appropriately supports custom feature counts.

Changing FeatureCount from a union of specific values (20 | 50 | 100) to number is the correct approach to accommodate user-defined feature counts while maintaining type safety.


26-29: Worktree branch props follow a consistent pattern.

The four new properties (useWorktreeBranch, onUseWorktreeBranchChange, worktreeBranch, onWorktreeBranchChange) are consistently defined across both CreateSpecDialogProps and RegenerateSpecDialogProps, maintaining symmetry in the dialog interfaces.

Also applies to: 50-53

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

40-43: LGTM! Worktree state wiring is straightforward and consistent.

The destructuring from useSpecGeneration and prop passing to both dialog components follows a clean, symmetrical pattern. The naming convention distinguishes create vs. regenerate flows (OnCreate / OnRegenerate suffixes) while keeping the structure identical.

Also applies to: 55-58, 122-125, 162-165

Comment on lines +365 to +367
// Create worktree for target branch if specified and doesn't exist
if (targetBranch && targetBranch !== 'main' && targetBranch !== 'master') {
try {
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:

# First, let's look at the file in question to understand the context better
cat -n apps/server/src/routes/app-spec/generate-spec.ts | sed -n '355,375p'

Repository: AutoMaker-Org/automaker

Length of output: 999


🏁 Script executed:

# Search for how the codebase determines or references primary/main branches
rg -n "primary.*branch|mainBranch|develop.*branch|master.*branch" apps/server/src --type ts

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Look for branch-related constants or configurations
rg -n "const.*branch|main.*master|develop" apps/server/src --type ts -B 1 -A 1 | head -n 100

Repository: AutoMaker-Org/automaker

Length of output: 7742


🏁 Script executed:

# Look for function documentation and more context around the worktree creation
cat -n apps/server/src/routes/app-spec/generate-spec.ts | sed -n '300,400p'

Repository: AutoMaker-Org/automaker

Length of output: 5011


🏁 Script executed:

# Search for any configuration or documentation about primary branches
rg -n "primary" apps/server/src --type ts -B 2 -A 2

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Check if there's documentation about the function or branch handling strategy
rg -n "develop" apps/server/src/routes/app-spec --type ts -B 1 -A 1

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Look for how baseBranch or similar concepts are handled
rg -n "baseBranch|base.*branch" apps/server/src --type ts -B 1 -A 1 | head -50

Repository: AutoMaker-Org/automaker

Length of output: 2541


🏁 Script executed:

# Get function signature and documentation
cat -n apps/server/src/routes/app-spec/generate-spec.ts | head -100

Repository: AutoMaker-Org/automaker

Length of output: 4632


🏁 Script executed:

# Search for where generateSpec function is called and how targetBranch is passed
rg -n "generateSpec|targetBranch" apps/server/src/routes --type ts -B 2 -A 2 | head -100

Repository: AutoMaker-Org/automaker

Length of output: 8523


🏁 Script executed:

# Look for any configuration or environment variables related to default/primary branches
rg -n "default.*branch|main.*master" apps/server/src --type ts | head -50

Repository: AutoMaker-Org/automaker

Length of output: 486


Add support for additional primary branches beyond just main and master.

The worktree creation logic currently skips only main and master, but doesn't account for develop or other primary branches a project might use. If a project designates develop as its primary branch, creating a worktree for it would be incorrect.

This pattern appears in multiple places (generate-spec.ts:366, parse-and-create-features.ts:123, and delete.ts:60). The worktree logic should detect or accept a configurable list of primary branches rather than hardcoding only two branch names.

🤖 Prompt for AI Agents
In @apps/server/src/routes/app-spec/generate-spec.ts around lines 365 - 367,
Replace the hardcoded check (targetBranch !== 'main' && targetBranch !==
'master') with a reusable primary-branch check using a configurable list: create
a helper (e.g., isPrimaryBranch(branch: string)) that reads PRIMARY_BRANCHES
from config/ENV (falling back to ['main','master']) and returns
PRIMARY_BRANCHES.includes(branch); then use if (targetBranch &&
!isPrimaryBranch(targetBranch)) when creating worktrees in generate-spec.ts (the
targetBranch conditional), parse-and-create-features.ts, and delete.ts so
additional primary branches like 'develop' are respected and the list can be
changed without code edits.

Comment on lines +171 to +178
if (fs.existsSync(worktreePath)) {
// Worktree created successfully
expect(fs.existsSync(worktreePath)).toBe(true);

// Verify it's a valid git worktree
const worktreeGitDir = path.join(worktreePath, '.git');
expect(fs.existsSync(worktreeGitDir)).toBe(true);
}
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

Redundant assertion inside conditional check.

The expect(fs.existsSync(worktreePath)).toBe(true) on line 173 is redundant since it's inside a block that only executes when fs.existsSync(worktreePath) is already true. This assertion will never fail.

Additionally, if the worktree is not created, the test silently passes without verifying the core functionality.

🔧 Suggested fix
     // Verify the worktree was created
     const worktreesDir = path.join(projectPath, '.worktrees');
     const worktreePath = path.join(worktreesDir, targetBranch);

-    if (fs.existsSync(worktreePath)) {
-      // Worktree created successfully
-      expect(fs.existsSync(worktreePath)).toBe(true);
-
-      // Verify it's a valid git worktree
-      const worktreeGitDir = path.join(worktreePath, '.git');
-      expect(fs.existsSync(worktreeGitDir)).toBe(true);
-    }
+    // Verify worktree was created
+    expect(fs.existsSync(worktreePath)).toBe(true);
+
+    // Verify it's a valid git worktree
+    const worktreeGitDir = path.join(worktreePath, '.git');
+    expect(fs.existsSync(worktreeGitDir)).toBe(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
if (fs.existsSync(worktreePath)) {
// Worktree created successfully
expect(fs.existsSync(worktreePath)).toBe(true);
// Verify it's a valid git worktree
const worktreeGitDir = path.join(worktreePath, '.git');
expect(fs.existsSync(worktreeGitDir)).toBe(true);
}
// Verify worktree was created
expect(fs.existsSync(worktreePath)).toBe(true);
// Verify it's a valid git worktree
const worktreeGitDir = path.join(worktreePath, '.git');
expect(fs.existsSync(worktreeGitDir)).toBe(true);

Comment on lines +180 to +203
// Verify features were created with correct branchName
const featuresDir = path.join(projectPath, '.automaker', 'features');
if (fs.existsSync(featuresDir)) {
const featureDirs = fs.readdirSync(featuresDir).filter((name) => {
const featureJsonPath = path.join(featuresDir, name, 'feature.json');
return fs.existsSync(featureJsonPath);
});

if (featureDirs.length > 0) {
// Check that features have correct branchName
for (const featureDir of featureDirs) {
const featureJsonPath = path.join(featuresDir, featureDir, 'feature.json');
const featureJson = JSON.parse(fs.readFileSync(featureJsonPath, 'utf-8'));

// Feature should have branchName field
expect(featureJson).toHaveProperty('branchName');

// If we successfully set the target branch, it should be in the feature
if (fs.existsSync(worktreePath)) {
expect(featureJson.branchName).toBe(targetBranch);
}
}
}
}
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

Feature verification wrapped in conditionals may never execute.

The feature verification block (lines 182-203) only runs if featuresDir exists. If it doesn't exist, the test passes without verifying that features were created with the correct branchName. Consider making this a hard assertion if features are expected.

🤖 Prompt for AI Agents
In @apps/ui/tests/projects/regenerate-spec-with-worktree.spec.ts around lines
180 - 203, The test currently skips verification if the features directory
doesn't exist; change it to assert its existence and that at least one feature
was found before inspecting feature.json files. Replace the conditional checks
around featuresDir and featureDirs with hard assertions (use
expect(fs.existsSync(featuresDir)).toBeTruthy() and
expect(featureDirs.length).toBeGreaterThan(0)) and then iterate featureDirs to
read each feature.json and assert featureJson has branchName and (if
worktreePath exists) that featureJson.branchName === targetBranch; keep the same
symbols: featuresDir, featureDirs, featureJsonPath, featureJson, worktreePath,
and targetBranch to locate the code to modify.

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

Caution

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

⚠️ Outside diff range comments (2)
apps/server/src/routes/app-spec/parse-and-create-features.ts (2)

92-117: Critical: validate/sanitize feature.id before using it in filesystem paths (path traversal).

feature.id originates from parsed agent output and is used to create directories/files (path.join(featuresDir, feature.id)). Without validation, a malicious/buggy spec can escape featuresDir (e.g., ../../...) or create unexpected nested paths. This is a must-fix before relying on branch cleanup/worktree isolation guarantees. (Based on learnings, features run in isolated worktrees, so keeping feature file I/O constrained is especially important.)

Proposed diff (minimal containment + basic id rules)
+    const featuresDirResolved = path.resolve(featuresDir);

     for (const feature of parsed.features) {
       logger.debug('Creating feature:', feature.id);
-      const featureDir = path.join(featuresDir, feature.id);
+      if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,127}$/.test(feature.id)) {
+        throw new Error(`Invalid feature id '${feature.id}' (refusing to write files)`);
+      }
+      const featureDir = path.resolve(featuresDir, feature.id);
+      if (!featureDir.startsWith(featuresDirResolved + path.sep)) {
+        throw new Error(`Invalid feature path for id '${feature.id}'`);
+      }
       await secureFs.mkdir(featureDir, { recursive: true });

106-133: branchName defaults to 'main' when targetBranch is omitted; worktreePath construction needs sanitization to match worktree creation logic.

  • Line 106: branchName: targetBranch || 'main' defaults to 'main' when targetBranch is undefined. The POST /generate-features endpoint never passes targetBranch (line 56 in generate-features.ts), so all features generated via that route are mislabeled as 'main' branch. Only POST /generate-spec passes it (line 442 in generate-spec.ts).
  • Line 124: worktreePath = path.join(projectPath, '.worktrees', targetBranch) constructs paths without sanitizing branch names. However, apps/server/src/routes/worktree/routes/create.ts (line 139) sanitizes using .replace(/[^a-zA-Z0-9_-]/g, '-') before constructing the same path. Branch names with /, :, or other special characters will create path mismatches. A sanitizeBranchName() utility exists in apps/server/src/lib/worktree-metadata.ts but is not exported for reuse.
🤖 Fix all issues with AI agents
In @apps/server/src/routes/app-spec/parse-and-create-features.ts:
- Around line 14-25: The parseAndCreateFeatures function logs the entire content
at info level which can be large or sensitive; change logging so info-level
retains only metadata (keep logger.info('Content length: ...') and
logger.info('targetBranch: ...') using the targetBranch variable) and move the
full payload to logger.debug or log a truncated preview (e.g., first N chars
with an ellipsis) instead of logger.info(content); update references in
parseAndCreateFeatures to use logger.debug for the full content and ensure any
truncated preview is clearly labeled to bound the payload size.
- Around line 58-88: When deleting existing features in the branch cleanup loop,
make the removal call to secureFs.rm(path.join(featuresDir, featureId), {
recursive: true }) more race-tolerant by adding force: true (i.e.
secureFs.rm(..., { recursive: true, force: true })) and tighten the catch
logging by replacing logger.debug(`Skipping ${featureId}: ${err}`) with
something that stringifies errors reliably (e.g. logger.debug(`Skipping
${featureId}: ${err instanceof Error ? err.message : String(err)}`)); keep the
rest of the loop and the outer try/catch behavior unchanged.

In @apps/server/src/routes/worktree/routes/get-current-branch.ts:
- Around line 1-2: The top-of-file doc comment incorrectly says "GET
/current-branch" while the route is registered as a POST; update the comment in
get-current-branch.ts to "POST /current-branch" (or otherwise reflect POST) so
the documentation matches the actual route registration and intent for the
handler exported in this file.
🧹 Nitpick comments (1)
apps/server/src/routes/worktree/routes/get-current-branch.ts (1)

28-57: Add timeout and consider distinguishing fallback responses.

Two concerns:

  1. No timeout: execAsync could hang indefinitely on unresponsive file systems. Consider adding a timeout:

    await execAsync('git branch --show-current', { cwd: projectPath, timeout: 5000 });
  2. Silent fallback masks errors: When git fails (lines 53-57), returning { success: true, branch: 'main' } is indistinguishable from the branch actually being main. If the path isn't a git repository, the caller receives misleading data.

Consider adding a fallback: true field or logging a warning so callers/operators can identify when detection failed.

♻️ Suggested improvement
       } catch (error) {
         logger.error('Failed to get current branch:', error);
-        // Return main as fallback
-        res.json({ success: true, branch: 'main' });
+        // Return main as fallback with indication
+        res.json({ success: true, branch: 'main', fallback: true });
       }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7cbc45c and 03ce309.

📒 Files selected for processing (2)
  • apps/server/src/routes/app-spec/parse-and-create-features.ts
  • apps/server/src/routes/worktree/routes/get-current-branch.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/routes/app-spec/parse-and-create-features.ts
  • apps/server/src/routes/worktree/routes/get-current-branch.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/app-spec/parse-and-create-features.ts
  • apps/server/src/routes/worktree/routes/get-current-branch.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/routes/app-spec/parse-and-create-features.ts
  • apps/server/src/routes/worktree/routes/get-current-branch.ts
🧠 Learnings (2)
📓 Common learnings
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
📚 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/routes/app-spec/parse-and-create-features.ts
🧬 Code graph analysis (1)
apps/server/src/routes/app-spec/parse-and-create-features.ts (2)
apps/server/src/lib/events.ts (1)
  • EventEmitter (13-16)
apps/server/src/lib/secure-fs.ts (1)
  • secureFs (8-39)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: e2e
🔇 Additional comments (2)
apps/server/src/routes/worktree/routes/get-current-branch.ts (2)

13-14: Handler factory pattern looks good.

The createGetCurrentBranchHandler factory pattern is consistent with the codebase conventions, and the use of @automaker/utils for logging follows the coding guidelines.


16-26: Path validation already exists at the middleware level.

The /current-branch route applies the validatePathParams('projectPath') middleware before the handler executes (line 43 of index.ts). This middleware calls validatePath() from @automaker/platform, which enforces the ALLOWED_ROOT_DIRECTORY security boundary. Any attempt to access paths outside the allowed directory (e.g., /etc, /root) will be rejected with a 403 error before the handler is reached. The validation uses path traversal detection that checks for relative paths starting with .., preventing the enumeration scenario described in the original comment.

Likely an incorrect or invalid review comment.

Comment on lines 14 to 25
export async function parseAndCreateFeatures(
projectPath: string,
content: string,
events: EventEmitter
events: EventEmitter,
targetBranch?: string
): Promise<void> {
logger.info('========== parseAndCreateFeatures() started ==========');
logger.info(`Content length: ${content.length} chars`);
logger.info('targetBranch:', targetBranch);
logger.info('========== CONTENT RECEIVED FOR PARSING ==========');
logger.info(content);
logger.info('========== END CONTENT ==========');
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

Avoid logging raw content at info-level; include targetBranch but keep payload logs bounded.

Adding targetBranch logging is helpful, but this function currently logs the full content at info, which can be huge and may include sensitive data. Consider moving the full payload to debug (or truncating) and keeping info to metadata (length, branch, counts).

🤖 Prompt for AI Agents
In @apps/server/src/routes/app-spec/parse-and-create-features.ts around lines 14
- 25, The parseAndCreateFeatures function logs the entire content at info level
which can be large or sensitive; change logging so info-level retains only
metadata (keep logger.info('Content length: ...') and logger.info('targetBranch:
...') using the targetBranch variable) and move the full payload to logger.debug
or log a truncated preview (e.g., first N chars with an ellipsis) instead of
logger.info(content); update references in parseAndCreateFeatures to use
logger.debug for the full content and ensure any truncated preview is clearly
labeled to bound the payload size.

Comment on lines 58 to 88
// Delete existing features for this branch before creating new ones
if (targetBranch) {
logger.info(`Deleting existing features for branch '${targetBranch}'...`);
try {
const existingFeatures = await secureFs.readdir(featuresDir);
let deletedCount = 0;

for (const featureId of existingFeatures) {
const featureJsonPath = path.join(featuresDir, featureId, 'feature.json');
try {
const featureContent = await secureFs.readFile(featureJsonPath, 'utf-8');
const featureData = JSON.parse(featureContent.toString());

// Delete if it matches the target branch
if (featureData.branchName === targetBranch) {
logger.info(`Deleting feature ${featureId} from branch ${targetBranch}`);
await secureFs.rm(path.join(featuresDir, featureId), { recursive: true });
deletedCount++;
}
} catch (err) {
// Skip if feature.json doesn't exist or can't be read
logger.debug(`Skipping ${featureId}: ${err}`);
}
}

logger.info(`✓ Deleted ${deletedCount} existing features for branch '${targetBranch}'`);
} catch (err) {
logger.warn('Failed to delete existing features:', err);
// Continue anyway - not critical
}
}
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

Branch cleanup: make deletion more race-tolerant and improve error logging.

  • secureFs.rm(..., { recursive: true }) can still throw on already-deleted paths; force: true makes this less flaky under concurrent operations.
  • logger.debug(\Skipping ${featureId}: ${err}`)will often log[object Object]; prefer err instanceof Error ? err.message : String(err)`.
Proposed diff
-              await secureFs.rm(path.join(featuresDir, featureId), { recursive: true });
+              await secureFs.rm(path.join(featuresDir, featureId), {
+                recursive: true,
+                force: true,
+              });
               deletedCount++;
             }
           } catch (err) {
             // Skip if feature.json doesn't exist or can't be read
-            logger.debug(`Skipping ${featureId}: ${err}`);
+            logger.debug(
+              `Skipping ${featureId}: ${err instanceof Error ? err.message : String(err)}`
+            );
           }
🤖 Prompt for AI Agents
In @apps/server/src/routes/app-spec/parse-and-create-features.ts around lines 58
- 88, When deleting existing features in the branch cleanup loop, make the
removal call to secureFs.rm(path.join(featuresDir, featureId), { recursive: true
}) more race-tolerant by adding force: true (i.e. secureFs.rm(..., { recursive:
true, force: true })) and tighten the catch logging by replacing
logger.debug(`Skipping ${featureId}: ${err}`) with something that stringifies
errors reliably (e.g. logger.debug(`Skipping ${featureId}: ${err instanceof
Error ? err.message : String(err)}`)); keep the rest of the loop and the outer
try/catch behavior unchanged.

Comment on lines +1 to +2
/**
* GET /current-branch endpoint - Get the current branch of a project
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

Documentation mismatch: comment says GET but route is POST.

According to the route registration, this endpoint is a POST route. Update the comment to match.

📝 Suggested fix
 /**
- * GET /current-branch endpoint - Get the current branch of a project
+ * POST /current-branch endpoint - Get the current branch of a project
  */
📝 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
/**
* GET /current-branch endpoint - Get the current branch of a project
/**
* POST /current-branch endpoint - Get the current branch of a project
🤖 Prompt for AI Agents
In @apps/server/src/routes/worktree/routes/get-current-branch.ts around lines 1
- 2, The top-of-file doc comment incorrectly says "GET /current-branch" while
the route is registered as a POST; update the comment in get-current-branch.ts
to "POST /current-branch" (or otherwise reflect POST) so the documentation
matches the actual route registration and intent for the handler exported in
this file.

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

🤖 Fix all issues with AI agents
In @apps/ui/src/components/views/board-view.tsx:
- Around line 1437-1463: In the onDeleted handler, reverse the delete order so
disk deletion runs before removing from state: call and await
handleDeleteFeature(feature.id) for each featureToHandle first, then call
removeFeature(feature.id) to avoid handleDeleteFeature returning early (it uses
features.find). Also replace the hardcoded main branch lookup worktrees.find((w)
=> w.isMain)?.branch || 'main' with
getPrimaryWorktreeBranch(currentProject.path) to obtain the repository’s primary
branch; use that value when building updates for reassigning features, keeping
updateFeature and persistFeatureUpdate calls as before, and preserve the
existing setWorktreeRefreshKey and setSelectedWorktreeForAction calls.

In @apps/ui/src/components/views/board-view/dialogs/delete-worktree-dialog.tsx:
- Around line 30-34: The onDeleted callback is declared/used synchronously but
callers (e.g., BoardView) pass an async handler, so change the onDeleted type to
return Promise<void>, await the call where it is invoked in DeleteWorktreeDialog
(both occurrences around the current close flow), and wrap the await in
try/catch to surface errors (log/show an error and avoid closing the dialog
prematurely if deletion failed); reference the onDeleted prop and the dialog
close flow to locate both spots to update.
🧹 Nitpick comments (3)
libs/git-utils/src/branch-validation.ts (1)

15-70: Comprehensive branch name validation with minor documentation inconsistency.

The validation logic effectively prevents command injection and enforces git ref constraints. However, the comment on line 63 states "Cannot contain: space, ~, ^, :, , ?, *, [" but the regex on line 64 also checks for ] via \]. Update the comment to include ] for accuracy.

📝 Proposed fix for comment
  // Additional git ref restrictions
- // Cannot contain: space, ~, ^, :, \, ?, *, [
+ // Cannot contain: space, ~, ^, :, \, ?, *, [, ]
  const invalidChars = /[\s~^:\\?*[\]]/;
apps/server/src/routes/app-spec/generate-spec.ts (1)

366-439: Consider refactoring worktree creation to use async fs operations and extract to utility.

The worktree creation logic is well-implemented with proper validation and error handling. However:

  1. Synchronous fs operations: Lines 380, 395-399 use fs.existsSync and fs.mkdirSync. In async functions, prefer async variants for consistency and to avoid blocking the event loop.

  2. Complex inline logic: The 70+ line worktree creation block could be extracted to a dedicated utility function (e.g., ensureWorktreeForBranch) in @automaker/git-utils for reusability and testability.

♻️ Suggested refactor for async fs operations
      // Check if worktree already exists
      const worktreePath = path.join(projectPath, '.worktrees', targetBranch);
-     const worktreeExists = fs.existsSync(worktreePath);
+     const worktreeExists = await secureFs.access(worktreePath).then(() => true).catch(() => false);

      if (worktreeExists) {
        logger.info(`Worktree for branch '${targetBranch}' already exists at ${worktreePath}`);
      } else {
        // ... branch existence check ...

        // Create .worktrees directory if it doesn't exist
        const worktreesDir = path.join(projectPath, '.worktrees');
-       if (!fs.existsSync(worktreesDir)) {
-         fs.mkdirSync(worktreesDir, { recursive: true });
-       }
+       await secureFs.mkdir(worktreesDir, { recursive: true });
apps/ui/src/components/views/board-view/dialogs/delete-worktree-dialog.tsx (1)

109-119: Add an explicit irreversible-delete warning when “Delete backlog” is checked.

Right now the “moved to main worktree” warning disappears (good), but there’s no replacement warning making it clear those features will be permanently deleted. Consider adding a short destructive callout when deleteFeatures === true.

Also applies to: 147-160

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 03ce309 and fcc4bec.

📒 Files selected for processing (6)
  • apps/server/src/routes/app-spec/generate-spec.ts
  • apps/server/src/routes/app-spec/parse-and-create-features.ts
  • apps/ui/src/components/views/board-view.tsx
  • apps/ui/src/components/views/board-view/dialogs/delete-worktree-dialog.tsx
  • libs/git-utils/src/branch-validation.ts
  • libs/git-utils/src/index.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/git-utils/src/index.ts
  • apps/server/src/routes/app-spec/parse-and-create-features.ts
  • libs/git-utils/src/branch-validation.ts
  • apps/ui/src/components/views/board-view.tsx
  • apps/ui/src/components/views/board-view/dialogs/delete-worktree-dialog.tsx
  • apps/server/src/routes/app-spec/generate-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/git-utils/src/index.ts
  • apps/server/src/routes/app-spec/parse-and-create-features.ts
  • libs/git-utils/src/branch-validation.ts
  • apps/ui/src/components/views/board-view.tsx
  • apps/ui/src/components/views/board-view/dialogs/delete-worktree-dialog.tsx
  • apps/server/src/routes/app-spec/generate-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/routes/app-spec/parse-and-create-features.ts
  • apps/server/src/routes/app-spec/generate-spec.ts
🧠 Learnings (3)
📓 Common learnings
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
📚 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/routes/app-spec/parse-and-create-features.ts
  • apps/ui/src/components/views/board-view.tsx
  • apps/server/src/routes/app-spec/generate-spec.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/board-view.tsx
  • apps/ui/src/components/views/board-view/dialogs/delete-worktree-dialog.tsx
🧬 Code graph analysis (3)
libs/git-utils/src/branch-validation.ts (1)
libs/git-utils/src/index.ts (2)
  • isSafeBranchName (22-22)
  • validateBranchName (22-22)
apps/ui/src/components/views/board-view/dialogs/delete-worktree-dialog.tsx (2)
apps/ui/src/types/electron.d.ts (1)
  • WorktreeInfo (601-606)
apps/ui/src/lib/electron.ts (1)
  • WorktreeInfo (191-191)
apps/server/src/routes/app-spec/generate-spec.ts (2)
libs/git-utils/src/branch-validation.ts (1)
  • validateBranchName (78-85)
libs/git-utils/src/index.ts (1)
  • validateBranchName (22-22)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: e2e
🔇 Additional comments (11)
libs/git-utils/src/index.ts (1)

20-22: LGTM!

Clean addition of branch validation utilities to the public API. The exports follow the established pattern in the module.

apps/server/src/routes/app-spec/parse-and-create-features.ts (4)

17-22: LGTM!

The optional targetBranch parameter enables branch-specific feature regeneration. Logging the parameter aids debugging.


58-88: Pre-regeneration cleanup prevents duplicate features.

The deletion logic correctly removes existing features for the target branch before creating new ones. Error handling is appropriate—failures are logged but don't interrupt the regeneration flow.


106-106: LGTM!

Setting branchName based on targetBranch ensures features are correctly associated with their branch.


121-133: LGTM!

The worktreePath calculation and event payload extension enable worktree-aware feature tracking in the UI. The conditional logic correctly excludes main/master branches from worktree path generation.

libs/git-utils/src/branch-validation.ts (1)

78-85: LGTM!

The validateBranchName wrapper provides clear error messaging when branch names fail validation.

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

29-29: LGTM!

Import follows coding guidelines by using the shared @automaker/git-utils package.


42-51: LGTM!

The optional targetBranch parameter extends spec generation to support worktree-based workflows. Logging aids debugging.


452-454: LGTM!

Correctly propagates targetBranch to feature generation, maintaining consistency across the spec regeneration workflow.

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

70-96: removeFeature addition looks reasonable, but verify it’s safe to call before deletions.

If useBoardActions().handleDeleteFeature(id) (or downstream persistence) does a state lookup by id, pre-removing via removeFeature(id) could prevent the actual disk delete from happening. Please double-check handleDeleteFeature is idempotent / doesn’t require the feature to still exist in local state.

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

51-57: Good safety net: reset destructive checkboxes on close.
Prevents accidental carry-over between dialog openings.

Comment on lines 1437 to 1463
onDeleted={async (deletedWorktree, _deletedBranch, deleteFeatures) => {
// Handle features assigned to the deleted worktree (by branch)
const featuresToHandle = hookFeatures.filter(
(f) => f.branchName === deletedWorktree.branch
);

if (deleteFeatures) {
// Remove all features from state first to prevent update race conditions
featuresToHandle.forEach((feature) => removeFeature(feature.id));

// Delete all features from disk in parallel
await Promise.all(featuresToHandle.map((feature) => handleDeleteFeature(feature.id)));
} else {
// Reassign all features to main branch
const mainBranch = worktrees.find((w) => w.isMain)?.branch || 'main';
const updates = { branchName: mainBranch };

featuresToHandle.forEach((feature) => {
updateFeature(feature.id, updates);
persistFeatureUpdate(feature.id, updates);
}
});
});
}

// Refresh worktree list after all operations complete
setWorktreeRefreshKey((k) => k + 1);
setSelectedWorktreeForAction(null);
}}
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:

# First, find the board-view.tsx file and understand the context around handleDeleteFeature
find . -type f -name "board-view.tsx" | head -5

Repository: AutoMaker-Org/automaker

Length of output: 112


🏁 Script executed:

# Search for getPrimaryWorktreeBranch function to see if it exists
rg "getPrimaryWorktreeBranch" --type ts --type tsx

Repository: AutoMaker-Org/automaker

Length of output: 94


🏁 Script executed:

# Search for handleDeleteFeature to understand its implementation
rg "handleDeleteFeature" --type ts --type tsx -B 5 -A 10

Repository: AutoMaker-Org/automaker

Length of output: 94


🏁 Script executed:

# Check for primary branch resolution patterns in the codebase
rg "isMain" --type ts --type tsx -A 2 -B 2

Repository: AutoMaker-Org/automaker

Length of output: 94


🏁 Script executed:

# Look for currentProject usage in board-view.tsx
rg "currentProject" apps/ui/src/components/views/board-view.tsx

Repository: AutoMaker-Org/automaker

Length of output: 3827


🏁 Script executed:

# Search for getPrimaryWorktreeBranch and handleDeleteFeature without type restriction
rg "getPrimaryWorktreeBranch|handleDeleteFeature" -B 3 -A 8

Repository: AutoMaker-Org/automaker

Length of output: 15601


🏁 Script executed:

# Read the board-view.tsx file to see lines around 1437-1463
wc -l apps/ui/src/components/views/board-view.tsx

Repository: AutoMaker-Org/automaker

Length of output: 115


🏁 Script executed:

# Get the full handleDeleteFeature implementation
rg "const handleDeleteFeature = useCallback" -A 30 apps/ui/src/components/views/board-view/hooks/use-board-actions.ts

Repository: AutoMaker-Org/automaker

Length of output: 1222


🏁 Script executed:

# Read exact lines 1437-1463 from board-view.tsx
sed -n '1437,1463p' apps/ui/src/components/views/board-view.tsx

Repository: AutoMaker-Org/automaker

Length of output: 1269


🏁 Script executed:

# Check if there are other worktree deletion callbacks to see patterns
rg "onDeleted\|worktree.*delete" -B 2 -A 15 apps/ui/src/components/views/board-view.tsx

Repository: AutoMaker-Org/automaker

Length of output: 49


Worktree delete: fix removal order and use getPrimaryWorktreeBranch() instead of hardcoding 'main'.

Two issues:

  1. handleDeleteFeature needs the feature in state: Currently removeFeature() is called before handleDeleteFeature(). Since handleDeleteFeature does features.find((f) => f.id === featureId) and returns early if not found, the disk deletion silently fails. Reverse the order: delete from disk first, then remove from state.

  2. Hardcoded primary branch: Replace worktrees.find((w) => w.isMain)?.branch || 'main' with getPrimaryWorktreeBranch(currentProject.path), which is already imported and handles repos where the primary branch is not main (e.g., master, develop).

Proposed adjustment
 onDeleted={async (deletedWorktree, _deletedBranch, deleteFeatures) => {
   // Handle features assigned to the deleted worktree (by branch)
   const featuresToHandle = hookFeatures.filter(
     (f) => f.branchName === deletedWorktree.branch
   );

   if (deleteFeatures) {
-    // Remove all features from state first to prevent update race conditions
-    featuresToHandle.forEach((feature) => removeFeature(feature.id));
-
-    // Delete all features from disk in parallel
-    await Promise.all(featuresToHandle.map((feature) => handleDeleteFeature(feature.id)));
+    // Delete from disk first (handleDeleteFeature requires feature in state),
+    // then remove from local state to prevent follow-up updates/races.
+    await Promise.all(featuresToHandle.map((feature) => handleDeleteFeature(feature.id)));
+    featuresToHandle.forEach((feature) => removeFeature(feature.id));
   } else {
     // Reassign all features to main branch
-    const mainBranch = worktrees.find((w) => w.isMain)?.branch || 'main';
+    const mainBranch = getPrimaryWorktreeBranch(currentProject.path) || 'main';
     const updates = { branchName: mainBranch };

     featuresToHandle.forEach((feature) => {
       updateFeature(feature.id, updates);
       persistFeatureUpdate(feature.id, updates);
     });
   }

   // Refresh worktree list after all operations complete
   setWorktreeRefreshKey((k) => k + 1);
   setSelectedWorktreeForAction(null);
 }}
📝 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
onDeleted={async (deletedWorktree, _deletedBranch, deleteFeatures) => {
// Handle features assigned to the deleted worktree (by branch)
const featuresToHandle = hookFeatures.filter(
(f) => f.branchName === deletedWorktree.branch
);
if (deleteFeatures) {
// Remove all features from state first to prevent update race conditions
featuresToHandle.forEach((feature) => removeFeature(feature.id));
// Delete all features from disk in parallel
await Promise.all(featuresToHandle.map((feature) => handleDeleteFeature(feature.id)));
} else {
// Reassign all features to main branch
const mainBranch = worktrees.find((w) => w.isMain)?.branch || 'main';
const updates = { branchName: mainBranch };
featuresToHandle.forEach((feature) => {
updateFeature(feature.id, updates);
persistFeatureUpdate(feature.id, updates);
}
});
});
}
// Refresh worktree list after all operations complete
setWorktreeRefreshKey((k) => k + 1);
setSelectedWorktreeForAction(null);
}}
onDeleted={async (deletedWorktree, _deletedBranch, deleteFeatures) => {
// Handle features assigned to the deleted worktree (by branch)
const featuresToHandle = hookFeatures.filter(
(f) => f.branchName === deletedWorktree.branch
);
if (deleteFeatures) {
// Delete from disk first (handleDeleteFeature requires feature in state),
// then remove from local state to prevent follow-up updates/races.
await Promise.all(featuresToHandle.map((feature) => handleDeleteFeature(feature.id)));
featuresToHandle.forEach((feature) => removeFeature(feature.id));
} else {
// Reassign all features to main branch
const mainBranch = getPrimaryWorktreeBranch(currentProject.path) || 'main';
const updates = { branchName: mainBranch };
featuresToHandle.forEach((feature) => {
updateFeature(feature.id, updates);
persistFeatureUpdate(feature.id, updates);
});
}
// Refresh worktree list after all operations complete
setWorktreeRefreshKey((k) => k + 1);
setSelectedWorktreeForAction(null);
}}
🤖 Prompt for AI Agents
In @apps/ui/src/components/views/board-view.tsx around lines 1437 - 1463, In the
onDeleted handler, reverse the delete order so disk deletion runs before
removing from state: call and await handleDeleteFeature(feature.id) for each
featureToHandle first, then call removeFeature(feature.id) to avoid
handleDeleteFeature returning early (it uses features.find). Also replace the
hardcoded main branch lookup worktrees.find((w) => w.isMain)?.branch || 'main'
with getPrimaryWorktreeBranch(currentProject.path) to obtain the repository’s
primary branch; use that value when building updates for reassigning features,
keeping updateFeature and persistFeatureUpdate calls as before, and preserve the
existing setWorktreeRefreshKey and setSelectedWorktreeForAction calls.

Comment on lines +30 to +34
onDeleted: (
deletedWorktree: WorktreeInfo,
deletedBranch: boolean,
deleteFeatures: boolean
) => void;
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

onDeleted should be async/awaited to avoid unhandled promise rejections.

BoardView passes an async handler; right now you call it without awaiting, and you immediately close the dialog. If the handler throws/rejects (e.g., feature deletion fails), this can become an unhandled rejection and skip follow-up UI consistency steps.

Proposed fix (type + await + error handling)
 interface DeleteWorktreeDialogProps {
   open: boolean;
   onOpenChange: (open: boolean) => void;
   projectPath: string;
   worktree: WorktreeInfo | null;
   onDeleted: (
     deletedWorktree: WorktreeInfo,
     deletedBranch: boolean,
     deleteFeatures: boolean
-  ) => void;
+  ) => Promise<void>;
   /** Number of features assigned to this worktree's branch */
   affectedFeatureCount?: number;
 }

 // ...

       if (result.success) {
         toast.success(`Worktree deleted`, {
           description: deleteBranch
             ? `Branch "${worktree.branch}" was also deleted`
             : `Branch "${worktree.branch}" was kept`,
         });
-        onDeleted(worktree, deleteBranch, deleteFeatures);
-        onOpenChange(false); // useEffect will reset checkbox state
+        try {
+          await onDeleted(worktree, deleteBranch, deleteFeatures);
+          onOpenChange(false); // useEffect will reset checkbox state
+        } catch (err) {
+          toast.error('Worktree deleted, but follow-up cleanup failed', {
+            description: err instanceof Error ? err.message : 'Unknown error',
+          });
+        }
       } else {

Also applies to: 77-79

🤖 Prompt for AI Agents
In @apps/ui/src/components/views/board-view/dialogs/delete-worktree-dialog.tsx
around lines 30 - 34, The onDeleted callback is declared/used synchronously but
callers (e.g., BoardView) pass an async handler, so change the onDeleted type to
return Promise<void>, await the call where it is invoked in DeleteWorktreeDialog
(both occurrences around the current close flow), and wrap the await in
try/catch to surface errors (log/show an error and avoid closing the dialog
prematurely if deletion failed); reference the onDeleted prop and the dialog
close flow to locate both spots to update.

@webdevcody
Copy link
Collaborator

there is a lot of code rabbit reviews you need to address, but thanks for this initial pr

@yumesha
Copy link
Contributor Author

yumesha commented Jan 10, 2026

I have manually test it and fix all coderabbit address review

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/tests/utils/views/spec-editor.ts (1)

31-33: Remove clickSaveSpec as it duplicates clickSaveButton and is unused.

Both functions target the same element (save-spec) but clickSaveButton adds wait-for-confirmation logic. Since clickSaveSpec is not used anywhere in the codebase and appears to be superseded by clickSaveButton, remove it to avoid confusion about which function to use in tests.

🤖 Fix all issues with AI agents
In @apps/ui/src/components/views/spec-view/dialogs/create-spec-dialog.tsx:
- Around line 2-3: The dialog currently stores isCustom/customFeatureCount
independently which can desync from the incoming prop featureCount across opens;
update CreateSpecDialog (and any related handlers) to derive initial isCustom
and customFeatureCount from featureCount when the dialog opens or when
featureCount changes (e.g., compute isCustom =
!presetValues.includes(featureCount) and set customFeatureCount = featureCount),
and reset those derived state values in the dialog open/reset logic so the UI
always reflects the current featureCount instead of stale state.

In @apps/ui/src/components/views/spec-view/dialogs/regenerate-spec-dialog.tsx:
- Around line 2-3: The dialog's local states isCustom and customFeatureCount
must be derived/reset from the incoming featureCount so the UI reflects preset
vs custom values; in the RegenerateSpecDialog component, initialize
isCustom/customFeatureCount based on whether featureCount is one of the preset
options and add a useEffect that runs when featureCount (or the dialog open
prop) changes to set isCustom = featureCount not in presets and
customFeatureCount = featureCount when custom, otherwise clear/reset them;
ensure submission uses customFeatureCount when isCustom is true and that the
custom input is shown whenever isCustom becomes true.
🧹 Nitpick comments (6)
apps/ui/tests/git/delete-worktree-with-features.spec.ts (4)

29-60: Use Page type instead of any for better type safety.

The helper function uses any for the page parameter, losing type checking benefits. Import and use the Page type from Playwright.

♻️ Suggested fix
+import { test, expect, Page } from '@playwright/test';
-import { test, expect } from '@playwright/test';

-async function enableWorktrees(page: any) {
+async function enableWorktrees(page: Page) {

62-82: Same typing issue: use Page type.

Apply the same fix to use Page instead of any. Additionally, the 2-second hardcoded wait on line 81 could potentially be replaced with a more reliable wait condition if there's a UI element that indicates completion.

♻️ Suggested fix
-async function createWorktree(page: any, branchName: string) {
+async function createWorktree(page: Page, branchName: string) {

169-172: Fragile selector pattern for actions dropdown.

Using .locator('..') (parent traversal) combined with .last() creates a brittle selector that will break if the UI structure changes. This pattern is repeated across all test cases (lines 258-259, 354-355, 402-403, 461-462).

Consider requesting a data-testid be added to the worktree actions dropdown button for more reliable selection.

♻️ Suggested improvement

If a data-testid is added to the UI:

-    const worktreeContainer = featureBranchTab.locator('..');
-    const actionsDropdown = worktreeContainer.locator('button').last();
+    const actionsDropdown = page.locator(`[data-testid="worktree-actions-${branchName}"]`);

Alternatively, a more specific selector using aria attributes:

-    const worktreeContainer = featureBranchTab.locator('..');
-    const actionsDropdown = worktreeContainer.locator('button').last();
+    const actionsDropdown = featureBranchTab.locator('xpath=..').locator('button[aria-haspopup="menu"]');

153-155: Consider replacing hardcoded timeouts with explicit wait conditions.

There are numerous waitForTimeout calls throughout the tests (e.g., lines 153, 155, 172, 195, 201, 274, 287, 293, etc.). While sometimes necessary, fixed timeouts can lead to flaky tests—either failing on slower CI environments or wasting time on faster ones.

Where possible, prefer waiting for specific conditions:

  • await expect(element).toBeVisible()
  • await page.waitForResponse(predicate)
  • await page.waitForSelector('[data-testid="..."]')
apps/ui/src/components/views/spec-view/dialogs/regenerate-spec-dialog.tsx (1)

81-114: Worktree branch input: consider normalizing the branch name before storing/submitting.
Users can paste leading/trailing whitespace; you already trim() for button disable, but the stored value may keep whitespace and surprise downstream validation.

Proposed tweak (trim on change)
- onChange={(e) => onWorktreeBranchChange(e.target.value)}
+ onChange={(e) => onWorktreeBranchChange(e.target.value.trim())}

Also applies to: 235-239

apps/ui/src/components/views/spec-view/dialogs/create-spec-dialog.tsx (1)

81-114: Worktree branch input: consider trimming on change to avoid whitespace-only branch names.
You already guard with trim() for button disabling; trimming at the source avoids passing whitespace downstream.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fcc4bec and 4d4799a.

📒 Files selected for processing (5)
  • apps/ui/src/components/views/board-view.tsx
  • apps/ui/src/components/views/spec-view/dialogs/create-spec-dialog.tsx
  • apps/ui/src/components/views/spec-view/dialogs/regenerate-spec-dialog.tsx
  • apps/ui/tests/git/delete-worktree-with-features.spec.ts
  • apps/ui/tests/utils/views/spec-editor.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:

  • apps/ui/src/components/views/board-view.tsx
  • apps/ui/tests/utils/views/spec-editor.ts
  • apps/ui/src/components/views/spec-view/dialogs/regenerate-spec-dialog.tsx
  • apps/ui/tests/git/delete-worktree-with-features.spec.ts
  • apps/ui/src/components/views/spec-view/dialogs/create-spec-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:

  • apps/ui/src/components/views/board-view.tsx
  • apps/ui/tests/utils/views/spec-editor.ts
  • apps/ui/src/components/views/spec-view/dialogs/regenerate-spec-dialog.tsx
  • apps/ui/tests/git/delete-worktree-with-features.spec.ts
  • apps/ui/src/components/views/spec-view/dialogs/create-spec-dialog.tsx
🧠 Learnings (3)
📓 Common learnings
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
📚 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/ui/src/components/views/board-view.tsx
  • apps/ui/tests/git/delete-worktree-with-features.spec.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/board-view.tsx
  • apps/ui/src/components/views/spec-view/dialogs/regenerate-spec-dialog.tsx
  • apps/ui/src/components/views/spec-view/dialogs/create-spec-dialog.tsx
🧬 Code graph analysis (3)
apps/ui/src/components/views/spec-view/dialogs/regenerate-spec-dialog.tsx (3)
apps/ui/src/components/views/spec-view/types.ts (1)
  • RegenerateSpecDialogProps (39-58)
apps/ui/src/components/views/spec-view/constants.ts (1)
  • FEATURE_COUNT_OPTIONS (10-19)
apps/ui/src/lib/utils.ts (1)
  • cn (5-7)
apps/ui/tests/git/delete-worktree-with-features.spec.ts (4)
apps/ui/tests/utils/git/worktree.ts (2)
  • createTempDirPath (52-55)
  • setupProjectWithPath (333-377)
apps/ui/tests/utils/core/interactions.ts (1)
  • handleLoginScreenIfPresent (65-115)
apps/ui/tests/utils/core/waiting.ts (1)
  • waitForNetworkIdle (9-11)
apps/ui/tests/utils/views/board.ts (2)
  • addFeature (190-198)
  • getKanbanColumn (13-15)
apps/ui/src/components/views/spec-view/dialogs/create-spec-dialog.tsx (3)
apps/ui/src/components/views/spec-view/types.ts (1)
  • CreateSpecDialogProps (15-36)
apps/ui/src/components/views/spec-view/constants.ts (1)
  • FEATURE_COUNT_OPTIONS (10-19)
apps/ui/src/lib/utils.ts (1)
  • cn (5-7)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: e2e
🔇 Additional comments (7)
apps/ui/tests/git/delete-worktree-with-features.spec.ts (5)

9-24: LGTM!

Imports are well-organized and appropriately leverage the shared test utilities from the ../utils barrel export. The use of promisify for exec is a good practice for async/await patterns.


84-103: LGTM!

The helper function correctly reads the features directory and filters by branch name. The synchronous file operations are acceptable for test assertion purposes.


110-131: LGTM!

Good test isolation pattern with proper setup and teardown. Each test gets a fresh git repository, and cleanup is properly ordered (afterEach for individual repos, afterAll for the temp directory).


309-319: LGTM!

Good verification that deleted features are actually removed from disk, not just moved. The loop checking that no remaining feature contains the "deleted" text in its description ensures complete cleanup.


376-433: LGTM!

Excellent coverage for the git branch deletion behavior. Using execAsync('git branch', ...) to verify the actual git state is a robust approach that validates the integration between the UI action and the underlying git operations.

Also applies to: 435-497

apps/ui/tests/utils/views/spec-editor.ts (1)

84-85: LGTM! Cross-platform keyboard shortcut improvement.

The change to ControlOrMeta+a properly handles select-all across different operating systems (Control on Windows/Linux, Command on macOS).

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

86-86: LGTM! Store method properly exposed.

The removeFeature method is correctly added to support the enhanced worktree deletion flow.

Comment on lines +1437 to +1461
onDeleted={async (deletedWorktree, _deletedBranch, deleteFeatures) => {
// Handle features assigned to the deleted worktree (by branch)
const featuresToHandle = hookFeatures.filter(
(f) => f.branchName === deletedWorktree.branch
);

// Compute mainBranch once before processing
const mainBranch = worktrees.find((w) => w.isMain)?.branch || 'main';

if (deleteFeatures) {
// Delete all features from disk in parallel
// handleDeleteFeature already calls removeFeature, so no need to call it separately
await Promise.all(featuresToHandle.map((feature) => handleDeleteFeature(feature.id)));
} else {
// Reassign all features to main branch
const updates = { branchName: mainBranch };

// Use Promise.all to await all updates in parallel
await Promise.all(
featuresToHandle.map((feature) => {
updateFeature(feature.id, updates);
return persistFeatureUpdate(feature.id, updates);
})
);
}
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:

# Search for handleDeleteFeature definition
rg -nA20 'const handleDeleteFeature|function handleDeleteFeature' --type=ts --type=tsx | head -100

Repository: AutoMaker-Org/automaker

Length of output: 94


🏁 Script executed:

# Search for removeFeature usage and definition
rg -nB2 -A10 'removeFeature' --type=ts --type=tsx | head -150

Repository: AutoMaker-Org/automaker

Length of output: 94


🏁 Script executed:

# Check the imports and context around line 1437-1461 in board-view.tsx
rg -nB5 -A5 'onDeleted.*async.*deletedWorktree' apps/ui/src/components/views/board-view.tsx | head -50

Repository: AutoMaker-Org/automaker

Length of output: 596


🏁 Script executed:

# Search for handleDeleteFeature definition without type filter
rg -nA20 'const handleDeleteFeature|function handleDeleteFeature' | head -150

Repository: AutoMaker-Org/automaker

Length of output: 2269


🏁 Script executed:

# Search for removeFeature without type filter
rg -nB2 -A10 'removeFeature' | head -200

Repository: AutoMaker-Org/automaker

Length of output: 6854


🏁 Script executed:

# Check imports at the top of board-view.tsx to find where handleDeleteFeature comes from
head -100 apps/ui/src/components/views/board-view.tsx

Repository: AutoMaker-Org/automaker

Length of output: 3765


🏁 Script executed:

sed -n '284,326p' apps/ui/src/components/views/board-view/hooks/use-board-actions.ts

Repository: AutoMaker-Org/automaker

Length of output: 1525


🏁 Script executed:

# Also search for persistFeatureDelete to understand what it does
rg -nB2 -A10 'persistFeatureDelete' | head -100

Repository: AutoMaker-Org/automaker

Length of output: 8251


🏁 Script executed:

# Get the complete persistFeatureDelete implementation
sed -n '63,82p' apps/ui/src/components/views/board-view/hooks/use-board-persistence.ts

Repository: AutoMaker-Org/automaker

Length of output: 555


Add error handling and user feedback for feature deletion in worktree deletion.

The deletion handler has two legitimate issues:

  1. Missing error handling and user feedback: The onDeleted callback has no try/catch around the Promise.all operations. While persistFeatureDelete catches errors silently (logging but not re-throwing), users receive no feedback if deletions or updates fail. Add a try/catch block to handle failures gracefully and provide toast notifications for success/failure.

  2. Silent error swallowing: persistFeatureDelete catches errors and logs them but doesn't re-throw or notify the user. If disk deletion fails, the feature is already removed from state but the user won't know the persistence operation failed.

Note: The order of state removal is already correct—handleDeleteFeature calls removeFeature before persistFeatureDelete, so the features are removed from state synchronously before any async disk operations.

🔧 Suggested error handling approach
         onDeleted={async (deletedWorktree, _deletedBranch, deleteFeatures) => {
+          try {
             // Handle features assigned to the deleted worktree (by branch)
             const featuresToHandle = hookFeatures.filter(
               (f) => f.branchName === deletedWorktree.branch
             );
 
             // Compute mainBranch once before processing
             const mainBranch = worktrees.find((w) => w.isMain)?.branch || 'main';
 
             if (deleteFeatures) {
               // Delete all features from disk in parallel
               // handleDeleteFeature already calls removeFeature, so no need to call it separately
-              await Promise.all(featuresToHandle.map((feature) => handleDeleteFeature(feature.id)));
+              const results = await Promise.allSettled(featuresToHandle.map((feature) => handleDeleteFeature(feature.id)));
+              const failures = results.filter((r) => r.status === 'rejected').length;
+              if (failures > 0) {
+                toast.error(`Failed to delete ${failures} of ${featuresToHandle.length} features`);
+              } else if (featuresToHandle.length > 0) {
+                toast.success(`Deleted ${featuresToHandle.length} features`);
+              }
             } else {
               // Reassign all features to main branch
               const updates = { branchName: mainBranch };
 
               // Use Promise.all to await all updates in parallel
-              await Promise.all(
+              const results = await Promise.allSettled(
                 featuresToHandle.map((feature) => {
                   updateFeature(feature.id, updates);
                   return persistFeatureUpdate(feature.id, updates);
                 })
               );
+              
+              const failures = results.filter((r) => r.status === 'rejected').length;
+              if (failures > 0) {
+                toast.error(`Failed to reassign ${failures} of ${featuresToHandle.length} features to ${mainBranch}`);
+              } else if (featuresToHandle.length > 0) {
+                toast.success(`Reassigned ${featuresToHandle.length} features to ${mainBranch}`);
+              }
             }
+          } catch (error) {
+            logger.error('Error handling worktree deletion:', error);
+            toast.error('Failed to process features during worktree deletion');
+          }
 
           // Refresh worktree list after all operations complete
           setWorktreeRefreshKey((k) => k + 1);
           setSelectedWorktreeForAction(null);
         }}

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

Caution

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

⚠️ Outside diff range comments (2)
apps/ui/src/components/views/spec-view/dialogs/regenerate-spec-dialog.tsx (2)

209-233: Custom count parsing should avoid parseInt + add invalid-state UX.
parseInt("1e2") === 1 and parseInt("1.5") === 1; also the user can type 0/empty and still regenerate with the previous propagated count. Prefer valueAsNumber + Number.isInteger, and disable regenerate (or show an inline error) when isCustom and the value is invalid.

Proposed fix (safer parsing + validity flag)
+  const customCountNum = Number(customFeatureCount);
+  const isCustomCountValid =
+    !generateFeatures ||
+    !isCustom ||
+    (Number.isInteger(customCountNum) && customCountNum >= 1 && customCountNum <= 200);

              {isCustom && (
                 <Input
                   type="number"
                   min="1"
                   max="200"
                   value={customFeatureCount}
                   onChange={(e) => {
                     setCustomFeatureCount(e.target.value);
-                    const num = parseInt(e.target.value, 10);
-                    if (!isNaN(num) && num >= 1 && num <= 200) {
-                      onFeatureCountChange(num);
-                    }
+                    const num = e.currentTarget.valueAsNumber;
+                    if (Number.isInteger(num) && num >= 1 && num <= 200) onFeatureCountChange(num);
                   }}
                   placeholder="Enter number of features (1-200)"
                   disabled={isDisabled}
                   className="text-sm"
                   data-testid="regenerate-feature-count-custom-input"
                 />
               )}
+              {!isCustomCountValid && (
+                <p className="text-xs text-destructive">Enter a whole number from 1–200.</p>
+              )}

And then use isCustomCountValid in the submit button (see next comment).


19-55: Refactor preset extraction to derive from FEATURE_COUNT_OPTIONS and remove unnecessary casting.

The hard-coded [20, 50, 100] preset list creates a maintenance risk—if FEATURE_COUNT_OPTIONS is updated, this list won't automatically reflect the change. Extract numeric presets directly from the options to keep them in sync. Additionally, remove the as number cast since FeatureCount is already typed as number, and the 'custom' guard is unnecessary (the type definition does not include 'custom' as a valid FeatureCount value).

Proposed fix
  useEffect(() => {
-   if (open) {
-     const presetValues = [20, 50, 100];
+   if (!open) return;
+
+   const numericOptions = FEATURE_COUNT_OPTIONS
+     .map((o) => o.value)
+     .filter((v): v is number => typeof v === 'number');
+
+   if (typeof featureCount === 'number') {
+     const isPreset = numericOptions.includes(featureCount);
      setIsCustom(!isPreset);
-     setCustomFeatureCount(isPreset ? '' : String(featureCount));
+     setCustomFeatureCount(isPreset ? '' : String(featureCount));
+     return;
    }
-  }, [open, featureCount]);
+  }, [open, featureCount]);
🤖 Fix all issues with AI agents
In @apps/ui/src/components/views/spec-view/dialogs/regenerate-spec-dialog.tsx:
- Around line 91-124: The Input currently trims on every keystroke via
onChange={(e) => onWorktreeBranchChange(e.target.value.trim())}, which causes
cursor jumps; change onChange to pass the raw value (e.g., onChange={(e) =>
onWorktreeBranchChange(e.target.value)}) and move trimming/validation to an
onBlur handler and the submit path (call
onWorktreeBranchChange(worktreeBranch.trim()) or trim before submit in the
function that handles the dialog submit); keep the existing props
(value={worktreeBranch}, data-testid) and add an onBlur that trims and updates
the same onWorktreeBranchChange to ensure final validation without trimming
during typing.

In @apps/ui/tests/git/delete-worktree-with-features.spec.ts:
- Around line 14-25: Remove the redundant second import of addFeature and
getKanbanColumn and instead import them from the centralized utils import
alongside the other helpers; update the first import statement (the one that
currently brings in waitForNetworkIdle, createTestGitRepo, cleanupTempDir,
createTempDirPath, setupProjectWithPath, waitForBoardView, authenticateForTests,
handleLoginScreenIfPresent) to also include addFeature and getKanbanColumn, then
delete the separate import that references addFeature/getKanbanColumn to keep
imports consistent and avoid duplication.
🧹 Nitpick comments (8)
apps/ui/src/components/views/spec-view/dialogs/create-spec-dialog.tsx (2)

44-52: Derive preset values from FEATURE_COUNT_OPTIONS to avoid duplication.

The hardcoded presetValues array duplicates values already defined in FEATURE_COUNT_OPTIONS (from apps/ui/src/components/views/spec-view/constants.ts). If the preset options change in constants, this code won't update automatically.

♻️ Proposed refactor to eliminate duplication
  useEffect(() => {
    if (open) {
-     const presetValues = [20, 50, 100];
+     const presetValues = FEATURE_COUNT_OPTIONS
+       .filter(o => typeof o.value === 'number')
+       .map(o => o.value as number);
      const isPreset = presetValues.includes(featureCount as number);
      setIsCustom(!isPreset);
      setCustomFeatureCount(isPreset ? '' : String(featureCount));
    }
  }, [open, featureCount]);

91-124: Consider moving .trim() to onBlur for better UX.

Trimming on every keystroke (line 115) prevents users from typing spaces during input, though this is unlikely to affect branch names. Moving .trim() to onBlur would provide a better typing experience while still ensuring clean values.

♻️ Optional refactor to improve input UX
  <Input
    type="text"
    value={worktreeBranch}
-   onChange={(e) => onWorktreeBranchChange(e.target.value.trim())}
+   onChange={(e) => onWorktreeBranchChange(e.target.value)}
+   onBlur={(e) => onWorktreeBranchChange(e.target.value.trim())}
    placeholder="e.g., test-worktree, feature-new"
    disabled={isCreatingSpec}
    className="font-mono text-sm"
    data-testid="create-worktree-branch-input"
  />
apps/ui/src/components/views/spec-view/dialogs/regenerate-spec-dialog.tsx (1)

174-207: Feature count selection logic is fine, but consider setting a default when switching to “custom”.
Today clicking “custom” with an empty input keeps the prior featureCount, which can be surprising. Optional: seed customFeatureCount from current featureCount (if numeric) when toggling into custom.

apps/ui/tests/git/delete-worktree-with-features.spec.ts (5)

30-60: Reduce flakiness: replace fixed sleeps + keyboard shortcut navigation with state-based waits.
waitForTimeout(500) and page.keyboard.press('k') make this helper sensitive to timing/shortcuts. Prefer asserting the checkbox becomes checked and navigating back via an explicit UI affordance (or a stable testid).

Proposed adjustment (state-based waits)
 async function enableWorktrees(page: Page) {
   await page.click('[data-testid="settings-button"]');
   await page.waitForSelector('[data-testid="settings-view"]', { timeout: 5000 });

   // Click on Feature Defaults section
   const featureDefaultsSection = page.getByRole('button', { name: /Feature Defaults/i });
   await featureDefaultsSection.click();
-  await page.waitForTimeout(500);

   // Scroll to and enable worktrees checkbox
   const worktreeCheckbox = page.locator('[data-testid="use-worktrees-checkbox"]');
   await worktreeCheckbox.scrollIntoViewIfNeeded();
   await worktreeCheckbox.waitFor({ state: 'visible', timeout: 5000 });
   const isChecked = await worktreeCheckbox.isChecked();
   if (!isChecked) {
     await worktreeCheckbox.click();
-    await page.waitForTimeout(500);
+    await expect(worktreeCheckbox).toBeChecked({ timeout: 5000 });
   }

-  // Navigate back to board using keyboard shortcut
-  await page.keyboard.press('k');
+  // Prefer explicit navigation to avoid shortcut dependency
+  // e.g. click a "Back" / "Board" button if available (or add a data-testid for it).
   await waitForBoardView(page);

63-82: createWorktree: avoid regex injection + replace unconditional 2s sleep with a concrete “worktree ready” signal.
text=/...${branchName}/i will break if branchName contains / or other regex delimiters, and waitForTimeout(2000) is a classic flake source. Prefer expect(...).toBeVisible() on the new worktree tab (or a deterministic API/network wait) instead of sleeping.


84-103: Harden getFeaturesForBranch: fail with clearer errors on malformed JSON (and keep tests debuggable).
A single corrupted feature.json will currently throw a raw JSON.parse exception without context. Consider wrapping parse with a message that includes featureJsonPath for fast diagnosis.


164-172: Brittle selector: locator('..').locator('button').last() can click the wrong control as UI evolves.
If the container gains another button, these tests will misclick. Prefer a stable selector (data-testid/aria-label) for the worktree “actions” (three-dots) button; if none exists, adding one will pay off quickly.

Also applies to: 253-261, 353-357, 398-406, 457-465


394-430: Git assertions: prefer exact-match checks over git branch substring matching.
expect(stdout).toContain('branch-to-delete') can false-positive if similarly named branches exist. Use git branch --list <name> or git show-ref --verify --quiet refs/heads/<name> and assert on exit code.

Also applies to: 453-494

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4d4799a and ced8aa9.

📒 Files selected for processing (4)
  • apps/ui/src/components/views/spec-view/dialogs/create-spec-dialog.tsx
  • apps/ui/src/components/views/spec-view/dialogs/regenerate-spec-dialog.tsx
  • apps/ui/tests/git/delete-worktree-with-features.spec.ts
  • apps/ui/tests/utils/views/spec-editor.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/ui/tests/utils/views/spec-editor.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:

  • apps/ui/src/components/views/spec-view/dialogs/regenerate-spec-dialog.tsx
  • apps/ui/src/components/views/spec-view/dialogs/create-spec-dialog.tsx
  • apps/ui/tests/git/delete-worktree-with-features.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/ui/src/components/views/spec-view/dialogs/regenerate-spec-dialog.tsx
  • apps/ui/src/components/views/spec-view/dialogs/create-spec-dialog.tsx
  • apps/ui/tests/git/delete-worktree-with-features.spec.ts
🧠 Learnings (3)
📓 Common learnings
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
📚 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/spec-view/dialogs/regenerate-spec-dialog.tsx
  • apps/ui/src/components/views/spec-view/dialogs/create-spec-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/ui/tests/git/delete-worktree-with-features.spec.ts
🧬 Code graph analysis (1)
apps/ui/src/components/views/spec-view/dialogs/create-spec-dialog.tsx (3)
apps/ui/src/components/views/spec-view/types.ts (1)
  • CreateSpecDialogProps (15-36)
apps/ui/src/components/views/spec-view/constants.ts (1)
  • FEATURE_COUNT_OPTIONS (10-19)
apps/ui/src/lib/utils.ts (1)
  • cn (5-7)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: e2e
  • GitHub Check: build
🔇 Additional comments (6)
apps/ui/src/components/views/spec-view/dialogs/create-spec-dialog.tsx (2)

1-17: LGTM!

The new imports (useState, useEffect, Input) are necessary for the worktree branch and custom feature count functionality.


30-33: LGTM!

The new worktree branch props are correctly integrated and match the interface defined in apps/ui/src/components/views/spec-view/types.ts.

apps/ui/src/components/views/spec-view/dialogs/regenerate-spec-dialog.tsx (2)

2-15: Imports look good; Input addition is appropriate.


245-249: Good: submit blocks empty worktree branch; also block invalid custom count.

Proposed tweak (if you add isCustomCountValid)
               disabled={
                 !projectDefinition.trim() ||
                 isDisabled ||
-                (useWorktreeBranch && !worktreeBranch.trim())
+                (useWorktreeBranch && !worktreeBranch.trim()) ||
+                !isCustomCountValid
               }
apps/ui/tests/git/delete-worktree-with-features.spec.ts (2)

113-131: No changes needed. cleanupTempDir() is synchronous (uses fs.rmSync() and returns void), so an await is not applicable. Additionally, createTempDirPath() already ensures worker uniqueness by incorporating process.pid and a random component into the directory path, so parallel test runs will not collide.

Likely an incorrect or invalid review comment.


133-220: Remove hard-coded 'main' references; the feature code already handles this dynamically, and tests are correct given their setup.

The delete-worktree feature already dynamically detects the base branch via worktrees.find((w) => w.isMain)?.branch (board-view.tsx:1444), which correctly retrieves whatever branch is the main worktree—not hard-coded to 'main'. Since createTestGitRepo explicitly enforces git branch -M main (worktree.ts:100), the test's hard-coded 'main' references are safe and accurate. The implementation is flexible; the tests are not flaky. No code changes needed.

Likely an incorrect or invalid review comment.

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

🧹 Nitpick comments (3)
apps/ui/src/components/views/spec-view/dialogs/regenerate-spec-dialog.tsx (3)

38-66: Significant code duplication with CreateSpecDialog.

The custom feature count state management (lines 38-66) is nearly identical to CreateSpecDialog. Consider extracting this into a custom hook like useCustomFeatureCount:

function useCustomFeatureCount(open: boolean, featureCount: FeatureCount, generateFeatures: boolean) {
  const [customFeatureCount, setCustomFeatureCount] = useState<string>('');
  const [isCustom, setIsCustom] = useState(false);
  
  useEffect(() => { /* sync logic */ }, [open, featureCount]);
  
  const isCustomCountValid = /* validation */;
  
  return { customFeatureCount, setCustomFeatureCount, isCustom, setIsCustom, isCustomCountValid };
}

This would reduce duplication and ensure consistent behavior across both dialogs.


103-137: Worktree branch UI block is duplicated.

This entire UI block (checkbox + conditional input) is identical to CreateSpecDialog. Consider extracting into a shared component like WorktreeBranchSection:

<WorktreeBranchSection
  id="regenerate-use-worktree-branch"
  checked={useWorktreeBranch}
  onCheckedChange={onUseWorktreeBranchChange}
  value={worktreeBranch}
  onChange={onWorktreeBranchChange}
  disabled={isDisabled}
  testIdPrefix="regenerate"
/>

187-220: Feature count selection UI is also duplicated.

The feature count buttons and custom input logic mirrors CreateSpecDialog. This is a third area where extracting a FeatureCountSelector component would improve maintainability:

<FeatureCountSelector
  featureCount={featureCount}
  onFeatureCountChange={onFeatureCountChange}
  disabled={isDisabled}
  testIdPrefix="regenerate"
/>

For now, the implementation is correct and functional.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ced8aa9 and 7e7abcc.

📒 Files selected for processing (3)
  • apps/ui/src/components/views/spec-view/dialogs/create-spec-dialog.tsx
  • apps/ui/src/components/views/spec-view/dialogs/regenerate-spec-dialog.tsx
  • apps/ui/tests/git/delete-worktree-with-features.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/ui/tests/git/delete-worktree-with-features.spec.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:

  • apps/ui/src/components/views/spec-view/dialogs/create-spec-dialog.tsx
  • apps/ui/src/components/views/spec-view/dialogs/regenerate-spec-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:

  • apps/ui/src/components/views/spec-view/dialogs/create-spec-dialog.tsx
  • apps/ui/src/components/views/spec-view/dialogs/regenerate-spec-dialog.tsx
🧠 Learnings (2)
📓 Common learnings
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
📚 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/spec-view/dialogs/create-spec-dialog.tsx
  • apps/ui/src/components/views/spec-view/dialogs/regenerate-spec-dialog.tsx
🧬 Code graph analysis (1)
apps/ui/src/components/views/spec-view/dialogs/regenerate-spec-dialog.tsx (4)
apps/ui/src/components/views/spec-view/types.ts (1)
  • RegenerateSpecDialogProps (39-58)
apps/ui/src/components/views/spec-view/constants.ts (1)
  • FEATURE_COUNT_OPTIONS (10-19)
apps/ui/src/components/ui/button.tsx (1)
  • Button (108-108)
apps/ui/src/lib/utils.ts (1)
  • cn (5-7)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: e2e
🔇 Additional comments (6)
apps/ui/src/components/views/spec-view/dialogs/create-spec-dialog.tsx (5)

41-68: Custom feature count state management looks correct.

The useEffect properly syncs local state when the dialog opens. The validation logic at lines 63-68 correctly handles the three cases: features disabled, preset selected, or valid custom count.

One minor observation: when customFeatureCount is an empty string, Number('') returns 0, which fails validation as expected. This is correct behavior.


103-137: Worktree branch UI implementation is well-structured.

The checkbox-controlled conditional input pattern is clean. The onBlur handler trims whitespace, and the input is properly disabled during spec creation.


196-206: Potential state inconsistency when selecting "Custom" with empty input.

When clicking the "Custom" button with an empty customFeatureCount, the parseInt('', 10) call is skipped (line 199-201), but isCustom is set to true. This leaves featureCount at its previous value while the UI shows the custom input as selected.

If the previous featureCount was a preset (e.g., 20), the validation will pass (isCustomCountValid is true when isCustom is false or when custom count is valid), but since isCustom is now true and customFeatureCount is empty, isCustomCountValid becomes false — which is actually correct behavior. The submit button will be disabled until a valid custom count is entered.


229-234: Consider using valueAsNumber consistently for validation.

The validation uses e.currentTarget.valueAsNumber which returns NaN for empty strings or invalid input. The Number.isInteger(NaN) check returns false, so invalid values won't propagate — this is correct.

However, a minor edge case: if a user types "1.5", valueAsNumber returns 1.5, and Number.isInteger(1.5) is false, so it won't update the parent. The displayed value will show "1.5" but the actual featureCount prop won't change. The validation message will appear, which is the intended behavior.


270-275: Submit button validation is comprehensive.

The disabled logic correctly covers:

  • Empty project overview
  • Spec creation in progress
  • Worktree branch enabled but empty
  • Invalid custom feature count
apps/ui/src/components/views/spec-view/dialogs/regenerate-spec-dialog.tsx (1)

265-270: Submit validation logic is correct and consistent.

The disabled conditions properly mirror the CreateSpecDialog validation, accounting for:

  • Empty project definition
  • Regeneration/feature generation in progress
  • Required worktree branch when enabled
  • Invalid custom feature count

@yumesha
Copy link
Contributor Author

yumesha commented Jan 10, 2026

@webdevcody the test get this error Test timeout of 180000ms exceeded

there no problem at all when I test it locally.

@webdevcody webdevcody changed the base branch from main to v0.11.0rc January 14, 2026 15:17
@webdevcody
Copy link
Collaborator

conflicts, also speak with @Shironex as he did some worktree fixes, I'm curious if these are still issues or not.

@webdevcody webdevcody deleted the branch AutoMaker-Org:v0.11.0rc January 15, 2026 01:31
@webdevcody webdevcody closed this Jan 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants