diff --git a/.claude/commands/gh-issue.md b/.claude/commands/gh-issue.md new file mode 100644 index 000000000..22c4925b8 --- /dev/null +++ b/.claude/commands/gh-issue.md @@ -0,0 +1,74 @@ +# GitHub Issue Fix Command + +Fetch a GitHub issue by number, verify it's a real issue, and fix it if valid. + +## Usage + +This command accepts a GitHub issue number as input (e.g., `123`). + +## Instructions + +1. **Get the issue number from the user** + - The issue number should be provided as an argument to this command + - If no number is provided, ask the user for it + +2. **Fetch the GitHub issue** + - Determine the current project path (check if there's a current project context) + - Verify the project has a GitHub remote: + ```bash + git remote get-url origin + ``` + - Fetch the issue details using GitHub CLI: + ```bash + gh issue view --json number,title,state,author,createdAt,labels,url,body,assignees + ``` + - If the command fails, report the error and stop + +3. **Verify the issue is real and valid** + - Check that the issue exists (not 404) + - Check the issue state: + - If **closed**: Inform the user and ask if they still want to proceed + - If **open**: Proceed with validation + - Review the issue content: + - Read the title and body to understand what needs to be fixed + - Check labels for context (bug, enhancement, etc.) + - Note any assignees or linked PRs + +4. **Validate the issue** + - Determine if this is a legitimate issue that needs fixing: + - Is the description clear and actionable? + - Does it describe a real problem or feature request? + - Are there any obvious signs it's spam or invalid? + - If the issue seems invalid or unclear: + - Report findings to the user + - Ask if they want to proceed anyway + - Stop if user confirms it's not valid + +5. **If the issue is valid, proceed to fix it** + - Analyze what needs to be done based on the issue description + - Check the current codebase state: + - Run relevant tests to see current behavior + - Check if the issue is already fixed + - Look for related code that might need changes + - Implement the fix: + - Make necessary code changes + - Update or add tests as needed + - Ensure the fix addresses the issue description + - Verify the fix: + - Run tests to ensure nothing broke + - If possible, manually verify the fix addresses the issue + +6. **Report summary** + - Issue number and title + - Issue state (open/closed) + - Whether the issue was validated as real + - What was fixed (if anything) + - Any tests that were updated or added + - Next steps (if any) + +## Error Handling + +- If GitHub CLI (`gh`) is not installed or authenticated, report error and stop +- If the project doesn't have a GitHub remote, report error and stop +- If the issue number doesn't exist, report error and stop +- If the issue is unclear or invalid, report findings and ask user before proceeding diff --git a/.claude/commands/release.md b/.claude/commands/release.md new file mode 100644 index 000000000..faf244475 --- /dev/null +++ b/.claude/commands/release.md @@ -0,0 +1,56 @@ +# Release Command + +Bump the package.json version (major, minor, or patch) and build the Electron app with the new version. + +## Usage + +This command accepts a version bump type as input: + +- `patch` - Bump patch version (0.1.0 -> 0.1.1) +- `minor` - Bump minor version (0.1.0 -> 0.2.0) +- `major` - Bump major version (0.1.0 -> 1.0.0) + +## Instructions + +1. **Get the bump type from the user** + - The bump type should be provided as an argument (patch, minor, or major) + - If no type is provided, ask the user which type they want + +2. **Bump the version** + - Run the version bump script: + ```bash + node apps/ui/scripts/bump-version.mjs + ``` + - This updates both `apps/ui/package.json` and `apps/server/package.json` with the new version (keeps them in sync) + - Verify the version was updated correctly by checking the output + +3. **Build the Electron app** + - Run the electron build: + ```bash + npm run build:electron --workspace=apps/ui + ``` + - The build process automatically: + - Uses the version from `package.json` for artifact names (e.g., `Automaker-1.2.3-x64.zip`) + - Injects the version into the app via Vite's `__APP_VERSION__` constant + - Displays the version below the logo in the sidebar + +4. **Verify the release** + - Check that the build completed successfully + - Confirm the version appears correctly in the built artifacts + - The version will be displayed in the app UI below the logo + +## Version Centralization + +The version is centralized and synchronized in both `apps/ui/package.json` and `apps/server/package.json`: + +- **Electron builds**: Automatically read from `apps/ui/package.json` via electron-builder's `${version}` variable in `artifactName` +- **App display**: Injected at build time via Vite's `define` config as `__APP_VERSION__` constant (defined in `apps/ui/vite.config.mts`) +- **Server API**: Read from `apps/server/package.json` via `apps/server/src/lib/version.ts` utility (used in health check endpoints) +- **Type safety**: Defined in `apps/ui/src/vite-env.d.ts` as `declare const __APP_VERSION__: string` + +This ensures consistency across: + +- Build artifact names (e.g., `Automaker-1.2.3-x64.zip`) +- App UI display (shown as `v1.2.3` below the logo in `apps/ui/src/components/layout/sidebar/components/automaker-logo.tsx`) +- Server health endpoints (`/` and `/detailed`) +- Package metadata (both UI and server packages stay in sync) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 000000000..af6bb48bd --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,117 @@ +name: Bug Report +description: File a bug report to help us improve Automaker +title: '[Bug]: ' +labels: ['bug'] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to report a bug! Please fill out the form below with as much detail as possible. + + - type: dropdown + id: operating-system + attributes: + label: Operating System + description: What operating system are you using? + options: + - macOS + - Windows + - Linux + - Other + default: 0 + validations: + required: true + + - type: dropdown + id: run-mode + attributes: + label: Run Mode + description: How are you running Automaker? + options: + - Electron (Desktop App) + - Web (Browser) + - Docker + default: 0 + validations: + required: true + + - type: input + id: app-version + attributes: + label: App Version + description: What version of Automaker are you using? (e.g., 0.1.0) + placeholder: '0.1.0' + validations: + required: true + + - type: textarea + id: bug-description + attributes: + label: Bug Description + description: A clear and concise description of what the bug is. + placeholder: Describe the bug... + validations: + required: true + + - type: textarea + id: steps-to-reproduce + attributes: + label: Steps to Reproduce + description: Steps to reproduce the behavior + placeholder: | + 1. Go to '...' + 2. Click on '...' + 3. Scroll down to '...' + 4. See error + validations: + required: true + + - type: textarea + id: expected-behavior + attributes: + label: Expected Behavior + description: A clear and concise description of what you expected to happen. + placeholder: What should have happened? + validations: + required: true + + - type: textarea + id: actual-behavior + attributes: + label: Actual Behavior + description: A clear and concise description of what actually happened. + placeholder: What actually happened? + validations: + required: true + + - type: textarea + id: screenshots + attributes: + label: Screenshots + description: If applicable, add screenshots to help explain your problem. + placeholder: Drag and drop screenshots here or paste image URLs + + - type: textarea + id: logs + attributes: + label: Relevant Logs + description: If applicable, paste relevant logs or error messages. + placeholder: Paste logs here... + render: shell + + - type: textarea + id: additional-context + attributes: + label: Additional Context + description: Add any other context about the problem here. + placeholder: Any additional information that might be helpful... + + - type: checkboxes + id: terms + attributes: + label: Checklist + options: + - label: I have searched existing issues to ensure this bug hasn't been reported already + required: true + - label: I have provided all required information above + required: true diff --git a/README.md b/README.md index 67dd17dde..c8e1b84ea 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- Automaker Logo + Automaker Logo

