auto-claude: 147-remove-outdated-compatibility-shims (#1465)

* auto-claude: subtask-1-1 - Remove validation_strategy backward compatibility shim

- Delete apps/backend/validation_strategy.py shim file that re-exported from spec.validation_strategy
- Update docstring in spec/validation_strategy.py to show correct import path

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* auto-claude: subtask-1-2 - Remove service_orchestrator shim

- Deleted apps/backend/service_orchestrator.py backward compatibility shim file
- Updated docstring in services/orchestrator.py to use correct import path
  (from services.orchestrator import instead of from service_orchestrator import)
- Verified no external imports of the shim remain in the codebase
- Import from services.orchestrator works correctly

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* auto-claude: subtask-2-1 - Remove Chunk/ChunkStatus aliases from implementation_plan

Removed backwards compatibility aliases as part of cleaning up outdated
compatibility shims:

- Removed ChunkStatus = SubtaskStatus from enums.py
- Removed Chunk = Subtask from subtask.py
- Removed Chunk/ChunkStatus exports from __init__.py
- Updated all test files to use canonical names (Subtask, SubtaskStatus)

This completes subtasks 2-1, 2-2, and 2-3 together since the test files
depend on all three changes being made atomically.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* auto-claude: subtask-2-4 - Remove deprecated use_orchestrator_review field from GitHub runner models

* auto-claude: subtask-3-1 - Update apps/frontend/src/main/index.ts to use platform imports

* auto-claude: subtask-3-2 - Update python-detector.ts to use platform imports

- Import isWindows from ./platform module
- Replace all process.platform === 'win32' checks with isWindows() calls
- Remove redundant local isWindows variable declarations

* auto-claude: subtask-3-3 - Update apps/frontend/src/main/claude-cli-utils.ts to use platform imports

* auto-claude: subtask-3-4 - Update apps/frontend/src/main/config-paths.ts to use platform imports

* auto-claude: subtask-3-5 - Update apps/frontend/src/main/memory-service.ts to use platform imports

* auto-claude: subtask-4-1 - Update claude-code-handlers.ts to use platform imports

Replace all direct process.platform checks with centralized platform
abstraction functions from ../platform module:
- isWindows() for Windows platform checks
- isMacOS() for macOS/Darwin platform checks
- isLinux() for Linux platform checks

This removes 6 instances of process.platform === '...' comparisons and
1 local isWindows variable assignment, replacing them with the platform
abstraction layer for better cross-platform consistency.

* auto-claude: subtask-4-2 - Update apps/frontend/src/main/ipc-handlers/mcp-handlers.ts to use platform imports

* auto-claude: subtask-4-3 - Update apps/frontend/src/main/ipc-handlers/memory-handlers.ts to use platform imports

- Replace process.platform checks with platform module functions
- Add getOllamaExecutablePaths(), getOllamaInstallCommand(), and getWhichCommand() to platform/paths.ts
- Export new functions from platform/index.ts
- Migrate checkOllamaInstalled() to use platform module for path resolution
- Migrate getOllamaInstallCommand() to delegate to platform module
- Update debug log to use getCurrentOS() instead of process.platform

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* auto-claude: subtask-4-4 - Update apps/frontend/src/main/ipc-handlers/termina

* auto-claude: subtask-4-5 - Update apps/frontend/src/main/ipc-handlers/github/

- Replace direct process.platform check with getWhichCommand() from platform abstraction
- Import getWhichCommand from ../../platform for cross-platform which/where command

* auto-claude: subtask-4-6 - Update apps/frontend/src/main/ipc-handlers/github/utils/subprocess-runner.ts to use platform imports

* auto-claude: subtask-5-1 - Update apps/frontend/src/main/agent/agent-process.ts

Replace direct process.platform checks with platform abstraction:
- Import isWindows from ../platform module
- Replace `process.platform !== 'win32'` with `!isWindows()`
- Replace `process.platform === 'win32'` with `isWindows()`

This ensures consistent platform detection using the centralized
platform abstraction layer.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* auto-claude: subtask-5-2 - Update apps/frontend/src/main/agent/agent-queue.ts

* auto-claude: subtask-5-3 - Update apps/frontend/src/main/terminal/pty-daemon.ts to use platform imports

* auto-claude: subtask-5-4 - Update pty-daemon-client.ts to use platform imports

Replace direct process.platform === 'win32' check with isWindows()
from the platform abstraction layer for consistent cross-platform
handling of socket paths.

* auto-claude: subtask-5-5 - Update apps/frontend/src/main/insights/config.ts to use platform imports

- Import isWindows() from '../platform'
- Replace process.platform === 'win32' checks with isWindows()
- Maintains case-insensitive path comparison on Windows

* auto-claude: subtask-5-6 - Update apps/frontend/src/main/changelog/version-suggester.ts to use platform imports

* auto-claude: subtask-5-7 - Update apps/frontend/src/main/changelog/generator.

* fix: Remove unused import and fix test import paths

- Remove unused `isWindows` import from memory-handlers.ts
- Fix test_service_orchestrator.py to import from services.orchestrator
  instead of the removed service_orchestrator shim
- Fix case sensitivity in path ("Apps" -> "apps")

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: Fix test import paths for case sensitivity and removed shims

- Fix path case sensitivity: "Apps" -> "apps" in 21 test files
- Update test_validation_strategy.py to import from spec.validation_strategy

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Andy
2026-01-25 12:31:06 +01:00
committed by GitHub
parent b955badf7f
commit 53111dbb95
53 changed files with 279 additions and 295 deletions
+2 -6
View File
@@ -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",
]
@@ -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
@@ -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
-3
View File
@@ -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)
)
-19
View File
@@ -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",
]
+1 -1
View File
@@ -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():
+1 -1
View File
@@ -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")
-3
View File
@@ -1,3 +0,0 @@
"""Backward compatibility shim - import from spec.validation_strategy instead."""
from spec.validation_strategy import * # noqa: F403
@@ -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<Record<CliTool, string>> = {
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<string, string> = {};
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) {
+3 -2
View File
@@ -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)
@@ -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<string, string> {
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',
@@ -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<string, string> {
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',
+3 -2
View File
@@ -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<string, string>): 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())
+2 -1
View File
@@ -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');
}
+8 -7
View File
@@ -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();
}
});
+3 -2
View File
@@ -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;
});
@@ -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<Claud
const installations: ClaudeInstallationInfo[] = [];
const seenPaths = new Set<string>();
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<Claud
// 2. Check system PATH via which/where
try {
if (isWindows) {
if (isWindows()) {
const result = await execFileAsync('where', ['claude'], { timeout: 5000 });
const paths = result.stdout.trim().split('\n').filter(p => p.trim());
for (const p of paths) {
@@ -166,14 +164,14 @@ async function scanClaudeInstallations(activePath: string | null): Promise<Claud
}
// 3. Homebrew paths (macOS) - from getClaudeDetectionPaths
if (process.platform === 'darwin') {
if (isMacOS()) {
for (const p of detectionPaths.homebrewPaths) {
await addInstallation(p, 'homebrew');
}
}
// 4. NVM paths (Unix) - check Node.js version manager
if (!isWindows && existsSync(detectionPaths.nvmVersionsDir)) {
if (!isWindows() && existsSync(detectionPaths.nvmVersionsDir)) {
try {
const entries = await fsPromises.readdir(detectionPaths.nvmVersionsDir, { withFileTypes: true });
const versionDirs = sortNvmVersionDirs(entries);
@@ -192,7 +190,7 @@ async function scanClaudeInstallations(activePath: string | null): Promise<Claud
}
// 6. Additional common paths not in getClaudeDetectionPaths (for broader scanning)
const additionalPaths = isWindows
const additionalPaths = isWindows()
? [] // Windows paths are well covered by detectionPaths.platformPaths
: [
path.join(homeDir, '.npm-global', 'bin', 'claude'),
@@ -338,7 +336,7 @@ async function fetchAvailableVersions(): Promise<string[]> {
* @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<void> {
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<void> {
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);
@@ -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 {
@@ -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<GitHubModu
result.ghCliInstalled = true;
} catch {
result.ghCliInstalled = false;
const installInstructions = process.platform === 'win32'
const installInstructions = isWindows()
? 'winget install --id GitHub.cli'
: process.platform === 'darwin'
: isMacOS()
? 'brew install gh'
: 'See https://cli.github.com/';
result.error = `GitHub CLI (gh) is not installed. Install it with:\n ${installInstructions}`;
@@ -9,6 +9,7 @@ import { IPC_CHANNELS } from '../../shared/constants/ipc';
import type { CustomMcpServer, McpHealthCheckResult, McpHealthStatus, McpTestConnectionResult } from '../../shared/types/project';
import { spawn } from 'child_process';
import { appLog } from '../app-logger';
import { isWindows } from '../platform';
/**
* Defense-in-depth: Frontend-side command validation
@@ -54,7 +55,7 @@ function areArgsSafe(args: string[] | undefined): boolean {
if (args.some(arg => 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 = '';
@@ -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<IPCResult<{ command: string }>> => {
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...');
@@ -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 {
+2 -1
View File
@@ -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');
+1 -1
View File
@@ -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
+65
View File
@@ -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
*
+6 -8
View File
@@ -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'
@@ -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;
@@ -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) {
+1 -1
View File
@@ -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:
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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,
+8 -8
View File
@@ -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}")
+1 -1
View File
@@ -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,
+43 -43
View File
@@ -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()
+1 -1
View File
@@ -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
+55 -55
View File
@@ -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
+1 -1
View File
@@ -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,
+1 -1
View File
@@ -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,
+1 -1
View File
@@ -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))
+1 -1
View File
@@ -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,
+1 -1
View File
@@ -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))
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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))
+1 -1
View File
@@ -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,
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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,
+1 -1
View File
@@ -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,
+2 -2
View File
@@ -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,
+1 -1
View File
@@ -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,
+1 -1
View File
@@ -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 = {}
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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,