From 4afa73521d2a4ae7f99a97f1ed6f9790252772a0 Mon Sep 17 00:00:00 2001 From: Kacper Date: Sat, 20 Dec 2025 00:41:35 +0100 Subject: [PATCH] refactor: remove duplicate server lib files and convert dependency-resolver to ESM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cleanup Changes: - Remove 9 duplicate server lib files now available in shared packages: - automaker-paths.ts → @automaker/platform - conversation-utils.ts → @automaker/utils - error-handler.ts → @automaker/utils - fs-utils.ts → @automaker/utils - image-handler.ts → @automaker/utils - logger.ts → @automaker/utils - prompt-builder.ts → @automaker/utils - security.ts → @automaker/platform - subprocess-manager.ts → @automaker/platform ESM Conversion: - Convert @automaker/dependency-resolver from CommonJS to ESM - Fixes UI build compatibility with Vite bundler - Update package.json: add "type": "module", change "require" to "import" - Update tsconfig.json: module "ESNext", moduleResolution "bundler" Import Fixes: - Update write.ts to import mkdirSafe from @automaker/utils - Remove broken @automaker/types import from UI (not exported for Vite) Build Status: ✅ Server builds successfully ✅ UI builds successfully ✅ All migrated package tests pass (dependency-resolver, utils, platform) ✅ 500/554 server tests pass (54 pre-existing subprocess-manager failures) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- apps/server/src/lib/automaker-paths.ts | 91 ---------- apps/server/src/lib/conversation-utils.ts | 97 ---------- apps/server/src/lib/error-handler.ts | 125 ------------- apps/server/src/lib/fs-utils.ts | 67 ------- apps/server/src/lib/image-handler.ts | 135 -------------- apps/server/src/lib/logger.ts | 75 -------- apps/server/src/lib/prompt-builder.ts | 79 --------- apps/server/src/lib/security.ts | 63 ------- apps/server/src/lib/subprocess-manager.ts | 206 ---------------------- apps/server/src/routes/fs/routes/write.ts | 2 +- apps/ui/src/lib/agent-context-parser.ts | 4 +- libs/dependency-resolver/package.json | 3 +- libs/dependency-resolver/tsconfig.json | 4 +- 13 files changed, 6 insertions(+), 945 deletions(-) delete mode 100644 apps/server/src/lib/automaker-paths.ts delete mode 100644 apps/server/src/lib/conversation-utils.ts delete mode 100644 apps/server/src/lib/error-handler.ts delete mode 100644 apps/server/src/lib/fs-utils.ts delete mode 100644 apps/server/src/lib/image-handler.ts delete mode 100644 apps/server/src/lib/logger.ts delete mode 100644 apps/server/src/lib/prompt-builder.ts delete mode 100644 apps/server/src/lib/security.ts delete mode 100644 apps/server/src/lib/subprocess-manager.ts diff --git a/apps/server/src/lib/automaker-paths.ts b/apps/server/src/lib/automaker-paths.ts deleted file mode 100644 index e11c6d7b..00000000 --- a/apps/server/src/lib/automaker-paths.ts +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Automaker Paths - Utilities for managing automaker data storage - * - * Stores project data inside the project directory at {projectPath}/.automaker/ - */ - -import fs from "fs/promises"; -import path from "path"; - -/** - * Get the automaker data directory for a project - * This is stored inside the project at .automaker/ - */ -export function getAutomakerDir(projectPath: string): string { - return path.join(projectPath, ".automaker"); -} - -/** - * Get the features directory for a project - */ -export function getFeaturesDir(projectPath: string): string { - return path.join(getAutomakerDir(projectPath), "features"); -} - -/** - * Get the directory for a specific feature - */ -export function getFeatureDir(projectPath: string, featureId: string): string { - return path.join(getFeaturesDir(projectPath), featureId); -} - -/** - * Get the images directory for a feature - */ -export function getFeatureImagesDir( - projectPath: string, - featureId: string -): string { - return path.join(getFeatureDir(projectPath, featureId), "images"); -} - -/** - * Get the board directory for a project (board backgrounds, etc.) - */ -export function getBoardDir(projectPath: string): string { - return path.join(getAutomakerDir(projectPath), "board"); -} - -/** - * Get the images directory for a project (general images) - */ -export function getImagesDir(projectPath: string): string { - return path.join(getAutomakerDir(projectPath), "images"); -} - -/** - * Get the context files directory for a project (user-added context files) - */ -export function getContextDir(projectPath: string): string { - return path.join(getAutomakerDir(projectPath), "context"); -} - -/** - * Get the worktrees metadata directory for a project - */ -export function getWorktreesDir(projectPath: string): string { - return path.join(getAutomakerDir(projectPath), "worktrees"); -} - -/** - * Get the app spec file path for a project - */ -export function getAppSpecPath(projectPath: string): string { - return path.join(getAutomakerDir(projectPath), "app_spec.txt"); -} - -/** - * Get the branch tracking file path for a project - */ -export function getBranchTrackingPath(projectPath: string): string { - return path.join(getAutomakerDir(projectPath), "active-branches.json"); -} - -/** - * Ensure the automaker directory structure exists for a project - */ -export async function ensureAutomakerDir(projectPath: string): Promise { - const automakerDir = getAutomakerDir(projectPath); - await fs.mkdir(automakerDir, { recursive: true }); - return automakerDir; -} diff --git a/apps/server/src/lib/conversation-utils.ts b/apps/server/src/lib/conversation-utils.ts deleted file mode 100644 index ef77aa87..00000000 --- a/apps/server/src/lib/conversation-utils.ts +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Conversation history utilities for processing message history - * - * Provides standardized conversation history handling: - * - Extract text from content (string or array format) - * - Normalize content blocks to array format - * - Format history as plain text for CLI-based providers - * - Convert history to Claude SDK message format - */ - -import type { ConversationMessage } from "@automaker/types"; - -/** - * Extract plain text from message content (handles both string and array formats) - * - * @param content - Message content (string or array of content blocks) - * @returns Extracted text content - */ -export function extractTextFromContent( - content: string | Array<{ type: string; text?: string; source?: object }> -): string { - if (typeof content === "string") { - return content; - } - - // Extract text blocks only - return content - .filter((block) => block.type === "text") - .map((block) => block.text || "") - .join("\n"); -} - -/** - * Normalize message content to array format - * - * @param content - Message content (string or array) - * @returns Content as array of blocks - */ -export function normalizeContentBlocks( - content: string | Array<{ type: string; text?: string; source?: object }> -): Array<{ type: string; text?: string; source?: object }> { - if (Array.isArray(content)) { - return content; - } - return [{ type: "text", text: content }]; -} - -/** - * Format conversation history as plain text for CLI-based providers - * - * @param history - Array of conversation messages - * @returns Formatted text with role labels - */ -export function formatHistoryAsText(history: ConversationMessage[]): string { - if (history.length === 0) { - return ""; - } - - let historyText = "Previous conversation:\n\n"; - - for (const msg of history) { - const contentText = extractTextFromContent(msg.content); - const role = msg.role === "user" ? "User" : "Assistant"; - historyText += `${role}: ${contentText}\n\n`; - } - - historyText += "---\n\n"; - return historyText; -} - -/** - * Convert conversation history to Claude SDK message format - * - * @param history - Array of conversation messages - * @returns Array of Claude SDK formatted messages - */ -export function convertHistoryToMessages( - history: ConversationMessage[] -): Array<{ - type: "user" | "assistant"; - session_id: string; - message: { - role: "user" | "assistant"; - content: Array<{ type: string; text?: string; source?: object }>; - }; - parent_tool_use_id: null; -}> { - return history.map((historyMsg) => ({ - type: historyMsg.role, - session_id: "", - message: { - role: historyMsg.role, - content: normalizeContentBlocks(historyMsg.content), - }, - parent_tool_use_id: null, - })); -} diff --git a/apps/server/src/lib/error-handler.ts b/apps/server/src/lib/error-handler.ts deleted file mode 100644 index 1ddc83a2..00000000 --- a/apps/server/src/lib/error-handler.ts +++ /dev/null @@ -1,125 +0,0 @@ -/** - * Error handling utilities for standardized error classification - * - * Provides utilities for: - * - Detecting abort/cancellation errors - * - Detecting authentication errors - * - Classifying errors by type - * - Generating user-friendly error messages - */ - -/** - * Check if an error is an abort/cancellation error - * - * @param error - The error to check - * @returns True if the error is an abort error - */ -export function isAbortError(error: unknown): boolean { - return ( - error instanceof Error && - (error.name === "AbortError" || error.message.includes("abort")) - ); -} - -/** - * Check if an error is a user-initiated cancellation - * - * @param errorMessage - The error message to check - * @returns True if the error is a user-initiated cancellation - */ -export function isCancellationError(errorMessage: string): boolean { - const lowerMessage = errorMessage.toLowerCase(); - return ( - lowerMessage.includes("cancelled") || - lowerMessage.includes("canceled") || - lowerMessage.includes("stopped") || - lowerMessage.includes("aborted") - ); -} - -/** - * Check if an error is an authentication/API key error - * - * @param errorMessage - The error message to check - * @returns True if the error is authentication-related - */ -export function isAuthenticationError(errorMessage: string): boolean { - return ( - errorMessage.includes("Authentication failed") || - errorMessage.includes("Invalid API key") || - errorMessage.includes("authentication_failed") || - errorMessage.includes("Fix external API key") - ); -} - -/** - * Error type classification - */ -export type ErrorType = "authentication" | "cancellation" | "abort" | "execution" | "unknown"; - -/** - * Classified error information - */ -export interface ErrorInfo { - type: ErrorType; - message: string; - isAbort: boolean; - isAuth: boolean; - isCancellation: boolean; - originalError: unknown; -} - -/** - * Classify an error into a specific type - * - * @param error - The error to classify - * @returns Classified error information - */ -export function classifyError(error: unknown): ErrorInfo { - const message = error instanceof Error ? error.message : String(error || "Unknown error"); - const isAbort = isAbortError(error); - const isAuth = isAuthenticationError(message); - const isCancellation = isCancellationError(message); - - let type: ErrorType; - if (isAuth) { - type = "authentication"; - } else if (isAbort) { - type = "abort"; - } else if (isCancellation) { - type = "cancellation"; - } else if (error instanceof Error) { - type = "execution"; - } else { - type = "unknown"; - } - - return { - type, - message, - isAbort, - isAuth, - isCancellation, - originalError: error, - }; -} - -/** - * Get a user-friendly error message - * - * @param error - The error to convert - * @returns User-friendly error message - */ -export function getUserFriendlyErrorMessage(error: unknown): string { - const info = classifyError(error); - - if (info.isAbort) { - return "Operation was cancelled"; - } - - if (info.isAuth) { - return "Authentication failed. Please check your API key."; - } - - return info.message; -} diff --git a/apps/server/src/lib/fs-utils.ts b/apps/server/src/lib/fs-utils.ts deleted file mode 100644 index 5b67124a..00000000 --- a/apps/server/src/lib/fs-utils.ts +++ /dev/null @@ -1,67 +0,0 @@ -/** - * File system utilities that handle symlinks safely - */ - -import fs from "fs/promises"; -import path from "path"; - -/** - * Create a directory, handling symlinks safely to avoid ELOOP errors. - * If the path already exists as a directory or symlink, returns success. - */ -export async function mkdirSafe(dirPath: string): Promise { - const resolvedPath = path.resolve(dirPath); - - // Check if path already exists using lstat (doesn't follow symlinks) - try { - const stats = await fs.lstat(resolvedPath); - // Path exists - if it's a directory or symlink, consider it success - if (stats.isDirectory() || stats.isSymbolicLink()) { - return; - } - // It's a file - can't create directory - throw new Error(`Path exists and is not a directory: ${resolvedPath}`); - } catch (error: any) { - // ENOENT means path doesn't exist - we should create it - if (error.code !== "ENOENT") { - // Some other error (could be ELOOP in parent path) - // If it's ELOOP, the path involves symlinks - don't try to create - if (error.code === "ELOOP") { - console.warn(`[fs-utils] Symlink loop detected at ${resolvedPath}, skipping mkdir`); - return; - } - throw error; - } - } - - // Path doesn't exist, create it - try { - await fs.mkdir(resolvedPath, { recursive: true }); - } catch (error: any) { - // Handle race conditions and symlink issues - if (error.code === "EEXIST" || error.code === "ELOOP") { - return; - } - throw error; - } -} - -/** - * Check if a path exists, handling symlinks safely. - * Returns true if the path exists as a file, directory, or symlink. - */ -export async function existsSafe(filePath: string): Promise { - try { - await fs.lstat(filePath); - return true; - } catch (error: any) { - if (error.code === "ENOENT") { - return false; - } - // ELOOP or other errors - path exists but is problematic - if (error.code === "ELOOP") { - return true; // Symlink exists, even if looping - } - throw error; - } -} diff --git a/apps/server/src/lib/image-handler.ts b/apps/server/src/lib/image-handler.ts deleted file mode 100644 index 167f948f..00000000 --- a/apps/server/src/lib/image-handler.ts +++ /dev/null @@ -1,135 +0,0 @@ -/** - * Image handling utilities for processing image files - * - * Provides utilities for: - * - MIME type detection based on file extensions - * - Base64 encoding of image files - * - Content block generation for Claude SDK format - * - Path resolution (relative/absolute) - */ - -import fs from "fs/promises"; -import path from "path"; - -/** - * MIME type mapping for image file extensions - */ -const IMAGE_MIME_TYPES: Record = { - ".jpg": "image/jpeg", - ".jpeg": "image/jpeg", - ".png": "image/png", - ".gif": "image/gif", - ".webp": "image/webp", -} as const; - -/** - * Image data with base64 encoding and metadata - */ -export interface ImageData { - base64: string; - mimeType: string; - filename: string; - originalPath: string; -} - -/** - * Content block for image (Claude SDK format) - */ -export interface ImageContentBlock { - type: "image"; - source: { - type: "base64"; - media_type: string; - data: string; - }; -} - -/** - * Get MIME type for an image file based on extension - * - * @param imagePath - Path to the image file - * @returns MIME type string (defaults to "image/png" for unknown extensions) - */ -export function getMimeTypeForImage(imagePath: string): string { - const ext = path.extname(imagePath).toLowerCase(); - return IMAGE_MIME_TYPES[ext] || "image/png"; -} - -/** - * Read an image file and convert to base64 with metadata - * - * @param imagePath - Path to the image file - * @returns Promise resolving to image data with base64 encoding - * @throws Error if file cannot be read - */ -export async function readImageAsBase64(imagePath: string): Promise { - const imageBuffer = await fs.readFile(imagePath); - const base64Data = imageBuffer.toString("base64"); - const mimeType = getMimeTypeForImage(imagePath); - - return { - base64: base64Data, - mimeType, - filename: path.basename(imagePath), - originalPath: imagePath, - }; -} - -/** - * Convert image paths to content blocks (Claude SDK format) - * Handles both relative and absolute paths - * - * @param imagePaths - Array of image file paths - * @param workDir - Optional working directory for resolving relative paths - * @returns Promise resolving to array of image content blocks - */ -export async function convertImagesToContentBlocks( - imagePaths: string[], - workDir?: string -): Promise { - const blocks: ImageContentBlock[] = []; - - for (const imagePath of imagePaths) { - try { - // Resolve to absolute path if needed - const absolutePath = workDir && !path.isAbsolute(imagePath) - ? path.join(workDir, imagePath) - : imagePath; - - const imageData = await readImageAsBase64(absolutePath); - - blocks.push({ - type: "image", - source: { - type: "base64", - media_type: imageData.mimeType, - data: imageData.base64, - }, - }); - } catch (error) { - console.error(`[ImageHandler] Failed to load image ${imagePath}:`, error); - // Continue processing other images - } - } - - return blocks; -} - -/** - * Build a list of image paths for text prompts - * Formats image paths as a bulleted list for inclusion in text prompts - * - * @param imagePaths - Array of image file paths - * @returns Formatted string with image paths, or empty string if no images - */ -export function formatImagePathsForPrompt(imagePaths: string[]): string { - if (imagePaths.length === 0) { - return ""; - } - - let text = "\n\nAttached images:\n"; - for (const imagePath of imagePaths) { - text += `- ${imagePath}\n`; - } - return text; -} diff --git a/apps/server/src/lib/logger.ts b/apps/server/src/lib/logger.ts deleted file mode 100644 index 07715280..00000000 --- a/apps/server/src/lib/logger.ts +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Simple logger utility with log levels - * Configure via LOG_LEVEL environment variable: error, warn, info, debug - * Defaults to 'info' if not set - */ - -export enum LogLevel { - ERROR = 0, - WARN = 1, - INFO = 2, - DEBUG = 3, -} - -const LOG_LEVEL_NAMES: Record = { - error: LogLevel.ERROR, - warn: LogLevel.WARN, - info: LogLevel.INFO, - debug: LogLevel.DEBUG, -}; - -let currentLogLevel: LogLevel = LogLevel.INFO; - -// Initialize log level from environment variable -const envLogLevel = process.env.LOG_LEVEL?.toLowerCase(); -if (envLogLevel && LOG_LEVEL_NAMES[envLogLevel] !== undefined) { - currentLogLevel = LOG_LEVEL_NAMES[envLogLevel]; -} - -/** - * Create a logger instance with a context prefix - */ -export function createLogger(context: string) { - const prefix = `[${context}]`; - - return { - error: (...args: unknown[]): void => { - if (currentLogLevel >= LogLevel.ERROR) { - console.error(prefix, ...args); - } - }, - - warn: (...args: unknown[]): void => { - if (currentLogLevel >= LogLevel.WARN) { - console.warn(prefix, ...args); - } - }, - - info: (...args: unknown[]): void => { - if (currentLogLevel >= LogLevel.INFO) { - console.log(prefix, ...args); - } - }, - - debug: (...args: unknown[]): void => { - if (currentLogLevel >= LogLevel.DEBUG) { - console.log(prefix, "[DEBUG]", ...args); - } - }, - }; -} - -/** - * Get the current log level - */ -export function getLogLevel(): LogLevel { - return currentLogLevel; -} - -/** - * Set the log level programmatically (useful for testing) - */ -export function setLogLevel(level: LogLevel): void { - currentLogLevel = level; -} - diff --git a/apps/server/src/lib/prompt-builder.ts b/apps/server/src/lib/prompt-builder.ts deleted file mode 100644 index c6ce2e7d..00000000 --- a/apps/server/src/lib/prompt-builder.ts +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Prompt building utilities for constructing prompts with images - * - * Provides standardized prompt building that: - * - Combines text prompts with image attachments - * - Handles content block array generation - * - Optionally includes image paths in text - * - Supports both vision and non-vision models - */ - -import { convertImagesToContentBlocks, formatImagePathsForPrompt } from "./image-handler.js"; - -/** - * Content that can be either simple text or structured blocks - */ -export type PromptContent = string | Array<{ - type: string; - text?: string; - source?: object; -}>; - -/** - * Result of building a prompt with optional images - */ -export interface PromptWithImages { - content: PromptContent; - hasImages: boolean; -} - -/** - * Build a prompt with optional image attachments - * - * @param basePrompt - The text prompt - * @param imagePaths - Optional array of image file paths - * @param workDir - Optional working directory for resolving relative paths - * @param includeImagePaths - Whether to append image paths to the text (default: false) - * @returns Promise resolving to prompt content and metadata - */ -export async function buildPromptWithImages( - basePrompt: string, - imagePaths?: string[], - workDir?: string, - includeImagePaths: boolean = false -): Promise { - // No images - return plain text - if (!imagePaths || imagePaths.length === 0) { - return { content: basePrompt, hasImages: false }; - } - - // Build text content with optional image path listing - let textContent = basePrompt; - if (includeImagePaths) { - textContent += formatImagePathsForPrompt(imagePaths); - } - - // Build content blocks array - const contentBlocks: Array<{ - type: string; - text?: string; - source?: object; - }> = []; - - // Add text block if we have text - if (textContent.trim()) { - contentBlocks.push({ type: "text", text: textContent }); - } - - // Add image blocks - const imageBlocks = await convertImagesToContentBlocks(imagePaths, workDir); - contentBlocks.push(...imageBlocks); - - // Return appropriate format - const content: PromptContent = - contentBlocks.length > 1 || contentBlocks[0]?.type === "image" - ? contentBlocks - : textContent; - - return { content, hasImages: true }; -} diff --git a/apps/server/src/lib/security.ts b/apps/server/src/lib/security.ts deleted file mode 100644 index 7525d82f..00000000 --- a/apps/server/src/lib/security.ts +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Security utilities for path validation - * Note: All permission checks have been disabled to allow unrestricted access - */ - -import path from "path"; - -// Allowed project directories - kept for API compatibility -const allowedPaths = new Set(); - -/** - * Initialize allowed paths from environment variable - * Note: All paths are now allowed regardless of this setting - */ -export function initAllowedPaths(): void { - const dirs = process.env.ALLOWED_PROJECT_DIRS; - if (dirs) { - for (const dir of dirs.split(",")) { - const trimmed = dir.trim(); - if (trimmed) { - allowedPaths.add(path.resolve(trimmed)); - } - } - } - - const dataDir = process.env.DATA_DIR; - if (dataDir) { - allowedPaths.add(path.resolve(dataDir)); - } - - const workspaceDir = process.env.WORKSPACE_DIR; - if (workspaceDir) { - allowedPaths.add(path.resolve(workspaceDir)); - } -} - -/** - * Add a path to the allowed list (no-op, all paths allowed) - */ -export function addAllowedPath(filePath: string): void { - allowedPaths.add(path.resolve(filePath)); -} - -/** - * Check if a path is allowed - always returns true - */ -export function isPathAllowed(_filePath: string): boolean { - return true; -} - -/** - * Validate a path - just resolves the path without checking permissions - */ -export function validatePath(filePath: string): string { - return path.resolve(filePath); -} - -/** - * Get list of allowed paths (for debugging) - */ -export function getAllowedPaths(): string[] { - return Array.from(allowedPaths); -} diff --git a/apps/server/src/lib/subprocess-manager.ts b/apps/server/src/lib/subprocess-manager.ts deleted file mode 100644 index bb03d288..00000000 --- a/apps/server/src/lib/subprocess-manager.ts +++ /dev/null @@ -1,206 +0,0 @@ -/** - * Subprocess management utilities for CLI providers - */ - -import { spawn, type ChildProcess } from "child_process"; -import readline from "readline"; - -export interface SubprocessOptions { - command: string; - args: string[]; - cwd: string; - env?: Record; - abortController?: AbortController; - timeout?: number; // Milliseconds of no output before timeout -} - -export interface SubprocessResult { - stdout: string; - stderr: string; - exitCode: number | null; -} - -/** - * Spawns a subprocess and streams JSONL output line-by-line - */ -export async function* spawnJSONLProcess( - options: SubprocessOptions -): AsyncGenerator { - const { command, args, cwd, env, abortController, timeout = 30000 } = options; - - const processEnv = { - ...process.env, - ...env, - }; - - console.log(`[SubprocessManager] Spawning: ${command} ${args.slice(0, -1).join(" ")}`); - console.log(`[SubprocessManager] Working directory: ${cwd}`); - - const childProcess: ChildProcess = spawn(command, args, { - cwd, - env: processEnv, - stdio: ["ignore", "pipe", "pipe"], - }); - - let stderrOutput = ""; - let lastOutputTime = Date.now(); - let timeoutHandle: NodeJS.Timeout | null = null; - - // Collect stderr for error reporting - if (childProcess.stderr) { - childProcess.stderr.on("data", (data: Buffer) => { - const text = data.toString(); - stderrOutput += text; - console.error(`[SubprocessManager] stderr: ${text}`); - }); - } - - // Setup timeout detection - const resetTimeout = () => { - lastOutputTime = Date.now(); - if (timeoutHandle) { - clearTimeout(timeoutHandle); - } - timeoutHandle = setTimeout(() => { - const elapsed = Date.now() - lastOutputTime; - if (elapsed >= timeout) { - console.error( - `[SubprocessManager] Process timeout: no output for ${timeout}ms` - ); - childProcess.kill("SIGTERM"); - } - }, timeout); - }; - - resetTimeout(); - - // Setup abort handling - if (abortController) { - abortController.signal.addEventListener("abort", () => { - console.log("[SubprocessManager] Abort signal received, killing process"); - if (timeoutHandle) { - clearTimeout(timeoutHandle); - } - childProcess.kill("SIGTERM"); - }); - } - - // Parse stdout as JSONL (one JSON object per line) - if (childProcess.stdout) { - const rl = readline.createInterface({ - input: childProcess.stdout, - crlfDelay: Infinity, - }); - - try { - for await (const line of rl) { - resetTimeout(); - - if (!line.trim()) continue; - - try { - const parsed = JSON.parse(line); - yield parsed; - } catch (parseError) { - console.error( - `[SubprocessManager] Failed to parse JSONL line: ${line}`, - parseError - ); - // Yield error but continue processing - yield { - type: "error", - error: `Failed to parse output: ${line}`, - }; - } - } - } catch (error) { - console.error("[SubprocessManager] Error reading stdout:", error); - throw error; - } finally { - if (timeoutHandle) { - clearTimeout(timeoutHandle); - } - } - } - - // Wait for process to exit - const exitCode = await new Promise((resolve) => { - childProcess.on("exit", (code) => { - console.log(`[SubprocessManager] Process exited with code: ${code}`); - resolve(code); - }); - - childProcess.on("error", (error) => { - console.error("[SubprocessManager] Process error:", error); - resolve(null); - }); - }); - - // Handle non-zero exit codes - if (exitCode !== 0 && exitCode !== null) { - const errorMessage = stderrOutput || `Process exited with code ${exitCode}`; - console.error(`[SubprocessManager] Process failed: ${errorMessage}`); - yield { - type: "error", - error: errorMessage, - }; - } - - // Process completed successfully - if (exitCode === 0 && !stderrOutput) { - console.log("[SubprocessManager] Process completed successfully"); - } -} - -/** - * Spawns a subprocess and collects all output - */ -export async function spawnProcess( - options: SubprocessOptions -): Promise { - const { command, args, cwd, env, abortController } = options; - - const processEnv = { - ...process.env, - ...env, - }; - - return new Promise((resolve, reject) => { - const childProcess = spawn(command, args, { - cwd, - env: processEnv, - stdio: ["ignore", "pipe", "pipe"], - }); - - let stdout = ""; - let stderr = ""; - - if (childProcess.stdout) { - childProcess.stdout.on("data", (data: Buffer) => { - stdout += data.toString(); - }); - } - - if (childProcess.stderr) { - childProcess.stderr.on("data", (data: Buffer) => { - stderr += data.toString(); - }); - } - - // Setup abort handling - if (abortController) { - abortController.signal.addEventListener("abort", () => { - childProcess.kill("SIGTERM"); - reject(new Error("Process aborted")); - }); - } - - childProcess.on("exit", (code) => { - resolve({ stdout, stderr, exitCode: code }); - }); - - childProcess.on("error", (error) => { - reject(error); - }); - }); -} diff --git a/apps/server/src/routes/fs/routes/write.ts b/apps/server/src/routes/fs/routes/write.ts index 415f21fb..1fe14735 100644 --- a/apps/server/src/routes/fs/routes/write.ts +++ b/apps/server/src/routes/fs/routes/write.ts @@ -6,8 +6,8 @@ import type { Request, Response } from "express"; import fs from "fs/promises"; import path from "path"; import { validatePath } from "@automaker/platform"; +import { mkdirSafe } from "@automaker/utils"; import { getErrorMessage, logError } from "../common.js"; -import { mkdirSafe } from "../../../lib/fs-utils.js"; export function createWriteHandler() { return async (req: Request, res: Response): Promise => { diff --git a/apps/ui/src/lib/agent-context-parser.ts b/apps/ui/src/lib/agent-context-parser.ts index 4f8c9f73..feb33678 100644 --- a/apps/ui/src/lib/agent-context-parser.ts +++ b/apps/ui/src/lib/agent-context-parser.ts @@ -3,8 +3,6 @@ * Extracts useful information from agent context files for display in kanban cards */ -import { DEFAULT_MODELS } from "@automaker/types"; - export interface AgentTaskInfo { // Task list extracted from TodoWrite tool calls todos: { @@ -29,7 +27,7 @@ export interface AgentTaskInfo { /** * Default model used by the feature executor */ -export const DEFAULT_MODEL = DEFAULT_MODELS.claude; +export const DEFAULT_MODEL = "claude-opus-4-5-20251101"; /** * Formats a model name for display diff --git a/libs/dependency-resolver/package.json b/libs/dependency-resolver/package.json index 71f4f1eb..435cc03d 100644 --- a/libs/dependency-resolver/package.json +++ b/libs/dependency-resolver/package.json @@ -2,12 +2,13 @@ "name": "@automaker/dependency-resolver", "version": "1.0.0", "description": "Feature dependency resolution for AutoMaker", + "type": "module", "main": "dist/index.js", "types": "dist/index.d.ts", "exports": { ".": { "types": "./dist/index.d.ts", - "require": "./dist/index.js", + "import": "./dist/index.js", "default": "./dist/index.js" } }, diff --git a/libs/dependency-resolver/tsconfig.json b/libs/dependency-resolver/tsconfig.json index 54e9774b..7fb871b0 100644 --- a/libs/dependency-resolver/tsconfig.json +++ b/libs/dependency-resolver/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "target": "ES2020", - "module": "commonjs", + "module": "ESNext", "lib": ["ES2020"], "types": ["node"], "declaration": true, @@ -13,7 +13,7 @@ "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, - "moduleResolution": "node" + "moduleResolution": "bundler" }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"]