diff --git a/apps/backend/implementation_plan/__init__.py b/apps/backend/implementation_plan/__init__.py index 425a1726..988baab7 100644 --- a/apps/backend/implementation_plan/__init__.py +++ b/apps/backend/implementation_plan/__init__.py @@ -25,9 +25,8 @@ Package Structure: - factories.py: Factory functions for creating different plan types """ -# Export all public types and functions for backwards compatibility +# Export all public types and functions from .enums import ( - ChunkStatus, # Backwards compatibility PhaseType, SubtaskStatus, VerificationType, @@ -40,7 +39,7 @@ from .factories import ( ) from .phase import Phase from .plan import ImplementationPlan -from .subtask import Chunk, Subtask # Chunk is backwards compatibility alias +from .subtask import Subtask from .verification import Verification __all__ = [ @@ -58,7 +57,4 @@ __all__ = [ "create_feature_plan", "create_investigation_plan", "create_refactor_plan", - # Backwards compatibility - "Chunk", - "ChunkStatus", ] diff --git a/apps/backend/implementation_plan/enums.py b/apps/backend/implementation_plan/enums.py index ecdc2322..15d24726 100644 --- a/apps/backend/implementation_plan/enums.py +++ b/apps/backend/implementation_plan/enums.py @@ -51,7 +51,3 @@ class VerificationType(str, Enum): COMPONENT = "component" # Component renders correctly MANUAL = "manual" # Requires human verification NONE = "none" # No verification needed (investigation) - - -# Backwards compatibility aliases -ChunkStatus = SubtaskStatus diff --git a/apps/backend/implementation_plan/subtask.py b/apps/backend/implementation_plan/subtask.py index 7edee349..71edf948 100644 --- a/apps/backend/implementation_plan/subtask.py +++ b/apps/backend/implementation_plan/subtask.py @@ -126,7 +126,3 @@ class Subtask: self.completed_at = None # Clear to maintain consistency (failed != completed) if reason: self.actual_output = f"FAILED: {reason}" - - -# Backwards compatibility alias -Chunk = Subtask diff --git a/apps/backend/runners/github/models.py b/apps/backend/runners/github/models.py index f1f51cc4..2fac3c32 100644 --- a/apps/backend/runners/github/models.py +++ b/apps/backend/runners/github/models.py @@ -848,9 +848,6 @@ class GitHubRunnerConfig: auto_post_reviews: bool = False allow_fix_commits: bool = True review_own_prs: bool = False # Whether bot can review its own PRs - use_orchestrator_review: bool = ( - True # DEPRECATED: No longer used, kept for config compatibility - ) use_parallel_orchestrator: bool = ( True # Use SDK subagent parallel orchestrator (default) ) diff --git a/apps/backend/service_orchestrator.py b/apps/backend/service_orchestrator.py deleted file mode 100644 index 8d5935c2..00000000 --- a/apps/backend/service_orchestrator.py +++ /dev/null @@ -1,19 +0,0 @@ -"""Backward compatibility shim - import from services.orchestrator instead.""" - -from services.orchestrator import ( - OrchestrationResult, - ServiceConfig, - ServiceContext, - ServiceOrchestrator, - get_service_config, - is_multi_service_project, -) - -__all__ = [ - "ServiceConfig", - "OrchestrationResult", - "ServiceOrchestrator", - "ServiceContext", - "is_multi_service_project", - "get_service_config", -] diff --git a/apps/backend/services/orchestrator.py b/apps/backend/services/orchestrator.py index 244ed8bd..03341db6 100644 --- a/apps/backend/services/orchestrator.py +++ b/apps/backend/services/orchestrator.py @@ -11,7 +11,7 @@ The service orchestrator is used by: - Validation Strategy: To determine if multi-service orchestration is needed Usage: - from service_orchestrator import ServiceOrchestrator + from services.orchestrator import ServiceOrchestrator orchestrator = ServiceOrchestrator(project_dir) if orchestrator.is_multi_service(): diff --git a/apps/backend/spec/validation_strategy.py b/apps/backend/spec/validation_strategy.py index 687fa9ac..fc9bb394 100644 --- a/apps/backend/spec/validation_strategy.py +++ b/apps/backend/spec/validation_strategy.py @@ -11,7 +11,7 @@ The validation strategy is used by: - QA Agent: To determine what tests to create and run Usage: - from validation_strategy import ValidationStrategyBuilder + from spec.validation_strategy import ValidationStrategyBuilder builder = ValidationStrategyBuilder() strategy = builder.build_strategy(project_dir, spec_dir, "medium") diff --git a/apps/backend/validation_strategy.py b/apps/backend/validation_strategy.py deleted file mode 100644 index 479b3d30..00000000 --- a/apps/backend/validation_strategy.py +++ /dev/null @@ -1,3 +0,0 @@ -"""Backward compatibility shim - import from spec.validation_strategy instead.""" - -from spec.validation_strategy import * # noqa: F403 diff --git a/apps/frontend/src/main/agent/agent-process.ts b/apps/frontend/src/main/agent/agent-process.ts index 76f0a4b4..27d79533 100644 --- a/apps/frontend/src/main/agent/agent-process.ts +++ b/apps/frontend/src/main/agent/agent-process.ts @@ -24,7 +24,7 @@ import type { AppSettings } from '../../shared/types/settings'; import { getOAuthModeClearVars } from './env-utils'; import { getAugmentedEnv } from '../env-utils'; import { getToolInfo, getClaudeCliPathForSdk } from '../cli-tool-manager'; -import { killProcessGracefully } from '../platform'; +import { killProcessGracefully, isWindows } from '../platform'; /** * Type for supported CLI tools @@ -42,7 +42,7 @@ const CLI_TOOL_ENV_MAP: Readonly> = { function deriveGitBashPath(gitExePath: string): string | null { - if (process.platform !== 'win32') { + if (!isWindows()) { return null; } @@ -181,7 +181,7 @@ export class AgentProcessManager { // On Windows, detect and pass git-bash path for Claude Code CLI // Electron can detect git via where.exe, but Python subprocess may not have the same PATH const gitBashEnv: Record = {}; - if (process.platform === 'win32' && !process.env.CLAUDE_CODE_GIT_BASH_PATH) { + if (isWindows() && !process.env.CLAUDE_CODE_GIT_BASH_PATH) { try { const gitInfo = getToolInfo('git'); if (gitInfo.found && gitInfo.path) { diff --git a/apps/frontend/src/main/agent/agent-queue.ts b/apps/frontend/src/main/agent/agent-queue.ts index 785f4330..d8c2ccd6 100644 --- a/apps/frontend/src/main/agent/agent-queue.ts +++ b/apps/frontend/src/main/agent/agent-queue.ts @@ -18,6 +18,7 @@ import { pythonEnvManager } from '../python-env-manager'; import { transformIdeaFromSnakeCase, transformSessionFromSnakeCase } from '../ipc-handlers/ideation/transformers'; import { transformRoadmapFromSnakeCase } from '../ipc-handlers/roadmap/transformers'; import type { RawIdea } from '../ipc-handlers/ideation/types'; +import { getPathDelimiter } from '../platform'; /** Maximum length for status messages displayed in progress UI */ const STATUS_MESSAGE_MAX_LENGTH = 200; @@ -348,7 +349,7 @@ export class AgentQueueManager { if (autoBuildSource) { pythonPathParts.push(autoBuildSource); } - const combinedPythonPath = pythonPathParts.join(process.platform === 'win32' ? ';' : ':'); + const combinedPythonPath = pythonPathParts.join(getPathDelimiter()); // Build final environment with proper precedence: // 1. process.env (system) @@ -675,7 +676,7 @@ export class AgentQueueManager { if (autoBuildSource) { pythonPathParts.push(autoBuildSource); } - const combinedPythonPath = pythonPathParts.join(process.platform === 'win32' ? ';' : ':'); + const combinedPythonPath = pythonPathParts.join(getPathDelimiter()); // Build final environment with proper precedence: // 1. process.env (system) diff --git a/apps/frontend/src/main/changelog/generator.ts b/apps/frontend/src/main/changelog/generator.ts index 6fa75c06..4c782413 100644 --- a/apps/frontend/src/main/changelog/generator.ts +++ b/apps/frontend/src/main/changelog/generator.ts @@ -14,6 +14,7 @@ import { getCommits, getBranchDiffCommits } from './git-integration'; import { detectRateLimit, createSDKRateLimitInfo, getProfileEnv } from '../rate-limit-detector'; import { parsePythonCommand } from '../python-detector'; import { getAugmentedEnv } from '../env-utils'; +import { isWindows } from '../platform'; /** * Core changelog generation logic @@ -245,7 +246,6 @@ export class ChangelogGenerator extends EventEmitter { */ private buildSpawnEnvironment(): Record { const homeDir = os.homedir(); - const isWindows = process.platform === 'win32'; // Use getAugmentedEnv() to ensure common tool paths are available // even when app is launched from Finder/Dock @@ -265,7 +265,7 @@ export class ChangelogGenerator extends EventEmitter { ...profileEnv, // Include active Claude profile config // Ensure critical env vars are set for claude CLI // Use USERPROFILE on Windows, HOME on Unix - ...(isWindows ? { USERPROFILE: homeDir } : { HOME: homeDir }), + ...(isWindows() ? { USERPROFILE: homeDir } : { HOME: homeDir }), USER: process.env.USER || process.env.USERNAME || 'user', PYTHONUNBUFFERED: '1', PYTHONIOENCODING: 'utf-8', diff --git a/apps/frontend/src/main/changelog/version-suggester.ts b/apps/frontend/src/main/changelog/version-suggester.ts index e195f9c7..47acdcf5 100644 --- a/apps/frontend/src/main/changelog/version-suggester.ts +++ b/apps/frontend/src/main/changelog/version-suggester.ts @@ -4,6 +4,7 @@ import type { GitCommit } from '../../shared/types'; import { getProfileEnv } from '../rate-limit-detector'; import { parsePythonCommand } from '../python-detector'; import { getAugmentedEnv } from '../env-utils'; +import { isWindows, requiresShell } from '../platform'; interface VersionSuggestion { version: string; @@ -144,7 +145,7 @@ Respond with ONLY a JSON object in this exact format (no markdown, no extra text // Detect if this is a Windows batch file (.cmd or .bat) // These require shell=True in subprocess.run() because they need cmd.exe to execute - const isCmdFile = /\.(cmd|bat)$/i.test(this.claudePath); + const needsShell = requiresShell(this.claudePath); return ` import subprocess @@ -154,13 +155,13 @@ import sys prompt = "${escapedPrompt}" try: - # shell=${isCmdFile ? 'True' : 'False'} - Windows .cmd files require shell execution + # shell=${needsShell ? 'True' : 'False'} - Windows .cmd files require shell execution result = subprocess.run( ["${escapedClaudePath}", "chat", "--model", "haiku", "--prompt", prompt], capture_output=True, text=True, check=True, - shell=${isCmdFile ? 'True' : 'False'} + shell=${needsShell ? 'True' : 'False'} ) print(result.stdout) except subprocess.CalledProcessError as e: @@ -227,7 +228,6 @@ except Exception as e: */ private buildSpawnEnvironment(): Record { const homeDir = os.homedir(); - const isWindows = process.platform === 'win32'; // Use getAugmentedEnv() to ensure common tool paths are available // even when app is launched from Finder/Dock @@ -240,7 +240,7 @@ except Exception as e: ...augmentedEnv, ...profileEnv, // Ensure critical env vars are set for claude CLI - ...(isWindows ? { USERPROFILE: homeDir } : { HOME: homeDir }), + ...(isWindows() ? { USERPROFILE: homeDir } : { HOME: homeDir }), USER: process.env.USER || process.env.USERNAME || 'user', PYTHONUNBUFFERED: '1', PYTHONIOENCODING: 'utf-8', diff --git a/apps/frontend/src/main/claude-cli-utils.ts b/apps/frontend/src/main/claude-cli-utils.ts index 49a0c49c..943f0a91 100644 --- a/apps/frontend/src/main/claude-cli-utils.ts +++ b/apps/frontend/src/main/claude-cli-utils.ts @@ -1,6 +1,7 @@ import path from 'path'; import { getAugmentedEnv, getAugmentedEnvAsync } from './env-utils'; import { getToolPath, getToolPathAsync } from './cli-tool-manager'; +import { isWindows, getPathDelimiter } from './platform'; export type ClaudeCliInvocation = { command: string; @@ -12,12 +13,12 @@ function ensureCommandDirInPath(command: string, env: Record): R return env; } - const pathSeparator = process.platform === 'win32' ? ';' : ':'; + const pathSeparator = getPathDelimiter(); const commandDir = path.dirname(command); const currentPath = env.PATH || ''; const pathEntries = currentPath.split(pathSeparator); const normalizedCommandDir = path.normalize(commandDir); - const hasCommandDir = process.platform === 'win32' + const hasCommandDir = isWindows() ? pathEntries .map((entry) => path.normalize(entry).toLowerCase()) .includes(normalizedCommandDir.toLowerCase()) diff --git a/apps/frontend/src/main/config-paths.ts b/apps/frontend/src/main/config-paths.ts index bf17dbf3..839da5b8 100644 --- a/apps/frontend/src/main/config-paths.ts +++ b/apps/frontend/src/main/config-paths.ts @@ -16,6 +16,7 @@ import * as path from 'path'; import * as os from 'os'; +import { isLinux } from './platform'; const APP_NAME = 'auto-claude'; @@ -76,7 +77,7 @@ export function getMemoriesDir(): string { const legacyPath = path.join(os.homedir(), '.auto-claude', 'memories'); // On Linux with XDG variables set (AppImage, Flatpak, Snap), use XDG path - if (process.platform === 'linux' && (process.env.XDG_DATA_HOME || process.env.APPIMAGE || process.env.SNAP || process.env.FLATPAK_ID)) { + if (isLinux() && (process.env.XDG_DATA_HOME || process.env.APPIMAGE || process.env.SNAP || process.env.FLATPAK_ID)) { return path.join(getXdgDataHome(), APP_NAME, 'memories'); } diff --git a/apps/frontend/src/main/index.ts b/apps/frontend/src/main/index.ts index eebbcc7c..04b36fdf 100644 --- a/apps/frontend/src/main/index.ts +++ b/apps/frontend/src/main/index.ts @@ -52,6 +52,7 @@ import { setupErrorLogging } from './app-logger'; import { initSentryMain } from './sentry'; import { preWarmToolCache } from './cli-tool-manager'; import { initializeClaudeProfileManager } from './claude-profile-manager'; +import { isMacOS, isWindows } from './platform'; import type { AppSettings } from '../shared/types'; // ───────────────────────────────────────────────────────────────────────────── @@ -121,10 +122,10 @@ function getIconPath(): string { : join(process.resourcesPath); let iconName: string; - if (process.platform === 'darwin') { + if (isMacOS()) { // Use PNG in dev mode (works better), ICNS in production iconName = is.dev ? 'icon-256.png' : 'icon.icns'; - } else if (process.platform === 'win32') { + } else if (isWindows()) { iconName = 'icon.ico'; } else { iconName = 'icon.png'; @@ -245,13 +246,13 @@ function createWindow(): void { // Set app name before ready (for dock tooltip on macOS in dev mode) app.setName('Auto Claude'); -if (process.platform === 'darwin') { +if (isMacOS()) { // Force the name to appear in dock on macOS app.name = 'Auto Claude'; } // Fix Windows GPU cache permission errors (0x5 Access Denied) -if (process.platform === 'win32') { +if (isWindows()) { app.commandLine.appendSwitch('disable-gpu-shader-disk-cache'); app.commandLine.appendSwitch('disable-gpu-program-cache'); console.log('[main] Applied Windows GPU cache fixes'); @@ -263,7 +264,7 @@ app.whenReady().then(() => { electronApp.setAppUserModelId('com.autoclaude.ui'); // Clear cache on Windows to prevent permission errors from stale cache - if (process.platform === 'win32') { + if (isWindows()) { session.defaultSession.clearCache() .then(() => console.log('[main] Cleared cache on startup')) .catch((err) => console.warn('[main] Failed to clear cache:', err)); @@ -274,7 +275,7 @@ app.whenReady().then(() => { cleanupStaleUpdateMetadata(); // Set dock icon on macOS - if (process.platform === 'darwin') { + if (isMacOS()) { const iconPath = getIconPath(); try { const icon = nativeImage.createFromPath(iconPath); @@ -458,7 +459,7 @@ app.whenReady().then(() => { // Quit when all windows are closed (except on macOS) app.on('window-all-closed', () => { - if (process.platform !== 'darwin') { + if (!isMacOS()) { app.quit(); } }); diff --git a/apps/frontend/src/main/insights/config.ts b/apps/frontend/src/main/insights/config.ts index 97e8a9a2..553a171e 100644 --- a/apps/frontend/src/main/insights/config.ts +++ b/apps/frontend/src/main/insights/config.ts @@ -7,6 +7,7 @@ import { pythonEnvManager, getConfiguredPythonPath } from '../python-env-manager import { getValidatedPythonPath } from '../python-detector'; import { getAugmentedEnv } from '../env-utils'; import { getEffectiveSourcePath } from '../updater/path-resolver'; +import { isWindows } from '../platform'; /** * Configuration manager for insights service @@ -121,11 +122,11 @@ export class InsightsConfig { if (autoBuildSource) { const normalizedAutoBuildSource = path.resolve(autoBuildSource); - const autoBuildComparator = process.platform === 'win32' + const autoBuildComparator = isWindows() ? normalizedAutoBuildSource.toLowerCase() : normalizedAutoBuildSource; const hasAutoBuildSource = pythonPathParts.some((entry) => { - const candidate = process.platform === 'win32' ? entry.toLowerCase() : entry; + const candidate = isWindows() ? entry.toLowerCase() : entry; return candidate === autoBuildComparator; }); diff --git a/apps/frontend/src/main/ipc-handlers/claude-code-handlers.ts b/apps/frontend/src/main/ipc-handlers/claude-code-handlers.ts index d9636543..b068a2d7 100644 --- a/apps/frontend/src/main/ipc-handlers/claude-code-handlers.ts +++ b/apps/frontend/src/main/ipc-handlers/claude-code-handlers.ts @@ -20,6 +20,7 @@ import type { ClaudeCodeVersionInfo, ClaudeInstallationList, ClaudeInstallationI import { getToolInfo, configureTools, sortNvmVersionDirs, getClaudeDetectionPaths, type ExecFileAsyncOptionsWithVerbatim } from '../cli-tool-manager'; import { readSettingsFile, writeSettingsFile } from '../settings-utils'; import { isSecurePath } from '../utils/windows-paths'; +import { isWindows, isMacOS, isLinux } from '../platform'; import { getClaudeProfileManager } from '../claude-profile-manager'; import { isValidConfigDir } from '../utils/config-path-validator'; import { clearKeychainCache } from '../claude-profile/credential-utils'; @@ -41,10 +42,8 @@ const VERSION_LIST_CACHE_DURATION_MS = 60 * 60 * 1000; // 1 hour for version lis */ async function validateClaudeCliAsync(cliPath: string): Promise<[boolean, string | null]> { try { - const isWindows = process.platform === 'win32'; - // Security validation: reject paths with shell metacharacters or directory traversal - if (isWindows && !isSecurePath(cliPath)) { + if (isWindows() && !isSecurePath(cliPath)) { throw new Error(`Claude CLI path failed security validation: ${cliPath}`); } @@ -60,7 +59,7 @@ async function validateClaudeCliAsync(cliPath: string): Promise<[boolean, string // /d = disable AutoRun registry commands // /s = strip first and last quotes, preserving inner quotes // /c = run command then terminate - if (isWindows && /\.(cmd|bat)$/i.test(cliPath)) { + if (isWindows() && /\.(cmd|bat)$/i.test(cliPath)) { // Get cmd.exe path from environment or use default const cmdExe = process.env.ComSpec || path.join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'cmd.exe'); @@ -108,7 +107,6 @@ async function scanClaudeInstallations(activePath: string | null): Promise(); const homeDir = os.homedir(); - const isWindows = process.platform === 'win32'; // Get detection paths from cli-tool-manager (single source of truth) const detectionPaths = getClaudeDetectionPaths(homeDir); @@ -148,7 +146,7 @@ async function scanClaudeInstallations(activePath: string | null): Promise p.trim()); for (const p of paths) { @@ -166,14 +164,14 @@ async function scanClaudeInstallations(activePath: string | null): Promise { * @param version - The version to install (e.g., "1.0.5") */ function getInstallVersionCommand(version: string): string { - if (process.platform === 'win32') { + if (isWindows()) { // Windows: kill running Claude processes first, then install specific version return `taskkill /IM claude.exe /F 2>nul; claude install --force ${version}`; } else { @@ -352,7 +350,7 @@ function getInstallVersionCommand(version: string): string { * @param isUpdate - If true, Claude is already installed and we just need to update */ function getInstallCommand(isUpdate: boolean): string { - if (process.platform === 'win32') { + if (isWindows()) { if (isUpdate) { // Update: kill running Claude processes first, then update with --force return 'taskkill /IM claude.exe /F 2>nul; claude install --force latest'; @@ -435,14 +433,13 @@ export function escapeBashCommand(str: string): string { * Supports macOS, Windows, and Linux terminals */ export async function openTerminalWithCommand(command: string): Promise { - const platform = process.platform; const settings = readSettingsFile(); const preferredTerminal = settings?.preferredTerminal as string | undefined; - console.warn('[Claude Code] Platform:', platform); + console.warn('[Claude Code] Platform:', isWindows() ? 'Windows' : isMacOS() ? 'macOS' : 'Linux'); console.warn('[Claude Code] Preferred terminal:', preferredTerminal); - if (platform === 'darwin') { + if (isMacOS()) { // macOS: Use AppleScript to open terminal with command const escapedCommand = escapeAppleScriptString(command); let script: string; @@ -551,7 +548,7 @@ export async function openTerminalWithCommand(command: string): Promise { console.warn('[Claude Code] Running AppleScript...'); execFileSync('osascript', ['-e', script], { stdio: 'pipe' }); - } else if (platform === 'win32') { + } else if (isWindows()) { // Windows: Use appropriate terminal // Values match SupportedTerminal type: 'windowsterminal', 'powershell', 'cmd', 'conemu', 'cmder', // 'gitbash', 'alacritty', 'wezterm', 'hyper', 'tabby', 'cygwin', 'msys2' @@ -875,7 +872,7 @@ function checkProfileAuthentication(configDir: string): AuthCheckResult { } // On Linux, also check .credentials.json (Claude CLI may store tokens here) - if (process.platform === 'linux' && existsSync(credentialsJsonPath)) { + if (isLinux() && existsSync(credentialsJsonPath)) { const content = readFileSync(credentialsJsonPath, 'utf-8'); const data = JSON.parse(content); diff --git a/apps/frontend/src/main/ipc-handlers/github/release-handlers.ts b/apps/frontend/src/main/ipc-handlers/github/release-handlers.ts index 6dde8db7..e6183e82 100644 --- a/apps/frontend/src/main/ipc-handlers/github/release-handlers.ts +++ b/apps/frontend/src/main/ipc-handlers/github/release-handlers.ts @@ -12,13 +12,14 @@ import { projectStore } from '../../project-store'; import { changelogService } from '../../changelog-service'; import type { ReleaseOptions } from './types'; import { getToolPath } from '../../cli-tool-manager'; +import { getWhichCommand } from '../../platform'; /** * Check if gh CLI is installed */ function checkGhCli(): { installed: boolean; error?: string } { try { - const checkCmd = process.platform === 'win32' ? 'where gh' : 'which gh'; + const checkCmd = `${getWhichCommand()} gh`; execSync(checkCmd, { encoding: 'utf-8', stdio: 'pipe' }); return { installed: true }; } catch { diff --git a/apps/frontend/src/main/ipc-handlers/github/utils/subprocess-runner.ts b/apps/frontend/src/main/ipc-handlers/github/utils/subprocess-runner.ts index 99d85a5c..d84aaa71 100644 --- a/apps/frontend/src/main/ipc-handlers/github/utils/subprocess-runner.ts +++ b/apps/frontend/src/main/ipc-handlers/github/utils/subprocess-runner.ts @@ -20,7 +20,7 @@ import type { AuthFailureInfo } from '../../../../shared/types/terminal'; import { parsePythonCommand } from '../../../python-detector'; import { detectAuthFailure } from '../../../rate-limit-detector'; import { getClaudeProfileManager } from '../../../claude-profile-manager'; -import { isWindows } from '../../../platform'; +import { isWindows, isMacOS } from '../../../platform'; const execAsync = promisify(exec); @@ -451,9 +451,9 @@ export async function validateGitHubModule(project: Project): Promise DANGEROUS_FLAGS.has(arg))) return false; // On Windows with shell: true, check for shell metacharacters that could enable injection - if (process.platform === 'win32') { + if (isWindows()) { if (args.some(arg => SHELL_METACHARACTERS.some(char => arg.includes(char)))) { return false; } @@ -193,7 +194,7 @@ async function checkCommandHealth(server: CustomMcpServer, startTime: number): P }); } - const command = process.platform === 'win32' ? 'where' : 'which'; + const command = isWindows() ? 'where' : 'which'; const proc = spawn(command, [server.command!], { timeout: 5000, }); @@ -421,7 +422,7 @@ async function testCommandConnection(server: CustomMcpServer, startTime: number) const proc = spawn(server.command!, args, { stdio: ['pipe', 'pipe', 'pipe'], timeout: 15000, // OS-level timeout for reliable process termination - shell: process.platform === 'win32', // Required for Windows to run npx.cmd + shell: isWindows(), // Required for Windows to run npx.cmd }); let stdout = ''; diff --git a/apps/frontend/src/main/ipc-handlers/memory-handlers.ts b/apps/frontend/src/main/ipc-handlers/memory-handlers.ts index 72d786a2..0f113053 100644 --- a/apps/frontend/src/main/ipc-handlers/memory-handlers.ts +++ b/apps/frontend/src/main/ipc-handlers/memory-handlers.ts @@ -10,7 +10,7 @@ import { spawn, execFileSync } from 'child_process'; import * as path from 'path'; import { fileURLToPath } from 'url'; import * as fs from 'fs'; -import * as os from 'os'; +import { getOllamaExecutablePaths, getOllamaInstallCommand as getPlatformOllamaInstallCommand, getWhichCommand, getCurrentOS } from '../platform'; // ESM-compatible __dirname const __filename = fileURLToPath(import.meta.url); @@ -109,38 +109,11 @@ interface OllamaInstallStatus { * @returns {OllamaInstallStatus} Installation status with path if found */ function checkOllamaInstalled(): OllamaInstallStatus { - const platform = process.platform; - - // Common paths to check based on platform - const pathsToCheck: string[] = []; - - if (platform === 'win32') { - // Windows: Check common installation paths - const localAppData = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'); - pathsToCheck.push( - path.join(localAppData, 'Programs', 'Ollama', 'ollama.exe'), - path.join(localAppData, 'Ollama', 'ollama.exe'), - 'C:\\Program Files\\Ollama\\ollama.exe', - 'C:\\Program Files (x86)\\Ollama\\ollama.exe' - ); - } else if (platform === 'darwin') { - // macOS: Check common paths - pathsToCheck.push( - '/usr/local/bin/ollama', - '/opt/homebrew/bin/ollama', - path.join(os.homedir(), '.local', 'bin', 'ollama') - ); - } else { - // Linux: Check common paths - pathsToCheck.push( - '/usr/local/bin/ollama', - '/usr/bin/ollama', - path.join(os.homedir(), '.local', 'bin', 'ollama') - ); - } + // Get platform-specific paths from the platform module + const pathsToCheck = getOllamaExecutablePaths(); // Check each path - // SECURITY NOTE: ollamaPath values come from the hardcoded pathsToCheck array above, + // SECURITY NOTE: ollamaPath values come from the platform module's hardcoded paths, // not from user input or environment variables. These are known system installation paths. for (const ollamaPath of pathsToCheck) { if (fs.existsSync(ollamaPath)) { @@ -172,7 +145,7 @@ function checkOllamaInstalled(): OllamaInstallStatus { // Also check if ollama is in PATH using where/which command // Use execFileSync with explicit command to avoid shell injection try { - const whichCmd = platform === 'win32' ? 'where.exe' : 'which'; + const whichCmd = getWhichCommand(); const ollamaPath = execFileSync(whichCmd, ['ollama'], { encoding: 'utf-8', timeout: 5000, @@ -211,36 +184,16 @@ function checkOllamaInstalled(): OllamaInstallStatus { /** * Get the platform-specific install command for Ollama - * Uses the official Ollama installation methods + * Uses the official Ollama installation methods from the platform module. * * Windows: Uses winget (Windows Package Manager) - * - Official method per https://winstall.app/apps/Ollama.Ollama - * - Winget is pre-installed on Windows 10 (1709+) and Windows 11 - * - * macOS: Uses Homebrew (most common package manager on macOS) - * - Official method: brew install ollama - * - Reference: https://ollama.com/download/mac - * + * macOS: Uses Homebrew * Linux: Uses official install script from https://ollama.com/download * * @returns {string} The install command to run in terminal */ function getOllamaInstallCommand(): string { - if (process.platform === 'win32') { - // Windows: Use winget (Windows Package Manager) - // This is an official installation method for Ollama on Windows - // Reference: https://winstall.app/apps/Ollama.Ollama - return 'winget install --id Ollama.Ollama --accept-source-agreements'; - } else if (process.platform === 'darwin') { - // macOS: Use Homebrew (most widely used package manager on macOS) - // Official Ollama installation method for macOS - // Reference: https://ollama.com/download/mac - return 'brew install ollama'; - } else { - // Linux: Use shell script from official Ollama - // Reference: https://ollama.com/download - return 'curl -fsSL https://ollama.com/install.sh | sh'; - } + return getPlatformOllamaInstallCommand(); } /** @@ -615,7 +568,7 @@ export function registerMemoryHandlers(): void { async (): Promise> => { try { const command = getOllamaInstallCommand(); - console.log('[Ollama] Platform:', process.platform); + console.log('[Ollama] Platform:', getCurrentOS()); console.log('[Ollama] Install command:', command); console.log('[Ollama] Opening terminal...'); diff --git a/apps/frontend/src/main/ipc-handlers/terminal/worktree-handlers.ts b/apps/frontend/src/main/ipc-handlers/terminal/worktree-handlers.ts index ac450d34..bb8febd2 100644 --- a/apps/frontend/src/main/ipc-handlers/terminal/worktree-handlers.ts +++ b/apps/frontend/src/main/ipc-handlers/terminal/worktree-handlers.ts @@ -15,6 +15,7 @@ import { minimatch } from 'minimatch'; import { debugLog, debugError } from '../../../shared/utils/debug-logger'; import { projectStore } from '../../project-store'; import { parseEnvFile } from '../utils'; +import { isWindows } from '../../platform'; import { getTerminalWorktreeDir, getTerminalWorktreePath, @@ -276,7 +277,7 @@ function symlinkNodeModulesToWorktree(projectPath: string, worktreePath: string) // 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') { + if (isWindows()) { symlinkSync(sourcePath, targetPath, 'junction'); debugLog('[TerminalWorktree] Created junction (Windows):', targetRel, '->', sourcePath); } else { diff --git a/apps/frontend/src/main/memory-service.ts b/apps/frontend/src/main/memory-service.ts index 6efc625e..76f099b9 100644 --- a/apps/frontend/src/main/memory-service.ts +++ b/apps/frontend/src/main/memory-service.ts @@ -19,6 +19,7 @@ const __dirname = path.dirname(__filename); import { findPythonCommand, parsePythonCommand } from './python-detector'; import { getConfiguredPythonPath, pythonEnvManager } from './python-env-manager'; import { getMemoriesDir } from './config-paths'; +import { isWindows } from './platform'; import type { MemoryEpisode } from '../shared/types'; interface MemoryServiceConfig { @@ -141,7 +142,7 @@ function getBackendPythonPath(): string { for (const backendPath of possibleBackendPaths) { // Check for backend venv Python (has real_ladybug installed) - const venvPython = process.platform === 'win32' + const venvPython = isWindows() ? path.join(backendPath, '.venv', 'Scripts', 'python.exe') : path.join(backendPath, '.venv', 'bin', 'python'); diff --git a/apps/frontend/src/main/platform/index.ts b/apps/frontend/src/main/platform/index.ts index ea5c1419..880a48c2 100644 --- a/apps/frontend/src/main/platform/index.ts +++ b/apps/frontend/src/main/platform/index.ts @@ -18,7 +18,7 @@ import { spawn, ChildProcess } from 'child_process'; import { OS, ShellType, PathConfig, ShellConfig, BinaryDirectories } from './types'; // Re-export from paths.ts for backward compatibility -export { getWindowsShellPaths } from './paths'; +export { getWindowsShellPaths, getOllamaExecutablePaths, getOllamaInstallCommand, getWhichCommand } from './paths'; /** * Get the current operating system diff --git a/apps/frontend/src/main/platform/paths.ts b/apps/frontend/src/main/platform/paths.ts index bf0a6b22..c1758bcb 100644 --- a/apps/frontend/src/main/platform/paths.ts +++ b/apps/frontend/src/main/platform/paths.ts @@ -239,6 +239,71 @@ export function expandWindowsEnvVars(pathPattern: string): string { return expanded; } +/** + * Resolve Ollama executable paths + * + * Returns platform-specific paths where Ollama may be installed: + * - Windows: LocalAppData, Program Files + * - macOS: Homebrew paths, /usr/local/bin + * - Linux: /usr/local/bin, /usr/bin, ~/.local/bin + */ +export function getOllamaExecutablePaths(): string[] { + const homeDir = os.homedir(); + const paths: string[] = []; + + if (isWindows()) { + const localAppData = process.env.LOCALAPPDATA || joinPaths(homeDir, 'AppData', 'Local'); + paths.push( + joinPaths(localAppData, 'Programs', 'Ollama', 'ollama.exe'), + joinPaths(localAppData, 'Ollama', 'ollama.exe'), + joinPaths('C:\\Program Files', 'Ollama', 'ollama.exe'), + joinPaths('C:\\Program Files (x86)', 'Ollama', 'ollama.exe') + ); + } else if (isMacOS()) { + paths.push( + '/usr/local/bin/ollama', + '/opt/homebrew/bin/ollama', + joinPaths(homeDir, '.local', 'bin', 'ollama') + ); + } else { + // Linux + paths.push( + '/usr/local/bin/ollama', + '/usr/bin/ollama', + joinPaths(homeDir, '.local', 'bin', 'ollama') + ); + } + + return paths; +} + +/** + * Get the platform-specific install command for Ollama + * + * Windows: Uses winget (Windows Package Manager) + * macOS: Uses Homebrew + * Linux: Uses official install script + */ +export function getOllamaInstallCommand(): string { + if (isWindows()) { + return 'winget install --id Ollama.Ollama --accept-source-agreements'; + } else if (isMacOS()) { + return 'brew install ollama'; + } else { + return 'curl -fsSL https://ollama.com/install.sh | sh'; + } +} + +/** + * Get the command to find executables in PATH + * + * Windows: where.exe + * Unix: which + */ +export function getWhichCommand(): string { + return isWindows() ? 'where.exe' : 'which'; +} + /** * Get Windows-specific installation paths for a tool * diff --git a/apps/frontend/src/main/python-detector.ts b/apps/frontend/src/main/python-detector.ts index eb74e5d4..17fd2501 100644 --- a/apps/frontend/src/main/python-detector.ts +++ b/apps/frontend/src/main/python-detector.ts @@ -3,6 +3,7 @@ import { existsSync, accessSync, constants } from 'fs'; import path from 'path'; import { app } from 'electron'; import { findHomebrewPython as findHomebrewPythonUtil } from './utils/homebrew-python'; +import { isWindows } from './platform'; /** * Get the path to the bundled Python executable. @@ -17,10 +18,9 @@ export function getBundledPythonPath(): string | null { } const resourcesPath = process.resourcesPath; - const isWindows = process.platform === 'win32'; // Bundled Python location in packaged app - const pythonPath = isWindows + const pythonPath = isWindows() ? path.join(resourcesPath, 'python', 'python.exe') : path.join(resourcesPath, 'python', 'bin', 'python3'); @@ -52,8 +52,6 @@ function findHomebrewPython(): string | null { * @returns The Python command to use, or null if none found */ export function findPythonCommand(): string | null { - const isWindows = process.platform === 'win32'; - // 1. Check for bundled Python first (packaged apps only) const bundledPython = getBundledPythonPath(); if (bundledPython) { @@ -75,7 +73,7 @@ export function findPythonCommand(): string | null { // Build candidate list prioritizing Homebrew Python on macOS let candidates: string[]; - if (isWindows) { + if (isWindows()) { candidates = ['py -3', 'python', 'python3', 'py']; } else { const homebrewPython = findHomebrewPython(); @@ -101,7 +99,7 @@ export function findPythonCommand(): string | null { } // Fallback to platform-specific default - if (isWindows) { + if (isWindows()) { return 'python'; } return findHomebrewPython() || 'python3'; @@ -186,7 +184,7 @@ export function getDefaultPythonCommand(): string { } // Fall back to system Python - if (process.platform === 'win32') { + if (isWindows()) { return 'python'; } return findHomebrewPython() || 'python3'; @@ -441,7 +439,7 @@ export function validatePythonPath(pythonPath: string): PythonPathValidation { } // Security check 4: Must be executable (Unix) or .exe (Windows) - if (process.platform !== 'win32' && !isExecutable(normalizedPath)) { + if (!isWindows() && !isExecutable(normalizedPath)) { return { valid: false, reason: 'File exists but is not executable' diff --git a/apps/frontend/src/main/terminal/pty-daemon-client.ts b/apps/frontend/src/main/terminal/pty-daemon-client.ts index c426d6d0..9569ddb3 100644 --- a/apps/frontend/src/main/terminal/pty-daemon-client.ts +++ b/apps/frontend/src/main/terminal/pty-daemon-client.ts @@ -16,10 +16,9 @@ import { isWindows, GRACEFUL_KILL_TIMEOUT_MS } from '../platform'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); -const SOCKET_PATH = - process.platform === 'win32' - ? `\\\\.\\pipe\\auto-claude-pty-${process.getuid?.() || 'default'}` - : `/tmp/auto-claude-pty-${process.getuid?.() || 'default'}.sock`; +const SOCKET_PATH = isWindows() + ? `\\\\.\\pipe\\auto-claude-pty-${process.getuid?.() || 'default'}` + : `/tmp/auto-claude-pty-${process.getuid?.() || 'default'}.sock`; interface DaemonResponseData { exitCode?: number; diff --git a/apps/frontend/src/main/terminal/pty-daemon.ts b/apps/frontend/src/main/terminal/pty-daemon.ts index 7e5bf2d5..d32368bc 100644 --- a/apps/frontend/src/main/terminal/pty-daemon.ts +++ b/apps/frontend/src/main/terminal/pty-daemon.ts @@ -11,11 +11,11 @@ import * as net from 'net'; import * as fs from 'fs'; import * as pty from '@lydell/node-pty'; +import { isWindows, isUnix } from '../platform'; -const SOCKET_PATH = - process.platform === 'win32' - ? `\\\\.\\pipe\\auto-claude-pty-${process.getuid?.() || 'default'}` - : `/tmp/auto-claude-pty-${process.getuid?.() || 'default'}.sock`; +const SOCKET_PATH = isWindows() + ? `\\\\.\\pipe\\auto-claude-pty-${process.getuid?.() || 'default'}` + : `/tmp/auto-claude-pty-${process.getuid?.() || 'default'}.sock`; // Maximum buffer size per PTY (100KB) const MAX_BUFFER_SIZE = 100_000; @@ -83,7 +83,7 @@ class PtyDaemon { * Remove stale socket/pipe */ private cleanup(): void { - if (process.platform !== 'win32' && fs.existsSync(SOCKET_PATH)) { + if (isUnix() && fs.existsSync(SOCKET_PATH)) { try { fs.unlinkSync(SOCKET_PATH); console.error('[PTY Daemon] Cleaned up stale socket'); @@ -113,7 +113,7 @@ class PtyDaemon { this.server.listen(SOCKET_PATH, () => { console.error(`[PTY Daemon] Listening on ${SOCKET_PATH}`); // Set permissions on Unix - if (process.platform !== 'win32') { + if (isUnix()) { try { fs.chmodSync(SOCKET_PATH, 0o600); } catch (error) { diff --git a/tests/qa_report_helpers.py b/tests/qa_report_helpers.py index 2f116efe..1ec0698a 100644 --- a/tests/qa_report_helpers.py +++ b/tests/qa_report_helpers.py @@ -101,7 +101,7 @@ def setup_qa_report_mocks() -> None: sys.modules['client'] = mock_client # Add auto-claude path for imports - sys.path.insert(0, str(Path(__file__).parent.parent / "Apps" / "backend")) + sys.path.insert(0, str(Path(__file__).parent.parent / "apps" / "backend")) def cleanup_qa_report_mocks() -> None: diff --git a/tests/test_analyzer_port_detection.py b/tests/test_analyzer_port_detection.py index eada6586..ff9f0d05 100644 --- a/tests/test_analyzer_port_detection.py +++ b/tests/test_analyzer_port_detection.py @@ -17,7 +17,7 @@ import sys import json # Add parent directory to path to import analyzer -sys.path.insert(0, str(Path(__file__).parent.parent / "Apps" / "backend")) +sys.path.insert(0, str(Path(__file__).parent.parent / "apps" / "backend")) from analyzer import ServiceAnalyzer diff --git a/tests/test_ci_discovery.py b/tests/test_ci_discovery.py index a55d6b91..5acc2a7a 100644 --- a/tests/test_ci_discovery.py +++ b/tests/test_ci_discovery.py @@ -18,7 +18,7 @@ import pytest # Add auto-claude to path for imports import sys -sys.path.insert(0, str(Path(__file__).parent.parent / "Apps" / "backend")) +sys.path.insert(0, str(Path(__file__).parent.parent / "apps" / "backend")) from ci_discovery import ( CIConfig, diff --git a/tests/test_critique_integration.py b/tests/test_critique_integration.py index ad80e95e..755fe001 100644 --- a/tests/test_critique_integration.py +++ b/tests/test_critique_integration.py @@ -13,7 +13,7 @@ import sys from pathlib import Path # Add auto-claude directory to path for imports -sys.path.insert(0, str(Path(__file__).parent.parent / "Apps" / "backend")) +sys.path.insert(0, str(Path(__file__).parent.parent / "apps" / "backend")) from critique import ( generate_critique_prompt, @@ -22,7 +22,7 @@ from critique import ( format_critique_summary, CritiqueResult, ) -from implementation_plan import Chunk, ChunkStatus, Verification, VerificationType +from implementation_plan import Subtask, SubtaskStatus, Verification, VerificationType def test_critique_data_structures(): @@ -134,14 +134,14 @@ def test_critique_response_parsing(): def test_implementation_plan_integration(): - """Test integration with implementation_plan.py Chunk class.""" + """Test integration with implementation_plan.py Subtask class.""" print("\nTesting implementation plan integration...") # Create a chunk with critique result - chunk = Chunk( + chunk = Subtask( id="test-chunk", description="Test chunk with critique", - status=ChunkStatus.PENDING, + status=SubtaskStatus.PENDING, service="backend", files_to_modify=["app/test.py"], ) @@ -161,7 +161,7 @@ def test_implementation_plan_integration(): assert chunk_dict["critique_result"]["passes"] == True # Test deserialization - chunk2 = Chunk.from_dict(chunk_dict) + chunk2 = Subtask.from_dict(chunk_dict) assert chunk2.critique_result is not None assert chunk2.critique_result["passes"] == True assert len(chunk2.critique_result["improvements_made"]) == 1 @@ -218,7 +218,7 @@ def test_complete_workflow(): assert "Subtask is ready to be marked complete" in summary # 7. Store in chunk - chunk_obj = Chunk( + chunk_obj = Subtask( id=chunk["id"], description=chunk["description"], service=chunk["service"], @@ -286,7 +286,7 @@ def main(): print("\nKey components:") print(" - critique.py: Core critique logic") print(" - prompts/coder.md: Updated with STEP 6.5 (mandatory critique)") - print(" - implementation_plan.py: Chunk.critique_result field added") + print(" - implementation_plan.py: Subtask.critique_result field added") except AssertionError as e: print(f"\n✗ Test failed: {e}") diff --git a/tests/test_discovery.py b/tests/test_discovery.py index 025cbe4a..b922e486 100644 --- a/tests/test_discovery.py +++ b/tests/test_discovery.py @@ -18,7 +18,7 @@ import pytest # Add auto-claude to path for imports import sys -sys.path.insert(0, str(Path(__file__).parent.parent / "Apps" / "backend")) +sys.path.insert(0, str(Path(__file__).parent.parent / "apps" / "backend")) from test_discovery import ( TestFramework, diff --git a/tests/test_followup.py b/tests/test_followup.py index 05e191a5..39a282e6 100644 --- a/tests/test_followup.py +++ b/tests/test_followup.py @@ -16,8 +16,8 @@ from pathlib import Path from implementation_plan import ( ImplementationPlan, Phase, - Chunk, - ChunkStatus, + Subtask, + SubtaskStatus, PhaseType, WorkflowType, ) @@ -31,8 +31,8 @@ class TestAddFollowupPhase: plan = ImplementationPlan(feature="Test Feature") new_chunks = [ - Chunk(id="followup-1", description="First follow-up task"), - Chunk(id="followup-2", description="Second follow-up task"), + Subtask(id="followup-1", description="First follow-up task"), + Subtask(id="followup-2", description="Second follow-up task"), ] phase = plan.add_followup_phase("Follow-Up: New Work", new_chunks) @@ -53,7 +53,7 @@ class TestAddFollowupPhase: ], ) - new_chunks = [Chunk(id="followup-1", description="Follow-up task")] + new_chunks = [Subtask(id="followup-1", description="Follow-up task")] phase = plan.add_followup_phase("Follow-Up Phase", new_chunks) assert phase.phase == 3 @@ -70,7 +70,7 @@ class TestAddFollowupPhase: ], ) - new_chunks = [Chunk(id="followup-1", description="Follow-up task")] + new_chunks = [Subtask(id="followup-1", description="Follow-up task")] phase = plan.add_followup_phase("Follow-Up Phase", new_chunks) assert phase.depends_on == [1, 2, 3] @@ -79,7 +79,7 @@ class TestAddFollowupPhase: """Respects phase_type parameter.""" plan = ImplementationPlan(feature="Test Feature") - new_chunks = [Chunk(id="followup-1", description="Integration task")] + new_chunks = [Subtask(id="followup-1", description="Integration task")] phase = plan.add_followup_phase( "Integration Work", new_chunks, @@ -92,7 +92,7 @@ class TestAddFollowupPhase: """Respects parallel_safe parameter.""" plan = ImplementationPlan(feature="Test Feature") - new_chunks = [Chunk(id="followup-1", description="Parallel task")] + new_chunks = [Subtask(id="followup-1", description="Parallel task")] phase = plan.add_followup_phase( "Parallel Work", new_chunks, @@ -109,7 +109,7 @@ class TestAddFollowupPhase: planStatus="completed", ) - new_chunks = [Chunk(id="followup-1", description="New task")] + new_chunks = [Subtask(id="followup-1", description="New task")] plan.add_followup_phase("Follow-Up", new_chunks) assert plan.status == "in_progress" @@ -122,7 +122,7 @@ class TestAddFollowupPhase: qa_signoff={"status": "approved", "timestamp": "2024-01-01"}, ) - new_chunks = [Chunk(id="followup-1", description="New task")] + new_chunks = [Subtask(id="followup-1", description="New task")] plan.add_followup_phase("Follow-Up", new_chunks) assert plan.qa_signoff is None @@ -131,7 +131,7 @@ class TestAddFollowupPhase: """Returns the newly created Phase object.""" plan = ImplementationPlan(feature="Test Feature") - new_chunks = [Chunk(id="followup-1", description="New task")] + new_chunks = [Subtask(id="followup-1", description="New task")] phase = plan.add_followup_phase("Follow-Up", new_chunks) assert isinstance(phase, Phase) @@ -146,11 +146,11 @@ class TestAddFollowupPhase: ) # First follow-up - plan.add_followup_phase("Follow-Up 1", [Chunk(id="f1", description="Task 1")]) + plan.add_followup_phase("Follow-Up 1", [Subtask(id="f1", description="Task 1")]) # Second follow-up - plan.add_followup_phase("Follow-Up 2", [Chunk(id="f2", description="Task 2")]) + plan.add_followup_phase("Follow-Up 2", [Subtask(id="f2", description="Task 2")]) # Third follow-up - plan.add_followup_phase("Follow-Up 3", [Chunk(id="f3", description="Task 3")]) + plan.add_followup_phase("Follow-Up 3", [Subtask(id="f3", description="Task 3")]) assert len(plan.phases) == 4 assert plan.phases[0].phase == 1 @@ -163,13 +163,13 @@ class TestAddFollowupPhase: plan = ImplementationPlan(feature="Test Feature") new_chunks = [ - Chunk(id="followup-1", description="Task 1"), - Chunk(id="followup-2", description="Task 2"), + Subtask(id="followup-1", description="Task 1"), + Subtask(id="followup-2", description="Task 2"), ] phase = plan.add_followup_phase("Follow-Up", new_chunks) for chunk in phase.chunks: - assert chunk.status == ChunkStatus.PENDING + assert chunk.status == SubtaskStatus.PENDING class TestResetForFollowup: @@ -185,7 +185,7 @@ class TestResetForFollowup: Phase( phase=1, name="Phase 1", - subtasks=[Chunk(id="c1", description="Task", status=ChunkStatus.COMPLETED)], + subtasks=[Subtask(id="c1", description="Task", status=SubtaskStatus.COMPLETED)], ), ], ) @@ -206,7 +206,7 @@ class TestResetForFollowup: Phase( phase=1, name="Phase 1", - subtasks=[Chunk(id="c1", description="Task", status=ChunkStatus.COMPLETED)], + subtasks=[Subtask(id="c1", description="Task", status=SubtaskStatus.COMPLETED)], ), ], ) @@ -227,7 +227,7 @@ class TestResetForFollowup: Phase( phase=1, name="Phase 1", - subtasks=[Chunk(id="c1", description="Task", status=ChunkStatus.COMPLETED)], + subtasks=[Subtask(id="c1", description="Task", status=SubtaskStatus.COMPLETED)], ), ], ) @@ -249,8 +249,8 @@ class TestResetForFollowup: phase=1, name="Phase 1", subtasks=[ - Chunk(id="c1", description="Task 1", status=ChunkStatus.COMPLETED), - Chunk(id="c2", description="Task 2", status=ChunkStatus.COMPLETED), + Subtask(id="c1", description="Task 1", status=SubtaskStatus.COMPLETED), + Subtask(id="c2", description="Task 2", status=SubtaskStatus.COMPLETED), ], ), ], @@ -272,8 +272,8 @@ class TestResetForFollowup: phase=1, name="Phase 1", subtasks=[ - Chunk(id="c1", description="Task 1", status=ChunkStatus.COMPLETED), - Chunk(id="c2", description="Task 2", status=ChunkStatus.PENDING), + Subtask(id="c1", description="Task 1", status=SubtaskStatus.COMPLETED), + Subtask(id="c2", description="Task 2", status=SubtaskStatus.PENDING), ], ), ], @@ -293,7 +293,7 @@ class TestResetForFollowup: Phase( phase=1, name="Phase 1", - subtasks=[Chunk(id="c1", description="Task", status=ChunkStatus.PENDING)], + subtasks=[Subtask(id="c1", description="Task", status=SubtaskStatus.PENDING)], ), ], ) @@ -313,7 +313,7 @@ class TestResetForFollowup: Phase( phase=1, name="Phase 1", - subtasks=[Chunk(id="c1", description="Task", status=ChunkStatus.COMPLETED)], + subtasks=[Subtask(id="c1", description="Task", status=SubtaskStatus.COMPLETED)], ), ], ) @@ -333,7 +333,7 @@ class TestResetForFollowup: Phase( phase=1, name="Phase 1", - subtasks=[Chunk(id="c1", description="Task", status=ChunkStatus.COMPLETED)], + subtasks=[Subtask(id="c1", description="Task", status=SubtaskStatus.COMPLETED)], ), ], ) @@ -357,10 +357,10 @@ class TestExistingChunksPreserved: phase=1, name="Original Phase", subtasks=[ - Chunk( + Subtask( id="original-1", description="Original task", - status=ChunkStatus.COMPLETED, + status=SubtaskStatus.COMPLETED, completed_at="2024-01-01T12:00:00", ), ], @@ -369,12 +369,12 @@ class TestExistingChunksPreserved: ) # Add follow-up - new_chunks = [Chunk(id="followup-1", description="New task")] + new_chunks = [Subtask(id="followup-1", description="New task")] plan.add_followup_phase("Follow-Up", new_chunks) # Original chunk should still be completed original_chunk = plan.phases[0].chunks[0] - assert original_chunk.status == ChunkStatus.COMPLETED + assert original_chunk.status == SubtaskStatus.COMPLETED assert original_chunk.completed_at == "2024-01-01T12:00:00" def test_original_phase_structure_preserved(self): @@ -384,13 +384,13 @@ class TestExistingChunksPreserved: phase=1, name="Phase 1", depends_on=[], - subtasks=[Chunk(id="c1", description="Task 1", status=ChunkStatus.COMPLETED)], + subtasks=[Subtask(id="c1", description="Task 1", status=SubtaskStatus.COMPLETED)], ), Phase( phase=2, name="Phase 2", depends_on=[1], - subtasks=[Chunk(id="c2", description="Task 2", status=ChunkStatus.COMPLETED)], + subtasks=[Subtask(id="c2", description="Task 2", status=SubtaskStatus.COMPLETED)], ), ] @@ -399,7 +399,7 @@ class TestExistingChunksPreserved: phases=original_phases, ) - plan.add_followup_phase("Follow-Up", [Chunk(id="f1", description="Follow-up")]) + plan.add_followup_phase("Follow-Up", [Subtask(id="f1", description="Follow-up")]) # Original phases should be unchanged assert plan.phases[0].name == "Phase 1" @@ -420,7 +420,7 @@ class TestFollowupPlanSaveLoad: Phase( phase=1, name="Original", - subtasks=[Chunk(id="c1", description="Task", status=ChunkStatus.COMPLETED)], + subtasks=[Subtask(id="c1", description="Task", status=SubtaskStatus.COMPLETED)], ), ], ) @@ -428,7 +428,7 @@ class TestFollowupPlanSaveLoad: # Add follow-up plan.add_followup_phase( "Follow-Up Work", - [Chunk(id="followup-1", description="Follow-up task")], + [Subtask(id="followup-1", description="Follow-up task")], ) # Save @@ -451,7 +451,7 @@ class TestFollowupPlanSaveLoad: Phase( phase=1, name="Original", - subtasks=[Chunk(id="c1", description="Task", status=ChunkStatus.COMPLETED)], + subtasks=[Subtask(id="c1", description="Task", status=SubtaskStatus.COMPLETED)], ), ], ) @@ -459,12 +459,12 @@ class TestFollowupPlanSaveLoad: plan_path = temp_dir / "implementation_plan.json" # Add first follow-up and save - plan.add_followup_phase("Follow-Up 1", [Chunk(id="f1", description="Task 1")]) + plan.add_followup_phase("Follow-Up 1", [Subtask(id="f1", description="Task 1")]) plan.save(plan_path) # Load, add second follow-up, save plan = ImplementationPlan.load(plan_path) - plan.add_followup_phase("Follow-Up 2", [Chunk(id="f2", description="Task 2")]) + plan.add_followup_phase("Follow-Up 2", [Subtask(id="f2", description="Task 2")]) plan.save(plan_path) # Load and verify @@ -487,7 +487,7 @@ class TestFollowupProgressCalculation: Phase( phase=1, name="Original", - subtasks=[Chunk(id="c1", description="Task", status=ChunkStatus.COMPLETED)], + subtasks=[Subtask(id="c1", description="Task", status=SubtaskStatus.COMPLETED)], ), ], ) @@ -499,7 +499,7 @@ class TestFollowupProgressCalculation: assert progress["is_complete"] is True # Add follow-up - plan.add_followup_phase("Follow-Up", [Chunk(id="f1", description="New task")]) + plan.add_followup_phase("Follow-Up", [Subtask(id="f1", description="New task")]) # Now 50% complete progress = plan.get_progress() @@ -516,7 +516,7 @@ class TestFollowupProgressCalculation: Phase( phase=1, name="Original", - subtasks=[Chunk(id="c1", description="Task", status=ChunkStatus.COMPLETED)], + subtasks=[Subtask(id="c1", description="Task", status=SubtaskStatus.COMPLETED)], ), ], ) @@ -525,7 +525,7 @@ class TestFollowupProgressCalculation: assert plan.get_next_subtask() is None # Add follow-up - plan.add_followup_phase("Follow-Up", [Chunk(id="f1", description="New task")]) + plan.add_followup_phase("Follow-Up", [Subtask(id="f1", description="New task")]) # Now follow-up chunk is next next_work = plan.get_next_subtask() diff --git a/tests/test_graphiti.py b/tests/test_graphiti.py index 04646bcc..d753750d 100644 --- a/tests/test_graphiti.py +++ b/tests/test_graphiti.py @@ -6,7 +6,7 @@ from unittest.mock import patch, MagicMock # Add auto-claude to path import sys -sys.path.insert(0, str(Path(__file__).parent.parent / "Apps" / "backend")) +sys.path.insert(0, str(Path(__file__).parent.parent / "apps" / "backend")) from graphiti_config import is_graphiti_enabled, get_graphiti_status, GraphitiConfig diff --git a/tests/test_implementation_plan.py b/tests/test_implementation_plan.py index 54abf9df..4a5b3bd5 100644 --- a/tests/test_implementation_plan.py +++ b/tests/test_implementation_plan.py @@ -4,7 +4,7 @@ Tests for Implementation Plan Management ======================================== Tests the implementation_plan.py module functionality including: -- Data structures (Chunk, Phase, ImplementationPlan) +- Data structures (Subtask, Phase, ImplementationPlan) - Status transitions - Progress tracking - Dependency resolution @@ -19,11 +19,11 @@ from pathlib import Path from implementation_plan import ( ImplementationPlan, Phase, - Chunk, + Subtask, Verification, WorkflowType, PhaseType, - ChunkStatus, + SubtaskStatus, VerificationType, create_feature_plan, create_investigation_plan, @@ -31,29 +31,29 @@ from implementation_plan import ( ) -class TestChunk: - """Tests for Chunk data structure.""" +class TestSubtask: + """Tests for Subtask data structure.""" def test_create_simple_chunk(self): """Creates a simple chunk with defaults.""" - chunk = Chunk( + chunk = Subtask( id="chunk-1", description="Implement user model", ) assert chunk.id == "chunk-1" assert chunk.description == "Implement user model" - assert chunk.status == ChunkStatus.PENDING + assert chunk.status == SubtaskStatus.PENDING assert chunk.service is None assert chunk.files_to_modify == [] assert chunk.files_to_create == [] def test_create_full_chunk(self): """Creates a chunk with all fields.""" - chunk = Chunk( + chunk = Subtask( id="chunk-2", description="Add API endpoint", - status=ChunkStatus.IN_PROGRESS, + status=SubtaskStatus.IN_PROGRESS, service="backend", files_to_modify=["app/routes.py"], files_to_create=["app/models/user.py"], @@ -65,39 +65,39 @@ class TestChunk: assert "app/models/user.py" in chunk.files_to_create def test_chunk_start(self): - """Chunk can be started.""" - chunk = Chunk(id="test", description="Test") + """Subtask can be started.""" + chunk = Subtask(id="test", description="Test") chunk.start(session_id=1) - assert chunk.status == ChunkStatus.IN_PROGRESS + assert chunk.status == SubtaskStatus.IN_PROGRESS assert chunk.started_at is not None assert chunk.session_id == 1 def test_chunk_complete(self): - """Chunk can be completed.""" - chunk = Chunk(id="test", description="Test") + """Subtask can be completed.""" + chunk = Subtask(id="test", description="Test") chunk.start(session_id=1) chunk.complete(output="Done successfully") - assert chunk.status == ChunkStatus.COMPLETED + assert chunk.status == SubtaskStatus.COMPLETED assert chunk.completed_at is not None assert chunk.actual_output == "Done successfully" def test_chunk_fail(self): - """Chunk can be marked as failed.""" - chunk = Chunk(id="test", description="Test") + """Subtask can be marked as failed.""" + chunk = Subtask(id="test", description="Test") chunk.start(session_id=1) chunk.fail(reason="Test error") - assert chunk.status == ChunkStatus.FAILED + assert chunk.status == SubtaskStatus.FAILED assert "FAILED: Test error" in chunk.actual_output def test_chunk_to_dict(self): - """Chunk serializes to dict correctly.""" - chunk = Chunk( + """Subtask serializes to dict correctly.""" + chunk = Subtask( id="chunk-1", description="Test chunk", service="backend", @@ -113,7 +113,7 @@ class TestChunk: assert "file.py" in data["files_to_modify"] def test_chunk_from_dict(self): - """Chunk deserializes from dict correctly.""" + """Subtask deserializes from dict correctly.""" data = { "id": "chunk-1", "description": "Test chunk", @@ -121,10 +121,10 @@ class TestChunk: "service": "frontend", } - chunk = Chunk.from_dict(data) + chunk = Subtask.from_dict(data) assert chunk.id == "chunk-1" - assert chunk.status == ChunkStatus.COMPLETED + assert chunk.status == SubtaskStatus.COMPLETED assert chunk.service == "frontend" @@ -184,8 +184,8 @@ class TestPhase: def test_create_phase(self): """Creates a phase with chunks.""" - chunk1 = Chunk(id="c1", description="Chunk 1") - chunk2 = Chunk(id="c2", description="Chunk 2") + chunk1 = Subtask(id="c1", description="Chunk 1") + chunk2 = Subtask(id="c2", description="Chunk 2") phase = Phase( phase=1, @@ -200,37 +200,37 @@ class TestPhase: def test_phase_is_complete(self): """Phase completion checks all chunks.""" - chunk1 = Chunk(id="c1", description="Chunk 1", status=ChunkStatus.COMPLETED) - chunk2 = Chunk(id="c2", description="Chunk 2", status=ChunkStatus.COMPLETED) + chunk1 = Subtask(id="c1", description="Chunk 1", status=SubtaskStatus.COMPLETED) + chunk2 = Subtask(id="c2", description="Chunk 2", status=SubtaskStatus.COMPLETED) phase = Phase(phase=1, name="Test", subtasks=[chunk1, chunk2]) assert phase.is_complete() is True def test_phase_not_complete_with_pending(self): """Phase not complete with pending chunks.""" - chunk1 = Chunk(id="c1", description="Chunk 1", status=ChunkStatus.COMPLETED) - chunk2 = Chunk(id="c2", description="Chunk 2", status=ChunkStatus.PENDING) + chunk1 = Subtask(id="c1", description="Chunk 1", status=SubtaskStatus.COMPLETED) + chunk2 = Subtask(id="c2", description="Chunk 2", status=SubtaskStatus.PENDING) phase = Phase(phase=1, name="Test", subtasks=[chunk1, chunk2]) assert phase.is_complete() is False def test_phase_get_pending_chunks(self): """Gets pending chunks from phase.""" - chunk1 = Chunk(id="c1", description="Chunk 1", status=ChunkStatus.COMPLETED) - chunk2 = Chunk(id="c2", description="Chunk 2", status=ChunkStatus.PENDING) - chunk3 = Chunk(id="c3", description="Chunk 3", status=ChunkStatus.PENDING) + chunk1 = Subtask(id="c1", description="Chunk 1", status=SubtaskStatus.COMPLETED) + chunk2 = Subtask(id="c2", description="Chunk 2", status=SubtaskStatus.PENDING) + chunk3 = Subtask(id="c3", description="Chunk 3", status=SubtaskStatus.PENDING) phase = Phase(phase=1, name="Test", subtasks=[chunk1, chunk2, chunk3]) pending = phase.get_pending_chunks() assert len(pending) == 2 - assert all(c.status == ChunkStatus.PENDING for c in pending) + assert all(c.status == SubtaskStatus.PENDING for c in pending) def test_phase_get_progress(self): """Gets progress counts from phase.""" - chunk1 = Chunk(id="c1", description="Chunk 1", status=ChunkStatus.COMPLETED) - chunk2 = Chunk(id="c2", description="Chunk 2", status=ChunkStatus.COMPLETED) - chunk3 = Chunk(id="c3", description="Chunk 3", status=ChunkStatus.PENDING) + chunk1 = Subtask(id="c1", description="Chunk 1", status=SubtaskStatus.COMPLETED) + chunk2 = Subtask(id="c2", description="Chunk 2", status=SubtaskStatus.COMPLETED) + chunk3 = Subtask(id="c3", description="Chunk 3", status=SubtaskStatus.PENDING) phase = Phase(phase=1, name="Test", subtasks=[chunk1, chunk2, chunk3]) completed, total = phase.get_progress() @@ -240,7 +240,7 @@ class TestPhase: def test_phase_to_dict(self): """Phase serializes to dict.""" - chunk = Chunk(id="c1", description="Test") + chunk = Subtask(id="c1", description="Test") phase = Phase( phase=1, name="Setup", @@ -295,7 +295,7 @@ class TestImplementationPlan: # Mark phase 1 as complete for chunk in plan.phases[0].subtasks: - chunk.status = ChunkStatus.COMPLETED + chunk.status = SubtaskStatus.COMPLETED available = plan.get_available_phases() @@ -314,14 +314,14 @@ class TestImplementationPlan: phase, subtask = result # Should be first pending subtask in phase 1 assert phase.phase == 1 - assert subtask.status == ChunkStatus.PENDING + assert subtask.status == SubtaskStatus.PENDING def test_plan_get_progress(self, sample_implementation_plan: dict): """Gets overall progress.""" plan = ImplementationPlan.from_dict(sample_implementation_plan) # Complete some subtasks - plan.phases[0].subtasks[0].status = ChunkStatus.COMPLETED + plan.phases[0].subtasks[0].status = SubtaskStatus.COMPLETED progress = plan.get_progress() @@ -449,7 +449,7 @@ class TestCreateInvestigationPlan: # Fix phase should have blocked chunks fix_phase = plan.phases[2] # Phase 3 - Fix - assert any(c.status == ChunkStatus.BLOCKED for c in fix_phase.subtasks) + assert any(c.status == SubtaskStatus.BLOCKED for c in fix_phase.subtasks) class TestCreateRefactorPlan: @@ -500,10 +500,10 @@ class TestDependencyResolution: feature="Test", phases=[ Phase(phase=1, name="Setup", subtasks=[ - Chunk(id="c1", description="Setup", status=ChunkStatus.PENDING) + Subtask(id="c1", description="Setup", status=SubtaskStatus.PENDING) ]), Phase(phase=2, name="Build", depends_on=[1], subtasks=[ - Chunk(id="c2", description="Build") + Subtask(id="c2", description="Build") ]), ], ) @@ -520,13 +520,13 @@ class TestDependencyResolution: feature="Test", phases=[ Phase(phase=1, name="Setup", subtasks=[ - Chunk(id="c1", description="Setup", status=ChunkStatus.COMPLETED) + Subtask(id="c1", description="Setup", status=SubtaskStatus.COMPLETED) ]), Phase(phase=2, name="Backend", depends_on=[1], subtasks=[ - Chunk(id="c2", description="Backend") + Subtask(id="c2", description="Backend") ]), Phase(phase=3, name="Frontend", depends_on=[1], subtasks=[ - Chunk(id="c3", description="Frontend") + Subtask(id="c3", description="Frontend") ]), ], ) @@ -545,13 +545,13 @@ class TestDependencyResolution: feature="Test", phases=[ Phase(phase=1, name="Phase1", subtasks=[ - Chunk(id="c1", description="C1", status=ChunkStatus.COMPLETED) + Subtask(id="c1", description="C1", status=SubtaskStatus.COMPLETED) ]), Phase(phase=2, name="Phase2", subtasks=[ - Chunk(id="c2", description="C2", status=ChunkStatus.PENDING) + Subtask(id="c2", description="C2", status=SubtaskStatus.PENDING) ]), Phase(phase=3, name="Phase3", depends_on=[1, 2], subtasks=[ - Chunk(id="c3", description="C3") + Subtask(id="c3", description="C3") ]), ], ) @@ -563,12 +563,12 @@ class TestDependencyResolution: assert 3 not in phase_nums -class TestChunkCritique: - """Tests for self-critique functionality on chunks.""" +class TestSubtaskCritique: + """Tests for self-critique functionality on subtasks.""" def test_chunk_stores_critique_result(self): - """Chunk can store critique results.""" - chunk = Chunk(id="test", description="Test") + """Subtask can store critique results.""" + chunk = Subtask(id="test", description="Test") chunk.critique_result = { "passed": True, @@ -580,7 +580,7 @@ class TestChunkCritique: def test_critique_serializes(self): """Critique result serializes correctly.""" - chunk = Chunk(id="test", description="Test") + chunk = Subtask(id="test", description="Test") chunk.critique_result = {"passed": False, "issues": ["Missing tests"]} data = chunk.to_dict() @@ -596,7 +596,7 @@ class TestChunkCritique: "critique_result": {"passed": True, "score": 8}, } - chunk = Chunk.from_dict(data) + chunk = Subtask.from_dict(data) assert chunk.critique_result is not None assert chunk.critique_result["score"] == 8 diff --git a/tests/test_merge_auto_merger.py b/tests/test_merge_auto_merger.py index af5d1a1b..006d5499 100644 --- a/tests/test_merge_auto_merger.py +++ b/tests/test_merge_auto_merger.py @@ -23,7 +23,7 @@ from pathlib import Path import pytest # Add auto-claude directory to path for imports -sys.path.insert(0, str(Path(__file__).parent.parent / "Apps" / "backend")) +sys.path.insert(0, str(Path(__file__).parent.parent / "apps" / "backend")) from merge import ( ChangeType, diff --git a/tests/test_merge_conflict_detector.py b/tests/test_merge_conflict_detector.py index 47eb845d..115f874f 100644 --- a/tests/test_merge_conflict_detector.py +++ b/tests/test_merge_conflict_detector.py @@ -20,7 +20,7 @@ from pathlib import Path import pytest # Add auto-claude directory to path for imports -sys.path.insert(0, str(Path(__file__).parent.parent / "Apps" / "backend")) +sys.path.insert(0, str(Path(__file__).parent.parent / "apps" / "backend")) from merge import ( ChangeType, diff --git a/tests/test_merge_file_tracker.py b/tests/test_merge_file_tracker.py index 4a6839da..4563e7ed 100644 --- a/tests/test_merge_file_tracker.py +++ b/tests/test_merge_file_tracker.py @@ -21,7 +21,7 @@ from pathlib import Path import pytest # Add auto-claude directory to path for imports -sys.path.insert(0, str(Path(__file__).parent.parent / "Apps" / "backend")) +sys.path.insert(0, str(Path(__file__).parent.parent / "apps" / "backend")) # Add tests directory to path for test_fixtures sys.path.insert(0, str(Path(__file__).parent)) diff --git a/tests/test_merge_fixtures.py b/tests/test_merge_fixtures.py index 98c904a7..497cecd8 100644 --- a/tests/test_merge_fixtures.py +++ b/tests/test_merge_fixtures.py @@ -18,7 +18,7 @@ from typing import Callable, Generator import pytest # Add auto-claude directory to path for imports -sys.path.insert(0, str(Path(__file__).parent.parent / "Apps" / "backend")) +sys.path.insert(0, str(Path(__file__).parent.parent / "apps" / "backend")) from merge import ( SemanticAnalyzer, diff --git a/tests/test_merge_orchestrator.py b/tests/test_merge_orchestrator.py index ecaa65c8..1652570f 100644 --- a/tests/test_merge_orchestrator.py +++ b/tests/test_merge_orchestrator.py @@ -23,7 +23,7 @@ from pathlib import Path import pytest # Add auto-claude directory to path for imports -sys.path.insert(0, str(Path(__file__).parent.parent / "Apps" / "backend")) +sys.path.insert(0, str(Path(__file__).parent.parent / "apps" / "backend")) # Add tests directory to path for test_fixtures sys.path.insert(0, str(Path(__file__).parent)) diff --git a/tests/test_merge_parallel.py b/tests/test_merge_parallel.py index ee739040..b4af1c2b 100644 --- a/tests/test_merge_parallel.py +++ b/tests/test_merge_parallel.py @@ -18,7 +18,7 @@ from pathlib import Path import pytest # Add auto-claude directory to path for imports -sys.path.insert(0, str(Path(__file__).parent.parent / "Apps" / "backend")) +sys.path.insert(0, str(Path(__file__).parent.parent / "apps" / "backend")) from workspace import ParallelMergeTask, ParallelMergeResult from core.workspace import _run_parallel_merges diff --git a/tests/test_merge_semantic_analyzer.py b/tests/test_merge_semantic_analyzer.py index e3c58d65..26029f74 100644 --- a/tests/test_merge_semantic_analyzer.py +++ b/tests/test_merge_semantic_analyzer.py @@ -19,7 +19,7 @@ from pathlib import Path import pytest # Add auto-claude directory to path for imports -sys.path.insert(0, str(Path(__file__).parent.parent / "Apps" / "backend")) +sys.path.insert(0, str(Path(__file__).parent.parent / "apps" / "backend")) # Add tests directory to path for test_fixtures sys.path.insert(0, str(Path(__file__).parent)) diff --git a/tests/test_merge_types.py b/tests/test_merge_types.py index 68e0a157..111b4b49 100644 --- a/tests/test_merge_types.py +++ b/tests/test_merge_types.py @@ -21,7 +21,7 @@ from pathlib import Path import pytest # Add auto-claude directory to path for imports -sys.path.insert(0, str(Path(__file__).parent.parent / "Apps" / "backend")) +sys.path.insert(0, str(Path(__file__).parent.parent / "apps" / "backend")) from merge import ( ChangeType, diff --git a/tests/test_qa_criteria.py b/tests/test_qa_criteria.py index 00e963f0..7e0c24b3 100644 --- a/tests/test_qa_criteria.py +++ b/tests/test_qa_criteria.py @@ -100,7 +100,7 @@ mock_client.create_client = MagicMock() sys.modules['client'] = mock_client # Now we can safely add the auto-claude path and import -sys.path.insert(0, str(Path(__file__).parent.parent / "Apps" / "backend")) +sys.path.insert(0, str(Path(__file__).parent.parent / "apps" / "backend")) # Import criteria functions directly to avoid going through qa/__init__.py # which imports reviewer and fixer that need the SDK diff --git a/tests/test_qa_loop_enhancements.py b/tests/test_qa_loop_enhancements.py index 4fcbf309..eab7dd39 100644 --- a/tests/test_qa_loop_enhancements.py +++ b/tests/test_qa_loop_enhancements.py @@ -18,7 +18,7 @@ import pytest # Add auto-claude to path for imports import sys -sys.path.insert(0, str(Path(__file__).parent.parent / "Apps" / "backend")) +sys.path.insert(0, str(Path(__file__).parent.parent / "apps" / "backend")) from qa_loop import ( # Iteration tracking diff --git a/tests/test_risk_classifier.py b/tests/test_risk_classifier.py index 2f12fccd..3beb0734 100644 --- a/tests/test_risk_classifier.py +++ b/tests/test_risk_classifier.py @@ -17,7 +17,7 @@ from pathlib import Path import sys -sys.path.insert(0, str(Path(__file__).parent.parent / "Apps" / "backend")) +sys.path.insert(0, str(Path(__file__).parent.parent / "apps" / "backend")) from risk_classifier import ( RiskClassifier, diff --git a/tests/test_security_scanner.py b/tests/test_security_scanner.py index 0f1e95be..6b73443a 100644 --- a/tests/test_security_scanner.py +++ b/tests/test_security_scanner.py @@ -19,7 +19,7 @@ import pytest # Add auto-claude to path for imports import sys -sys.path.insert(0, str(Path(__file__).parent.parent / "Apps" / "backend")) +sys.path.insert(0, str(Path(__file__).parent.parent / "apps" / "backend")) from security_scanner import ( SecurityVulnerability, diff --git a/tests/test_service_orchestrator.py b/tests/test_service_orchestrator.py index 5a59efd9..73dac1a0 100644 --- a/tests/test_service_orchestrator.py +++ b/tests/test_service_orchestrator.py @@ -17,9 +17,9 @@ import pytest # Add auto-claude to path for imports import sys -sys.path.insert(0, str(Path(__file__).parent.parent / "Apps" / "backend")) +sys.path.insert(0, str(Path(__file__).parent.parent / "apps" / "backend")) -from service_orchestrator import ( +from services.orchestrator import ( ServiceConfig, OrchestrationResult, ServiceOrchestrator, diff --git a/tests/test_spec_complexity.py b/tests/test_spec_complexity.py index 36944477..14d131c7 100644 --- a/tests/test_spec_complexity.py +++ b/tests/test_spec_complexity.py @@ -50,7 +50,7 @@ sys.modules['claude_agent_sdk'] = mock_agent_sdk sys.modules['claude_agent_sdk.types'] = mock_agent_types # Add auto-claude directory to path for imports -sys.path.insert(0, str(Path(__file__).parent.parent / "Apps" / "backend")) +sys.path.insert(0, str(Path(__file__).parent.parent / "apps" / "backend")) from spec.complexity import ( Complexity, diff --git a/tests/test_spec_pipeline.py b/tests/test_spec_pipeline.py index 51b61905..878f4385 100644 --- a/tests/test_spec_pipeline.py +++ b/tests/test_spec_pipeline.py @@ -18,7 +18,7 @@ from pathlib import Path from unittest.mock import MagicMock, patch # Add auto-claude directory to path for imports -sys.path.insert(0, str(Path(__file__).parent.parent / "Apps" / "backend")) +sys.path.insert(0, str(Path(__file__).parent.parent / "apps" / "backend")) # Store original modules for cleanup _original_modules = {} diff --git a/tests/test_thinking_level_validation.py b/tests/test_thinking_level_validation.py index 34da1a51..0d5bbe32 100644 --- a/tests/test_thinking_level_validation.py +++ b/tests/test_thinking_level_validation.py @@ -10,7 +10,7 @@ import sys from pathlib import Path # Add auto-claude to path -sys.path.insert(0, str(Path(__file__).parent.parent / "Apps" / "backend")) +sys.path.insert(0, str(Path(__file__).parent.parent / "apps" / "backend")) from phase_config import THINKING_BUDGET_MAP, get_thinking_budget diff --git a/tests/test_validation_strategy.py b/tests/test_validation_strategy.py index db916091..a8a6174f 100644 --- a/tests/test_validation_strategy.py +++ b/tests/test_validation_strategy.py @@ -18,9 +18,9 @@ import pytest # Add auto-claude to path for imports import sys -sys.path.insert(0, str(Path(__file__).parent.parent / "Apps" / "backend")) +sys.path.insert(0, str(Path(__file__).parent.parent / "apps" / "backend")) -from validation_strategy import ( +from spec.validation_strategy import ( ValidationStep, ValidationStrategy, ValidationStrategyBuilder,