Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 66 additions & 30 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# Preserve git worktree context - prevent HEAD corruption in worktrees
if [ -f ".git" ]; then
WORKTREE_GIT_DIR=$(cat .git | sed 's/gitdir: //')
WORKTREE_GIT_DIR=$(sed 's/^gitdir: //' .git)
if [ -n "$WORKTREE_GIT_DIR" ]; then
export GIT_DIR="$WORKTREE_GIT_DIR"
export GIT_WORK_TREE="$(pwd)"
Comment on lines 2 to 8

This comment was marked as outdated.

Expand Down Expand Up @@ -170,45 +170,81 @@ fi
# Check if there are staged files in apps/frontend
if git diff --cached --name-only | grep -q "^apps/frontend/"; then
echo "Frontend changes detected, running frontend checks..."
# Use subshell to isolate directory changes and prevent worktree corruption
(
cd apps/frontend

# Run lint-staged (handles staged .ts/.tsx files)
npm exec lint-staged
if [ $? -ne 0 ]; then
echo "lint-staged failed. Please fix linting errors before committing."
exit 1
fi
# Detect if we're in a worktree and check if dependencies are available
IS_WORKTREE=false
DEPS_AVAILABLE=true

# Run TypeScript type check
echo "Running type check..."
npm run typecheck
if [ $? -ne 0 ]; then
echo "Type check failed. Please fix TypeScript errors before committing."
exit 1
fi
if [ -f ".git" ]; then
# .git is a file (not directory) in worktrees
IS_WORKTREE=true
echo "Detected git worktree environment"
fi

# Run linting
echo "Running lint..."
npm run lint
if [ $? -ne 0 ]; then
echo "Lint failed. Run 'npm run lint:fix' to auto-fix issues."
# Check if node_modules has actual dependencies by looking for a known package
# @lydell/node-pty is required for terminal code and is a common source of TypeScript errors
# It may be in root node_modules (hoisted) or apps/frontend/node_modules
# Note: -d follows symlinks automatically, so this works for both real dirs and symlinks
if [ ! -d "node_modules/@lydell" ] && [ ! -d "apps/frontend/node_modules/@lydell" ]; then
DEPS_AVAILABLE=false
fi

if [ "$DEPS_AVAILABLE" = false ]; then
if [ "$IS_WORKTREE" = true ]; then
# In worktree without dependencies - warn but allow commit
echo ""
echo "⚠️ WARNING: node_modules not available in this worktree."
echo " TypeScript and lint checks will be skipped."
echo " This is expected for auto-claude worktrees."
echo " Full validation will occur when PR is created/merged."
echo ""
else
# Main repo without dependencies - this is an error
echo "Error: node_modules not found. Run 'npm install' first."
exit 1
fi
else
# Dependencies available - run full frontend checks
# Use subshell to isolate directory changes and prevent worktree corruption
(
cd apps/frontend

# Run lint-staged (handles staged .ts/.tsx files)
npm exec lint-staged
if [ $? -ne 0 ]; then
echo "lint-staged failed. Please fix linting errors before committing."
exit 1
fi

# Run TypeScript type check
echo "Running type check..."
npm run typecheck
if [ $? -ne 0 ]; then
echo "Type check failed. Please fix TypeScript errors before committing."
exit 1
fi

# Run linting
echo "Running lint..."
npm run lint
if [ $? -ne 0 ]; then
echo "Lint failed. Run 'npm run lint:fix' to auto-fix issues."
exit 1
fi

# Check for vulnerabilities (only high severity)
echo "Checking for vulnerabilities..."
npm audit --audit-level=high
# Check for vulnerabilities (only high severity)
echo "Checking for vulnerabilities..."
npm audit --audit-level=high
if [ $? -ne 0 ]; then
echo "High severity vulnerabilities found. Run 'npm audit fix' to resolve."
exit 1
fi
)
if [ $? -ne 0 ]; then
echo "High severity vulnerabilities found. Run 'npm audit fix' to resolve."
exit 1
fi
)
if [ $? -ne 0 ]; then
exit 1
echo "Frontend checks passed!"
fi
echo "Frontend checks passed!"
fi

echo "All pre-commit checks passed!"
97 changes: 97 additions & 0 deletions apps/backend/core/workspace/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
"""

import json
import os
import shutil
import subprocess
import sys
from pathlib import Path

Expand Down Expand Up @@ -181,6 +183,93 @@ def copy_env_files_to_worktree(project_dir: Path, worktree_path: Path) -> list[s
return copied


def symlink_node_modules_to_worktree(
project_dir: Path, worktree_path: Path
) -> list[str]:
"""
Symlink node_modules directories from project root to worktree.