> **[!TIP]** @@ -81,22 +81,6 @@ Automaker leverages the [Claude Agent SDK](https://www.npmjs.com/package/@anthro The future of software development is **agentic coding**—where developers become architects directing AI agents rather than manual coders. Automaker puts this future in your hands today, letting you experience what it's like to build software 10x faster with AI agents handling the implementation while you focus on architecture and business logic. ---- - -> **[!CAUTION]** -> -> ## Security Disclaimer -> -> **This software uses AI-powered tooling that has access to your operating system and can read, modify, and delete files. Use at your own risk.** -> -> We have reviewed this codebase for security vulnerabilities, but you assume all risk when running this software. You should review the code yourself before running it. -> -> **We do not recommend running Automaker directly on your local computer** due to the risk of AI agents having access to your entire file system. Please sandbox this application using Docker or a virtual machine. -> -> **[Read the full disclaimer](./DISCLAIMER.md)** - ---- - ## Community & Support Join the **Agentic Jumpstart** to connect with other builders exploring **agentic coding** and autonomous development workflows. @@ -624,6 +608,22 @@ data/ └── {sessionId}.json ``` +--- + +> **[!CAUTION]** +> +> ## Security Disclaimer +> +> **This software uses AI-powered tooling that has access to your operating system and can read, modify, and delete files. Use at your own risk.** +> +> We have reviewed this codebase for security vulnerabilities, but you assume all risk when running this software. You should review the code yourself before running it. +> +> **We do not recommend running Automaker directly on your local computer** due to the risk of AI agents having access to your entire file system. Please sandbox this application using Docker or a virtual machine. +> +> **[Read the full disclaimer](./DISCLAIMER.md)** + +--- + ## Learn More ### Documentation diff --git a/apps/server/package.json b/apps/server/package.json index d923fa9bb..28ee4b2a9 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -1,6 +1,6 @@ { "name": "@automaker/server", - "version": "0.1.0", + "version": "0.7.1", "description": "Backend server for Automaker - provides API for both web and Electron modes", "author": "AutoMaker Team", "license": "SEE LICENSE IN LICENSE", @@ -24,7 +24,7 @@ "test:unit": "vitest run tests/unit" }, "dependencies": { - "@anthropic-ai/claude-agent-sdk": "0.1.72", + "@anthropic-ai/claude-agent-sdk": "0.1.76", "@automaker/dependency-resolver": "1.0.0", "@automaker/git-utils": "1.0.0", "@automaker/model-resolver": "1.0.0", diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts index 769b63abd..0f97255f3 100644 --- a/apps/server/src/index.ts +++ b/apps/server/src/index.ts @@ -133,7 +133,11 @@ app.use( } // For local development, allow localhost origins - if (origin.startsWith('http://localhost:') || origin.startsWith('http://127.0.0.1:')) { + if ( + origin.startsWith('http://localhost:') || + origin.startsWith('http://127.0.0.1:') || + origin.startsWith('http://[::1]:') + ) { callback(null, origin); return; } diff --git a/apps/server/src/lib/settings-helpers.ts b/apps/server/src/lib/settings-helpers.ts index 36775315e..b6e86ff2a 100644 --- a/apps/server/src/lib/settings-helpers.ts +++ b/apps/server/src/lib/settings-helpers.ts @@ -74,7 +74,7 @@ export async function getEnableSandboxModeSetting( try { const globalSettings = await settingsService.getGlobalSettings(); - const result = globalSettings.enableSandboxMode ?? true; + const result = globalSettings.enableSandboxMode ?? false; logger.info(`${logPrefix} enableSandboxMode from global settings: ${result}`); return result; } catch (error) { diff --git a/apps/server/src/lib/version.ts b/apps/server/src/lib/version.ts new file mode 100644 index 000000000..61e182e30 --- /dev/null +++ b/apps/server/src/lib/version.ts @@ -0,0 +1,33 @@ +/** + * Version utility - Reads version from package.json + */ + +import { readFileSync } from 'fs'; +import { fileURLToPath } from 'url'; +import { dirname, join } from 'path'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +let cachedVersion: string | null = null; + +/** + * Get the version from package.json + * Caches the result for performance + */ +export function getVersion(): string { + if (cachedVersion) { + return cachedVersion; + } + + try { + const packageJsonPath = join(__dirname, '..', '..', 'package.json'); + const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')); + const version = packageJson.version || '0.0.0'; + cachedVersion = version; + return version; + } catch (error) { + console.warn('Failed to read version from package.json:', error); + return '0.0.0'; + } +} diff --git a/apps/server/src/routes/health/routes/detailed.ts b/apps/server/src/routes/health/routes/detailed.ts index 5aa2e6b16..d51984666 100644 --- a/apps/server/src/routes/health/routes/detailed.ts +++ b/apps/server/src/routes/health/routes/detailed.ts @@ -4,13 +4,14 @@ import type { Request, Response } from 'express'; import { getAuthStatus } from '../../../lib/auth.js'; +import { getVersion } from '../../../lib/version.js'; export function createDetailedHandler() { return (_req: Request, res: Response): void => { res.json({ status: 'ok', timestamp: new Date().toISOString(), - version: process.env.npm_package_version || '0.1.0', + version: getVersion(), uptime: process.uptime(), memory: process.memoryUsage(), dataDir: process.env.DATA_DIR || './data', diff --git a/apps/server/src/routes/health/routes/index.ts b/apps/server/src/routes/health/routes/index.ts index 1501f6a68..f956a96f7 100644 --- a/apps/server/src/routes/health/routes/index.ts +++ b/apps/server/src/routes/health/routes/index.ts @@ -3,13 +3,14 @@ */ import type { Request, Response } from 'express'; +import { getVersion } from '../../../lib/version.js'; export function createIndexHandler() { return (_req: Request, res: Response): void => { res.json({ status: 'ok', timestamp: new Date().toISOString(), - version: process.env.npm_package_version || '0.1.0', + version: getVersion(), }); }; } diff --git a/apps/server/src/routes/worktree/common.ts b/apps/server/src/routes/worktree/common.ts index 6527ab77f..4f63a382b 100644 --- a/apps/server/src/routes/worktree/common.ts +++ b/apps/server/src/routes/worktree/common.ts @@ -158,8 +158,13 @@ export const logError = createLogError(logger); /** * Ensure the repository has at least one commit so git commands that rely on HEAD work. * Returns true if an empty commit was created, false if the repo already had commits. + * @param repoPath - Path to the git repository + * @param env - Optional environment variables to pass to git (e.g., GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL) */ -export async function ensureInitialCommit(repoPath: string): Promise { +export async function ensureInitialCommit( + repoPath: string, + env?: Record +): Promise { try { await execAsync('git rev-parse --verify HEAD', { cwd: repoPath }); return false; @@ -167,6 +172,7 @@ export async function ensureInitialCommit(repoPath: string): Promise { try { await execAsync(`git commit --allow-empty -m "${AUTOMAKER_INITIAL_COMMIT_MESSAGE}"`, { cwd: repoPath, + env: { ...process.env, ...env }, }); logger.info(`[Worktree] Created initial empty commit to enable worktrees in ${repoPath}`); return true; diff --git a/apps/server/src/routes/worktree/routes/create.ts b/apps/server/src/routes/worktree/routes/create.ts index 943d3bddf..4eb2b2c9b 100644 --- a/apps/server/src/routes/worktree/routes/create.ts +++ b/apps/server/src/routes/worktree/routes/create.ts @@ -100,7 +100,14 @@ export function createCreateHandler() { } // Ensure the repository has at least one commit so worktree commands referencing HEAD succeed - await ensureInitialCommit(projectPath); + // Pass git identity env vars so commits work without global git config + const gitEnv = { + GIT_AUTHOR_NAME: 'Automaker', + GIT_AUTHOR_EMAIL: 'automaker@localhost', + GIT_COMMITTER_NAME: 'Automaker', + GIT_COMMITTER_EMAIL: 'automaker@localhost', + }; + await ensureInitialCommit(projectPath, gitEnv); // First, check if git already has a worktree for this branch (anywhere) const existingWorktree = await findExistingWorktreeForBranch(projectPath, branchName); diff --git a/apps/server/src/services/auto-mode-service.ts b/apps/server/src/services/auto-mode-service.ts index ad1b3efa5..54f2f8f16 100644 --- a/apps/server/src/services/auto-mode-service.ts +++ b/apps/server/src/services/auto-mode-service.ts @@ -190,6 +190,10 @@ interface AutoModeConfig { projectPath: string; } +// Constants for consecutive failure tracking +const CONSECUTIVE_FAILURE_THRESHOLD = 3; // Pause after 3 consecutive failures +const FAILURE_WINDOW_MS = 60000; // Failures within 1 minute count as consecutive + export class AutoModeService { private events: EventEmitter; private runningFeatures = new Map(); @@ -200,12 +204,89 @@ export class AutoModeService { private config: AutoModeConfig | null = null; private pendingApprovals = new Map(); private settingsService: SettingsService | null = null; + // Track consecutive failures to detect quota/API issues + private consecutiveFailures: { timestamp: number; error: string }[] = []; + private pausedDueToFailures = false; constructor(events: EventEmitter, settingsService?: SettingsService) { this.events = events; this.settingsService = settingsService ?? null; } + /** + * Track a failure and check if we should pause due to consecutive failures. + * This handles cases where the SDK doesn't return useful error messages. + */ + private trackFailureAndCheckPause(errorInfo: { type: string; message: string }): boolean { + const now = Date.now(); + + // Add this failure + this.consecutiveFailures.push({ timestamp: now, error: errorInfo.message }); + + // Remove old failures outside the window + this.consecutiveFailures = this.consecutiveFailures.filter( + (f) => now - f.timestamp < FAILURE_WINDOW_MS + ); + + // Check if we've hit the threshold + if (this.consecutiveFailures.length >= CONSECUTIVE_FAILURE_THRESHOLD) { + return true; // Should pause + } + + // Also immediately pause for known quota/rate limit errors + if (errorInfo.type === 'quota_exhausted' || errorInfo.type === 'rate_limit') { + return true; + } + + return false; + } + + /** + * Signal that we should pause due to repeated failures or quota exhaustion. + * This will pause the auto loop to prevent repeated failures. + */ + private signalShouldPause(errorInfo: { type: string; message: string }): void { + if (this.pausedDueToFailures) { + return; // Already paused + } + + this.pausedDueToFailures = true; + const failureCount = this.consecutiveFailures.length; + console.log( + `[AutoMode] Pausing auto loop after ${failureCount} consecutive failures. Last error: ${errorInfo.type}` + ); + + // Emit event to notify UI + this.emitAutoModeEvent('auto_mode_paused_failures', { + message: + failureCount >= CONSECUTIVE_FAILURE_THRESHOLD + ? `Auto Mode paused: ${failureCount} consecutive failures detected. This may indicate a quota limit or API issue. Please check your usage and try again.` + : 'Auto Mode paused: Usage limit or API error detected. Please wait for your quota to reset or check your API configuration.', + errorType: errorInfo.type, + originalError: errorInfo.message, + failureCount, + projectPath: this.config?.projectPath, + }); + + // Stop the auto loop + this.stopAutoLoop(); + } + + /** + * Reset failure tracking (called when user manually restarts auto mode) + */ + private resetFailureTracking(): void { + this.consecutiveFailures = []; + this.pausedDueToFailures = false; + } + + /** + * Record a successful feature completion to reset consecutive failure count + */ + private recordSuccess(): void { + this.consecutiveFailures = []; + } + /** * Start the auto mode loop - continuously picks and executes pending features */ @@ -214,6 +295,9 @@ export class AutoModeService { throw new Error('Auto mode is already running'); } + // Reset failure tracking when user manually starts auto mode + this.resetFailureTracking(); + this.autoLoopRunning = true; this.autoLoopAbortController = new AbortController(); this.config = { @@ -502,6 +586,9 @@ export class AutoModeService { const finalStatus = feature.skipTests ? 'waiting_approval' : 'verified'; await this.updateFeatureStatus(projectPath, featureId, finalStatus); + // Record success to reset consecutive failure tracking + this.recordSuccess(); + this.emitAutoModeEvent('auto_mode_feature_complete', { featureId, passes: true, @@ -529,6 +616,21 @@ export class AutoModeService { errorType: errorInfo.type, projectPath, }); + + // Track this failure and check if we should pause auto mode + // This handles both specific quota/rate limit errors AND generic failures + // that may indicate quota exhaustion (SDK doesn't always return useful errors) + const shouldPause = this.trackFailureAndCheckPause({ + type: errorInfo.type, + message: errorInfo.message, + }); + + if (shouldPause) { + this.signalShouldPause({ + type: errorInfo.type, + message: errorInfo.message, + }); + } } } finally { console.log(`[AutoMode] Feature ${featureId} execution ended, cleaning up runningFeatures`); @@ -689,6 +791,11 @@ Complete the pipeline step instructions above. Review the previous work and appl this.cancelPlanApproval(featureId); running.abortController.abort(); + + // Remove from running features immediately to allow resume + // The abort signal will still propagate to stop any ongoing execution + this.runningFeatures.delete(featureId); + return true; } @@ -926,6 +1033,9 @@ Address the follow-up instructions above. Review the previous work and make the const finalStatus = feature?.skipTests ? 'waiting_approval' : 'verified'; await this.updateFeatureStatus(projectPath, featureId, finalStatus); + // Record success to reset consecutive failure tracking + this.recordSuccess(); + this.emitAutoModeEvent('auto_mode_feature_complete', { featureId, passes: true, @@ -941,6 +1051,19 @@ Address the follow-up instructions above. Review the previous work and make the errorType: errorInfo.type, projectPath, }); + + // Track this failure and check if we should pause auto mode + const shouldPause = this.trackFailureAndCheckPause({ + type: errorInfo.type, + message: errorInfo.message, + }); + + if (shouldPause) { + this.signalShouldPause({ + type: errorInfo.type, + message: errorInfo.message, + }); + } } } finally { this.runningFeatures.delete(featureId); @@ -1940,7 +2063,9 @@ This mock response was generated because AUTOMAKER_MOCK_AGENT=true was set. }; // Execute via provider + console.log(`[AutoMode] Starting stream for feature ${featureId}...`); const stream = provider.executeQuery(executeOptions); + console.log(`[AutoMode] Stream created, starting to iterate...`); // Initialize with previous content if this is a follow-up, with a separator let responseText = previousContent ? `${previousContent}\n\n---\n\n## Follow-up Session\n\n` @@ -1978,6 +2103,7 @@ This mock response was generated because AUTOMAKER_MOCK_AGENT=true was set. }; streamLoop: for await (const msg of stream) { + console.log(`[AutoMode] Stream message received:`, msg.type, msg.subtype || ''); if (msg.type === 'assistant' && msg.message?.content) { for (const block of msg.message.content) { if (block.type === 'text') { @@ -2433,6 +2559,9 @@ Implement all the changes described in the plan above.`; // Only emit progress for non-marker text (marker was already handled above) if (!specDetected) { + console.log( + `[AutoMode] Emitting progress event for ${featureId}, content length: ${block.text?.length || 0}` + ); this.emitAutoModeEvent('auto_mode_progress', { featureId, content: block.text, diff --git a/apps/server/src/services/settings-service.ts b/apps/server/src/services/settings-service.ts index 288bde186..a88d2421a 100644 --- a/apps/server/src/services/settings-service.ts +++ b/apps/server/src/services/settings-service.ts @@ -124,6 +124,8 @@ export class SettingsService { * Missing fields are filled in from DEFAULT_GLOBAL_SETTINGS for forward/backward * compatibility during schema migrations. * + * Also applies version-based migrations for breaking changes. + * * @returns Promise resolving to complete GlobalSettings object */ async getGlobalSettings(): Promise { @@ -131,7 +133,7 @@ export class SettingsService { const settings = await readJsonFile(settingsPath, DEFAULT_GLOBAL_SETTINGS); // Apply any missing defaults (for backwards compatibility) - return { + let result: GlobalSettings = { ...DEFAULT_GLOBAL_SETTINGS, ...settings, keyboardShortcuts: { @@ -139,6 +141,32 @@ export class SettingsService { ...settings.keyboardShortcuts, }, }; + + // Version-based migrations + const storedVersion = settings.version || 1; + let needsSave = false; + + // Migration v1 -> v2: Force enableSandboxMode to false for existing users + // Sandbox mode can cause issues on some systems, so we're disabling it by default + if (storedVersion < 2) { + logger.info('Migrating settings from v1 to v2: disabling sandbox mode'); + result.enableSandboxMode = false; + result.version = SETTINGS_VERSION; + needsSave = true; + } + + // Save migrated settings if needed + if (needsSave) { + try { + await ensureDataDir(this.dataDir); + await atomicWriteJson(settingsPath, result); + logger.info('Settings migration complete'); + } catch (error) { + logger.error('Failed to save migrated settings:', error); + } + } + + return result; } /** diff --git a/apps/server/tests/integration/helpers/git-test-repo.ts b/apps/server/tests/integration/helpers/git-test-repo.ts index 4ec959264..7871e8e80 100644 --- a/apps/server/tests/integration/helpers/git-test-repo.ts +++ b/apps/server/tests/integration/helpers/git-test-repo.ts @@ -22,13 +22,21 @@ export async function createTestGitRepo(): Promise { // Initialize git repo await execAsync('git init', { cwd: tmpDir }); - await execAsync('git config user.email "test@example.com"', { cwd: tmpDir }); - await execAsync('git config user.name "Test User"', { cwd: tmpDir }); + + // Use environment variables instead of git config to avoid affecting user's git config + // These env vars override git config without modifying it + const gitEnv = { + ...process.env, + GIT_AUTHOR_NAME: 'Test User', + GIT_AUTHOR_EMAIL: 'test@example.com', + GIT_COMMITTER_NAME: 'Test User', + GIT_COMMITTER_EMAIL: 'test@example.com', + }; // Create initial commit await fs.writeFile(path.join(tmpDir, 'README.md'), '# Test Project\n'); - await execAsync('git add .', { cwd: tmpDir }); - await execAsync('git commit -m "Initial commit"', { cwd: tmpDir }); + await execAsync('git add .', { cwd: tmpDir, env: gitEnv }); + await execAsync('git commit -m "Initial commit"', { cwd: tmpDir, env: gitEnv }); // Create main branch explicitly await execAsync('git branch -M main', { cwd: tmpDir }); diff --git a/apps/server/tests/integration/routes/worktree/create.integration.test.ts b/apps/server/tests/integration/routes/worktree/create.integration.test.ts index 433b610ab..6d274a0d6 100644 --- a/apps/server/tests/integration/routes/worktree/create.integration.test.ts +++ b/apps/server/tests/integration/routes/worktree/create.integration.test.ts @@ -15,10 +15,8 @@ describe('worktree create route - repositories without commits', () => { async function initRepoWithoutCommit() { repoPath = await fs.mkdtemp(path.join(os.tmpdir(), 'automaker-no-commit-')); await execAsync('git init', { cwd: repoPath }); - await execAsync('git config user.email "test@example.com"', { - cwd: repoPath, - }); - await execAsync('git config user.name "Test User"', { cwd: repoPath }); + // Don't set git config - use environment variables in commit operations instead + // to avoid affecting user's git config // Intentionally skip creating an initial commit } diff --git a/apps/ui/package.json b/apps/ui/package.json index fb846c159..a2e70b985 100644 --- a/apps/ui/package.json +++ b/apps/ui/package.json @@ -1,6 +1,6 @@ { "name": "@automaker/ui", - "version": "0.1.0", + "version": "0.7.1", "description": "An autonomous AI development studio that helps you build software faster using AI-powered agents", "homepage": "https://github.com/AutoMaker-Org/automaker", "repository": { diff --git a/apps/ui/public/readme_logo.svg b/apps/ui/public/readme_logo.svg new file mode 100644 index 000000000..86177aeab --- /dev/null +++ b/apps/ui/public/readme_logo.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + automaker. + + diff --git a/apps/ui/scripts/bump-version.mjs b/apps/ui/scripts/bump-version.mjs new file mode 100755 index 000000000..ae4d95168 --- /dev/null +++ b/apps/ui/scripts/bump-version.mjs @@ -0,0 +1,92 @@ +#!/usr/bin/env node +/** + * Bumps the version in apps/ui/package.json and apps/server/package.json + * Usage: node scripts/bump-version.mjs [major|minor|patch] + * Example: node scripts/bump-version.mjs patch + */ + +import { readFileSync, writeFileSync } from 'fs'; +import { fileURLToPath } from 'url'; +import { dirname, join } from 'path'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const bumpType = process.argv[2]?.toLowerCase(); + +if (!bumpType || !['major', 'minor', 'patch'].includes(bumpType)) { + console.error('Error: Bump type argument is required'); + console.error('Usage: node scripts/bump-version.mjs [major|minor|patch]'); + console.error('Example: node scripts/bump-version.mjs patch'); + process.exit(1); +} + +const uiPackageJsonPath = join(__dirname, '..', 'package.json'); +const serverPackageJsonPath = join(__dirname, '..', '..', 'server', 'package.json'); + +function bumpVersion(packageJsonPath, packageName) { + try { + const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')); + const oldVersion = packageJson.version; + + // Parse version + const versionParts = oldVersion.split('.').map(Number); + if (versionParts.length !== 3) { + console.error(`Error: Invalid version format in ${packageName}: ${oldVersion}`); + console.error('Expected format: X.Y.Z (e.g., 1.2.3)'); + process.exit(1); + } + + // Bump version + let [major, minor, patch] = versionParts; + + switch (bumpType) { + case 'major': + major += 1; + minor = 0; + patch = 0; + break; + case 'minor': + minor += 1; + patch = 0; + break; + case 'patch': + patch += 1; + break; + } + + const newVersion = `${major}.${minor}.${patch}`; + packageJson.version = newVersion; + + writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n', 'utf8'); + + return newVersion; + } catch (error) { + console.error(`Error bumping version in ${packageName}: ${error.message}`); + process.exit(1); + } +} + +try { + // Bump UI package version + const uiOldVersion = JSON.parse(readFileSync(uiPackageJsonPath, 'utf8')).version; + const uiNewVersion = bumpVersion(uiPackageJsonPath, '@automaker/ui'); + + // Bump server package version (sync with UI) + const serverOldVersion = JSON.parse(readFileSync(serverPackageJsonPath, 'utf8')).version; + const serverNewVersion = bumpVersion(serverPackageJsonPath, '@automaker/server'); + + // Verify versions match + if (uiNewVersion !== serverNewVersion) { + console.error(`Error: Version mismatch! UI: ${uiNewVersion}, Server: ${serverNewVersion}`); + process.exit(1); + } + + console.log(`✅ Bumped version from ${uiOldVersion} to ${uiNewVersion} (${bumpType})`); + console.log(`📦 Updated @automaker/ui: ${uiOldVersion} -> ${uiNewVersion}`); + console.log(`📦 Updated @automaker/server: ${serverOldVersion} -> ${serverNewVersion}`); + console.log(`📦 Version is now: ${uiNewVersion}`); +} catch (error) { + console.error(`Error bumping version: ${error.message}`); + process.exit(1); +} diff --git a/apps/ui/src/components/dialogs/board-background-modal.tsx b/apps/ui/src/components/dialogs/board-background-modal.tsx index 2738ec79c..c1acdfd92 100644 --- a/apps/ui/src/components/dialogs/board-background-modal.tsx +++ b/apps/ui/src/components/dialogs/board-background-modal.tsx @@ -13,7 +13,7 @@ import { Label } from '@/components/ui/label'; import { Checkbox } from '@/components/ui/checkbox'; import { cn } from '@/lib/utils'; import { useAppStore, defaultBackgroundSettings } from '@/store/app-store'; -import { getHttpApiClient } from '@/lib/http-api-client'; +import { getHttpApiClient, getServerUrlSync } from '@/lib/http-api-client'; import { useBoardBackgroundSettings } from '@/hooks/use-board-background-settings'; import { toast } from 'sonner'; import { @@ -62,7 +62,7 @@ export function BoardBackgroundModal({ open, onOpenChange }: BoardBackgroundModa // Update preview image when background settings change useEffect(() => { if (currentProject && backgroundSettings.imagePath) { - const serverUrl = import.meta.env.VITE_SERVER_URL || 'http://localhost:3008'; + const serverUrl = import.meta.env.VITE_SERVER_URL || getServerUrlSync(); // Add cache-busting query parameter to force browser to reload image const cacheBuster = imageVersion ? `&v=${imageVersion}` : `&v=${Date.now()}`; const imagePath = `${serverUrl}/api/fs/image?path=${encodeURIComponent( diff --git a/apps/ui/src/components/layout/sidebar/components/automaker-logo.tsx b/apps/ui/src/components/layout/sidebar/components/automaker-logo.tsx index 66345b92e..ac8ed22d2 100644 --- a/apps/ui/src/components/layout/sidebar/components/automaker-logo.tsx +++ b/apps/ui/src/components/layout/sidebar/components/automaker-logo.tsx @@ -7,6 +7,8 @@ interface AutomakerLogoProps { } export function AutomakerLogo({ sidebarOpen, navigate }: AutomakerLogoProps) { + const appVersion = typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : '0.0.0'; + return (
{!sidebarOpen ? ( -
+
+ + v{appVersion} +
) : ( -
- - - - - - - - - - - - +
+ - - - - - - - automaker. + + + + + + + + + + + + + + + + + + automaker. + +
+ + v{appVersion}
)} diff --git a/apps/ui/src/components/ui/description-image-dropzone.tsx b/apps/ui/src/components/ui/description-image-dropzone.tsx index 0a84ed8ad..9df5e0e6e 100644 --- a/apps/ui/src/components/ui/description-image-dropzone.tsx +++ b/apps/ui/src/components/ui/description-image-dropzone.tsx @@ -3,6 +3,7 @@ import { cn } from '@/lib/utils'; import { ImageIcon, X, Loader2, FileText } from 'lucide-react'; import { Textarea } from '@/components/ui/textarea'; import { getElectronAPI } from '@/lib/electron'; +import { getServerUrlSync } from '@/lib/http-api-client'; import { useAppStore, type FeatureImagePath, type FeatureTextFilePath } from '@/store/app-store'; import { sanitizeFilename, @@ -93,7 +94,7 @@ export function DescriptionImageDropZone({ // Construct server URL for loading saved images const getImageServerUrl = useCallback( (imagePath: string): string => { - const serverUrl = import.meta.env.VITE_SERVER_URL || 'http://localhost:3008'; + const serverUrl = import.meta.env.VITE_SERVER_URL || getServerUrlSync(); const projectPath = currentProject?.path || ''; return `${serverUrl}/api/fs/image?path=${encodeURIComponent(imagePath)}&projectPath=${encodeURIComponent(projectPath)}`; }, diff --git a/apps/ui/src/components/views/board-view.tsx b/apps/ui/src/components/views/board-view.tsx index 2c39c1fe6..655643048 100644 --- a/apps/ui/src/components/views/board-view.tsx +++ b/apps/ui/src/components/views/board-view.tsx @@ -206,6 +206,7 @@ export function BoardView() { checkContextExists, features: hookFeatures, isLoading, + featuresWithContext, setFeaturesWithContext, }); diff --git a/apps/ui/src/components/views/board-view/components/kanban-card/card-actions.tsx b/apps/ui/src/components/views/board-view/components/kanban-card/card-actions.tsx index df0d87078..b791216b0 100644 --- a/apps/ui/src/components/views/board-view/components/kanban-card/card-actions.tsx +++ b/apps/ui/src/components/views/board-view/components/kanban-card/card-actions.tsx @@ -143,7 +143,7 @@ export function CardActions({ Verify - ) : hasContext && onResume ? ( + ) : onResume ? ( - ) : onVerify ? ( - ) : null} {onViewOutput && !feature.skipTests && (