Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
98 changes: 68 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,83 @@ 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
# We check for the full package path (@lydell/node-pty) rather than just the namespace
# for precise detection - ensures the actual dependency is installed, not just any @lydell package
if [ ! -d "node_modules/@lydell/node-pty" ] && [ ! -d "apps/frontend/node_modules/@lydell/node-pty" ]; 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!"
110 changes: 110 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,106 @@ 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 = []

# Node modules locations to symlink for TypeScript and tooling support.
# These are the standard locations for this monorepo structure.
#
# Design rationale:
# - Hardcoded paths are intentional for simplicity and reliability
# - Dynamic discovery (reading workspaces from package.json) would add complexity
# and potential failure points without significant benefit
# - This monorepo uses npm workspaces with hoisting, so dependencies are primarily
# in root node_modules with workspace-specific deps in apps/frontend/node_modules
#
# To add new workspace locations:
# 1. Add (source_rel, target_rel) tuple below
# 2. Update the parallel TypeScript implementation in
# apps/frontend/src/main/ipc-handlers/terminal/worktree-handlers.ts
# 3. Update the pre-commit hook check in .husky/pre-commit if needed
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 +370,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
Loading
Loading