This ensures the worktree has access to dependencies for TypeScript checks
and other tooling without requiring a separate npm install.

Works with npm workspace hoisting where dependencies are hoisted to root
and workspace-specific dependencies remain in nested node_modules.

Args:
project_dir: The main project directory
worktree_path: Path to the worktree

Returns:
List of symlinked paths (relative to worktree)
"""
symlinked = []

# Directories to symlink: (source_relative, target_relative)
# These are the standard locations for npm/yarn/pnpm workspaces
node_modules_locations = [
("node_modules", "node_modules"),
("apps/frontend/node_modules", "apps/frontend/node_modules"),
]

for source_rel, target_rel in node_modules_locations:
source_path = project_dir / source_rel
target_path = worktree_path / target_rel

# Skip if source doesn't exist
if not source_path.exists():
debug(MODULE, f"Skipping {source_rel} - source does not exist")
continue

# Skip if target already exists (don't overwrite existing node_modules)
if target_path.exists():
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The check if target_path.exists(): is not sufficient to prevent errors when a broken symlink exists at the target path. pathlib.Path.exists() returns False for broken symlinks on POSIX systems, which would cause the code to attempt to create a new symlink and fail with a FileExistsError. Using target_path.lexists() checks for the existence of the path without following symlinks, correctly identifying broken symlinks and preventing the error.

Suggested change
if target_path.exists():
if target_path.lexists():

debug(MODULE, f"Skipping {target_rel} - target already exists")
continue

# Also skip if target is a symlink (even if broken - exists() returns False for broken symlinks)
if target_path.is_symlink():
debug(
MODULE,
f"Skipping {target_rel} - symlink already exists (possibly broken)",
)
continue

# Ensure parent directory exists
target_path.parent.mkdir(parents=True, exist_ok=True)

try:
if sys.platform == "win32":
# On Windows, use junctions instead of symlinks (no admin rights required)
# Junctions require absolute paths
result = subprocess.run(
["cmd", "/c", "mklink", "/J", str(target_path), str(source_path)],
capture_output=True,
text=True,
)
Copy link

Choose a reason for hiding this comment

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

Windows junction fails when paths contain spaces

Medium Severity

The Windows junction creation uses subprocess.run(["cmd", "/c", "mklink", "/J", str(target_path), str(source_path)]). When cmd.exe receives /c, it concatenates all following arguments with spaces into a single command line. If target_path or source_path contain spaces (e.g., C:\My Projects\app), mklink receives incorrectly split arguments and fails. The paths need to be quoted (e.g., f'"{target_path}"'). The TypeScript version uses Node's symlinkSync() API which handles this correctly.

Fix in Cursor Fix in Web

if result.returncode != 0:
raise OSError(result.stderr or "mklink /J failed")
else:
# On macOS/Linux, use relative symlinks for portability
relative_source = os.path.relpath(source_path, target_path.parent)
Comment on lines +252 to +265
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Find the platform abstraction module in the backend
find apps/backend -type d -name "platform" 2>/dev/null

Repository: AndyMik90/Auto-Claude

Length of output: 91


🏁 Script executed:

# Check the content of the setup.py file around lines 239-252
sed -n '230,260p' apps/backend/core/workspace/setup.py

Repository: AndyMik90/Auto-Claude

Length of output: 1462


🏁 Script executed:

# List files in the platform directory to see available modules
ls -la apps/backend/core/platform/ 2>/dev/null || echo "Platform directory not found"

Repository: AndyMik90/Auto-Claude

Length of output: 240


🏁 Script executed:

# Read the platform abstraction module to see available functions
cat -n apps/backend/core/platform/__init__.py

Repository: AndyMik90/Auto-Claude

Length of output: 18278


Replace sys.platform check with the platform abstraction module function.

Use is_windows() from core.platform instead of the direct sys.platform == "win32" check. The platform abstraction module provides the canonical functions for OS detection and should be used consistently across the backend codebase.

Change:

if sys.platform == "win32":

To:

if is_windows():

This requires adding the import: from core.platform import is_windows

🤖 Prompt for AI Agents
In `@apps/backend/core/workspace/setup.py` around lines 239 - 252, Replace the
direct platform check in the workspace setup code by using the platform
abstraction: in the block that currently tests "if sys.platform == 'win32'"
(around the symlink/junction creation logic), change that condition to use
is_windows() and add the import "from core.platform import is_windows" at the
top of the module; keep the existing junction creation logic (subprocess.run(...
"mklink" "/J"...)) unchanged except for the new condition.

os.symlink(relative_source, target_path)
symlinked.append(target_rel)
debug(MODULE, f"Symlinked {target_rel} -> {source_path}")
except OSError as e:
# Symlink/junction creation can fail on some systems (e.g., FAT32 filesystem)
# Log warning but don't fail - worktree is still usable, just without
# TypeScript checking
debug_warning(
MODULE,
f"Could not symlink {target_rel}: {e}. TypeScript checks may fail.",
)
# Warn user - pre-commit hooks may fail without dependencies
print_status(
f"Warning: Could not link {target_rel} - TypeScript checks may fail",
"warning",
)

return symlinked


def copy_spec_to_worktree(
source_spec_dir: Path,
worktree_path: Path,
Expand Down Expand Up @@ -268,6 +357,14 @@ def setup_workspace(
f"Environment files copied: {', '.join(copied_env_files)}", "success"
)

# Symlink node_modules to worktree for TypeScript and tooling support
# This allows pre-commit hooks to run typecheck without npm install in worktree
symlinked_modules = symlink_node_modules_to_worktree(
project_dir, worktree_info.path
)
if symlinked_modules:
print_status(f"Dependencies linked: {', '.join(symlinked_modules)}", "success")

# Copy security configuration files if they exist
# Note: Unlike env files, security files always overwrite to ensure
# the worktree uses the same security rules as the main project.
Expand Down
12 changes: 12 additions & 0 deletions apps/frontend/src/main/__tests__/ipc-handlers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,18 @@ vi.mock("electron-log/main.js", () => ({
},
}));

// Mock cli-tool-manager to avoid blocking tool detection on Windows
vi.mock("../cli-tool-manager", () => ({
getToolInfo: vi.fn(() => ({ found: false, path: null, source: "mock" })),
getToolPath: vi.fn((tool: string) => tool),
deriveGitBashPath: vi.fn(() => null),
clearCache: vi.fn(),
clearToolCache: vi.fn(),
configureTools: vi.fn(),
preWarmToolCache: vi.fn(() => Promise.resolve()),
getToolPathAsync: vi.fn((tool: string) => Promise.resolve(tool)),
}));

// Mock modules before importing
vi.mock("electron", () => {
const mockIpcMain = new (class extends EventEmitter {
Expand Down
12 changes: 12 additions & 0 deletions apps/frontend/src/main/agent/agent-process.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,18 @@ vi.mock('electron', () => ({
}
}));

// Mock cli-tool-manager to avoid blocking tool detection on Windows
vi.mock('../cli-tool-manager', () => ({
getToolInfo: vi.fn(() => ({ found: false, path: null, source: 'mock' })),
deriveGitBashPath: vi.fn(() => null),
clearCache: vi.fn()
}));

// Mock env-utils to avoid blocking environment augmentation
vi.mock('../env-utils', () => ({
getAugmentedEnv: vi.fn(() => ({ ...process.env }))
}));
Comment on lines +114 to +124
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify which cli-tool-manager functions are actually imported/used by agent-process.ts
echo "=== Checking cli-tool-manager imports in agent-process.ts ==="
rg -n "from.*cli-tool-manager" apps/frontend/src/main/agent/agent-process.ts

echo ""
echo "=== Checking cli-tool-manager function usage in agent-process.ts ==="
rg -n "(getToolInfo|getToolPath|deriveGitBashPath|clearCache|clearToolCache|configureTools|preWarmToolCache|getToolPathAsync)" apps/frontend/src/main/agent/agent-process.ts

echo ""
echo "=== Checking for platform-specific code and platform mocking in test file ==="
rg -n "process\.platform" apps/frontend/src/main/agent/agent-process.test.ts

echo ""
echo "=== Checking for platform abstraction function usage ==="
rg -n "(isWindows|isMacOS|isLinux)" apps/frontend/src/main/agent/agent-process.ts

Repository: AndyMik90/Auto-Claude

Length of output: 674


🏁 Script executed:

#!/bin/bash
echo "=== Checking env-utils imports and usage in agent-process.ts ==="
rg -n "env-utils" apps/frontend/src/main/agent/agent-process.ts

echo ""
echo "=== All imports from cli-tool-manager and env-utils in agent-process.ts ==="
rg -n "^import.*from.*(cli-tool-manager|env-utils)" apps/frontend/src/main/agent/agent-process.ts

echo ""
echo "=== Checking what getToolInfo does in cli-tool-manager ==="
head -100 apps/frontend/src/main/cli-tool-manager.ts | cat -n

echo ""
echo "=== Looking for platform-specific logic in agent-process.ts (Windows checks) ==="
rg -n -i "(windows|win32|platform|bash)" apps/frontend/src/main/agent/agent-process.ts | head -20

Repository: AndyMik90/Auto-Claude

Length of output: 5738


🏁 Script executed:

#!/bin/bash
echo "=== Checking getAugmentedEnv usage in agent-process.ts ==="
rg -n "getAugmentedEnv" apps/frontend/src/main/agent/agent-process.ts

echo ""
echo "=== Checking complete deriveGitBashPath function signature ==="
sed -n '29,80p' apps/frontend/src/main/agent/agent-process.ts | cat -n

echo ""
echo "=== Checking test file to see if it tests cross-platform ==="
cat apps/frontend/src/main/agent/agent-process.test.ts | head -150 | tail -50

Repository: AndyMik90/Auto-Claude

Length of output: 4261


Fix incorrect mock and coding guideline violations in test and implementation.

The mocks have structural issues and the code violates platform abstraction guidelines:

  1. Incorrect mock structure: deriveGitBashPath is a local function in agent-process.ts (line 29), not exported from cli-tool-manager. Remove it from the mock.

  2. Missing mock: getOAuthModeClearVars is imported from ./env-utils but not mocked.

  3. Unnecessary mock: clearCache is mocked but not imported or used by agent-process.ts.

  4. Code violation: deriveGitBashPath uses process.platform !== 'win32' (line 30) and process.platform === 'win32' (line 128) directly, violating the coding guideline to use platform abstraction functions (isWindows(), etc.) from the platform module instead.

  5. Missing cross-platform test coverage: Per coding guidelines, tests for platform-specific code must mock process.platform to test all three platforms (Windows, macOS, Linux). The current test lacks this coverage.

🤖 Prompt for AI Agents
In `@apps/frontend/src/main/agent/agent-process.test.ts` around lines 114 - 124,
Update the test mocks and implementation to match exports and platform
abstraction: remove deriveGitBashPath and clearCache from the mocked
'../cli-tool-manager' and only mock getToolInfo there; add a mock for
getOAuthModeClearVars in './env-utils' alongside getAugmentedEnv; in
agent-process.ts replace direct process.platform checks (references to
deriveGitBashPath usage and the lines that check process.platform === 'win32' /
!== 'win32') with the platform helpers isWindows(), isMac(), isLinux() from the
platform module and update any logic that previously called deriveGitBashPath
accordingly; finally extend agent-process.test.ts to explicitly mock
process.platform (or the platform helpers) to exercise Windows, macOS and Linux
code paths so platform-specific behavior is covered.


// Mock fs.existsSync for getAutoBuildSourcePath path validation
vi.mock('fs', async (importOriginal) => {
const actual = await importOriginal<typeof import('fs')>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type {
TerminalWorktreeResult,
} from '../../../shared/types';
import path from 'path';
import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, rmSync } from 'fs';
import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, rmSync, symlinkSync, lstatSync } from 'fs';
import { execFileSync } from 'child_process';
import { minimatch } from 'minimatch';
import { debugLog, debugError } from '../../../shared/utils/debug-logger';
Expand Down Expand Up @@ -193,6 +193,80 @@ function getDefaultBranch(projectPath: string): string {
}
}

/**
* Symlink node_modules from project root to worktree for TypeScript and tooling support.
* This allows pre-commit hooks and IDE features to work without npm install in the worktree.
*
* @param projectPath - The main project directory
* @param worktreePath - Path to the worktree
* @returns Array of symlinked paths (relative to worktree)
*/
function symlinkNodeModulesToWorktree(projectPath: string, worktreePath: string): string[] {
const symlinked: string[] = [];

// Standard locations for npm/yarn/pnpm workspaces
const nodeModulesLocations = [
['node_modules', 'node_modules'],
['apps/frontend/node_modules', 'apps/frontend/node_modules'],
];

for (const [sourceRel, targetRel] of nodeModulesLocations) {
const sourcePath = path.join(projectPath, sourceRel);
const targetPath = path.join(worktreePath, targetRel);

// Skip if source doesn't exist
if (!existsSync(sourcePath)) {
debugLog('[TerminalWorktree] Skipping symlink - source does not exist:', sourceRel);
continue;
}

// Skip if target already exists (don't overwrite existing node_modules)
if (existsSync(targetPath)) {
debugLog('[TerminalWorktree] Skipping symlink - target already exists:', targetRel);
continue;
}

// Also skip if target is a symlink (even if broken)
try {
lstatSync(targetPath);
debugLog('[TerminalWorktree] Skipping symlink - target exists (possibly broken symlink):', targetRel);
continue;
} catch {
// Target doesn't exist at all - good, we can create symlink
}
Comment on lines +237 to +249
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The logic to check if the target path exists before creating a symlink can be simplified. You are currently using existsSync() followed by a try/catch block with lstatSync() to handle regular files/directories and broken symlinks. This can be consolidated into a single try/catch block with lstatSync(), which makes the code cleaner and more direct. lstatSync() does not follow symlinks, so if it succeeds, it means something already exists at that path. If it fails with ENOENT, the path is clear.

Suggested change
if (existsSync(targetPath)) {
debugLog('[TerminalWorktree] Skipping symlink - target already exists:', targetRel);
continue;
}
// Also skip if target is a symlink (even if broken)
try {
lstatSync(targetPath);
debugLog('[TerminalWorktree] Skipping symlink - target exists (possibly broken symlink):', targetRel);
continue;
} catch {
// Target doesn't exist at all - good, we can create symlink
}
// Skip if target already exists (file, dir, or symlink)
try {
lstatSync(targetPath);
// If lstatSync succeeds, a file/symlink/directory already exists.
debugLog('[TerminalWorktree] Skipping symlink - target already exists:', targetRel);
continue;
} catch (e: any) {
// If the error is anything other than "file not found", it's an
// unexpected issue (e.g., permissions), so we should skip.
if (e.code !== 'ENOENT') {
debugError('[TerminalWorktree] Error checking target path, skipping:', targetRel, e);
continue;
}
// ENOENT is the expected error when the path is free, so we can proceed.
}


// Ensure parent directory exists
const targetDir = path.dirname(targetPath);
if (!existsSync(targetDir)) {
mkdirSync(targetDir, { recursive: true });
}
Copy link

Choose a reason for hiding this comment

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

Missing error handling for mkdir causes unexpected failures

Medium Severity

The mkdirSync call (TypeScript) and target_path.parent.mkdir() call (Python) are placed outside the try/catch blocks that handle symlink creation errors. The code comments explicitly state the intent is non-fatal behavior: "Log warning but don't fail - worktree is still usable, just without TypeScript checking." However, if mkdir fails (permission denied, disk full, etc.), an unhandled exception propagates up, causing the entire worktree creation to fail in TypeScript or workspace setup to fail in Python. This contradicts the stated design intent where symlink-related failures should only produce warnings.

Additional Locations (1)

Fix in Cursor Fix in Web


try {
// Platform-specific symlink creation:
// - Windows: Use 'junction' type which requires absolute paths (no admin rights required)
// - Unix (macOS/Linux): Use relative paths for portability (worktree can be moved)
if (process.platform === 'win32') {
symlinkSync(sourcePath, targetPath, 'junction');
debugLog('[TerminalWorktree] Created junction (Windows):', targetRel, '->', sourcePath);
} else {
// On Unix, use relative symlinks for portability (matches Python implementation)
const relativePath = path.relative(path.dirname(targetPath), sourcePath);
symlinkSync(relativePath, targetPath);
debugLog('[TerminalWorktree] Created symlink (Unix):', targetRel, '->', relativePath);
Comment on lines +257 to +268
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Find the platform abstraction module referenced in the guidelines
fd -t f "platform" apps/frontend/src/main --type d

Repository: AndyMik90/Auto-Claude

Length of output: 186


🏁 Script executed:

# Search for platform abstraction module files
find apps/frontend/src/main -name "*platform*" -type f

Repository: AndyMik90/Auto-Claude

Length of output: 123


🏁 Script executed:

# Look for the main platform abstraction file
fd platform . -t f | grep -E "(frontend|main)" | head -20

Repository: AndyMik90/Auto-Claude

Length of output: 125


🏁 Script executed:

# Find the main platform module file (likely index.ts or platform.ts)
fd -t f . apps/frontend/src/main/platform --maxdepth 1

Repository: AndyMik90/Auto-Claude

Length of output: 187


🏁 Script executed:

# Read the platform module to see exported functions
cat apps/frontend/src/main/platform/index.ts 2>/dev/null || cat apps/frontend/src/main/platform/platform.ts 2>/dev/null || ls -la apps/frontend/src/main/platform/

Repository: AndyMik90/Auto-Claude

Length of output: 10296


🏁 Script executed:

# Verify the problematic file exists
wc -l apps/frontend/src/main/ipc-handlers/terminal/worktree-handlers.ts

Repository: AndyMik90/Auto-Claude

Length of output: 134


🏁 Script executed:

# Read the problematic file around lines 244-255
sed -n '240,260p' apps/frontend/src/main/ipc-handlers/terminal/worktree-handlers.ts

Repository: AndyMik90/Auto-Claude

Length of output: 1179


🏁 Script executed:

# Check for imports in the file
head -50 apps/frontend/src/main/ipc-handlers/terminal/worktree-handlers.ts | grep -E "import|from"

Repository: AndyMik90/Auto-Claude

Length of output: 640


🏁 Script executed:

# Check for any other process.platform usage in this file
grep -n "process\.platform" apps/frontend/src/main/ipc-handlers/terminal/worktree-handlers.ts

Repository: AndyMik90/Auto-Claude

Length of output: 110


Replace process.platform check with isWindows() helper.

Line 248 uses direct process.platform === 'win32' check. This file is in apps/frontend/src/main/** and must use platform abstraction helpers from the platform module. Import isWindows() and replace the check:

if (isWindows()) {

Add to imports:

import { isWindows } from '../../platform';
🤖 Prompt for AI Agents
In `@apps/frontend/src/main/ipc-handlers/terminal/worktree-handlers.ts` around
lines 244 - 255, Replace the direct platform check in the symlink creation block
by importing and using the platform helper: add an import for isWindows from
'../../platform' and change the condition from process.platform === 'win32' to
if (isWindows()) (the block around symlinkSync, relativePath, and debugLog in
worktree-handlers.ts); ensure no other references to process.platform remain in
that branch so the platform abstraction is used consistently.

}
symlinked.push(targetRel);
} catch (error) {
// Symlink creation can fail on some systems (e.g., FAT32 filesystem, or permission issues)
// Log warning but don't fail - worktree is still usable, just without TypeScript checking
// Note: This warning appears in dev console. Users may see TypeScript errors in pre-commit hooks.
debugError('[TerminalWorktree] Could not create symlink for', targetRel, ':', error);
console.warn(`[TerminalWorktree] Warning: Failed to link ${targetRel} - TypeScript checks may fail in this worktree`);
}
}

return symlinked;
}
Comment on lines 196 to 281
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add cross-platform tests for symlinkNodeModulesToWorktree.

This function has OS-specific branches; please add unit tests that mock platform detection for Windows, macOS, and Linux to validate junction vs. relative symlink behavior. As per coding guidelines, platform-specific logic should be tested across all three OSes.

🤖 Prompt for AI Agents
In `@apps/frontend/src/main/ipc-handlers/terminal/worktree-handlers.ts` around
lines 196 - 268, Add unit tests for symlinkNodeModulesToWorktree that cover all
platform branches (win32, darwin, linux): mock process.platform to each value,
stub filesystem calls (existsSync, lstatSync, mkdirSync, symlinkSync) and ensure
symlinkSync is called with 'junction' and absolute source on Windows and with a
relative path on Unix; also validate that targets already existing or missing
sources are skipped (using nodeModulesLocations), that debugError is invoked on
symlink errors, and that the returned symlinked array contains the expected
targetRel entries; use your test runner's spy/stub utilities (e.g., jest.spyOn
or sinon) and restore originals after each test.


function saveWorktreeConfig(projectPath: string, name: string, config: TerminalWorktreeConfig): void {
const metadataDir = getTerminalWorktreeMetadataDir(projectPath);
mkdirSync(metadataDir, { recursive: true });
Expand Down Expand Up @@ -342,6 +416,13 @@ async function createTerminalWorktree(
debugLog('[TerminalWorktree] Created worktree in detached HEAD mode from', baseRef);
}

// Symlink node_modules for TypeScript and tooling support
// This allows pre-commit hooks to run typecheck without npm install in worktree
const symlinkedModules = symlinkNodeModulesToWorktree(projectPath, worktreePath);
if (symlinkedModules.length > 0) {
debugLog('[TerminalWorktree] Symlinked dependencies:', symlinkedModules.join(', '));
}

const config: TerminalWorktreeConfig = {
name,
worktreePath,
Expand Down
Loading
Loading