diff --git a/.auto-claude-security.json b/.auto-claude-security.json new file mode 100644 index 00000000..4480db4d --- /dev/null +++ b/.auto-claude-security.json @@ -0,0 +1,192 @@ +{ + "base_commands": [ + ".", + "[", + "[[", + "ag", + "awk", + "basename", + "bash", + "bc", + "break", + "cat", + "cd", + "chmod", + "clear", + "cmp", + "column", + "comm", + "command", + "continue", + "cp", + "curl", + "cut", + "date", + "df", + "diff", + "dig", + "dirname", + "du", + "echo", + "egrep", + "env", + "eval", + "exec", + "exit", + "expand", + "export", + "expr", + "false", + "fd", + "fgrep", + "file", + "find", + "fmt", + "fold", + "gawk", + "gh", + "git", + "grep", + "gunzip", + "gzip", + "head", + "help", + "host", + "iconv", + "id", + "jobs", + "join", + "jq", + "kill", + "killall", + "less", + "let", + "ln", + "ls", + "lsof", + "man", + "mkdir", + "mktemp", + "more", + "mv", + "nl", + "paste", + "pgrep", + "ping", + "pkill", + "popd", + "printenv", + "printf", + "ps", + "pushd", + "pwd", + "read", + "readlink", + "realpath", + "reset", + "return", + "rev", + "rg", + "rm", + "rmdir", + "sed", + "seq", + "set", + "sh", + "shuf", + "sleep", + "sort", + "source", + "split", + "stat", + "tail", + "tar", + "tee", + "test", + "time", + "timeout", + "touch", + "tr", + "tree", + "true", + "type", + "uname", + "unexpand", + "uniq", + "unset", + "unzip", + "watch", + "wc", + "wget", + "whereis", + "which", + "whoami", + "xargs", + "yes", + "yq", + "zip", + "zsh" + ], + "stack_commands": [ + "ar", + "clang", + "clang++", + "cmake", + "dive", + "docker", + "docker-buildx", + "docker-compose", + "dockerfile", + "g++", + "gcc", + "ipython", + "jupyter", + "ld", + "make", + "meson", + "ninja", + "nm", + "notebook", + "objdump", + "pdb", + "pip", + "pip3", + "pipx", + "pudb", + "python", + "python3", + "redis-benchmark", + "redis-cli", + "redis-server", + "strip" + ], + "script_commands": [], + "custom_commands": [], + "detected_stack": { + "languages": [ + "python", + "c" + ], + "package_managers": [], + "frameworks": [], + "databases": [ + "redis" + ], + "infrastructure": [ + "docker" + ], + "cloud_providers": [], + "code_quality_tools": [], + "version_managers": [] + }, + "custom_scripts": { + "npm_scripts": [], + "make_targets": [], + "poetry_scripts": [], + "cargo_aliases": [], + "shell_scripts": [] + }, + "project_dir": "/Users/andremikalsen/Documents/Coding/autonomous-coding", + "created_at": "2025-12-10T15:55:09.016762", + "project_hash": "983ec2eb75cd87788b47de48294e8bf8" +} \ No newline at end of file diff --git a/.auto-build-status b/.auto-claude-status similarity index 100% rename from .auto-build-status rename to .auto-claude-status diff --git a/.claude/commands/setup-statusline.md b/.claude/commands/setup-statusline.md index 5fb0a037..7791e8ca 100644 --- a/.claude/commands/setup-statusline.md +++ b/.claude/commands/setup-statusline.md @@ -1,11 +1,11 @@ # Setup ccstatusline Integration for Auto-Build -Configure ccstatusline to display real-time auto-build progress in your Claude Code status bar. +Configure ccstatusline to display real-time auto-claude progress in your Claude Code status bar. ## Prerequisites 1. **ccstatusline** must be installed and configured -2. **auto-build** must be in your project +2. **auto-claude** must be in your project ## Installation @@ -26,7 +26,7 @@ This launches the interactive TUI to configure your status line. In the ccstatusline TUI config, add a **Custom Command** widget with: ``` -Command: python /path/to/your/project/auto-build/statusline.py --format compact +Command: python /path/to/your/project/auto-claude/statusline.py --format compact ``` **Recommended widget settings:** @@ -41,7 +41,7 @@ Edit `~/.config/ccstatusline/settings.json` and add to your widgets array: ```json { "type": "custom", - "command": "python /path/to/your/project/auto-build/statusline.py --format compact", + "command": "python /path/to/your/project/auto-claude/statusline.py --format compact", "interval": 5, "showWhenEmpty": false } @@ -80,7 +80,7 @@ Output: Raw JSON status data ## Status File -Auto-build writes status to `.auto-build-status` in your project root: +Auto-build writes status to `.auto-claude-status` in your project root: ```json { @@ -121,9 +121,9 @@ When active, you'll see these indicators: ## Troubleshooting ### Status not showing? -1. Check if `.auto-build-status` exists in your project root +1. Check if `.auto-claude-status` exists in your project root 2. Verify the path to `statusline.py` is correct -3. Try running the command manually: `python auto-build/statusline.py --format compact` +3. Try running the command manually: `python auto-claude/statusline.py --format compact` ### Updates too slow? - Decrease the polling interval in ccstatusline config (minimum 1 second) @@ -136,16 +136,16 @@ When active, you'll see these indicators: ### Minimal Status Line Just chunks and phase: ``` -python auto-build/statusline.py --format compact +python auto-claude/statusline.py --format compact ``` ### With Specific Spec Monitor a specific spec: ``` -python auto-build/statusline.py --format compact --spec 001-my-feature +python auto-claude/statusline.py --format compact --spec 001-my-feature ``` ### Full Path for Global Use ``` -python ~/projects/my-app/auto-build/statusline.py --format compact --project-dir ~/projects/my-app +python ~/projects/my-app/auto-claude/statusline.py --format compact --project-dir ~/projects/my-app ``` diff --git a/CLAUDE.md b/CLAUDE.md index 05afc12b..902912cc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,61 +4,61 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project Overview -Auto-Build is a multi-agent autonomous coding framework that builds software through coordinated AI agent sessions. It uses the Claude Code SDK to run agents in isolated workspaces with security controls. +Auto Claude is a multi-agent autonomous coding framework that builds software through coordinated AI agent sessions. It uses the Claude Code SDK to run agents in isolated workspaces with security controls. ## Commands ### Setup ```bash -# Install dependencies (from auto-build/) +# Install dependencies (from auto-claude/) uv venv && uv pip install -r requirements.txt # Or: python3 -m venv .venv && source .venv/bin/activate && pip install -r requirements.txt # Set up OAuth token claude setup-token -# Add to auto-build/.env: CLAUDE_CODE_OAUTH_TOKEN=your-token +# Add to auto-claude/.env: CLAUDE_CODE_OAUTH_TOKEN=your-token ``` ### Creating and Running Specs ```bash # Create a spec interactively -python auto-build/spec_runner.py --interactive +python auto-claude/spec_runner.py --interactive # Create spec from task description -python auto-build/spec_runner.py --task "Add user authentication" +python auto-claude/spec_runner.py --task "Add user authentication" # Force complexity level (simple/standard/complex) -python auto-build/spec_runner.py --task "Fix button" --complexity simple +python auto-claude/spec_runner.py --task "Fix button" --complexity simple # Run autonomous build -python auto-build/run.py --spec 001 +python auto-claude/run.py --spec 001 # Run with parallel workers -python auto-build/run.py --spec 001 --parallel 2 +python auto-claude/run.py --spec 001 --parallel 2 # List all specs -python auto-build/run.py --list +python auto-claude/run.py --list ``` ### Workspace Management ```bash # Review changes in isolated worktree -python auto-build/run.py --spec 001 --review +python auto-claude/run.py --spec 001 --review # Merge completed build into project -python auto-build/run.py --spec 001 --merge +python auto-claude/run.py --spec 001 --merge # Discard build -python auto-build/run.py --spec 001 --discard +python auto-claude/run.py --spec 001 --discard ``` ### QA Validation ```bash # Run QA manually -python auto-build/run.py --spec 001 --qa +python auto-claude/run.py --spec 001 --qa # Check QA status -python auto-build/run.py --spec 001 --qa-status +python auto-claude/run.py --spec 001 --qa-status ``` ### Testing @@ -78,7 +78,7 @@ pytest tests/ -m "not slow" ### Spec Validation ```bash -python auto-build/validate_spec.py --spec-dir auto-build/specs/001-feature --checkpoint all +python auto-claude/validate_spec.py --spec-dir auto-claude/specs/001-feature --checkpoint all ``` ## Architecture @@ -106,7 +106,7 @@ python auto-build/validate_spec.py --spec-dir auto-build/specs/001-feature --che - **graphiti_memory.py** - Optional graph-based cross-session memory (requires FalkorDB) - **linear_updater.py** - Optional Linear integration for progress tracking -### Agent Prompts (auto-build/prompts/) +### Agent Prompts (auto-claude/prompts/) | Prompt | Purpose | |--------|---------| @@ -123,7 +123,7 @@ python auto-build/validate_spec.py --spec-dir auto-build/specs/001-feature --che ### Spec Directory Structure -Each spec in `auto-build/specs/XXX-name/` contains: +Each spec in `auto-claude/specs/XXX-name/` contains: - `spec.md` - Feature specification - `requirements.json` - Structured user requirements - `context.json` - Discovered codebase context @@ -138,12 +138,12 @@ Three-layer defense: 2. **Filesystem Permissions** - Operations restricted to project directory 3. **Command Allowlist** - Dynamic allowlist from project analysis (security.py + project_analyzer.py) -Security profile cached in `.auto-build-security.json`. +Security profile cached in `.auto-claude-security.json`. ## Development Mode -Use `--dev` flag to work in `dev/auto-build/specs/` (gitignored) when developing the framework itself: +Use `--dev` flag to work in `dev/auto-claude/specs/` (gitignored) when developing the framework itself: ```bash -python auto-build/spec_runner.py --dev --task "Test feature" -python auto-build/run.py --dev --spec 001 +python auto-claude/spec_runner.py --dev --task "Test feature" +python auto-claude/run.py --dev --spec 001 ``` diff --git a/README.md b/README.md index 2371e355..8cf88436 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -# Auto-Build Framework +# Auto Claude A production-ready framework for autonomous multi-session AI coding. Build complete applications or add features to existing projects through coordinated AI agent sessions. ## What It Does -Auto-Build uses a **multi-agent pattern** to build software autonomously: +Auto Claude uses a **multi-agent pattern** to build software autonomously: ### Spec Creation Pipeline (8 phases) 1. **Discovery** - Analyzes project structure @@ -33,18 +33,18 @@ Each session runs with a fresh context window. Progress is tracked via `implemen ### Setup -**Step 1:** Copy the `auto-build` folder into your project +**Step 1:** Copy the `auto-claude` folder into your project ```bash -# Copy the auto-build folder to your project root -cp -r auto-build /path/to/your/project/ +# Copy the auto-claude folder to your project root +cp -r auto-claude /path/to/your/project/ ``` **Step 2:** Set up Python environment ```bash cd your-project -cd auto-build +cd auto-claude # Using uv (recommended) uv venv && uv pip install -r requirements.txt @@ -69,13 +69,13 @@ claude setup-token ```bash # Activate the virtual environment -source auto-build/.venv/bin/activate +source auto-claude/.venv/bin/activate # Create a spec interactively -python auto-build/spec_runner.py --interactive +python auto-claude/spec_runner.py --interactive # Or with a task description -python auto-build/spec_runner.py --task "Add user authentication with OAuth" +python auto-claude/spec_runner.py --task "Add user authentication with OAuth" ``` The spec orchestrator will: @@ -91,25 +91,25 @@ The spec orchestrator will: **Step 5:** Run the autonomous build ```bash -python auto-build/run.py --spec 001 +python auto-claude/run.py --spec 001 ``` ### Managing Specs ```bash # List all specs and their status -python auto-build/run.py --list +python auto-claude/run.py --list # Run a specific spec -python auto-build/run.py --spec 001 -python auto-build/run.py --spec 001-feature-name +python auto-claude/run.py --spec 001 +python auto-claude/run.py --spec 001-feature-name # Run with parallel workers (2-3x speedup for independent phases) -python auto-build/run.py --spec 001 --parallel 2 -python auto-build/run.py --spec 001 --parallel 3 +python auto-claude/run.py --spec 001 --parallel 2 +python auto-claude/run.py --spec 001 --parallel 3 # Limit iterations for testing -python auto-build/run.py --spec 001 --max-iterations 5 +python auto-claude/run.py --spec 001 --max-iterations 5 ``` ### QA Validation @@ -119,13 +119,13 @@ After all chunks are complete, QA validation runs automatically: ```bash # QA runs automatically after build completes # To skip automatic QA: -python auto-build/run.py --spec 001 --skip-qa +python auto-claude/run.py --spec 001 --skip-qa # Run QA validation manually on a completed build -python auto-build/run.py --spec 001 --qa +python auto-claude/run.py --spec 001 --qa # Check QA status -python auto-build/run.py --spec 001 --qa-status +python auto-claude/run.py --spec 001 --qa-status ``` The QA validation loop: @@ -141,19 +141,19 @@ The `spec_runner.py` orchestrator **automatically assesses task complexity** and ```bash # Simple task (auto-detected) - runs 3 phases -python auto-build/spec_runner.py --task "Fix button color in Header" +python auto-claude/spec_runner.py --task "Fix button color in Header" # Complex task (auto-detected) - runs 8 phases -python auto-build/spec_runner.py --task "Add Graphiti memory integration with FalkorDB" +python auto-claude/spec_runner.py --task "Add Graphiti memory integration with FalkorDB" # Force a specific complexity level -python auto-build/spec_runner.py --task "Update text" --complexity simple +python auto-claude/spec_runner.py --task "Update text" --complexity simple # Interactive mode -python auto-build/spec_runner.py --interactive +python auto-claude/spec_runner.py --interactive # Continue an interrupted spec -python auto-build/spec_runner.py --continue 001-feature +python auto-claude/spec_runner.py --continue 001-feature ``` **Complexity Tiers:** @@ -185,17 +185,17 @@ python auto-build/spec_runner.py --continue 001-feature **Manual validation:** ```bash -python auto-build/validate_spec.py --spec-dir auto-build/specs/001-feature --checkpoint all +python auto-claude/validate_spec.py --spec-dir auto-claude/specs/001-feature --checkpoint all ``` ### Isolated Worktrees (Safe by Default) -Auto-Build uses Git worktrees to keep your work completely safe. All AI-generated code is built in a separate workspace (`.worktrees/auto-build/`) - your current files are never touched until you explicitly merge. +Auto Claude uses Git worktrees to keep your work completely safe. All AI-generated code is built in a separate workspace (`.worktrees/auto-claude/`) - your current files are never touched until you explicitly merge. **How it works:** -1. When you run auto-build, it creates an isolated workspace -2. All coding happens in `.worktrees/auto-build/` on its own branch +1. When you run auto-claude, it creates an isolated workspace +2. All coding happens in `.worktrees/auto-claude/` on its own branch 3. You can `cd` into the worktree to test the feature before accepting 4. Only when you're satisfied, merge the changes into your project @@ -203,27 +203,27 @@ Auto-Build uses Git worktrees to keep your work completely safe. All AI-generate ```bash # Test the feature in the isolated workspace -cd .worktrees/auto-build/ +cd .worktrees/auto-claude/ npm run dev # or your project's run command # See what was changed -python auto-build/run.py --spec 001 --review +python auto-claude/run.py --spec 001 --review # Add changes to your project -python auto-build/run.py --spec 001 --merge +python auto-claude/run.py --spec 001 --merge # Discard if you don't like it (requires confirmation) -python auto-build/run.py --spec 001 --discard +python auto-claude/run.py --spec 001 --discard ``` **Key benefits:** -- **Safety**: Your uncommitted work is protected - auto-build won't touch it +- **Safety**: Your uncommitted work is protected - auto-claude won't touch it - **Testability**: Run and test the feature before committing to it - **Easy rollback**: Don't like it? Just discard the worktree - **Parallel-safe**: Multiple workers can build without conflicts -If you have uncommitted changes, auto-build automatically uses isolated mode. With a clean working directory, you can choose between isolated (recommended) or direct mode. +If you have uncommitted changes, auto-claude automatically uses isolated mode. With a clean working directory, you can choose between isolated (recommended) or direct mode. ### Interactive Controls @@ -243,10 +243,10 @@ Ctrl+C (twice) **Alternative (file-based):** ```bash # Create PAUSE file to pause after current session -touch auto-build/specs/001-name/PAUSE +touch auto-claude/specs/001-name/PAUSE # Manually edit instructions file -echo "Focus on fixing the login bug first" > auto-build/specs/001-name/HUMAN_INPUT.md +echo "Focus on fixing the login bug first" > auto-claude/specs/001-name/HUMAN_INPUT.md ``` ## Project Structure @@ -254,8 +254,8 @@ echo "Focus on fixing the login bug first" > auto-build/specs/001-name/HUMAN_INP ``` your-project/ ├── .worktrees/ # Created during build (git-ignored) -│ └── auto-build/ # Isolated workspace for AI coding -├── auto-build/ +│ └── auto-claude/ # Isolated workspace for AI coding +├── auto-claude/ │ ├── run.py # Build entry point │ ├── spec_runner.py # Spec creation orchestrator (8-phase pipeline) │ ├── validate_spec.py # Spec validation with JSON schemas @@ -312,7 +312,7 @@ your-project/ ## Graphiti Memory Integration (Optional) -Auto-Build includes an optional **Graphiti-based persistent memory layer** that enables context retention across coding sessions. This uses FalkorDB as a graph database to store codebase patterns, session insights, and cross-session learnings. +Auto Claude includes an optional **Graphiti-based persistent memory layer** that enables context retention across coding sessions. This uses FalkorDB as a graph database to store codebase patterns, session insights, and cross-session learnings. ### Why Use Graphiti Memory? @@ -350,13 +350,13 @@ OPENAI_API_KEY=sk-your-openai-key-here **Step 4:** Verify it's working ```bash -python auto-build/run.py --list +python auto-claude/run.py --list # Should show: "Graphiti memory: ENABLED" ``` ### When Disabled -When `GRAPHITI_ENABLED` is not set (default), Auto-Build uses file-based memory only. This is the zero-dependency default that works out of the box. +When `GRAPHITI_ENABLED` is not set (default), Auto Claude uses file-based memory only. This is the zero-dependency default that works out of the box. ## Environment Variables @@ -375,7 +375,7 @@ For parallel execution details: - Best practices - Troubleshooting -See [auto-build/PARALLEL_EXECUTION.md](auto-build/PARALLEL_EXECUTION.md) +See [auto-claude/PARALLEL_EXECUTION.md](auto-claude/PARALLEL_EXECUTION.md) ## Acknowledgments diff --git a/auto-build-ui/.gitignore b/auto-claude-ui/.gitignore similarity index 100% rename from auto-build-ui/.gitignore rename to auto-claude-ui/.gitignore diff --git a/auto-build-ui/.npmrc b/auto-claude-ui/.npmrc similarity index 100% rename from auto-build-ui/.npmrc rename to auto-claude-ui/.npmrc diff --git a/auto-build-ui/README.md b/auto-claude-ui/README.md similarity index 90% rename from auto-build-ui/README.md rename to auto-claude-ui/README.md index 8eaf34a1..659c1d21 100644 --- a/auto-build-ui/README.md +++ b/auto-claude-ui/README.md @@ -1,10 +1,10 @@ -# Auto-Build UI +# Auto Claude UI -A desktop application for managing AI-driven development tasks using the auto-build autonomous coding framework. +A desktop application for managing AI-driven development tasks using the Auto Claude autonomous coding framework. ## Overview -Auto-Build UI provides a visual Kanban board interface for creating, monitoring, and managing auto-build tasks. It replaces the terminal-based workflow with an intuitive GUI while preserving all CLI functionality. +Auto Claude UI provides a visual Kanban board interface for creating, monitoring, and managing auto-claude tasks. It replaces the terminal-based workflow with an intuitive GUI while preserving all CLI functionality. ## Features @@ -28,7 +28,7 @@ Auto-Build UI provides a visual Kanban board interface for creating, monitoring, ## Project Structure ``` -auto-build-ui/ +auto-claude-ui/ ├── src/ │ ├── main/ # Electron main process │ │ ├── index.ts # App entry point @@ -68,13 +68,13 @@ auto-build-ui/ - Node.js 18+ - npm or pnpm -- Python 3.10+ (for auto-build backend) +- Python 3.10+ (for auto-claude backend) ### Installation ```bash -# Navigate to auto-build-ui directory -cd auto-build-ui +# Navigate to auto-claude-ui directory +cd auto-claude-ui # Install dependencies npm install @@ -154,7 +154,7 @@ The application follows Electron security best practices: ## Environment Variables -- `CLAUDE_CODE_OAUTH_TOKEN`: OAuth token for Claude Code SDK (from auto-build/.env) +- `CLAUDE_CODE_OAUTH_TOKEN`: OAuth token for Claude Code SDK (from auto-claude/.env) - `FALKORDB_URL`: FalkorDB connection URL (optional, defaults to localhost:6379) ## License diff --git a/auto-build-ui/design.json b/auto-claude-ui/design.json similarity index 100% rename from auto-build-ui/design.json rename to auto-claude-ui/design.json diff --git a/auto-build-ui/e2e/electron-helper.ts b/auto-claude-ui/e2e/electron-helper.ts similarity index 98% rename from auto-build-ui/e2e/electron-helper.ts rename to auto-claude-ui/e2e/electron-helper.ts index 786fa65f..fcf0acfc 100644 --- a/auto-build-ui/e2e/electron-helper.ts +++ b/auto-claude-ui/e2e/electron-helper.ts @@ -23,7 +23,7 @@ export async function launchElectronApp(): Promise { ...process.env, NODE_ENV: 'test', // Use test-specific user data directory - ELECTRON_USER_DATA_PATH: '/tmp/auto-build-ui-e2e' + ELECTRON_USER_DATA_PATH: '/tmp/auto-claude-ui-e2e' } }); diff --git a/auto-build-ui/e2e/flows.e2e.ts b/auto-claude-ui/e2e/flows.e2e.ts similarity index 93% rename from auto-build-ui/e2e/flows.e2e.ts rename to auto-claude-ui/e2e/flows.e2e.ts index fdb0c3ae..1702d5ee 100644 --- a/auto-build-ui/e2e/flows.e2e.ts +++ b/auto-claude-ui/e2e/flows.e2e.ts @@ -13,7 +13,7 @@ import { mkdirSync, rmSync, existsSync, writeFileSync } from 'fs'; import path from 'path'; // Test data directory -const TEST_DATA_DIR = '/tmp/auto-build-ui-e2e'; +const TEST_DATA_DIR = '/tmp/auto-claude-ui-e2e'; const TEST_PROJECT_DIR = path.join(TEST_DATA_DIR, 'test-project'); // Setup test environment @@ -23,7 +23,7 @@ function setupTestEnvironment(): void { } mkdirSync(TEST_DATA_DIR, { recursive: true }); mkdirSync(TEST_PROJECT_DIR, { recursive: true }); - mkdirSync(path.join(TEST_PROJECT_DIR, 'auto-build', 'specs'), { recursive: true }); + mkdirSync(path.join(TEST_PROJECT_DIR, 'auto-claude', 'specs'), { recursive: true }); } // Cleanup test environment @@ -35,7 +35,7 @@ function cleanupTestEnvironment(): void { // Helper to create a test spec function createTestSpec(specId: string, status: 'pending' | 'in_progress' | 'completed' = 'pending'): void { - const specDir = path.join(TEST_PROJECT_DIR, 'auto-build', 'specs', specId); + const specDir = path.join(TEST_PROJECT_DIR, 'auto-claude', 'specs', specId); mkdirSync(specDir, { recursive: true }); const chunkStatus = status === 'completed' ? 'completed' : status === 'in_progress' ? 'in_progress' : 'pending'; @@ -123,7 +123,7 @@ test.describe('Add Project Flow', () => { await app.evaluate(({ dialog }) => { dialog.showOpenDialog = async () => ({ canceled: false, - filePaths: ['/tmp/auto-build-ui-e2e/test-project'] + filePaths: ['/tmp/auto-claude-ui-e2e/test-project'] }); }); @@ -199,7 +199,7 @@ test.describe('E2E Test Infrastructure', () => { setupTestEnvironment(); createTestSpec('001-test-spec'); - const specDir = path.join(TEST_PROJECT_DIR, 'auto-build', 'specs', '001-test-spec'); + const specDir = path.join(TEST_PROJECT_DIR, 'auto-claude', 'specs', '001-test-spec'); expect(existsSync(specDir)).toBe(true); expect(existsSync(path.join(specDir, 'implementation_plan.json'))).toBe(true); expect(existsSync(path.join(specDir, 'spec.md'))).toBe(true); @@ -214,7 +214,7 @@ test.describe('E2E Test Infrastructure', () => { createTestSpec('002-in-progress', 'in_progress'); createTestSpec('003-completed', 'completed'); - const specsDir = path.join(TEST_PROJECT_DIR, 'auto-build', 'specs'); + const specsDir = path.join(TEST_PROJECT_DIR, 'auto-claude', 'specs'); expect(existsSync(path.join(specsDir, '001-pending'))).toBe(true); expect(existsSync(path.join(specsDir, '002-in-progress'))).toBe(true); expect(existsSync(path.join(specsDir, '003-completed'))).toBe(true); @@ -232,8 +232,8 @@ test.describe('E2E Flow Verification (Mock-based)', () => { const projectPath = TEST_PROJECT_DIR; expect(existsSync(projectPath)).toBe(true); - // Check for auto-build directory detection - const autoBuildPath = path.join(projectPath, 'auto-build'); + // Check for auto-claude directory detection + const autoBuildPath = path.join(projectPath, 'auto-claude'); expect(existsSync(autoBuildPath)).toBe(true); cleanupTestEnvironment(); @@ -244,7 +244,7 @@ test.describe('E2E Flow Verification (Mock-based)', () => { // Simulate what would happen when creating a task const specId = '001-new-task'; - const specDir = path.join(TEST_PROJECT_DIR, 'auto-build', 'specs', specId); + const specDir = path.join(TEST_PROJECT_DIR, 'auto-claude', 'specs', specId); mkdirSync(specDir, { recursive: true }); // Write spec file @@ -263,7 +263,7 @@ test.describe('E2E Flow Verification (Mock-based)', () => { // Simulate status update when task starts const planPath = path.join( TEST_PROJECT_DIR, - 'auto-build', + 'auto-claude', 'specs', '001-task', 'implementation_plan.json' @@ -288,7 +288,7 @@ test.describe('E2E Flow Verification (Mock-based)', () => { // Simulate approval const qaReportPath = path.join( TEST_PROJECT_DIR, - 'auto-build', + 'auto-claude', 'specs', '001-review', 'qa_report.md' @@ -311,7 +311,7 @@ test.describe('E2E Flow Verification (Mock-based)', () => { // Simulate rejection const fixRequestPath = path.join( TEST_PROJECT_DIR, - 'auto-build', + 'auto-claude', 'specs', '001-reject', 'QA_FIX_REQUEST.md' diff --git a/auto-build-ui/e2e/playwright.config.ts b/auto-claude-ui/e2e/playwright.config.ts similarity index 100% rename from auto-build-ui/e2e/playwright.config.ts rename to auto-claude-ui/e2e/playwright.config.ts diff --git a/auto-build-ui/electron.vite.config.ts b/auto-claude-ui/electron.vite.config.ts similarity index 100% rename from auto-build-ui/electron.vite.config.ts rename to auto-claude-ui/electron.vite.config.ts diff --git a/auto-build-ui/eslint.config.mjs b/auto-claude-ui/eslint.config.mjs similarity index 100% rename from auto-build-ui/eslint.config.mjs rename to auto-claude-ui/eslint.config.mjs diff --git a/auto-build-ui/package.json b/auto-claude-ui/package.json similarity index 91% rename from auto-build-ui/package.json rename to auto-claude-ui/package.json index a634ad4f..51c66fb6 100644 --- a/auto-build-ui/package.json +++ b/auto-claude-ui/package.json @@ -1,9 +1,9 @@ { - "name": "auto-build-ui", + "name": "auto-claude-ui", "version": "0.1.0", - "description": "Desktop UI for Auto-Build autonomous coding framework", + "description": "Desktop UI for Auto Claude autonomous coding framework", "main": "./out/main/index.js", - "author": "Auto-Build Team", + "author": "Auto Claude Team", "license": "MIT", "scripts": { "postinstall": "electron-rebuild", @@ -26,6 +26,8 @@ "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", + "@radix-ui/react-checkbox": "^1.1.4", + "@radix-ui/react-collapsible": "^1.1.3", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-progress": "^1.1.8", @@ -91,8 +93,8 @@ ] }, "build": { - "appId": "com.autobuild.ui", - "productName": "Auto-Build UI", + "appId": "com.autoclaude.ui", + "productName": "Auto Claude", "directories": { "output": "dist", "buildResources": "resources" diff --git a/auto-build-ui/pnpm-lock.yaml b/auto-claude-ui/pnpm-lock.yaml similarity index 98% rename from auto-build-ui/pnpm-lock.yaml rename to auto-claude-ui/pnpm-lock.yaml index 85480b8b..d4d37b31 100644 --- a/auto-build-ui/pnpm-lock.yaml +++ b/auto-claude-ui/pnpm-lock.yaml @@ -21,6 +21,12 @@ importers: '@dnd-kit/utilities': specifier: ^3.2.2 version: 3.2.2(react@19.2.1) + '@radix-ui/react-checkbox': + specifier: ^1.1.4 + version: 1.3.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-collapsible': + specifier: ^1.1.3 + version: 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@radix-ui/react-dialog': specifier: ^1.1.15 version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) @@ -660,6 +666,32 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-checkbox@1.3.3': + resolution: {integrity: sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collapsible@1.1.12': + resolution: {integrity: sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-collection@1.1.7': resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} peerDependencies: @@ -4176,6 +4208,38 @@ snapshots: '@types/react': 19.2.7 '@types/react-dom': 19.2.3(@types/react@19.2.7) + '@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.1) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.7)(react@19.2.1) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.7)(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + + '@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.7)(react@19.2.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.1) diff --git a/auto-build-ui/postcss.config.js b/auto-claude-ui/postcss.config.js similarity index 100% rename from auto-build-ui/postcss.config.js rename to auto-claude-ui/postcss.config.js diff --git a/auto-build-ui/src/__mocks__/electron.ts b/auto-claude-ui/src/__mocks__/electron.ts similarity index 100% rename from auto-build-ui/src/__mocks__/electron.ts rename to auto-claude-ui/src/__mocks__/electron.ts diff --git a/auto-build-ui/src/__tests__/integration/file-watcher.test.ts b/auto-claude-ui/src/__tests__/integration/file-watcher.test.ts similarity index 100% rename from auto-build-ui/src/__tests__/integration/file-watcher.test.ts rename to auto-claude-ui/src/__tests__/integration/file-watcher.test.ts diff --git a/auto-build-ui/src/__tests__/integration/ipc-bridge.test.ts b/auto-claude-ui/src/__tests__/integration/ipc-bridge.test.ts similarity index 100% rename from auto-build-ui/src/__tests__/integration/ipc-bridge.test.ts rename to auto-claude-ui/src/__tests__/integration/ipc-bridge.test.ts diff --git a/auto-build-ui/src/__tests__/integration/subprocess-spawn.test.ts b/auto-claude-ui/src/__tests__/integration/subprocess-spawn.test.ts similarity index 97% rename from auto-build-ui/src/__tests__/integration/subprocess-spawn.test.ts rename to auto-claude-ui/src/__tests__/integration/subprocess-spawn.test.ts index 244772db..924e9a8c 100644 --- a/auto-build-ui/src/__tests__/integration/subprocess-spawn.test.ts +++ b/auto-claude-ui/src/__tests__/integration/subprocess-spawn.test.ts @@ -32,15 +32,15 @@ vi.mock('child_process', () => ({ // Setup test directories function setupTestDirs(): void { mkdirSync(TEST_PROJECT_PATH, { recursive: true }); - mkdirSync(path.join(TEST_PROJECT_PATH, 'auto-build'), { recursive: true }); + mkdirSync(path.join(TEST_PROJECT_PATH, 'auto-claude'), { recursive: true }); // Create mock spec_runner.py writeFileSync( - path.join(TEST_PROJECT_PATH, 'auto-build', 'spec_runner.py'), + path.join(TEST_PROJECT_PATH, 'auto-claude', 'spec_runner.py'), '# Mock spec runner\nprint("Starting spec creation")' ); // Create mock run.py writeFileSync( - path.join(TEST_PROJECT_PATH, 'auto-build', 'run.py'), + path.join(TEST_PROJECT_PATH, 'auto-claude', 'run.py'), '# Mock run.py\nprint("Starting task execution")' ); } diff --git a/auto-build-ui/src/__tests__/setup.ts b/auto-claude-ui/src/__tests__/setup.ts similarity index 96% rename from auto-build-ui/src/__tests__/setup.ts rename to auto-claude-ui/src/__tests__/setup.ts index 06913de9..26e1e443 100644 --- a/auto-build-ui/src/__tests__/setup.ts +++ b/auto-claude-ui/src/__tests__/setup.ts @@ -6,7 +6,7 @@ import { mkdirSync, rmSync, existsSync } from 'fs'; import path from 'path'; // Test data directory for isolated file operations -export const TEST_DATA_DIR = '/tmp/auto-build-ui-tests'; +export const TEST_DATA_DIR = '/tmp/auto-claude-ui-tests'; // Create fresh test directory before each test beforeEach(() => { diff --git a/auto-build-ui/src/main/__tests__/ipc-handlers.test.ts b/auto-claude-ui/src/main/__tests__/ipc-handlers.test.ts similarity index 98% rename from auto-build-ui/src/main/__tests__/ipc-handlers.test.ts rename to auto-claude-ui/src/main/__tests__/ipc-handlers.test.ts index 22bfc5a2..05cf6f98 100644 --- a/auto-build-ui/src/main/__tests__/ipc-handlers.test.ts +++ b/auto-claude-ui/src/main/__tests__/ipc-handlers.test.ts @@ -59,7 +59,7 @@ vi.mock('electron', () => { // Setup test project structure function setupTestProject(): void { mkdirSync(TEST_PROJECT_PATH, { recursive: true }); - mkdirSync(path.join(TEST_PROJECT_PATH, 'auto-build', 'specs'), { recursive: true }); + mkdirSync(path.join(TEST_PROJECT_PATH, 'auto-claude', 'specs'), { recursive: true }); } // Cleanup test directories @@ -296,7 +296,7 @@ describe('IPC Handlers', () => { const projectId = (addResult as { data: { id: string } }).data.id; // Create a spec directory with implementation plan - const specDir = path.join(TEST_PROJECT_PATH, 'auto-build', 'specs', '001-test-feature'); + const specDir = path.join(TEST_PROJECT_PATH, 'auto-claude', 'specs', '001-test-feature'); mkdirSync(specDir, { recursive: true }); writeFileSync(path.join(specDir, 'implementation_plan.json'), JSON.stringify({ feature: 'Test Feature', diff --git a/auto-build-ui/src/main/__tests__/project-store.test.ts b/auto-claude-ui/src/main/__tests__/project-store.test.ts similarity index 95% rename from auto-build-ui/src/main/__tests__/project-store.test.ts rename to auto-claude-ui/src/main/__tests__/project-store.test.ts index 5bb1cfd7..c66f2986 100644 --- a/auto-build-ui/src/main/__tests__/project-store.test.ts +++ b/auto-claude-ui/src/main/__tests__/project-store.test.ts @@ -82,16 +82,16 @@ describe('ProjectStore', () => { expect(project1.id).toBe(project2.id); }); - it('should detect auto-build directory if present', async () => { - // Create auto-build directory - mkdirSync(path.join(TEST_PROJECT_PATH, 'auto-build'), { recursive: true }); + it('should detect auto-claude directory if present', async () => { + // Create auto-claude directory + mkdirSync(path.join(TEST_PROJECT_PATH, 'auto-claude'), { recursive: true }); const { ProjectStore } = await import('../project-store'); const store = new ProjectStore(); const project = store.addProject(TEST_PROJECT_PATH); - expect(project.autoBuildPath).toBe('auto-build'); + expect(project.autoBuildPath).toBe('auto-claude'); }); it('should set empty autoBuildPath if not present', async () => { @@ -279,7 +279,7 @@ describe('ProjectStore', () => { it('should read tasks from filesystem correctly', async () => { // Create spec directory structure - const specsDir = path.join(TEST_PROJECT_PATH, 'auto-build', 'specs', '001-test-feature'); + const specsDir = path.join(TEST_PROJECT_PATH, 'auto-claude', 'specs', '001-test-feature'); mkdirSync(specsDir, { recursive: true }); const plan = { @@ -325,7 +325,7 @@ describe('ProjectStore', () => { }); it('should determine status as backlog when no chunks completed', async () => { - const specsDir = path.join(TEST_PROJECT_PATH, 'auto-build', 'specs', '002-pending'); + const specsDir = path.join(TEST_PROJECT_PATH, 'auto-claude', 'specs', '002-pending'); mkdirSync(specsDir, { recursive: true }); const plan = { @@ -364,7 +364,7 @@ describe('ProjectStore', () => { }); it('should determine status as ai_review when all chunks completed', async () => { - const specsDir = path.join(TEST_PROJECT_PATH, 'auto-build', 'specs', '003-complete'); + const specsDir = path.join(TEST_PROJECT_PATH, 'auto-claude', 'specs', '003-complete'); mkdirSync(specsDir, { recursive: true }); const plan = { @@ -403,7 +403,7 @@ describe('ProjectStore', () => { }); it('should determine status as human_review when QA report rejected', async () => { - const specsDir = path.join(TEST_PROJECT_PATH, 'auto-build', 'specs', '004-rejected'); + const specsDir = path.join(TEST_PROJECT_PATH, 'auto-claude', 'specs', '004-rejected'); mkdirSync(specsDir, { recursive: true }); const plan = { @@ -446,7 +446,7 @@ describe('ProjectStore', () => { }); it('should determine status as done when QA report approved', async () => { - const specsDir = path.join(TEST_PROJECT_PATH, 'auto-build', 'specs', '005-approved'); + const specsDir = path.join(TEST_PROJECT_PATH, 'auto-claude', 'specs', '005-approved'); mkdirSync(specsDir, { recursive: true }); const plan = { diff --git a/auto-build-ui/src/main/agent-manager.ts b/auto-claude-ui/src/main/agent-manager.ts similarity index 91% rename from auto-build-ui/src/main/agent-manager.ts rename to auto-claude-ui/src/main/agent-manager.ts index 2be0b003..bc3b3dba 100644 --- a/auto-build-ui/src/main/agent-manager.ts +++ b/auto-claude-ui/src/main/agent-manager.ts @@ -17,19 +17,19 @@ export interface AgentManagerEvents { } /** - * Manages Python subprocess spawning for auto-build agents + * Manages Python subprocess spawning for auto-claude agents */ export class AgentManager extends EventEmitter { private processes: Map = new Map(); private pythonPath: string = 'python3'; - private autoBuildSourcePath: string = ''; // Source auto-build repo location + private autoBuildSourcePath: string = ''; // Source auto-claude repo location constructor() { super(); } /** - * Configure paths for Python and auto-build source + * Configure paths for Python and auto-claude source */ configure(pythonPath?: string, autoBuildSourcePath?: string): void { if (pythonPath) { @@ -41,7 +41,7 @@ export class AgentManager extends EventEmitter { } /** - * Get the auto-build source path (detects automatically if not configured) + * Get the auto-claude source path (detects automatically if not configured) */ private getAutoBuildSourcePath(): string | null { // If manually configured, use that @@ -51,12 +51,12 @@ export class AgentManager extends EventEmitter { // Auto-detect from app location const possiblePaths = [ - // Dev mode: from dist/main -> ../../auto-build (sibling to auto-build-ui) - path.resolve(__dirname, '..', '..', '..', 'auto-build'), + // Dev mode: from dist/main -> ../../auto-claude (sibling to auto-claude-ui) + path.resolve(__dirname, '..', '..', '..', 'auto-claude'), // Alternative: from app root - path.resolve(app.getAppPath(), '..', 'auto-build'), + path.resolve(app.getAppPath(), '..', 'auto-claude'), // If running from repo root - path.resolve(process.cwd(), 'auto-build') + path.resolve(process.cwd(), 'auto-claude') ]; for (const p of possiblePaths) { @@ -68,7 +68,7 @@ export class AgentManager extends EventEmitter { } /** - * Load environment variables from auto-build .env file + * Load environment variables from auto-claude .env file */ private loadAutoBuildEnv(): Record { const autoBuildSource = this.getAutoBuildSourcePath(); @@ -121,7 +121,7 @@ export class AgentManager extends EventEmitter { projectPath: string, taskDescription: string ): void { - const autoBuildDir = path.join(projectPath, 'auto-build'); + const autoBuildDir = path.join(projectPath, 'auto-claude'); const specRunnerPath = path.join(autoBuildDir, 'spec_runner.py'); const args = [specRunnerPath, '--task', taskDescription]; @@ -138,7 +138,7 @@ export class AgentManager extends EventEmitter { specId: string, options: { parallel?: boolean; workers?: number } = {} ): void { - const autoBuildDir = path.join(projectPath, 'auto-build'); + const autoBuildDir = path.join(projectPath, 'auto-claude'); const runPath = path.join(autoBuildDir, 'run.py'); const args = [runPath, '--spec', specId]; @@ -158,7 +158,7 @@ export class AgentManager extends EventEmitter { projectPath: string, specId: string ): void { - const autoBuildDir = path.join(projectPath, 'auto-build'); + const autoBuildDir = path.join(projectPath, 'auto-claude'); const runPath = path.join(autoBuildDir, 'run.py'); const args = [runPath, '--spec', specId, '--qa']; @@ -174,7 +174,7 @@ export class AgentManager extends EventEmitter { projectPath: string, refresh: boolean = false ): void { - // Use source auto-build path (the repo), not the project's auto-build + // Use source auto-claude path (the repo), not the project's auto-claude const autoBuildSource = this.getAutoBuildSourcePath(); if (!autoBuildSource) { @@ -213,7 +213,7 @@ export class AgentManager extends EventEmitter { }, refresh: boolean = false ): void { - // Use source auto-build path (the repo), not the project's auto-build + // Use source auto-claude path (the repo), not the project's auto-claude const autoBuildSource = this.getAutoBuildSourcePath(); if (!autoBuildSource) { @@ -267,18 +267,18 @@ export class AgentManager extends EventEmitter { // Kill existing process for this project if any this.killTask(projectId); - // Run from auto-build source directory so imports work correctly + // Run from auto-claude source directory so imports work correctly const autoBuildSource = this.getAutoBuildSourcePath(); const cwd = autoBuildSource || process.cwd(); - // Load environment variables from auto-build .env file + // Load environment variables from auto-claude .env file const autoBuildEnv = this.loadAutoBuildEnv(); const childProcess = spawn(this.pythonPath, args, { cwd, env: { ...process.env, - ...autoBuildEnv, // Include auto-build .env variables (like CLAUDE_CODE_OAUTH_TOKEN) + ...autoBuildEnv, // Include auto-claude .env variables (like CLAUDE_CODE_OAUTH_TOKEN) PYTHONUNBUFFERED: '1' } }); @@ -397,18 +397,18 @@ export class AgentManager extends EventEmitter { // Kill existing process for this project if any this.killTask(projectId); - // Run from auto-build source directory so imports work correctly + // Run from auto-claude source directory so imports work correctly const autoBuildSource = this.getAutoBuildSourcePath(); const cwd = autoBuildSource || process.cwd(); - // Load environment variables from auto-build .env file + // Load environment variables from auto-claude .env file const autoBuildEnv = this.loadAutoBuildEnv(); const childProcess = spawn(this.pythonPath, args, { cwd, env: { ...process.env, - ...autoBuildEnv, // Include auto-build .env variables (like CLAUDE_CODE_OAUTH_TOKEN) + ...autoBuildEnv, // Include auto-claude .env variables (like CLAUDE_CODE_OAUTH_TOKEN) PYTHONUNBUFFERED: '1' } }); diff --git a/auto-build-ui/src/main/auto-build-updater.ts b/auto-claude-ui/src/main/auto-claude-updater.ts similarity index 91% rename from auto-build-ui/src/main/auto-build-updater.ts rename to auto-claude-ui/src/main/auto-claude-updater.ts index c579f089..2f314168 100644 --- a/auto-build-ui/src/main/auto-build-updater.ts +++ b/auto-claude-ui/src/main/auto-claude-updater.ts @@ -1,8 +1,8 @@ /** * Auto-Build Source Updater * - * Checks GitHub for updates to the auto-build framework and downloads them. - * This allows users to get new auto-build features without requiring a full app update. + * Checks GitHub for updates to the auto-claude framework and downloads them. + * This allows users to get new auto-claude features without requiring a full app update. * * Update flow: * 1. Check GitHub for latest VERSION file @@ -26,9 +26,9 @@ const execAsync = promisify(exec); */ const GITHUB_CONFIG = { owner: 'anthropics', // Update to actual repo owner - repo: 'auto-build', // Update to actual repo name + repo: 'auto-claude', // Update to actual repo name branch: 'main', - autoBuildPath: 'auto-build' // Path within repo + autoBuildPath: 'auto-claude' // Path within repo }; /** @@ -61,21 +61,21 @@ export type UpdateProgressCallback = (progress: { }) => void; /** - * Get the path to the bundled auto-build source + * Get the path to the bundled auto-claude source */ export function getBundledSourcePath(): string { // In production, use app resources - // In development, use the repo's auto-build folder + // In development, use the repo's auto-claude folder if (app.isPackaged) { - return path.join(process.resourcesPath, 'auto-build'); + return path.join(process.resourcesPath, 'auto-claude'); } - // Development mode - look for auto-build in various locations + // Development mode - look for auto-claude in various locations const possiblePaths = [ - path.join(app.getAppPath(), '..', 'auto-build'), - path.join(app.getAppPath(), '..', '..', 'auto-build'), - path.join(process.cwd(), 'auto-build'), - path.join(process.cwd(), '..', 'auto-build') + path.join(app.getAppPath(), '..', 'auto-claude'), + path.join(app.getAppPath(), '..', '..', 'auto-claude'), + path.join(process.cwd(), 'auto-claude'), + path.join(process.cwd(), '..', 'auto-claude') ]; for (const p of possiblePaths) { @@ -85,14 +85,14 @@ export function getBundledSourcePath(): string { } // Fallback - return path.join(app.getAppPath(), '..', 'auto-build'); + return path.join(app.getAppPath(), '..', 'auto-claude'); } /** * Get the path for storing downloaded updates */ function getUpdateCachePath(): string { - return path.join(app.getPath('userData'), 'auto-build-updates'); + return path.join(app.getPath('userData'), 'auto-claude-updates'); } /** @@ -276,7 +276,7 @@ function downloadFile( } /** - * Download and apply the latest auto-build update + * Download and apply the latest auto-claude update * * Note: In production, this updates the bundled source in userData. * For packaged apps, we can't modify resourcesPath directly, @@ -301,7 +301,7 @@ export async function downloadAndApplyUpdate( // Get download URL for the tarball const tarballUrl = `https://api.github.com/repos/${GITHUB_CONFIG.owner}/${GITHUB_CONFIG.repo}/tarball/${GITHUB_CONFIG.branch}`; - const tarballPath = path.join(cachePath, 'auto-build-update.tar.gz'); + const tarballPath = path.join(cachePath, 'auto-claude-update.tar.gz'); const extractPath = path.join(cachePath, 'extracted'); // Clean up previous extraction @@ -333,7 +333,7 @@ export async function downloadAndApplyUpdate( // Extract the tarball await extractTarball(tarballPath, extractPath); - // Find the auto-build folder in extracted content + // Find the auto-claude folder in extracted content // GitHub tarballs have a root folder like "owner-repo-hash/" const extractedDirs = readdirSync(extractPath); if (extractedDirs.length === 0) { @@ -344,7 +344,7 @@ export async function downloadAndApplyUpdate( const autoBuildSource = path.join(rootDir, GITHUB_CONFIG.autoBuildPath); if (!existsSync(autoBuildSource)) { - throw new Error('auto-build folder not found in download'); + throw new Error('auto-claude folder not found in download'); } // Determine where to install the update @@ -352,7 +352,7 @@ export async function downloadAndApplyUpdate( if (app.isPackaged) { // For packaged apps, store in userData as a source override - targetPath = path.join(app.getPath('userData'), 'auto-build-source'); + targetPath = path.join(app.getPath('userData'), 'auto-claude-source'); } else { // In development, update the actual source targetPath = getBundledSourcePath(); @@ -503,7 +503,7 @@ function copyDirectoryRecursive( export function getEffectiveSourcePath(): string { if (app.isPackaged) { // Check for user-updated source first - const overridePath = path.join(app.getPath('userData'), 'auto-build-source'); + const overridePath = path.join(app.getPath('userData'), 'auto-claude-source'); if (existsSync(overridePath)) { return overridePath; } @@ -520,7 +520,7 @@ export function hasPendingSourceUpdate(): boolean { return false; } - const overridePath = path.join(app.getPath('userData'), 'auto-build-source'); + const overridePath = path.join(app.getPath('userData'), 'auto-claude-source'); const metadataPath = path.join(overridePath, '.update-metadata.json'); if (!existsSync(metadataPath)) { diff --git a/auto-claude-ui/src/main/changelog-service.ts b/auto-claude-ui/src/main/changelog-service.ts new file mode 100644 index 00000000..9296f248 --- /dev/null +++ b/auto-claude-ui/src/main/changelog-service.ts @@ -0,0 +1,582 @@ +import { EventEmitter } from 'events'; +import path from 'path'; +import { existsSync, readFileSync, writeFileSync } from 'fs'; +import { spawn } from 'child_process'; +import { app } from 'electron'; +import { AUTO_BUILD_PATHS, DEFAULT_CHANGELOG_PATH } from '../shared/constants'; +import type { + ChangelogTask, + TaskSpecContent, + ChangelogGenerationRequest, + ChangelogGenerationResult, + ChangelogSaveRequest, + ChangelogSaveResult, + ChangelogGenerationProgress, + ExistingChangelog, + Task, + ImplementationPlan +} from '../shared/types'; + +/** + * Service for generating changelogs from completed tasks + */ +export class ChangelogService extends EventEmitter { + private pythonPath: string = 'python3'; + private autoBuildSourcePath: string = ''; + private generationProcesses: Map> = new Map(); + + constructor() { + super(); + } + + /** + * Configure paths for Python and auto-claude source + */ + configure(pythonPath?: string, autoBuildSourcePath?: string): void { + if (pythonPath) { + this.pythonPath = pythonPath; + } + if (autoBuildSourcePath) { + this.autoBuildSourcePath = autoBuildSourcePath; + } + } + + /** + * Get the auto-claude source path (detects automatically if not configured) + */ + private getAutoBuildSourcePath(): string | null { + if (this.autoBuildSourcePath && existsSync(this.autoBuildSourcePath)) { + return this.autoBuildSourcePath; + } + + const possiblePaths = [ + path.resolve(__dirname, '..', '..', '..', 'auto-claude'), + path.resolve(app.getAppPath(), '..', 'auto-claude'), + path.resolve(process.cwd(), 'auto-claude') + ]; + + for (const p of possiblePaths) { + if (existsSync(p) && existsSync(path.join(p, 'VERSION'))) { + return p; + } + } + return null; + } + + /** + * Load environment variables from auto-claude .env file + */ + private loadAutoBuildEnv(): Record { + const autoBuildSource = this.getAutoBuildSourcePath(); + if (!autoBuildSource) return {}; + + const envPath = path.join(autoBuildSource, '.env'); + if (!existsSync(envPath)) return {}; + + try { + const envContent = readFileSync(envPath, 'utf-8'); + const envVars: Record = {}; + + for (const line of envContent.split('\n')) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith('#')) continue; + + const eqIndex = trimmed.indexOf('='); + if (eqIndex > 0) { + const key = trimmed.substring(0, eqIndex).trim(); + let value = trimmed.substring(eqIndex + 1).trim(); + + if ((value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'"))) { + value = value.slice(1, -1); + } + + envVars[key] = value; + } + } + + return envVars; + } catch { + return {}; + } + } + + /** + * Get completed tasks from a project + */ + getCompletedTasks(projectPath: string, tasks: Task[]): ChangelogTask[] { + const specsDir = path.join(projectPath, AUTO_BUILD_PATHS.SPECS_DIR); + + return tasks + .filter(task => task.status === 'done') + .map(task => { + const specDir = path.join(specsDir, task.specId); + const hasSpecs = existsSync(specDir) && existsSync(path.join(specDir, AUTO_BUILD_PATHS.SPEC_FILE)); + + return { + id: task.id, + specId: task.specId, + title: task.title, + description: task.description, + completedAt: task.updatedAt, + hasSpecs + }; + }) + .sort((a, b) => new Date(b.completedAt).getTime() - new Date(a.completedAt).getTime()); + } + + /** + * Load spec files for given tasks + */ + async loadTaskSpecs(projectPath: string, taskIds: string[], tasks: Task[]): Promise { + const specsDir = path.join(projectPath, AUTO_BUILD_PATHS.SPECS_DIR); + const results: TaskSpecContent[] = []; + + for (const taskId of taskIds) { + const task = tasks.find(t => t.id === taskId); + if (!task) continue; + + const specDir = path.join(specsDir, task.specId); + const content: TaskSpecContent = { + taskId, + specId: task.specId + }; + + try { + // Load spec.md + const specPath = path.join(specDir, AUTO_BUILD_PATHS.SPEC_FILE); + if (existsSync(specPath)) { + content.spec = readFileSync(specPath, 'utf-8'); + } + + // Load requirements.json + const requirementsPath = path.join(specDir, AUTO_BUILD_PATHS.REQUIREMENTS); + if (existsSync(requirementsPath)) { + content.requirements = JSON.parse(readFileSync(requirementsPath, 'utf-8')); + } + + // Load qa_report.md + const qaReportPath = path.join(specDir, AUTO_BUILD_PATHS.QA_REPORT); + if (existsSync(qaReportPath)) { + content.qaReport = readFileSync(qaReportPath, 'utf-8'); + } + + // Load implementation_plan.json + const planPath = path.join(specDir, AUTO_BUILD_PATHS.IMPLEMENTATION_PLAN); + if (existsSync(planPath)) { + content.implementationPlan = JSON.parse(readFileSync(planPath, 'utf-8')) as ImplementationPlan; + } + } catch (error) { + content.error = error instanceof Error ? error.message : 'Failed to load spec files'; + } + + results.push(content); + } + + return results; + } + + /** + * Generate changelog using Claude AI + */ + generateChangelog( + projectId: string, + projectPath: string, + request: ChangelogGenerationRequest, + specs: TaskSpecContent[] + ): void { + // Kill existing process if any + this.cancelGeneration(projectId); + + // Emit initial progress + this.emitProgress(projectId, { + stage: 'loading_specs', + progress: 10, + message: 'Preparing changelog generation...' + }); + + // Build the prompt for Claude + const prompt = this.buildChangelogPrompt(request, specs); + + // Use Claude Code SDK via subprocess + const autoBuildSource = this.getAutoBuildSourcePath(); + if (!autoBuildSource) { + this.emitError(projectId, 'Auto-build source path not found'); + return; + } + + // Create a temporary Python script to call Claude + const script = this.createGenerationScript(prompt, request); + + this.emitProgress(projectId, { + stage: 'generating', + progress: 30, + message: 'Generating changelog with Claude AI...' + }); + + const autoBuildEnv = this.loadAutoBuildEnv(); + + const childProcess = spawn(this.pythonPath, ['-c', script], { + cwd: autoBuildSource, + env: { + ...process.env, + ...autoBuildEnv, + PYTHONUNBUFFERED: '1' + } + }); + + this.generationProcesses.set(projectId, childProcess); + + let output = ''; + let errorOutput = ''; + + childProcess.stdout?.on('data', (data: Buffer) => { + output += data.toString(); + + this.emitProgress(projectId, { + stage: 'generating', + progress: 50, + message: 'Generating changelog content...' + }); + }); + + childProcess.stderr?.on('data', (data: Buffer) => { + errorOutput += data.toString(); + }); + + childProcess.on('exit', (code: number | null) => { + this.generationProcesses.delete(projectId); + + if (code === 0 && output.trim()) { + this.emitProgress(projectId, { + stage: 'formatting', + progress: 90, + message: 'Formatting changelog...' + }); + + // Extract changelog from output + const changelog = this.extractChangelog(output.trim()); + + this.emitProgress(projectId, { + stage: 'complete', + progress: 100, + message: 'Changelog generation complete' + }); + + const result: ChangelogGenerationResult = { + success: true, + changelog, + version: request.version, + tasksIncluded: request.taskIds.length + }; + + this.emit('generation-complete', projectId, result); + } else { + const error = errorOutput || `Generation failed with exit code ${code}`; + this.emitError(projectId, error); + } + }); + + childProcess.on('error', (err: Error) => { + this.generationProcesses.delete(projectId); + this.emitError(projectId, err.message); + }); + } + + /** + * Build the prompt for changelog generation + */ + private buildChangelogPrompt( + request: ChangelogGenerationRequest, + specs: TaskSpecContent[] + ): string { + const audienceInstructions = { + 'technical': `You are a technical documentation specialist creating a changelog for software developers. +Use precise technical language. Include API changes, architecture details, affected modules. +Note any breaking changes explicitly. Include migration steps if needed.`, + 'user-facing': `You are a product manager writing release notes for end users who may not be technical. +Use clear, non-technical language. Focus on user benefits and value. +Explain "what" changed, not "how". Use active voice and positive framing.`, + 'marketing': `You are a marketing specialist writing release notes that emphasize value and benefits. +Focus on outcomes and user impact. Use compelling language that highlights improvements. +Emphasize competitive advantages and user success stories.` + }; + + const formatInstructions = { + 'keep-a-changelog': `Use Keep-a-Changelog format with these sections: +## [${request.version}] - ${request.date} + +### Added +- [New features] + +### Changed +- [Modifications] + +### Fixed +- [Bug fixes] + +### Removed +- [Deprecations/removals]`, + 'simple-list': `Use a simple, clean format: +# Release v${request.version} (${request.date}) + +**New Features:** +- [List features] + +**Improvements:** +- [List improvements] + +**Bug Fixes:** +- [List fixes]`, + 'github-release': `Use GitHub Release format with emojis: +## 🎉 What's New in v${request.version} + +### ✨ New Features +- 🚀 **Feature Name**: Description + +### 🔧 Improvements +- ⚡ **Improvement**: Description + +### 🐛 Bug Fixes +- Fixed [issue description]` + }; + + // Build task context + const taskContext = specs.map(spec => { + let context = `## Task: ${spec.specId}\n`; + + if (spec.spec) { + // Extract key parts from spec.md + context += `### Specification:\n${spec.spec.substring(0, 2000)}...\n\n`; + } + + if (spec.qaReport) { + context += `### QA Validation:\n${spec.qaReport.substring(0, 500)}...\n\n`; + } + + if (spec.implementationPlan) { + context += `### Implementation: ${spec.implementationPlan.feature}\n`; + context += `Type: ${spec.implementationPlan.workflow_type}\n`; + } + + return context; + }).join('\n---\n'); + + return `${audienceInstructions[request.audience]} + +Generate a changelog entry in the following format: +${formatInstructions[request.format]} + +CONTEXT - The following tasks have been completed: +${taskContext} + +${request.customInstructions ? `ADDITIONAL INSTRUCTIONS: ${request.customInstructions}` : ''} + +Generate only the changelog content. Be comprehensive but concise. Do not include any explanation or preamble - just the formatted changelog.`; + } + + /** + * Create Python script for Claude generation + */ + private createGenerationScript(prompt: string, _request: ChangelogGenerationRequest): string { + // Escape the prompt for Python string + const escapedPrompt = prompt + .replace(/\\/g, '\\\\') + .replace(/"/g, '\\"') + .replace(/\n/g, '\\n'); + + return ` +import subprocess +import sys + +prompt = """${escapedPrompt}""" + +# Use Claude Code CLI to generate +result = subprocess.run( + ['claude', '-p', prompt, '--output-format', 'text'], + capture_output=True, + text=True, + timeout=120 +) + +if result.returncode == 0: + print(result.stdout) +else: + print(result.stderr, file=sys.stderr) + sys.exit(1) +`; + } + + /** + * Extract changelog content from Claude output + */ + private extractChangelog(output: string): string { + // Claude output should be the changelog directly + // Clean up any potential wrapper text + let changelog = output.trim(); + + // Remove any "Here's the changelog:" or similar prefixes + const prefixes = [ + /^Here['']s the changelog[:\s]*/i, + /^The changelog[:\s]*/i, + /^Changelog[:\s]*/i + ]; + + for (const prefix of prefixes) { + changelog = changelog.replace(prefix, ''); + } + + return changelog.trim(); + } + + /** + * Save changelog to file + */ + saveChangelog( + projectPath: string, + request: ChangelogSaveRequest + ): ChangelogSaveResult { + const filePath = request.filePath + ? path.join(projectPath, request.filePath) + : path.join(projectPath, DEFAULT_CHANGELOG_PATH); + + let finalContent = request.content; + + if (request.mode === 'prepend' && existsSync(filePath)) { + const existing = readFileSync(filePath, 'utf-8'); + // Add separator between new and existing content + finalContent = `${request.content}\n\n${existing}`; + } else if (request.mode === 'append' && existsSync(filePath)) { + const existing = readFileSync(filePath, 'utf-8'); + finalContent = `${existing}\n\n${request.content}`; + } + + writeFileSync(filePath, finalContent, 'utf-8'); + + return { + filePath, + bytesWritten: Buffer.byteLength(finalContent, 'utf-8') + }; + } + + /** + * Read existing changelog file + */ + readExistingChangelog(projectPath: string): ExistingChangelog { + const filePath = path.join(projectPath, DEFAULT_CHANGELOG_PATH); + + if (!existsSync(filePath)) { + return { exists: false }; + } + + try { + const content = readFileSync(filePath, 'utf-8'); + + // Try to extract last version using common patterns + const versionPatterns = [ + /##\s*\[(\d+\.\d+\.\d+)\]/, // Keep-a-changelog format + /v(\d+\.\d+\.\d+)/, // v1.2.3 format + /Version\s+(\d+\.\d+\.\d+)/i // Version 1.2.3 format + ]; + + let lastVersion: string | undefined; + for (const pattern of versionPatterns) { + const match = content.match(pattern); + if (match) { + lastVersion = match[1]; + break; + } + } + + return { + exists: true, + content, + lastVersion + }; + } catch (error) { + return { + exists: true, + error: error instanceof Error ? error.message : 'Failed to read changelog' + }; + } + } + + /** + * Suggest next version based on task types + */ + suggestVersion(specs: TaskSpecContent[], currentVersion?: string): string { + // Default starting version + if (!currentVersion) { + return '1.0.0'; + } + + const parts = currentVersion.split('.').map(Number); + if (parts.length !== 3 || parts.some(isNaN)) { + return '1.0.0'; + } + + let [major, minor, patch] = parts; + + // Analyze specs for version increment decision + let hasBreakingChanges = false; + let hasNewFeatures = false; + + for (const spec of specs) { + const content = (spec.spec || '').toLowerCase(); + + if (content.includes('breaking change') || content.includes('breaking:')) { + hasBreakingChanges = true; + } + + if (spec.implementationPlan?.workflow_type === 'new_feature' || + content.includes('new feature') || + content.includes('## added')) { + hasNewFeatures = true; + } + } + + if (hasBreakingChanges) { + return `${major + 1}.0.0`; + } else if (hasNewFeatures) { + return `${major}.${minor + 1}.0`; + } else { + return `${major}.${minor}.${patch + 1}`; + } + } + + /** + * Cancel ongoing generation + */ + cancelGeneration(projectId: string): boolean { + const process = this.generationProcesses.get(projectId); + if (process) { + process.kill('SIGTERM'); + this.generationProcesses.delete(projectId); + return true; + } + return false; + } + + /** + * Emit progress update + */ + private emitProgress(projectId: string, progress: ChangelogGenerationProgress): void { + this.emit('generation-progress', projectId, progress); + } + + /** + * Emit error + */ + private emitError(projectId: string, error: string): void { + this.emit('generation-progress', projectId, { + stage: 'error', + progress: 0, + message: error, + error + }); + this.emit('generation-error', projectId, error); + } +} + +// Export singleton instance +export const changelogService = new ChangelogService(); diff --git a/auto-build-ui/src/main/file-watcher.ts b/auto-claude-ui/src/main/file-watcher.ts similarity index 100% rename from auto-build-ui/src/main/file-watcher.ts rename to auto-claude-ui/src/main/file-watcher.ts diff --git a/auto-build-ui/src/main/index.ts b/auto-claude-ui/src/main/index.ts similarity index 100% rename from auto-build-ui/src/main/index.ts rename to auto-claude-ui/src/main/index.ts diff --git a/auto-build-ui/src/main/ipc-handlers.ts b/auto-claude-ui/src/main/ipc-handlers.ts similarity index 95% rename from auto-build-ui/src/main/ipc-handlers.ts rename to auto-claude-ui/src/main/ipc-handlers.ts index f6b62ef7..b7afb4d0 100644 --- a/auto-build-ui/src/main/ipc-handlers.ts +++ b/auto-claude-ui/src/main/ipc-handlers.ts @@ -58,7 +58,8 @@ import { downloadAndApplyUpdate, getBundledVersion, getEffectiveSourcePath -} from './auto-build-updater'; +} from './auto-claude-updater'; +import { changelogService } from './changelog-service'; import type { AutoBuildSourceUpdateProgress } from '../shared/types'; /** @@ -131,22 +132,22 @@ export function setupIpcHandlers( const settingsPath = path.join(app.getPath('userData'), 'settings.json'); /** - * Auto-detect the auto-build source path relative to the app location - * In dev: auto-build-ui/../auto-build + * Auto-detect the auto-claude source path relative to the app location + * In dev: auto-claude-ui/../auto-claude * In prod: Could be bundled or configured */ const detectAutoBuildSourcePath = (): string | null => { // Try relative to app directory (works in dev and if repo structure is maintained) // __dirname in main process points to out/main in dev const possiblePaths = [ - // Dev mode: from out/main -> ../../../auto-build (sibling to auto-build-ui) - path.resolve(__dirname, '..', '..', '..', 'auto-build'), + // Dev mode: from out/main -> ../../../auto-claude (sibling to auto-claude-ui) + path.resolve(__dirname, '..', '..', '..', 'auto-claude'), // Alternative: from app root (useful in some packaged scenarios) - path.resolve(app.getAppPath(), '..', 'auto-build'), + path.resolve(app.getAppPath(), '..', 'auto-claude'), // If running from repo root - path.resolve(process.cwd(), 'auto-build'), + path.resolve(process.cwd(), 'auto-claude'), // Try one more level up (in case of different build output structure) - path.resolve(__dirname, '..', '..', 'auto-build') + path.resolve(__dirname, '..', '..', 'auto-claude') ]; console.log('[Auto-Build] Detecting source path, checking:', possiblePaths); @@ -162,7 +163,7 @@ export function setupIpcHandlers( }; /** - * Get the configured auto-build source path from settings, or auto-detect + * Get the configured auto-claude source path from settings, or auto-detect */ const getAutoBuildSourcePath = (): string | null => { // First check if manually configured @@ -203,7 +204,7 @@ export function setupIpcHandlers( if (result.success) { // Update project's autoBuildPath - projectStore.updateAutoBuildPath(projectId, '.auto-build'); + projectStore.updateAutoBuildPath(projectId, '.auto-claude'); } return { success: result.success, data: result, error: result.error }; @@ -3180,4 +3181,121 @@ ${issue.body || 'No description provided.'} mainWindow.webContents.send(IPC_CHANNELS.IDEATION_ERROR, projectId, error); } }); + + // ============================================ + // Changelog Operations + // ============================================ + + ipcMain.handle( + IPC_CHANNELS.CHANGELOG_GET_DONE_TASKS, + async (_, projectId: string): Promise> => { + const project = projectStore.getProject(projectId); + if (!project) { + return { success: false, error: 'Project not found' }; + } + + const tasks = projectStore.getTasks(projectId); + const doneTasks = changelogService.getCompletedTasks(project.path, tasks); + + return { success: true, data: doneTasks }; + } + ); + + ipcMain.handle( + IPC_CHANNELS.CHANGELOG_LOAD_TASK_SPECS, + async (_, projectId: string, taskIds: string[]): Promise> => { + const project = projectStore.getProject(projectId); + if (!project) { + return { success: false, error: 'Project not found' }; + } + + const tasks = projectStore.getTasks(projectId); + const specs = await changelogService.loadTaskSpecs(project.path, taskIds, tasks); + + return { success: true, data: specs }; + } + ); + + ipcMain.on( + IPC_CHANNELS.CHANGELOG_GENERATE, + async (_, request: import('../shared/types').ChangelogGenerationRequest) => { + const mainWindow = getMainWindow(); + if (!mainWindow) return; + + const project = projectStore.getProject(request.projectId); + if (!project) { + mainWindow.webContents.send( + IPC_CHANNELS.CHANGELOG_GENERATION_ERROR, + request.projectId, + 'Project not found' + ); + return; + } + + // Load specs for selected tasks + const tasks = projectStore.getTasks(request.projectId); + const specs = await changelogService.loadTaskSpecs(project.path, request.taskIds, tasks); + + // Start generation + changelogService.generateChangelog(request.projectId, project.path, request, specs); + } + ); + + ipcMain.handle( + IPC_CHANNELS.CHANGELOG_SAVE, + async (_, request: import('../shared/types').ChangelogSaveRequest): Promise> => { + const project = projectStore.getProject(request.projectId); + if (!project) { + return { success: false, error: 'Project not found' }; + } + + try { + const result = changelogService.saveChangelog(project.path, request); + return { success: true, data: result }; + } catch (error) { + return { + success: false, + error: error instanceof Error ? error.message : 'Failed to save changelog' + }; + } + } + ); + + ipcMain.handle( + IPC_CHANNELS.CHANGELOG_READ_EXISTING, + async (_, projectId: string): Promise> => { + const project = projectStore.getProject(projectId); + if (!project) { + return { success: false, error: 'Project not found' }; + } + + const result = changelogService.readExistingChangelog(project.path); + return { success: true, data: result }; + } + ); + + // ============================================ + // Changelog Agent Events → Renderer + // ============================================ + + changelogService.on('generation-progress', (projectId: string, progress: import('../shared/types').ChangelogGenerationProgress) => { + const mainWindow = getMainWindow(); + if (mainWindow) { + mainWindow.webContents.send(IPC_CHANNELS.CHANGELOG_GENERATION_PROGRESS, projectId, progress); + } + }); + + changelogService.on('generation-complete', (projectId: string, result: import('../shared/types').ChangelogGenerationResult) => { + const mainWindow = getMainWindow(); + if (mainWindow) { + mainWindow.webContents.send(IPC_CHANNELS.CHANGELOG_GENERATION_COMPLETE, projectId, result); + } + }); + + changelogService.on('generation-error', (projectId: string, error: string) => { + const mainWindow = getMainWindow(); + if (mainWindow) { + mainWindow.webContents.send(IPC_CHANNELS.CHANGELOG_GENERATION_ERROR, projectId, error); + } + }); } diff --git a/auto-build-ui/src/main/project-initializer.ts b/auto-claude-ui/src/main/project-initializer.ts similarity index 86% rename from auto-build-ui/src/main/project-initializer.ts rename to auto-claude-ui/src/main/project-initializer.ts index a2bd9343..fb5a639b 100644 --- a/auto-build-ui/src/main/project-initializer.ts +++ b/auto-claude-ui/src/main/project-initializer.ts @@ -3,7 +3,7 @@ import path from 'path'; import crypto from 'crypto'; /** - * Files and directories to exclude when copying auto-build + * Files and directories to exclude when copying auto-claude */ const EXCLUDE_PATTERNS = [ '__pycache__', @@ -23,7 +23,7 @@ const PRESERVE_ON_UPDATE = [ ]; /** - * Version metadata stored in .auto-build/.version.json + * Version metadata stored in .auto-claude/.version.json */ export interface VersionMetadata { version: string; @@ -151,7 +151,7 @@ function calculateDirectoryHash(dirPath: string): string { } /** - * Read version from VERSION file in auto-build source + * Read version from VERSION file in auto-claude source */ function readSourceVersion(sourcePath: string): string { const versionFile = path.join(sourcePath, 'VERSION'); @@ -211,16 +211,16 @@ export function hasCustomEnv(autoBuildPath: string): boolean { /** * Check version status for a project - * If an existing auto-build folder is found without version metadata, + * If an existing auto-claude folder is found without version metadata, * create a retroactive .version.json to enable future update tracking. */ export function checkVersion( projectPath: string, sourcePath: string ): VersionCheckResult { - // Check for both .auto-build and auto-build folders - const dotAutoBuildPath = path.join(projectPath, '.auto-build'); - const autoBuildPath = path.join(projectPath, 'auto-build'); + // Check for both .auto-claude and auto-claude folders + const dotAutoBuildPath = path.join(projectPath, '.auto-claude'); + const autoBuildPath = path.join(projectPath, 'auto-claude'); let installedPath: string | null = null; if (existsSync(dotAutoBuildPath)) { @@ -279,17 +279,24 @@ export function checkVersion( const sourceVersion = readSourceVersion(sourcePath); const sourceHash = calculateDirectoryHash(sourcePath); + // Only show update available if hash differs AND version is actually different + // This prevents false positives when versions match but hashes differ due to + // metadata files or other non-functional differences + const hashDiffers = metadata.sourceHash !== sourceHash; + const versionDiffers = metadata.version !== sourceVersion; + const updateAvailable = hashDiffers && versionDiffers; + return { isInitialized: true, currentVersion: metadata.version, sourceVersion, - updateAvailable: metadata.sourceHash !== sourceHash, + updateAvailable, sourcePath: installedPath }; } /** - * Initialize auto-build in a project + * Initialize auto-claude in a project */ export function initializeProject( projectPath: string, @@ -312,18 +319,18 @@ export function initializeProject( } // Check if already initialized - const dotAutoBuildPath = path.join(projectPath, '.auto-build'); - const autoBuildPath = path.join(projectPath, 'auto-build'); + const dotAutoBuildPath = path.join(projectPath, '.auto-claude'); + const autoBuildPath = path.join(projectPath, 'auto-claude'); if (existsSync(dotAutoBuildPath) || existsSync(autoBuildPath)) { return { success: false, - error: 'Project already has auto-build initialized' + error: 'Project already has auto-claude initialized' }; } try { - // Copy files to .auto-build + // Copy files to .auto-claude copyDirectoryRecursive(sourcePath, dotAutoBuildPath, false); // Create specs directory @@ -369,7 +376,7 @@ export function initializeProject( } /** - * Update auto-build in a project + * Update auto-claude in a project */ export function updateProject( projectPath: string, @@ -383,9 +390,9 @@ export function updateProject( }; } - // Find existing auto-build folder - const dotAutoBuildPath = path.join(projectPath, '.auto-build'); - const autoBuildPath = path.join(projectPath, 'auto-build'); + // Find existing auto-claude folder + const dotAutoBuildPath = path.join(projectPath, '.auto-claude'); + const autoBuildPath = path.join(projectPath, 'auto-claude'); let targetPath: string; if (existsSync(dotAutoBuildPath)) { @@ -395,7 +402,7 @@ export function updateProject( } else { return { success: false, - error: 'No auto-build folder found to update' + error: 'No auto-claude folder found to update' }; } @@ -431,16 +438,16 @@ export function updateProject( } /** - * Get the auto-build folder path for a project (either .auto-build or auto-build) + * Get the auto-claude folder path for a project (either .auto-claude or auto-claude) */ export function getAutoBuildPath(projectPath: string): string | null { - const dotAutoBuildPath = path.join(projectPath, '.auto-build'); - const autoBuildPath = path.join(projectPath, 'auto-build'); + const dotAutoBuildPath = path.join(projectPath, '.auto-claude'); + const autoBuildPath = path.join(projectPath, 'auto-claude'); if (existsSync(dotAutoBuildPath)) { - return '.auto-build'; + return '.auto-claude'; } else if (existsSync(autoBuildPath)) { - return 'auto-build'; + return 'auto-claude'; } return null; } diff --git a/auto-build-ui/src/main/project-store.ts b/auto-claude-ui/src/main/project-store.ts similarity index 98% rename from auto-build-ui/src/main/project-store.ts rename to auto-claude-ui/src/main/project-store.ts index 8f67d385..35e95416 100644 --- a/auto-build-ui/src/main/project-store.ts +++ b/auto-claude-ui/src/main/project-store.ts @@ -74,7 +74,7 @@ export class ProjectStore { // Derive name from path if not provided const projectName = name || path.basename(projectPath); - // Determine auto-build path (supports both 'auto-build' and '.auto-build') + // Determine auto-claude path (supports both 'auto-claude' and '.auto-claude') const autoBuildPath = getAutoBuildPath(projectPath) || ''; const project: Project = { @@ -157,7 +157,7 @@ export class ProjectStore { if (!project) return []; // Use project's autoBuildPath if set, otherwise fallback to default - const autoBuildDir = project.autoBuildPath || 'auto-build'; + const autoBuildDir = project.autoBuildPath || 'auto-claude'; const specsDir = path.join(project.path, autoBuildDir, 'specs'); if (!existsSync(specsDir)) return []; diff --git a/auto-build-ui/src/main/terminal-manager.ts b/auto-claude-ui/src/main/terminal-manager.ts similarity index 100% rename from auto-build-ui/src/main/terminal-manager.ts rename to auto-claude-ui/src/main/terminal-manager.ts diff --git a/auto-build-ui/src/preload/index.ts b/auto-claude-ui/src/preload/index.ts similarity index 87% rename from auto-build-ui/src/preload/index.ts rename to auto-claude-ui/src/preload/index.ts index 29cc5cce..0bfd94b0 100644 --- a/auto-build-ui/src/preload/index.ts +++ b/auto-claude-ui/src/preload/index.ts @@ -39,7 +39,15 @@ import type { IdeationStatus, IdeationGenerationStatus, AutoBuildSourceUpdateCheck, - AutoBuildSourceUpdateProgress + AutoBuildSourceUpdateProgress, + ChangelogTask, + TaskSpecContent, + ChangelogGenerationRequest, + ChangelogGenerationResult, + ChangelogSaveRequest, + ChangelogSaveResult, + ChangelogGenerationProgress, + ExistingChangelog } from '../shared/types'; // Expose a secure API to the renderer process @@ -585,6 +593,77 @@ const electronAPI: ElectronAPI = { return () => { ipcRenderer.removeListener(IPC_CHANNELS.AUTOBUILD_SOURCE_PROGRESS, handler); }; + }, + + // ============================================ + // Changelog Operations + // ============================================ + + getChangelogDoneTasks: (projectId: string): Promise> => + ipcRenderer.invoke(IPC_CHANNELS.CHANGELOG_GET_DONE_TASKS, projectId), + + loadTaskSpecs: (projectId: string, taskIds: string[]): Promise> => + ipcRenderer.invoke(IPC_CHANNELS.CHANGELOG_LOAD_TASK_SPECS, projectId, taskIds), + + generateChangelog: (request: ChangelogGenerationRequest): void => + ipcRenderer.send(IPC_CHANNELS.CHANGELOG_GENERATE, request), + + saveChangelog: (request: ChangelogSaveRequest): Promise> => + ipcRenderer.invoke(IPC_CHANNELS.CHANGELOG_SAVE, request), + + readExistingChangelog: (projectId: string): Promise> => + ipcRenderer.invoke(IPC_CHANNELS.CHANGELOG_READ_EXISTING, projectId), + + // ============================================ + // Changelog Event Listeners + // ============================================ + + onChangelogGenerationProgress: ( + callback: (projectId: string, progress: ChangelogGenerationProgress) => void + ): (() => void) => { + const handler = ( + _event: Electron.IpcRendererEvent, + projectId: string, + progress: ChangelogGenerationProgress + ): void => { + callback(projectId, progress); + }; + ipcRenderer.on(IPC_CHANNELS.CHANGELOG_GENERATION_PROGRESS, handler); + return () => { + ipcRenderer.removeListener(IPC_CHANNELS.CHANGELOG_GENERATION_PROGRESS, handler); + }; + }, + + onChangelogGenerationComplete: ( + callback: (projectId: string, result: ChangelogGenerationResult) => void + ): (() => void) => { + const handler = ( + _event: Electron.IpcRendererEvent, + projectId: string, + result: ChangelogGenerationResult + ): void => { + callback(projectId, result); + }; + ipcRenderer.on(IPC_CHANNELS.CHANGELOG_GENERATION_COMPLETE, handler); + return () => { + ipcRenderer.removeListener(IPC_CHANNELS.CHANGELOG_GENERATION_COMPLETE, handler); + }; + }, + + onChangelogGenerationError: ( + callback: (projectId: string, error: string) => void + ): (() => void) => { + const handler = ( + _event: Electron.IpcRendererEvent, + projectId: string, + error: string + ): void => { + callback(projectId, error); + }; + ipcRenderer.on(IPC_CHANNELS.CHANGELOG_GENERATION_ERROR, handler); + return () => { + ipcRenderer.removeListener(IPC_CHANNELS.CHANGELOG_GENERATION_ERROR, handler); + }; } }; diff --git a/auto-build-ui/src/renderer/App.tsx b/auto-claude-ui/src/renderer/App.tsx similarity index 98% rename from auto-build-ui/src/renderer/App.tsx rename to auto-claude-ui/src/renderer/App.tsx index ca1978d1..1d223731 100644 --- a/auto-build-ui/src/renderer/App.tsx +++ b/auto-claude-ui/src/renderer/App.tsx @@ -18,6 +18,7 @@ import { Roadmap } from './components/Roadmap'; import { Context } from './components/Context'; import { Ideation } from './components/Ideation'; import { GitHubIssues } from './components/GitHubIssues'; +import { Changelog } from './components/Changelog'; import { useProjectStore, loadProjects } from './stores/project-store'; import { useTaskStore, loadTasks } from './stores/task-store'; import { useSettingsStore, loadSettings } from './stores/settings-store'; @@ -193,6 +194,9 @@ export function App() { {activeView === 'github-issues' && selectedProjectId && ( setIsProjectSettingsOpen(true)} /> )} + {activeView === 'changelog' && selectedProjectId && ( + + )} {activeView === 'agent-tools' && (
diff --git a/auto-build-ui/src/renderer/__tests__/task-store.test.ts b/auto-claude-ui/src/renderer/__tests__/task-store.test.ts similarity index 100% rename from auto-build-ui/src/renderer/__tests__/task-store.test.ts rename to auto-claude-ui/src/renderer/__tests__/task-store.test.ts diff --git a/auto-build-ui/src/renderer/components/AppSettings.tsx b/auto-claude-ui/src/renderer/components/AppSettings.tsx similarity index 99% rename from auto-build-ui/src/renderer/components/AppSettings.tsx rename to auto-claude-ui/src/renderer/components/AppSettings.tsx index f5f94ad2..afbdc39b 100644 --- a/auto-build-ui/src/renderer/components/AppSettings.tsx +++ b/auto-claude-ui/src/renderer/components/AppSettings.tsx @@ -65,7 +65,7 @@ export function AppSettingsDialog({ open, onOpenChange }: AppSettingsDialogProps loadSettings(); window.electronAPI.getAppVersion().then(setVersion); - // Check for auto-build source updates + // Check for auto-claude source updates checkForSourceUpdates(); }, []); @@ -284,14 +284,14 @@ export function AppSettingsDialog({ open, onOpenChange }: AppSettingsDialogProps setSettings({ ...settings, autoBuildPath: e.target.value }) } />

- Relative path to auto-build directory in projects + Relative path to auto-claude directory in projects

diff --git a/auto-claude-ui/src/renderer/components/Changelog.tsx b/auto-claude-ui/src/renderer/components/Changelog.tsx new file mode 100644 index 00000000..b8e7a556 --- /dev/null +++ b/auto-claude-ui/src/renderer/components/Changelog.tsx @@ -0,0 +1,552 @@ +import { useEffect, useState } from 'react'; +import { + FileText, + RefreshCw, + Copy, + Save, + AlertCircle, + CheckCircle, + Sparkles, + ChevronDown, + ChevronUp +} from 'lucide-react'; +import { Button } from './ui/button'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from './ui/card'; +import { Input } from './ui/input'; +import { Label } from './ui/label'; +import { Textarea } from './ui/textarea'; +import { Checkbox } from './ui/checkbox'; +import { Badge } from './ui/badge'; +import { Progress } from './ui/progress'; +import { ScrollArea } from './ui/scroll-area'; +import { Separator } from './ui/separator'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue +} from './ui/select'; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger +} from './ui/tooltip'; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger +} from './ui/collapsible'; +import { useProjectStore } from '../stores/project-store'; +import { + useChangelogStore, + loadChangelogData, + generateChangelog, + saveChangelog, + copyChangelogToClipboard +} from '../stores/changelog-store'; +import { + CHANGELOG_FORMAT_LABELS, + CHANGELOG_FORMAT_DESCRIPTIONS, + CHANGELOG_AUDIENCE_LABELS, + CHANGELOG_AUDIENCE_DESCRIPTIONS, + CHANGELOG_STAGE_LABELS +} from '../../shared/constants'; +import type { + ChangelogFormat, + ChangelogAudience, + ChangelogTask +} from '../../shared/types'; +import { cn } from '../lib/utils'; + +export function Changelog() { + const selectedProjectId = useProjectStore((state) => state.selectedProjectId); + + const doneTasks = useChangelogStore((state) => state.doneTasks); + const selectedTaskIds = useChangelogStore((state) => state.selectedTaskIds); + const existingChangelog = useChangelogStore((state) => state.existingChangelog); + const version = useChangelogStore((state) => state.version); + const date = useChangelogStore((state) => state.date); + const format = useChangelogStore((state) => state.format); + const audience = useChangelogStore((state) => state.audience); + const customInstructions = useChangelogStore((state) => state.customInstructions); + const generationProgress = useChangelogStore((state) => state.generationProgress); + const generatedChangelog = useChangelogStore((state) => state.generatedChangelog); + const isGenerating = useChangelogStore((state) => state.isGenerating); + const error = useChangelogStore((state) => state.error); + + const toggleTaskSelection = useChangelogStore((state) => state.toggleTaskSelection); + const selectAllTasks = useChangelogStore((state) => state.selectAllTasks); + const deselectAllTasks = useChangelogStore((state) => state.deselectAllTasks); + const setVersion = useChangelogStore((state) => state.setVersion); + const setDate = useChangelogStore((state) => state.setDate); + const setFormat = useChangelogStore((state) => state.setFormat); + const setAudience = useChangelogStore((state) => state.setAudience); + const setCustomInstructions = useChangelogStore((state) => state.setCustomInstructions); + const updateGeneratedChangelog = useChangelogStore((state) => state.updateGeneratedChangelog); + const setError = useChangelogStore((state) => state.setError); + const setIsGenerating = useChangelogStore((state) => state.setIsGenerating); + const setGenerationProgress = useChangelogStore((state) => state.setGenerationProgress); + + const [showAdvanced, setShowAdvanced] = useState(false); + const [saveSuccess, setSaveSuccess] = useState(false); + const [copySuccess, setCopySuccess] = useState(false); + + // Load data when project changes + useEffect(() => { + if (selectedProjectId) { + loadChangelogData(selectedProjectId); + } + }, [selectedProjectId]); + + // Set up event listeners for generation + useEffect(() => { + const cleanupProgress = window.electronAPI.onChangelogGenerationProgress( + (projectId, progress) => { + if (projectId === selectedProjectId) { + setGenerationProgress(progress); + } + } + ); + + const cleanupComplete = window.electronAPI.onChangelogGenerationComplete( + (projectId, result) => { + if (projectId === selectedProjectId) { + setIsGenerating(false); + if (result.success) { + updateGeneratedChangelog(result.changelog); + setGenerationProgress({ + stage: 'complete', + progress: 100, + message: 'Changelog generated successfully!' + }); + } else { + setError(result.error || 'Generation failed'); + } + } + } + ); + + const cleanupError = window.electronAPI.onChangelogGenerationError( + (projectId, errorMsg) => { + if (projectId === selectedProjectId) { + setIsGenerating(false); + setError(errorMsg); + setGenerationProgress({ + stage: 'error', + progress: 0, + message: errorMsg, + error: errorMsg + }); + } + } + ); + + return () => { + cleanupProgress(); + cleanupComplete(); + cleanupError(); + }; + }, [selectedProjectId]); + + const handleGenerate = () => { + if (selectedProjectId) { + generateChangelog(selectedProjectId); + } + }; + + const handleSave = async () => { + if (selectedProjectId) { + const success = await saveChangelog(selectedProjectId, 'prepend'); + if (success) { + setSaveSuccess(true); + setTimeout(() => setSaveSuccess(false), 2000); + } + } + }; + + const handleCopy = () => { + const success = copyChangelogToClipboard(); + if (success) { + setCopySuccess(true); + setTimeout(() => setCopySuccess(false), 2000); + } + }; + + const canGenerate = selectedTaskIds.length > 0 && !isGenerating; + const canSave = generatedChangelog.length > 0 && !isGenerating; + + if (!selectedProjectId) { + return ( +
+
+ +

No Project Selected

+

+ Select a project from the sidebar to generate changelogs. +

+
+
+ ); + } + + return ( + +
+ {/* Header */} +
+
+

Changelog Generator

+

+ Generate release notes from completed tasks +

+
+ +
+ +
+ {/* Left Panel - Configuration */} +
+
+ {/* Version & Date */} + + + Release Info + + +
+
+ + setVersion(e.target.value)} + placeholder="1.0.0" + /> +
+
+ + setDate(e.target.value)} + /> +
+
+ {existingChangelog?.lastVersion && ( +

+ Previous version: {existingChangelog.lastVersion} +

+ )} +
+
+ + {/* Format & Audience */} + + + Output Style + + +
+ + +
+ +
+ + +
+
+
+ + {/* Task Selection */} + + +
+ + Tasks to Include ({selectedTaskIds.length}/{doneTasks.length}) + +
+ + +
+
+
+ + + {doneTasks.length === 0 ? ( +
+ No completed tasks found. +
+ Complete tasks in the Kanban board to include them here. +
+ ) : ( +
+ {doneTasks.map((task) => ( + toggleTaskSelection(task.id)} + /> + ))} +
+ )} +
+
+
+ + {/* Advanced Options */} + + + + + + + +
+ +