Rename to Auto Claude
This commit is contained in:
@@ -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"
|
||||
}
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -23,7 +23,7 @@ export async function launchElectronApp(): Promise<ElectronTestContext> {
|
||||
...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'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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'
|
||||
@@ -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"
|
||||
@@ -21,6 +21,12 @@ importers:
|
||||
'@dnd-kit/utilities':
|
||||
specifier: ^3.2.2
|
||||
version: 3.2.2([email protected])
|
||||
'@radix-ui/react-checkbox':
|
||||
specifier: ^1.1.4
|
||||
version: 1.3.3(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])
|
||||
'@radix-ui/react-collapsible':
|
||||
specifier: ^1.1.3
|
||||
version: 1.1.12(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])
|
||||
'@radix-ui/react-dialog':
|
||||
specifier: ^1.1.15
|
||||
version: 1.1.15(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])
|
||||
@@ -660,6 +666,32 @@ packages:
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/[email protected]':
|
||||
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/[email protected]':
|
||||
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/[email protected]':
|
||||
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/[email protected])
|
||||
|
||||
'@radix-ui/[email protected](@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/[email protected])([email protected])
|
||||
'@radix-ui/react-context': 1.1.2(@types/[email protected])([email protected])
|
||||
'@radix-ui/react-presence': 1.1.5(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/[email protected])([email protected])
|
||||
'@radix-ui/react-use-previous': 1.1.1(@types/[email protected])([email protected])
|
||||
'@radix-ui/react-use-size': 1.1.1(@types/[email protected])([email protected])
|
||||
react: 19.2.1
|
||||
react-dom: 19.2.1([email protected])
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.7
|
||||
'@types/react-dom': 19.2.3(@types/[email protected])
|
||||
|
||||
'@radix-ui/[email protected](@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/[email protected])([email protected])
|
||||
'@radix-ui/react-context': 1.1.2(@types/[email protected])([email protected])
|
||||
'@radix-ui/react-id': 1.1.1(@types/[email protected])([email protected])
|
||||
'@radix-ui/react-presence': 1.1.5(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/[email protected])([email protected])
|
||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/[email protected])([email protected])
|
||||
react: 19.2.1
|
||||
react-dom: 19.2.1([email protected])
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.7
|
||||
'@types/react-dom': 19.2.3(@types/[email protected])
|
||||
|
||||
'@radix-ui/[email protected](@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])':
|
||||
dependencies:
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/[email protected])([email protected])
|
||||
+3
-3
@@ -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")'
|
||||
);
|
||||
}
|
||||
@@ -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(() => {
|
||||
+2
-2
@@ -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',
|
||||
+9
-9
@@ -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 = {
|
||||
+20
-20
@@ -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<string, AgentProcess> = 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<string, string> {
|
||||
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'
|
||||
}
|
||||
});
|
||||
+21
-21
@@ -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)) {
|
||||
@@ -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<string, ReturnType<typeof spawn>> = 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<string, string> {
|
||||
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<string, string> = {};
|
||||
|
||||
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<TaskSpecContent[]> {
|
||||
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();
|
||||
@@ -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<IPCResult<import('../shared/types').ChangelogTask[]>> => {
|
||||
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<IPCResult<import('../shared/types').TaskSpecContent[]>> => {
|
||||
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<IPCResult<import('../shared/types').ChangelogSaveResult>> => {
|
||||
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<IPCResult<import('../shared/types').ExistingChangelog>> => {
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
+30
-23
@@ -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;
|
||||
}
|
||||
@@ -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 [];
|
||||
|
||||
@@ -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<IPCResult<ChangelogTask[]>> =>
|
||||
ipcRenderer.invoke(IPC_CHANNELS.CHANGELOG_GET_DONE_TASKS, projectId),
|
||||
|
||||
loadTaskSpecs: (projectId: string, taskIds: string[]): Promise<IPCResult<TaskSpecContent[]>> =>
|
||||
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<IPCResult<ChangelogSaveResult>> =>
|
||||
ipcRenderer.invoke(IPC_CHANNELS.CHANGELOG_SAVE, request),
|
||||
|
||||
readExistingChangelog: (projectId: string): Promise<IPCResult<ExistingChangelog>> =>
|
||||
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);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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 && (
|
||||
<GitHubIssues onOpenSettings={() => setIsProjectSettingsOpen(true)} />
|
||||
)}
|
||||
{activeView === 'changelog' && selectedProjectId && (
|
||||
<Changelog />
|
||||
)}
|
||||
{activeView === 'agent-tools' && (
|
||||
<div className="flex h-full items-center justify-center">
|
||||
<div className="text-center">
|
||||
+3
-3
@@ -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
|
||||
<Label htmlFor="autoBuildPath" className="text-sm font-medium text-foreground">Auto-Build Path</Label>
|
||||
<Input
|
||||
id="autoBuildPath"
|
||||
placeholder="auto-build (default)"
|
||||
placeholder="auto-claude (default)"
|
||||
value={settings.autoBuildPath || ''}
|
||||
onChange={(e) =>
|
||||
setSettings({ ...settings, autoBuildPath: e.target.value })
|
||||
}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Relative path to auto-build directory in projects
|
||||
Relative path to auto-claude directory in projects
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
@@ -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 (
|
||||
<div className="flex h-full items-center justify-center">
|
||||
<div className="text-center">
|
||||
<FileText className="mx-auto h-12 w-12 text-muted-foreground/50" />
|
||||
<h3 className="mt-4 text-lg font-medium">No Project Selected</h3>
|
||||
<p className="mt-2 text-sm text-muted-foreground">
|
||||
Select a project from the sidebar to generate changelogs.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<div className="flex h-full flex-col">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between border-b border-border px-6 py-4">
|
||||
<div>
|
||||
<h1 className="text-xl font-semibold">Changelog Generator</h1>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Generate release notes from completed tasks
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => selectedProjectId && loadChangelogData(selectedProjectId)}
|
||||
>
|
||||
<RefreshCw className="mr-2 h-4 w-4" />
|
||||
Refresh
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-1 overflow-hidden">
|
||||
{/* Left Panel - Configuration */}
|
||||
<div className="w-96 flex-shrink-0 border-r border-border overflow-y-auto">
|
||||
<div className="p-6 space-y-6">
|
||||
{/* Version & Date */}
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-sm">Release Info</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="version">Version</Label>
|
||||
<Input
|
||||
id="version"
|
||||
value={version}
|
||||
onChange={(e) => setVersion(e.target.value)}
|
||||
placeholder="1.0.0"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="date">Date</Label>
|
||||
<Input
|
||||
id="date"
|
||||
type="date"
|
||||
value={date}
|
||||
onChange={(e) => setDate(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{existingChangelog?.lastVersion && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Previous version: {existingChangelog.lastVersion}
|
||||
</p>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Format & Audience */}
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-sm">Output Style</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label>Format</Label>
|
||||
<Select
|
||||
value={format}
|
||||
onValueChange={(value) => setFormat(value as ChangelogFormat)}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{Object.entries(CHANGELOG_FORMAT_LABELS).map(([value, label]) => (
|
||||
<SelectItem key={value} value={value}>
|
||||
<div>
|
||||
<div>{label}</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{CHANGELOG_FORMAT_DESCRIPTIONS[value]}
|
||||
</div>
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>Audience</Label>
|
||||
<Select
|
||||
value={audience}
|
||||
onValueChange={(value) => setAudience(value as ChangelogAudience)}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{Object.entries(CHANGELOG_AUDIENCE_LABELS).map(([value, label]) => (
|
||||
<SelectItem key={value} value={value}>
|
||||
<div>
|
||||
<div>{label}</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{CHANGELOG_AUDIENCE_DESCRIPTIONS[value]}
|
||||
</div>
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Task Selection */}
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-sm">
|
||||
Tasks to Include ({selectedTaskIds.length}/{doneTasks.length})
|
||||
</CardTitle>
|
||||
<div className="flex gap-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={selectAllTasks}
|
||||
className="h-7 px-2 text-xs"
|
||||
>
|
||||
All
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={deselectAllTasks}
|
||||
className="h-7 px-2 text-xs"
|
||||
>
|
||||
None
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ScrollArea className="h-48">
|
||||
{doneTasks.length === 0 ? (
|
||||
<div className="text-center py-4 text-sm text-muted-foreground">
|
||||
No completed tasks found.
|
||||
<br />
|
||||
Complete tasks in the Kanban board to include them here.
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{doneTasks.map((task) => (
|
||||
<TaskItem
|
||||
key={task.id}
|
||||
task={task}
|
||||
isSelected={selectedTaskIds.includes(task.id)}
|
||||
onToggle={() => toggleTaskSelection(task.id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</ScrollArea>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Advanced Options */}
|
||||
<Collapsible open={showAdvanced} onOpenChange={setShowAdvanced}>
|
||||
<CollapsibleTrigger asChild>
|
||||
<Button variant="ghost" className="w-full justify-between">
|
||||
Advanced Options
|
||||
{showAdvanced ? (
|
||||
<ChevronUp className="h-4 w-4" />
|
||||
) : (
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent className="pt-2">
|
||||
<Card>
|
||||
<CardContent className="pt-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="instructions">Custom Instructions</Label>
|
||||
<Textarea
|
||||
id="instructions"
|
||||
value={customInstructions}
|
||||
onChange={(e) => setCustomInstructions(e.target.value)}
|
||||
placeholder="Add any special instructions for the AI..."
|
||||
rows={3}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Optional. Guide the AI on tone, specific details to include, etc.
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
|
||||
{/* Generate Button */}
|
||||
<Button
|
||||
className="w-full"
|
||||
onClick={handleGenerate}
|
||||
disabled={!canGenerate}
|
||||
>
|
||||
{isGenerating ? (
|
||||
<>
|
||||
<RefreshCw className="mr-2 h-4 w-4 animate-spin" />
|
||||
Generating...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Sparkles className="mr-2 h-4 w-4" />
|
||||
Generate Changelog
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
{/* Progress */}
|
||||
{generationProgress && isGenerating && (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span>{CHANGELOG_STAGE_LABELS[generationProgress.stage]}</span>
|
||||
<span>{generationProgress.progress}%</span>
|
||||
</div>
|
||||
<Progress value={generationProgress.progress} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Error */}
|
||||
{error && (
|
||||
<div className="rounded-lg border border-destructive/50 bg-destructive/10 p-3 text-sm">
|
||||
<div className="flex items-start gap-2">
|
||||
<AlertCircle className="h-4 w-4 text-destructive mt-0.5 shrink-0" />
|
||||
<span className="text-destructive">{error}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Panel - Preview */}
|
||||
<div className="flex-1 flex flex-col overflow-hidden">
|
||||
{/* Preview Header */}
|
||||
<div className="flex items-center justify-between border-b border-border px-6 py-3">
|
||||
<h2 className="font-medium">Preview</h2>
|
||||
<div className="flex items-center gap-2">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleCopy}
|
||||
disabled={!canSave}
|
||||
>
|
||||
{copySuccess ? (
|
||||
<CheckCircle className="mr-2 h-4 w-4 text-success" />
|
||||
) : (
|
||||
<Copy className="mr-2 h-4 w-4" />
|
||||
)}
|
||||
{copySuccess ? 'Copied!' : 'Copy'}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Copy to clipboard</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="default"
|
||||
size="sm"
|
||||
onClick={handleSave}
|
||||
disabled={!canSave}
|
||||
>
|
||||
{saveSuccess ? (
|
||||
<CheckCircle className="mr-2 h-4 w-4" />
|
||||
) : (
|
||||
<Save className="mr-2 h-4 w-4" />
|
||||
)}
|
||||
{saveSuccess ? 'Saved!' : 'Save to CHANGELOG.md'}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
Prepend to CHANGELOG.md in project root
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Preview Content */}
|
||||
<div className="flex-1 overflow-hidden p-6">
|
||||
{generatedChangelog ? (
|
||||
<Textarea
|
||||
className="h-full w-full resize-none font-mono text-sm"
|
||||
value={generatedChangelog}
|
||||
onChange={(e) => updateGeneratedChangelog(e.target.value)}
|
||||
placeholder="Generated changelog will appear here..."
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-full items-center justify-center">
|
||||
<div className="text-center">
|
||||
<FileText className="mx-auto h-12 w-12 text-muted-foreground/30" />
|
||||
<p className="mt-4 text-sm text-muted-foreground">
|
||||
Select tasks and click "Generate Changelog" to create release notes.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
||||
|
||||
interface TaskItemProps {
|
||||
task: ChangelogTask;
|
||||
isSelected: boolean;
|
||||
onToggle: () => void;
|
||||
}
|
||||
|
||||
function TaskItem({ task, isSelected, onToggle }: TaskItemProps) {
|
||||
const completedDate = new Date(task.completedAt).toLocaleDateString();
|
||||
|
||||
return (
|
||||
<label
|
||||
className={cn(
|
||||
'flex items-start gap-3 rounded-lg border p-3 cursor-pointer transition-colors',
|
||||
isSelected
|
||||
? 'border-primary bg-primary/5'
|
||||
: 'border-border hover:border-primary/50'
|
||||
)}
|
||||
>
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
onCheckedChange={onToggle}
|
||||
className="mt-0.5"
|
||||
/>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium text-sm truncate">{task.title}</span>
|
||||
{task.hasSpecs && (
|
||||
<Badge variant="secondary" className="text-xs shrink-0">
|
||||
Has Specs
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
{task.description && (
|
||||
<p className="text-xs text-muted-foreground truncate mt-1">
|
||||
{task.description}
|
||||
</p>
|
||||
)}
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
Completed: {completedDate}
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
);
|
||||
}
|
||||
+6
-4
@@ -15,7 +15,8 @@ import {
|
||||
AlertCircle,
|
||||
Download,
|
||||
RefreshCw,
|
||||
Github
|
||||
Github,
|
||||
FileText
|
||||
} from 'lucide-react';
|
||||
import { Button } from './ui/button';
|
||||
import { ScrollArea } from './ui/scroll-area';
|
||||
@@ -53,7 +54,7 @@ import {
|
||||
import { useSettingsStore, saveSettings } from '../stores/settings-store';
|
||||
import type { Project, AutoBuildVersionInfo } from '../../shared/types';
|
||||
|
||||
export type SidebarView = 'kanban' | 'terminals' | 'roadmap' | 'context' | 'ideation' | 'agent-tools' | 'github-issues';
|
||||
export type SidebarView = 'kanban' | 'terminals' | 'roadmap' | 'context' | 'ideation' | 'agent-tools' | 'github-issues' | 'changelog';
|
||||
|
||||
interface SidebarProps {
|
||||
onSettingsClick: () => void;
|
||||
@@ -77,6 +78,7 @@ const projectNavItems: NavItem[] = [
|
||||
const toolsNavItems: NavItem[] = [
|
||||
{ id: 'roadmap', label: 'Roadmap', icon: Map, shortcut: 'D' },
|
||||
{ id: 'ideation', label: 'Ideation', icon: Lightbulb, shortcut: 'I' },
|
||||
{ id: 'changelog', label: 'Changelog', icon: FileText, shortcut: 'L' },
|
||||
{ id: 'github-issues', label: 'GitHub Issues', icon: Github, shortcut: 'G' },
|
||||
{ id: 'context', label: 'Context', icon: BookOpen, shortcut: 'C' },
|
||||
{ id: 'agent-tools', label: 'Agent Tools', icon: Wrench, shortcut: 'T' }
|
||||
@@ -158,7 +160,7 @@ export function Sidebar({
|
||||
if (path) {
|
||||
const project = await addProject(path);
|
||||
if (project && !project.autoBuildPath) {
|
||||
// Project doesn't have auto-build, show init dialog
|
||||
// Project doesn't have auto-claude, show init dialog
|
||||
setPendingProject(project);
|
||||
setShowInitDialog(true);
|
||||
}
|
||||
@@ -421,7 +423,7 @@ export function Sidebar({
|
||||
<div className="rounded-lg bg-muted p-4 text-sm">
|
||||
<p className="font-medium mb-2">This will:</p>
|
||||
<ul className="list-disc list-inside space-y-1 text-muted-foreground">
|
||||
<li>Create a <code className="text-xs bg-background px-1 py-0.5 rounded">.auto-build</code> folder in your project</li>
|
||||
<li>Create a <code className="text-xs bg-background px-1 py-0.5 rounded">.auto-claude</code> folder in your project</li>
|
||||
<li>Copy the Auto-Build framework files</li>
|
||||
<li>Set up the specs directory for your tasks</li>
|
||||
</ul>
|
||||
+1
@@ -9,3 +9,4 @@ export * from './AppSettings';
|
||||
export * from './Context';
|
||||
export * from './Ideation';
|
||||
export * from './GitHubIssues';
|
||||
export * from './Changelog';
|
||||
@@ -0,0 +1,30 @@
|
||||
import * as React from 'react';
|
||||
import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
|
||||
import { Check } from 'lucide-react';
|
||||
import { cn } from '../../lib/utils';
|
||||
|
||||
const Checkbox = React.forwardRef<
|
||||
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<CheckboxPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background',
|
||||
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
||||
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||
'data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<CheckboxPrimitive.Indicator
|
||||
className={cn('flex items-center justify-center text-current')}
|
||||
>
|
||||
<Check className="h-3 w-3" />
|
||||
</CheckboxPrimitive.Indicator>
|
||||
</CheckboxPrimitive.Root>
|
||||
));
|
||||
Checkbox.displayName = CheckboxPrimitive.Root.displayName;
|
||||
|
||||
export { Checkbox };
|
||||
@@ -0,0 +1,9 @@
|
||||
import * as CollapsiblePrimitive from '@radix-ui/react-collapsible';
|
||||
|
||||
const Collapsible = CollapsiblePrimitive.Root;
|
||||
|
||||
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
|
||||
|
||||
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;
|
||||
|
||||
export { Collapsible, CollapsibleTrigger, CollapsibleContent };
|
||||
@@ -0,0 +1,263 @@
|
||||
import { create } from 'zustand';
|
||||
import type {
|
||||
ChangelogTask,
|
||||
TaskSpecContent,
|
||||
ChangelogFormat,
|
||||
ChangelogAudience,
|
||||
ChangelogGenerationProgress,
|
||||
ChangelogGenerationResult,
|
||||
ExistingChangelog
|
||||
} from '../../shared/types';
|
||||
|
||||
interface ChangelogState {
|
||||
// Data
|
||||
doneTasks: ChangelogTask[];
|
||||
selectedTaskIds: string[];
|
||||
loadedSpecs: TaskSpecContent[];
|
||||
existingChangelog: ExistingChangelog | null;
|
||||
|
||||
// Generation config
|
||||
version: string;
|
||||
date: string;
|
||||
format: ChangelogFormat;
|
||||
audience: ChangelogAudience;
|
||||
customInstructions: string;
|
||||
|
||||
// Generation state
|
||||
generationProgress: ChangelogGenerationProgress | null;
|
||||
generatedChangelog: string;
|
||||
isGenerating: boolean;
|
||||
error: string | null;
|
||||
|
||||
// Actions
|
||||
setDoneTasks: (tasks: ChangelogTask[]) => void;
|
||||
setSelectedTaskIds: (ids: string[]) => void;
|
||||
toggleTaskSelection: (taskId: string) => void;
|
||||
selectAllTasks: () => void;
|
||||
deselectAllTasks: () => void;
|
||||
setLoadedSpecs: (specs: TaskSpecContent[]) => void;
|
||||
setExistingChangelog: (changelog: ExistingChangelog | null) => void;
|
||||
|
||||
// Config actions
|
||||
setVersion: (version: string) => void;
|
||||
setDate: (date: string) => void;
|
||||
setFormat: (format: ChangelogFormat) => void;
|
||||
setAudience: (audience: ChangelogAudience) => void;
|
||||
setCustomInstructions: (instructions: string) => void;
|
||||
|
||||
// Generation actions
|
||||
setGenerationProgress: (progress: ChangelogGenerationProgress | null) => void;
|
||||
setGeneratedChangelog: (changelog: string) => void;
|
||||
setIsGenerating: (isGenerating: boolean) => void;
|
||||
setError: (error: string | null) => void;
|
||||
|
||||
// Compound actions
|
||||
reset: () => void;
|
||||
updateGeneratedChangelog: (changelog: string) => void;
|
||||
}
|
||||
|
||||
const getDefaultDate = (): string => {
|
||||
return new Date().toISOString().split('T')[0];
|
||||
};
|
||||
|
||||
const initialState = {
|
||||
doneTasks: [],
|
||||
selectedTaskIds: [],
|
||||
loadedSpecs: [],
|
||||
existingChangelog: null,
|
||||
|
||||
version: '1.0.0',
|
||||
date: getDefaultDate(),
|
||||
format: 'keep-a-changelog' as ChangelogFormat,
|
||||
audience: 'user-facing' as ChangelogAudience,
|
||||
customInstructions: '',
|
||||
|
||||
generationProgress: null,
|
||||
generatedChangelog: '',
|
||||
isGenerating: false,
|
||||
error: null
|
||||
};
|
||||
|
||||
export const useChangelogStore = create<ChangelogState>((set, get) => ({
|
||||
...initialState,
|
||||
|
||||
// Data actions
|
||||
setDoneTasks: (tasks) => set({ doneTasks: tasks }),
|
||||
|
||||
setSelectedTaskIds: (ids) => set({ selectedTaskIds: ids }),
|
||||
|
||||
toggleTaskSelection: (taskId) =>
|
||||
set((state) => ({
|
||||
selectedTaskIds: state.selectedTaskIds.includes(taskId)
|
||||
? state.selectedTaskIds.filter((id) => id !== taskId)
|
||||
: [...state.selectedTaskIds, taskId]
|
||||
})),
|
||||
|
||||
selectAllTasks: () =>
|
||||
set((state) => ({
|
||||
selectedTaskIds: state.doneTasks.map((task) => task.id)
|
||||
})),
|
||||
|
||||
deselectAllTasks: () => set({ selectedTaskIds: [] }),
|
||||
|
||||
setLoadedSpecs: (specs) => set({ loadedSpecs: specs }),
|
||||
|
||||
setExistingChangelog: (changelog) => {
|
||||
set({ existingChangelog: changelog });
|
||||
// Auto-suggest next version if we found a previous version
|
||||
if (changelog?.lastVersion) {
|
||||
const parts = changelog.lastVersion.split('.').map(Number);
|
||||
if (parts.length === 3 && !parts.some(isNaN)) {
|
||||
const [major, minor, patch] = parts;
|
||||
set({ version: `${major}.${minor}.${patch + 1}` });
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Config actions
|
||||
setVersion: (version) => set({ version }),
|
||||
setDate: (date) => set({ date }),
|
||||
setFormat: (format) => set({ format }),
|
||||
setAudience: (audience) => set({ audience }),
|
||||
setCustomInstructions: (instructions) => set({ customInstructions: instructions }),
|
||||
|
||||
// Generation actions
|
||||
setGenerationProgress: (progress) => set({ generationProgress: progress }),
|
||||
setGeneratedChangelog: (changelog) => set({ generatedChangelog: changelog }),
|
||||
setIsGenerating: (isGenerating) => set({ isGenerating }),
|
||||
setError: (error) => set({ error }),
|
||||
|
||||
// Compound actions
|
||||
reset: () => set({ ...initialState, date: getDefaultDate() }),
|
||||
|
||||
updateGeneratedChangelog: (changelog) => set({ generatedChangelog: changelog })
|
||||
}));
|
||||
|
||||
// Helper functions for loading data
|
||||
export async function loadChangelogData(projectId: string): Promise<void> {
|
||||
const store = useChangelogStore.getState();
|
||||
|
||||
try {
|
||||
// Load done tasks
|
||||
const tasksResult = await window.electronAPI.getChangelogDoneTasks(projectId);
|
||||
if (tasksResult.success && tasksResult.data) {
|
||||
store.setDoneTasks(tasksResult.data);
|
||||
}
|
||||
|
||||
// Load existing changelog
|
||||
const changelogResult = await window.electronAPI.readExistingChangelog(projectId);
|
||||
if (changelogResult.success && changelogResult.data) {
|
||||
store.setExistingChangelog(changelogResult.data);
|
||||
}
|
||||
} catch (error) {
|
||||
store.setError(error instanceof Error ? error.message : 'Failed to load changelog data');
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadTaskSpecs(projectId: string, taskIds: string[]): Promise<void> {
|
||||
const store = useChangelogStore.getState();
|
||||
|
||||
try {
|
||||
const result = await window.electronAPI.loadTaskSpecs(projectId, taskIds);
|
||||
if (result.success && result.data) {
|
||||
store.setLoadedSpecs(result.data);
|
||||
}
|
||||
} catch (error) {
|
||||
store.setError(error instanceof Error ? error.message : 'Failed to load task specs');
|
||||
}
|
||||
}
|
||||
|
||||
export function generateChangelog(projectId: string): void {
|
||||
const store = useChangelogStore.getState();
|
||||
|
||||
if (store.selectedTaskIds.length === 0) {
|
||||
store.setError('Please select at least one task to include in the changelog');
|
||||
return;
|
||||
}
|
||||
|
||||
store.setIsGenerating(true);
|
||||
store.setError(null);
|
||||
store.setGenerationProgress({
|
||||
stage: 'loading_specs',
|
||||
progress: 0,
|
||||
message: 'Starting changelog generation...'
|
||||
});
|
||||
|
||||
window.electronAPI.generateChangelog({
|
||||
projectId,
|
||||
taskIds: store.selectedTaskIds,
|
||||
version: store.version,
|
||||
date: store.date,
|
||||
format: store.format,
|
||||
audience: store.audience,
|
||||
customInstructions: store.customInstructions || undefined
|
||||
});
|
||||
}
|
||||
|
||||
export async function saveChangelog(
|
||||
projectId: string,
|
||||
mode: 'prepend' | 'overwrite' | 'append' = 'prepend'
|
||||
): Promise<boolean> {
|
||||
const store = useChangelogStore.getState();
|
||||
|
||||
if (!store.generatedChangelog) {
|
||||
store.setError('No changelog to save');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await window.electronAPI.saveChangelog({
|
||||
projectId,
|
||||
content: store.generatedChangelog,
|
||||
mode
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
return true;
|
||||
} else {
|
||||
store.setError(result.error || 'Failed to save changelog');
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
store.setError(error instanceof Error ? error.message : 'Failed to save changelog');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function copyChangelogToClipboard(): boolean {
|
||||
const store = useChangelogStore.getState();
|
||||
|
||||
if (!store.generatedChangelog) {
|
||||
store.setError('No changelog to copy');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
navigator.clipboard.writeText(store.generatedChangelog);
|
||||
return true;
|
||||
} catch (error) {
|
||||
store.setError('Failed to copy to clipboard');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Selectors
|
||||
export function getSelectedTasks(): ChangelogTask[] {
|
||||
const store = useChangelogStore.getState();
|
||||
return store.doneTasks.filter((task) => store.selectedTaskIds.includes(task.id));
|
||||
}
|
||||
|
||||
export function getTasksWithSpecs(): ChangelogTask[] {
|
||||
const store = useChangelogStore.getState();
|
||||
return store.doneTasks.filter((task) => task.hasSpecs);
|
||||
}
|
||||
|
||||
export function canGenerate(): boolean {
|
||||
const store = useChangelogStore.getState();
|
||||
return store.selectedTaskIds.length > 0 && !store.isGenerating;
|
||||
}
|
||||
|
||||
export function canSave(): boolean {
|
||||
const store = useChangelogStore.getState();
|
||||
return store.generatedChangelog.length > 0 && !store.isGenerating;
|
||||
}
|
||||
+5
-5
@@ -155,7 +155,7 @@ export async function updateProjectSettings(
|
||||
}
|
||||
|
||||
/**
|
||||
* Check auto-build version status for a project
|
||||
* Check auto-claude version status for a project
|
||||
*/
|
||||
export async function checkProjectVersion(
|
||||
projectId: string
|
||||
@@ -172,7 +172,7 @@ export async function checkProjectVersion(
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize auto-build in a project
|
||||
* Initialize auto-claude in a project
|
||||
*/
|
||||
export async function initializeProject(
|
||||
projectId: string
|
||||
@@ -184,7 +184,7 @@ export async function initializeProject(
|
||||
if (result.success && result.data) {
|
||||
// Update the project's autoBuildPath in local state
|
||||
if (result.data.success) {
|
||||
store.updateProject(projectId, { autoBuildPath: '.auto-build' });
|
||||
store.updateProject(projectId, { autoBuildPath: '.auto-claude' });
|
||||
}
|
||||
return result.data;
|
||||
}
|
||||
@@ -197,7 +197,7 @@ export async function initializeProject(
|
||||
}
|
||||
|
||||
/**
|
||||
* Update auto-build in a project
|
||||
* Update auto-claude in a project
|
||||
*/
|
||||
export async function updateProjectAutoBuild(
|
||||
projectId: string
|
||||
@@ -209,7 +209,7 @@ export async function updateProjectAutoBuild(
|
||||
if (result.success && result.data) {
|
||||
return result.data;
|
||||
}
|
||||
store.setError(result.error || 'Failed to update auto-build');
|
||||
store.setError(result.error || 'Failed to update auto-claude');
|
||||
return null;
|
||||
} catch (error) {
|
||||
store.setError(error instanceof Error ? error.message : 'Unknown error');
|
||||
@@ -175,14 +175,26 @@ export const IPC_CHANNELS = {
|
||||
AUTOBUILD_SOURCE_CHECK: 'autobuild:source:check',
|
||||
AUTOBUILD_SOURCE_DOWNLOAD: 'autobuild:source:download',
|
||||
AUTOBUILD_SOURCE_VERSION: 'autobuild:source:version',
|
||||
AUTOBUILD_SOURCE_PROGRESS: 'autobuild:source:progress'
|
||||
AUTOBUILD_SOURCE_PROGRESS: 'autobuild:source:progress',
|
||||
|
||||
// Changelog operations
|
||||
CHANGELOG_GET_DONE_TASKS: 'changelog:getDoneTasks',
|
||||
CHANGELOG_LOAD_TASK_SPECS: 'changelog:loadTaskSpecs',
|
||||
CHANGELOG_GENERATE: 'changelog:generate',
|
||||
CHANGELOG_SAVE: 'changelog:save',
|
||||
CHANGELOG_READ_EXISTING: 'changelog:readExisting',
|
||||
|
||||
// Changelog events (main -> renderer)
|
||||
CHANGELOG_GENERATION_PROGRESS: 'changelog:generationProgress',
|
||||
CHANGELOG_GENERATION_COMPLETE: 'changelog:generationComplete',
|
||||
CHANGELOG_GENERATION_ERROR: 'changelog:generationError'
|
||||
} as const;
|
||||
|
||||
// File paths relative to project
|
||||
export const AUTO_BUILD_PATHS = {
|
||||
SPECS_DIR: 'auto-build/specs',
|
||||
ROADMAP_DIR: 'auto-build/roadmap',
|
||||
IDEATION_DIR: 'auto-build/ideation',
|
||||
SPECS_DIR: 'auto-claude/specs',
|
||||
ROADMAP_DIR: 'auto-claude/roadmap',
|
||||
IDEATION_DIR: 'auto-claude/ideation',
|
||||
IMPLEMENTATION_PLAN: 'implementation_plan.json',
|
||||
SPEC_FILE: 'spec.md',
|
||||
QA_REPORT: 'qa_report.md',
|
||||
@@ -193,7 +205,7 @@ export const AUTO_BUILD_PATHS = {
|
||||
ROADMAP_DISCOVERY: 'roadmap_discovery.json',
|
||||
IDEATION_FILE: 'ideation.json',
|
||||
IDEATION_CONTEXT: 'ideation_context.json',
|
||||
PROJECT_INDEX: 'auto-build/project_index.json',
|
||||
PROJECT_INDEX: 'auto-claude/project_index.json',
|
||||
GRAPHITI_STATE: '.graphiti_state.json'
|
||||
} as const;
|
||||
|
||||
@@ -386,3 +398,45 @@ export const GITHUB_COMPLEXITY_COLORS: Record<string, string> = {
|
||||
standard: 'bg-warning/10 text-warning',
|
||||
complex: 'bg-destructive/10 text-destructive'
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// Changelog Constants
|
||||
// ============================================
|
||||
|
||||
// Changelog format labels and descriptions
|
||||
export const CHANGELOG_FORMAT_LABELS: Record<string, string> = {
|
||||
'keep-a-changelog': 'Keep a Changelog',
|
||||
'simple-list': 'Simple List',
|
||||
'github-release': 'GitHub Release'
|
||||
};
|
||||
|
||||
export const CHANGELOG_FORMAT_DESCRIPTIONS: Record<string, string> = {
|
||||
'keep-a-changelog': 'Structured format with Added/Changed/Fixed/Removed sections',
|
||||
'simple-list': 'Clean bulleted list with categories',
|
||||
'github-release': 'GitHub-style release notes with emojis'
|
||||
};
|
||||
|
||||
// Changelog audience labels and descriptions
|
||||
export const CHANGELOG_AUDIENCE_LABELS: Record<string, string> = {
|
||||
'technical': 'Technical',
|
||||
'user-facing': 'User-Facing',
|
||||
'marketing': 'Marketing'
|
||||
};
|
||||
|
||||
export const CHANGELOG_AUDIENCE_DESCRIPTIONS: Record<string, string> = {
|
||||
'technical': 'Detailed technical changes for developers',
|
||||
'user-facing': 'Clear, non-technical descriptions for end users',
|
||||
'marketing': 'Value-focused copy emphasizing benefits'
|
||||
};
|
||||
|
||||
// Changelog generation stage labels
|
||||
export const CHANGELOG_STAGE_LABELS: Record<string, string> = {
|
||||
'loading_specs': 'Loading spec files...',
|
||||
'generating': 'Generating changelog...',
|
||||
'formatting': 'Formatting output...',
|
||||
'complete': 'Complete',
|
||||
'error': 'Error'
|
||||
};
|
||||
|
||||
// Default changelog file path
|
||||
export const DEFAULT_CHANGELOG_PATH = 'CHANGELOG.md';
|
||||
@@ -76,7 +76,7 @@ export interface Task {
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
// Implementation Plan (from auto-build)
|
||||
// Implementation Plan (from auto-claude)
|
||||
export interface ImplementationPlan {
|
||||
feature: string;
|
||||
workflow_type: string;
|
||||
@@ -685,6 +685,76 @@ export interface GitHubInvestigationStatus {
|
||||
error?: string;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Changelog Types
|
||||
// ============================================
|
||||
|
||||
export type ChangelogFormat = 'keep-a-changelog' | 'simple-list' | 'github-release';
|
||||
export type ChangelogAudience = 'technical' | 'user-facing' | 'marketing';
|
||||
|
||||
export interface ChangelogTask {
|
||||
id: string;
|
||||
specId: string;
|
||||
title: string;
|
||||
description: string;
|
||||
completedAt: Date;
|
||||
hasSpecs: boolean;
|
||||
}
|
||||
|
||||
export interface TaskSpecContent {
|
||||
taskId: string;
|
||||
specId: string;
|
||||
spec?: string; // Content of spec.md
|
||||
requirements?: Record<string, unknown>; // Parsed requirements.json
|
||||
qaReport?: string; // Content of qa_report.md
|
||||
implementationPlan?: ImplementationPlan; // Parsed implementation_plan.json
|
||||
error?: string; // Error message if loading failed
|
||||
}
|
||||
|
||||
export interface ChangelogGenerationRequest {
|
||||
projectId: string;
|
||||
taskIds: string[];
|
||||
version: string;
|
||||
date: string; // ISO format
|
||||
format: ChangelogFormat;
|
||||
audience: ChangelogAudience;
|
||||
customInstructions?: string;
|
||||
}
|
||||
|
||||
export interface ChangelogGenerationResult {
|
||||
success: boolean;
|
||||
changelog: string;
|
||||
version: string;
|
||||
tasksIncluded: number;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface ChangelogSaveRequest {
|
||||
projectId: string;
|
||||
content: string;
|
||||
filePath?: string; // Optional custom path, defaults to CHANGELOG.md
|
||||
mode: 'prepend' | 'overwrite' | 'append';
|
||||
}
|
||||
|
||||
export interface ChangelogSaveResult {
|
||||
filePath: string;
|
||||
bytesWritten: number;
|
||||
}
|
||||
|
||||
export interface ChangelogGenerationProgress {
|
||||
stage: 'loading_specs' | 'generating' | 'formatting' | 'complete' | 'error';
|
||||
progress: number; // 0-100
|
||||
message: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface ExistingChangelog {
|
||||
exists: boolean;
|
||||
content?: string;
|
||||
lastVersion?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Auto-Build Source Update Types
|
||||
// ============================================
|
||||
@@ -850,6 +920,24 @@ export interface ElectronAPI {
|
||||
onAutoBuildSourceUpdateProgress: (
|
||||
callback: (progress: AutoBuildSourceUpdateProgress) => void
|
||||
) => () => void;
|
||||
|
||||
// Changelog operations
|
||||
getChangelogDoneTasks: (projectId: string) => Promise<IPCResult<ChangelogTask[]>>;
|
||||
loadTaskSpecs: (projectId: string, taskIds: string[]) => Promise<IPCResult<TaskSpecContent[]>>;
|
||||
generateChangelog: (request: ChangelogGenerationRequest) => void; // Async with progress events
|
||||
saveChangelog: (request: ChangelogSaveRequest) => Promise<IPCResult<ChangelogSaveResult>>;
|
||||
readExistingChangelog: (projectId: string) => Promise<IPCResult<ExistingChangelog>>;
|
||||
|
||||
// Changelog event listeners
|
||||
onChangelogGenerationProgress: (
|
||||
callback: (projectId: string, progress: ChangelogGenerationProgress) => void
|
||||
) => () => void;
|
||||
onChangelogGenerationComplete: (
|
||||
callback: (projectId: string, result: ChangelogGenerationResult) => void
|
||||
) => () => void;
|
||||
onChangelogGenerationError: (
|
||||
callback: (projectId: string, error: string) => void
|
||||
) => () => void;
|
||||
}
|
||||
|
||||
declare global {
|
||||
@@ -556,7 +556,7 @@ async def run_autonomous_agent(
|
||||
|
||||
Args:
|
||||
project_dir: Root directory for the project
|
||||
spec_dir: Directory containing the spec (auto-build/specs/001-name/)
|
||||
spec_dir: Directory containing the spec (auto-claude/specs/001-name/)
|
||||
model: Claude model to use
|
||||
max_iterations: Maximum number of iterations (None for unlimited)
|
||||
verbose: Whether to show detailed output
|
||||
@@ -651,7 +651,7 @@ async def run_autonomous_agent(
|
||||
print(f"\nTo resume, delete the PAUSE file:")
|
||||
print(f" rm {pause_file}")
|
||||
print(f"\nThen run again:")
|
||||
print(f" python auto-build/run.py --spec {spec_dir.name}")
|
||||
print(f" python auto-claude/run.py --spec {spec_dir.name}")
|
||||
return
|
||||
|
||||
# Check max iterations
|
||||
@@ -849,14 +849,14 @@ async def run_autonomous_agent(
|
||||
bold(f"{icon(Icons.PLAY)} NEXT STEPS"),
|
||||
"",
|
||||
f"{total - completed} chunks remaining.",
|
||||
f"Run again: {highlight(f'python auto-build/run.py --spec {spec_dir.name}')}",
|
||||
f"Run again: {highlight(f'python auto-claude/run.py --spec {spec_dir.name}')}",
|
||||
]
|
||||
else:
|
||||
content = [
|
||||
bold(f"{icon(Icons.SUCCESS)} NEXT STEPS"),
|
||||
"",
|
||||
"All chunks completed!",
|
||||
" 1. Review the auto-build/* branch",
|
||||
" 1. Review the auto-claude/* branch",
|
||||
" 2. Run manual tests",
|
||||
" 3. Merge to main",
|
||||
]
|
||||
@@ -8,13 +8,13 @@ Supports monorepos with multiple services.
|
||||
|
||||
Usage:
|
||||
# Index entire project (creates project_index.json)
|
||||
python auto-build/analyzer.py --index
|
||||
python auto-claude/analyzer.py --index
|
||||
|
||||
# Analyze specific service
|
||||
python auto-build/analyzer.py --service backend
|
||||
python auto-claude/analyzer.py --service backend
|
||||
|
||||
# Output to specific file
|
||||
python auto-build/analyzer.py --index --output path/to/output.json
|
||||
python auto-claude/analyzer.py --index --output path/to/output.json
|
||||
|
||||
The analyzer will:
|
||||
1. Detect if this is a monorepo or single project
|
||||
@@ -48,7 +48,7 @@ SKIP_DIRS = {
|
||||
"vendor",
|
||||
".idea",
|
||||
".vscode",
|
||||
"auto-build",
|
||||
"auto-claude",
|
||||
".pytest_cache",
|
||||
".mypy_cache",
|
||||
"coverage",
|
||||
@@ -8,14 +8,14 @@ This is the "RAG-like" component that finds what files matter for THIS task.
|
||||
|
||||
Usage:
|
||||
# Find context for a task across specific services
|
||||
python auto-build/context.py \
|
||||
python auto-claude/context.py \
|
||||
--services backend,scraper \
|
||||
--keywords "retry,error,proxy" \
|
||||
--task "Add retry logic when proxies fail" \
|
||||
--output auto-build/specs/001-retry/context.json
|
||||
--output auto-claude/specs/001-retry/context.json
|
||||
|
||||
# Use project index to auto-suggest services
|
||||
python auto-build/context.py \
|
||||
python auto-claude/context.py \
|
||||
--task "Add retry logic when proxies fail" \
|
||||
--output context.json
|
||||
|
||||
@@ -37,7 +37,7 @@ from dataclasses import dataclass, field, asdict
|
||||
# Directories to skip
|
||||
SKIP_DIRS = {
|
||||
"node_modules", ".git", "__pycache__", ".venv", "venv", "dist", "build",
|
||||
".next", ".nuxt", "target", "vendor", ".idea", ".vscode", "auto-build",
|
||||
".next", ".nuxt", "target", "vendor", ".idea", ".vscode", "auto-claude",
|
||||
".pytest_cache", ".mypy_cache", "coverage", ".turbo", ".cache",
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ class ContextBuilder:
|
||||
|
||||
def _load_project_index(self) -> dict:
|
||||
"""Load project index from file or create new one."""
|
||||
index_file = self.project_dir / "auto-build" / "project_index.json"
|
||||
index_file = self.project_dir / "auto-claude" / "project_index.json"
|
||||
if index_file.exists():
|
||||
with open(index_file) as f:
|
||||
return json.load(f)
|
||||
@@ -7,7 +7,7 @@ Implements a swarm coordination pattern for parallel execution of independent ch
|
||||
All work is collected in a single STAGING worktree that the user can test before merging.
|
||||
|
||||
Architecture:
|
||||
1. Create ONE staging worktree: .worktrees/auto-build/
|
||||
1. Create ONE staging worktree: .worktrees/auto-claude/
|
||||
2. Each worker gets a temporary worktree for isolation during work
|
||||
3. Workers merge INTO staging (not base branch)
|
||||
4. User can cd into staging, run the app, test the feature
|
||||
@@ -366,7 +366,7 @@ class SwarmCoordinator:
|
||||
# Merge the worker branch into staging
|
||||
result = subprocess.run(
|
||||
["git", "merge", "--no-ff", branch_name,
|
||||
"-m", f"auto-build: Merge {branch_name}"],
|
||||
"-m", f"auto-claude: Merge {branch_name}"],
|
||||
cwd=staging_path,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
@@ -499,7 +499,7 @@ class SwarmCoordinator:
|
||||
check=False,
|
||||
)
|
||||
result = subprocess.run(
|
||||
["git", "commit", "-m", f"auto-build: Complete {chunk.id}\n\n{chunk.description}"],
|
||||
["git", "commit", "-m", f"auto-claude: Complete {chunk.id}\n\n{chunk.description}"],
|
||||
cwd=worktree_path,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
@@ -767,11 +767,11 @@ class SwarmCoordinator:
|
||||
print()
|
||||
|
||||
print("When you're happy with it:")
|
||||
print(highlight(f" python auto-build/run.py --spec {spec_name} --merge"))
|
||||
print(highlight(f" python auto-claude/run.py --spec {spec_name} --merge"))
|
||||
print()
|
||||
|
||||
print("To see what changed:")
|
||||
print(muted(f" python auto-build/run.py --spec {spec_name} --review"))
|
||||
print(muted(f" python auto-claude/run.py --spec {spec_name} --review"))
|
||||
print()
|
||||
|
||||
return staging_path
|
||||
@@ -109,7 +109,7 @@ class GraphitiConfig:
|
||||
|
||||
@dataclass
|
||||
class GraphitiState:
|
||||
"""State of Graphiti integration for an auto-build spec."""
|
||||
"""State of Graphiti integration for an auto-claude spec."""
|
||||
initialized: bool = False
|
||||
database: Optional[str] = None
|
||||
indices_built: bool = False
|
||||
@@ -66,7 +66,7 @@ RETRY_DELAY_SECONDS = 1
|
||||
|
||||
class GraphitiMemory:
|
||||
"""
|
||||
Manages Graphiti-based persistent memory for auto-build sessions.
|
||||
Manages Graphiti-based persistent memory for auto-claude sessions.
|
||||
|
||||
This class provides a high-level interface for:
|
||||
- Storing session insights as episodes
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user