rebrand: rename AutoCoder to AutoForge across entire codebase

Complete project rebrand from AutoCoder to AutoForge, touching 62 files
across Python backend, FastAPI server, React UI, documentation, config,
and CI/CD.

Key changes:
- Rename autocoder_paths.py -> autoforge_paths.py with backward-compat
  migration from .autocoder/ -> .autoforge/ directories
- Update registry.py to migrate ~/.autocoder/ -> ~/.autoforge/ global
  config directory with fallback support
- Update security.py with fallback reads from legacy .autocoder/ paths
- Rename .claude/commands and skills from gsd-to-autocoder-spec to
  gsd-to-autoforge-spec
- Update all Python modules: client, prompts, progress, agent,
  orchestrator, server routers and services
- Update React UI: package.json name, index.html title, localStorage
  keys, all documentation sections, component references
- Update start scripts (bat/sh/py), examples, and .env.example
- Update CLAUDE.md and README.md with new branding and paths
- Update test files for new .autoforge/ directory structure
- Transfer git remote from leonvanzyl/autocoder to
  AutoForgeAI/autoforge

Backward compatibility preserved: legacy .autocoder/ directories are
auto-detected and migrated on next agent start. Config fallback chain
checks both new and old paths.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Auto
2026-02-04 12:02:06 +02:00
parent f6510b4dd8
commit c2ad993e75
63 changed files with 405 additions and 354 deletions

View File

@@ -97,7 +97,7 @@ Fix ALL issues before considering the implementation complete. Never leave linti
## Project-Specific Context
For this project (autocoder):
For this project (autoforge):
- **Python Backend**: Uses SQLAlchemy, FastAPI, follows patterns in `api/`, `mcp_server/`
- **React UI**: Uses React 18, TypeScript, TanStack Query, Tailwind CSS v4, Radix UI
- **Design System**: Neobrutalism style with specific color tokens and animations

View File

@@ -8,7 +8,7 @@ This command **requires** the project directory as an argument via `$ARGUMENTS`.
**Example:** `/create-spec generations/my-app`
**Output location:** `$ARGUMENTS/.autocoder/prompts/app_spec.txt` and `$ARGUMENTS/.autocoder/prompts/initializer_prompt.md`
**Output location:** `$ARGUMENTS/.autoforge/prompts/app_spec.txt` and `$ARGUMENTS/.autoforge/prompts/initializer_prompt.md`
If `$ARGUMENTS` is empty, inform the user they must provide a project path and exit.
@@ -347,13 +347,13 @@ First ask in conversation if they want to make changes.
## Output Directory
The output directory is: `$ARGUMENTS/.autocoder/prompts/`
The output directory is: `$ARGUMENTS/.autoforge/prompts/`
Once the user approves, generate these files:
## 1. Generate `app_spec.txt`
**Output path:** `$ARGUMENTS/.autocoder/prompts/app_spec.txt`
**Output path:** `$ARGUMENTS/.autoforge/prompts/app_spec.txt`
Create a new file using this XML structure:
@@ -489,7 +489,7 @@ Create a new file using this XML structure:
## 2. Update `initializer_prompt.md`
**Output path:** `$ARGUMENTS/.autocoder/prompts/initializer_prompt.md`
**Output path:** `$ARGUMENTS/.autoforge/prompts/initializer_prompt.md`
If the output directory has an existing `initializer_prompt.md`, read it and update the feature count.
If not, copy from `.claude/templates/initializer_prompt.template.md` first, then update.
@@ -512,7 +512,7 @@ After: **CRITICAL:** You must create exactly **25** features using the `feature
## 3. Write Status File (REQUIRED - Do This Last)
**Output path:** `$ARGUMENTS/.autocoder/prompts/.spec_status.json`
**Output path:** `$ARGUMENTS/.autoforge/prompts/.spec_status.json`
**CRITICAL:** After you have completed ALL requested file changes, write this status file to signal completion to the UI. This is required for the "Continue to Project" button to appear.
@@ -524,8 +524,8 @@ Write this JSON file:
"version": 1,
"timestamp": "[current ISO 8601 timestamp, e.g., 2025-01-15T14:30:00.000Z]",
"files_written": [
".autocoder/prompts/app_spec.txt",
".autocoder/prompts/initializer_prompt.md"
".autoforge/prompts/app_spec.txt",
".autoforge/prompts/initializer_prompt.md"
],
"feature_count": [the feature count from Phase 4L]
}
@@ -539,9 +539,9 @@ Write this JSON file:
"version": 1,
"timestamp": "2025-01-15T14:30:00.000Z",
"files_written": [
".autocoder/prompts/app_spec.txt",
".autocoder/prompts/initializer_prompt.md",
".autocoder/prompts/coding_prompt.md"
".autoforge/prompts/app_spec.txt",
".autoforge/prompts/initializer_prompt.md",
".autoforge/prompts/coding_prompt.md"
],
"feature_count": 35
}
@@ -559,11 +559,11 @@ Write this JSON file:
Once files are generated, tell the user what to do next:
> "Your specification files have been created in `$ARGUMENTS/.autocoder/prompts/`!
> "Your specification files have been created in `$ARGUMENTS/.autoforge/prompts/`!
>
> **Files created:**
> - `$ARGUMENTS/.autocoder/prompts/app_spec.txt`
> - `$ARGUMENTS/.autocoder/prompts/initializer_prompt.md`
> - `$ARGUMENTS/.autoforge/prompts/app_spec.txt`
> - `$ARGUMENTS/.autoforge/prompts/initializer_prompt.md`
>
> The **Continue to Project** button should now appear. Click it to start the autonomous coding agent!
>

View File

@@ -42,7 +42,7 @@ You are the **Project Expansion Assistant** - an expert at understanding existin
# FIRST: Read and Understand Existing Project
**Step 1:** Read the existing specification:
- Read `$ARGUMENTS/.autocoder/prompts/app_spec.txt`
- Read `$ARGUMENTS/.autoforge/prompts/app_spec.txt`
**Step 2:** Present a summary to the user:
@@ -231,4 +231,4 @@ If they want to add more, go back to Phase 1.
# BEGIN
Start by reading the app specification file at `$ARGUMENTS/.autocoder/prompts/app_spec.txt`, then greet the user with a summary of their existing project and ask what they want to add.
Start by reading the app specification file at `$ARGUMENTS/.autoforge/prompts/app_spec.txt`, then greet the user with a summary of their existing project and ask what they want to add.

View File

@@ -1,10 +0,0 @@
---
allowed-tools: Read, Write, Bash, Glob, Grep
description: Convert GSD codebase mapping to Autocoder app_spec.txt
---
# GSD to Autocoder Spec
Convert `.planning/codebase/*.md` (from `/gsd:map-codebase`) to Autocoder's `.autocoder/prompts/app_spec.txt`.
@.claude/skills/gsd-to-autocoder-spec/SKILL.md

View File

@@ -0,0 +1,10 @@
---
allowed-tools: Read, Write, Bash, Glob, Grep
description: Convert GSD codebase mapping to AutoForge app_spec.txt
---
# GSD to AutoForge Spec
Convert `.planning/codebase/*.md` (from `/gsd:map-codebase`) to AutoForge's `.autoforge/prompts/app_spec.txt`.
@.claude/skills/gsd-to-autoforge-spec/SKILL.md

View File

@@ -1,21 +1,21 @@
---
name: gsd-to-autocoder-spec
name: gsd-to-autoforge-spec
description: |
Convert GSD codebase mapping to Autocoder app_spec.txt. This skill should be used when
the user has run /gsd:map-codebase and wants to use Autocoder on an existing project.
Triggers: "convert to autocoder", "gsd to spec", "create app_spec from codebase",
"use autocoder on existing project", after /gsd:map-codebase completion.
Convert GSD codebase mapping to AutoForge app_spec.txt. This skill should be used when
the user has run /gsd:map-codebase and wants to use AutoForge on an existing project.
Triggers: "convert to autoforge", "gsd to spec", "create app_spec from codebase",
"use autoforge on existing project", after /gsd:map-codebase completion.
---
# GSD to Autocoder Spec Converter
# GSD to AutoForge Spec Converter
Converts `.planning/codebase/*.md` (GSD mapping output) to `.autocoder/prompts/app_spec.txt` (Autocoder format).
Converts `.planning/codebase/*.md` (GSD mapping output) to `.autoforge/prompts/app_spec.txt` (AutoForge format).
## When to Use
- After running `/gsd:map-codebase` on an existing project
- When onboarding an existing codebase to Autocoder
- User wants Autocoder to continue development on existing code
- When onboarding an existing codebase to AutoForge
- User wants AutoForge to continue development on existing code
## Prerequisites
@@ -84,12 +84,12 @@ Extract:
Create `prompts/` directory:
```bash
mkdir -p .autocoder/prompts
mkdir -p .autoforge/prompts
```
**Mapping GSD Documents to Autocoder Spec:**
**Mapping GSD Documents to AutoForge Spec:**
| GSD Source | Autocoder Target |
| GSD Source | AutoForge Target |
|------------|------------------|
| STACK.md Languages | `<technology_stack>` |
| STACK.md Frameworks | `<frontend>`, `<backend>` |
@@ -114,7 +114,7 @@ mkdir -p .autocoder/prompts
**Write the spec file** using the XML format from [references/app-spec-format.md](references/app-spec-format.md):
```bash
cat > .autocoder/prompts/app_spec.txt << 'EOF'
cat > .autoforge/prompts/app_spec.txt << 'EOF'
<project_specification>
<project_name>{from package.json or directory}</project_name>
@@ -173,9 +173,9 @@ EOF
### Step 5: Verify Generated Spec
```bash
head -100 .autocoder/prompts/app_spec.txt
head -100 .autoforge/prompts/app_spec.txt
echo "---"
grep -c "User can\|System\|API\|Feature" .autocoder/prompts/app_spec.txt || echo "0"
grep -c "User can\|System\|API\|Feature" .autoforge/prompts/app_spec.txt || echo "0"
```
**Validation checklist:**
@@ -194,15 +194,15 @@ Output:
app_spec.txt generated from GSD codebase mapping.
Source: .planning/codebase/*.md
Output: .autocoder/prompts/app_spec.txt
Output: .autoforge/prompts/app_spec.txt
Next: Start Autocoder
Next: Start AutoForge
cd {project_dir}
python ~/projects/autocoder/start.py
python ~/projects/autoforge/start.py
Or via UI:
~/projects/autocoder/start_ui.sh
~/projects/autoforge/start_ui.sh
The Initializer will create features.db from this spec.
```

View File

@@ -1,6 +1,6 @@
# Autocoder app_spec.txt XML Format
# AutoForge app_spec.txt XML Format
Complete reference for the XML structure expected by Autocoder's Initializer agent.
Complete reference for the XML structure expected by AutoForge's Initializer agent.
## Root Structure
@@ -275,7 +275,7 @@ The Initializer agent expects features distributed across categories:
| Medium web app | 200-250 | 10-15 |
| Complex full-stack | 300-400 | 15-20 |
## GSD to Autocoder Mapping
## GSD to AutoForge Mapping
When converting from GSD codebase mapping:

View File

@@ -36,7 +36,7 @@
# GLM/Alternative API Configuration (Optional)
# To use Zhipu AI's GLM models instead of Claude, uncomment and set these variables.
# This only affects AutoCoder - your global Claude Code settings remain unchanged.
# This only affects AutoForge - your global Claude Code settings remain unchanged.
# Get an API key at: https://z.ai/subscribe
#
# ANTHROPIC_BASE_URL=https://api.z.ai/api/anthropic

View File

@@ -140,7 +140,7 @@ Configuration in `pyproject.toml`:
- `start.py` - CLI launcher with project creation/selection menu
- `autonomous_agent_demo.py` - Entry point for running the agent (supports `--yolo`, `--parallel`, `--batch-size`, `--batch-features`)
- `autocoder_paths.py` - Central path resolution with dual-path backward compatibility and migration
- `autoforge_paths.py` - Central path resolution with dual-path backward compatibility and migration
- `agent.py` - Agent session loop using Claude Agent SDK
- `client.py` - ClaudeSDKClient configuration with security hooks, MCP servers, and Vertex AI support
- `security.py` - Bash command allowlist validation (ALLOWED_COMMANDS whitelist)
@@ -158,7 +158,7 @@ Configuration in `pyproject.toml`:
### Project Registry
Projects can be stored in any directory. The registry maps project names to paths using SQLite:
- **All platforms**: `~/.autocoder/registry.db`
- **All platforms**: `~/.autoforge/registry.db`
The registry uses:
- SQLite database with SQLAlchemy ORM
@@ -254,18 +254,18 @@ Keyboard shortcuts (press `?` for help):
### Project Structure for Generated Apps
Projects can be stored in any directory (registered in `~/.autocoder/registry.db`). Each project contains:
- `.autocoder/prompts/app_spec.txt` - Application specification (XML format)
- `.autocoder/prompts/initializer_prompt.md` - First session prompt
- `.autocoder/prompts/coding_prompt.md` - Continuation session prompt
- `.autocoder/features.db` - SQLite database with feature test cases
- `.autocoder/.agent.lock` - Lock file to prevent multiple agent instances
- `.autocoder/allowed_commands.yaml` - Project-specific bash command allowlist (optional)
- `.autocoder/.gitignore` - Ignores runtime files
Projects can be stored in any directory (registered in `~/.autoforge/registry.db`). Each project contains:
- `.autoforge/prompts/app_spec.txt` - Application specification (XML format)
- `.autoforge/prompts/initializer_prompt.md` - First session prompt
- `.autoforge/prompts/coding_prompt.md` - Continuation session prompt
- `.autoforge/features.db` - SQLite database with feature test cases
- `.autoforge/.agent.lock` - Lock file to prevent multiple agent instances
- `.autoforge/allowed_commands.yaml` - Project-specific bash command allowlist (optional)
- `.autoforge/.gitignore` - Ignores runtime files
- `CLAUDE.md` - Stays at project root (SDK convention)
- `app_spec.txt` - Root copy for agent template compatibility
Legacy projects with files at root level (e.g., `features.db`, `prompts/`) are auto-migrated to `.autocoder/` on next agent start. Dual-path resolution ensures old and new layouts work transparently.
Legacy projects with files at root level (e.g., `features.db`, `prompts/`) are auto-migrated to `.autoforge/` on next agent start. Dual-path resolution ensures old and new layouts work transparently.
### Security Model
@@ -311,14 +311,14 @@ The agent's bash command access is controlled through a hierarchical configurati
**Command Hierarchy (highest to lowest priority):**
1. **Hardcoded Blocklist** (`security.py`) - NEVER allowed (dd, sudo, shutdown, etc.)
2. **Org Blocklist** (`~/.autocoder/config.yaml`) - Cannot be overridden by projects
3. **Org Allowlist** (`~/.autocoder/config.yaml`) - Available to all projects
2. **Org Blocklist** (`~/.autoforge/config.yaml`) - Cannot be overridden by projects
3. **Org Allowlist** (`~/.autoforge/config.yaml`) - Available to all projects
4. **Global Allowlist** (`security.py`) - Default commands (npm, git, curl, etc.)
5. **Project Allowlist** (`.autocoder/allowed_commands.yaml`) - Project-specific commands
5. **Project Allowlist** (`.autoforge/allowed_commands.yaml`) - Project-specific commands
**Project Configuration:**
Each project can define custom allowed commands in `.autocoder/allowed_commands.yaml`:
Each project can define custom allowed commands in `.autoforge/allowed_commands.yaml`:
```yaml
version: 1
@@ -338,7 +338,7 @@ commands:
**Organization Configuration:**
System administrators can set org-wide policies in `~/.autocoder/config.yaml`:
System administrators can set org-wide policies in `~/.autoforge/config.yaml`:
```yaml
version: 1
@@ -405,7 +405,7 @@ Run coding agents using local models via Ollama v0.14.0+:
ANTHROPIC_DEFAULT_OPUS_MODEL=qwen3-coder
ANTHROPIC_DEFAULT_HAIKU_MODEL=qwen3-coder
```
5. Run autocoder normally - it will use your local Ollama models
5. Run AutoForge normally - it will use your local Ollama models
**Recommended coding models:**
- `qwen3-coder` - Good balance of speed and capability
@@ -427,7 +427,7 @@ Run coding agents using local models via Ollama v0.14.0+:
**Slash commands** (`.claude/commands/`):
- `/create-spec` - Interactive spec creation for new projects
- `/expand-project` - Expand existing project with new features
- `/gsd-to-autocoder-spec` - Convert GSD codebase mapping to app_spec.txt
- `/gsd-to-autoforge-spec` - Convert GSD codebase mapping to app_spec.txt
- `/check-code` - Run lint and type-check for code quality
- `/checkpoint` - Create comprehensive checkpoint commit
- `/review-pr` - Review pull requests
@@ -439,7 +439,7 @@ Run coding agents using local models via Ollama v0.14.0+:
**Skills** (`.claude/skills/`):
- `frontend-design` - Distinctive, production-grade UI design
- `gsd-to-autocoder-spec` - Convert GSD codebase mapping to Autocoder app_spec format
- `gsd-to-autoforge-spec` - Convert GSD codebase mapping to AutoForge app_spec format
**Other:**
- `.claude/templates/` - Prompt templates copied to new projects
@@ -449,12 +449,12 @@ Run coding agents using local models via Ollama v0.14.0+:
### Prompt Loading Fallback Chain
1. Project-specific: `{project_dir}/.autocoder/prompts/{name}.md` (or legacy `{project_dir}/prompts/{name}.md`)
1. Project-specific: `{project_dir}/.autoforge/prompts/{name}.md` (or legacy `{project_dir}/prompts/{name}.md`)
2. Base template: `.claude/templates/{name}.template.md`
### Agent Session Flow
1. Check if `.autocoder/features.db` has features (determines initializer vs coding agent)
1. Check if `.autoforge/features.db` has features (determines initializer vs coding agent)
2. Create ClaudeSDKClient with security settings
3. Send prompt and stream response
4. Auto-continue with 3-second delay between sessions

View File

@@ -1,4 +1,4 @@
# AutoCoder
# AutoForge
[![Buy Me A Coffee](https://img.shields.io/badge/Buy%20Me%20A%20Coffee-FFDD00?style=flat&logo=buy-me-a-coffee&logoColor=black)](https://www.buymeacoffee.com/leonvanzyl)
@@ -290,7 +290,7 @@ When test progress increases, the agent sends:
### Using GLM Models (Alternative to Claude)
To use Zhipu AI's GLM models instead of Claude, add these variables to your `.env` file in the AutoCoder directory:
To use Zhipu AI's GLM models instead of Claude, add these variables to your `.env` file in the AutoForge directory:
```bash
ANTHROPIC_BASE_URL=https://api.z.ai/api/anthropic
@@ -301,7 +301,7 @@ ANTHROPIC_DEFAULT_OPUS_MODEL=glm-4.7
ANTHROPIC_DEFAULT_HAIKU_MODEL=glm-4.5-air
```
This routes AutoCoder's API requests through Zhipu's Claude-compatible API, allowing you to use GLM-4.7 and other models. **This only affects AutoCoder** - your global Claude Code settings remain unchanged.
This routes AutoForge's API requests through Zhipu's Claude-compatible API, allowing you to use GLM-4.7 and other models. **This only affects AutoForge** - your global Claude Code settings remain unchanged.
Get an API key at: https://z.ai/subscribe

View File

@@ -183,7 +183,7 @@ class ScheduleOverride(Base):
def get_database_path(project_dir: Path) -> Path:
"""Return the path to the SQLite database for a project."""
from autocoder_paths import get_features_db_path
from autoforge_paths import get_features_db_path
return get_features_db_path(project_dir)
@@ -385,7 +385,7 @@ def create_database(project_dir: Path) -> tuple:
db_url = get_database_url(project_dir)
# Ensure parent directory exists (for .autocoder/ layout)
# Ensure parent directory exists (for .autoforge/ layout)
db_path = get_database_path(project_dir)
db_path.parent.mkdir(parents=True, exist_ok=True)

View File

@@ -1,17 +1,19 @@
"""
Autocoder Path Resolution
AutoForge Path Resolution
=========================
Central module for resolving paths to autocoder-generated files within a project.
Central module for resolving paths to autoforge-generated files within a project.
Implements a dual-path resolution strategy for backward compatibility:
Implements a tri-path resolution strategy for backward compatibility:
1. Check ``project_dir / ".autocoder" / X`` (new layout)
2. Check ``project_dir / X`` (legacy root-level layout)
3. Default to the new location for fresh projects
1. Check ``project_dir / ".autoforge" / X`` (current layout)
2. Check ``project_dir / ".autocoder" / X`` (legacy layout)
3. Check ``project_dir / X`` (legacy root-level layout)
4. Default to the new location for fresh projects
This allows existing projects with root-level ``features.db``, ``.agent.lock``,
etc. to keep working while new projects store everything under ``.autocoder/``.
etc. to keep working while new projects store everything under ``.autoforge/``.
Projects using the old ``.autocoder/`` directory are auto-migrated on next start.
The ``migrate_project_layout`` function can move an old-layout project to the
new layout safely, with full integrity checks for SQLite databases.
@@ -25,10 +27,10 @@ from pathlib import Path
logger = logging.getLogger(__name__)
# ---------------------------------------------------------------------------
# .gitignore content written into every .autocoder/ directory
# .gitignore content written into every .autoforge/ directory
# ---------------------------------------------------------------------------
_GITIGNORE_CONTENT = """\
# Autocoder runtime files
# AutoForge runtime files
features.db
features.db-wal
features.db-shm
@@ -49,15 +51,18 @@ assistant.db-shm
# ---------------------------------------------------------------------------
def _resolve_path(project_dir: Path, filename: str) -> Path:
"""Resolve a file path using dual-path strategy.
"""Resolve a file path using tri-path strategy.
Checks the new ``.autocoder/`` location first, then falls back to the
legacy root-level location. If neither exists, returns the new location
so that newly-created files land in ``.autocoder/``.
Checks the new ``.autoforge/`` location first, then the legacy
``.autocoder/`` location, then the root-level location. If none exist,
returns the new location so that newly-created files land in ``.autoforge/``.
"""
new = project_dir / ".autocoder" / filename
new = project_dir / ".autoforge" / filename
if new.exists():
return new
legacy = project_dir / ".autocoder" / filename
if legacy.exists():
return legacy
old = project_dir / filename
if old.exists():
return old
@@ -65,14 +70,17 @@ def _resolve_path(project_dir: Path, filename: str) -> Path:
def _resolve_dir(project_dir: Path, dirname: str) -> Path:
"""Resolve a directory path using dual-path strategy.
"""Resolve a directory path using tri-path strategy.
Same logic as ``_resolve_path`` but intended for directories such as
``prompts/``.
"""
new = project_dir / ".autocoder" / dirname
new = project_dir / ".autoforge" / dirname
if new.exists():
return new
legacy = project_dir / ".autocoder" / dirname
if legacy.exists():
return legacy
old = project_dir / dirname
if old.exists():
return old
@@ -80,27 +88,27 @@ def _resolve_dir(project_dir: Path, dirname: str) -> Path:
# ---------------------------------------------------------------------------
# .autocoder directory management
# .autoforge directory management
# ---------------------------------------------------------------------------
def get_autocoder_dir(project_dir: Path) -> Path:
"""Return the ``.autocoder`` directory path. Does NOT create it."""
return project_dir / ".autocoder"
def get_autoforge_dir(project_dir: Path) -> Path:
"""Return the ``.autoforge`` directory path. Does NOT create it."""
return project_dir / ".autoforge"
def ensure_autocoder_dir(project_dir: Path) -> Path:
"""Create the ``.autocoder/`` directory (if needed) and write its ``.gitignore``.
def ensure_autoforge_dir(project_dir: Path) -> Path:
"""Create the ``.autoforge/`` directory (if needed) and write its ``.gitignore``.
Returns:
The path to the ``.autocoder`` directory.
The path to the ``.autoforge`` directory.
"""
autocoder_dir = get_autocoder_dir(project_dir)
autocoder_dir.mkdir(parents=True, exist_ok=True)
autoforge_dir = get_autoforge_dir(project_dir)
autoforge_dir.mkdir(parents=True, exist_ok=True)
gitignore_path = autocoder_dir / ".gitignore"
gitignore_path = autoforge_dir / ".gitignore"
gitignore_path.write_text(_GITIGNORE_CONTENT, encoding="utf-8")
return autocoder_dir
return autoforge_dir
# ---------------------------------------------------------------------------
@@ -154,9 +162,9 @@ def get_prompts_dir(project_dir: Path) -> Path:
def get_expand_settings_path(project_dir: Path, uuid_hex: str) -> Path:
"""Return the path for an ephemeral expand-session settings file.
These files are short-lived and always stored in ``.autocoder/``.
These files are short-lived and always stored in ``.autoforge/``.
"""
return project_dir / ".autocoder" / f".claude_settings.expand.{uuid_hex}.json"
return project_dir / ".autoforge" / f".claude_settings.expand.{uuid_hex}.json"
# ---------------------------------------------------------------------------
@@ -166,8 +174,9 @@ def get_expand_settings_path(project_dir: Path, uuid_hex: str) -> Path:
def has_agent_running(project_dir: Path) -> bool:
"""Check whether any agent or dev-server lock file exists at either location.
Inspects both the legacy root-level paths and the new ``.autocoder/``
paths so that a running agent is detected regardless of project layout.
Inspects the legacy root-level paths, the old ``.autocoder/`` paths, and
the new ``.autoforge/`` paths so that a running agent is detected
regardless of project layout.
Returns:
``True`` if any ``.agent.lock`` or ``.devserver.lock`` exists.
@@ -176,8 +185,11 @@ def has_agent_running(project_dir: Path) -> bool:
for name in lock_names:
if (project_dir / name).exists():
return True
# Check both old and new directory names for backward compatibility
if (project_dir / ".autocoder" / name).exists():
return True
if (project_dir / ".autoforge" / name).exists():
return True
return False
@@ -186,7 +198,7 @@ def has_agent_running(project_dir: Path) -> bool:
# ---------------------------------------------------------------------------
def migrate_project_layout(project_dir: Path) -> list[str]:
"""Migrate a project from the legacy root-level layout to ``.autocoder/``.
"""Migrate a project from the legacy root-level layout to ``.autoforge/``.
The migration is incremental and safe:
@@ -199,7 +211,7 @@ def migrate_project_layout(project_dir: Path) -> list[str]:
Returns:
A list of human-readable descriptions of what was migrated, e.g.
``["prompts/ -> .autocoder/prompts/", "features.db -> .autocoder/features.db"]``.
``["prompts/ -> .autoforge/prompts/", "features.db -> .autoforge/features.db"]``.
An empty list means nothing was migrated (either everything is
already migrated, or the agent is running).
"""
@@ -208,18 +220,31 @@ def migrate_project_layout(project_dir: Path) -> list[str]:
logger.warning("Migration skipped: agent or dev-server is running for %s", project_dir)
return []
autocoder_dir = ensure_autocoder_dir(project_dir)
migrated: list[str] = []
# --- 0. Migrate .autocoder/ → .autoforge/ directory -------------------
old_autocoder_dir = project_dir / ".autocoder"
new_autoforge_dir = project_dir / ".autoforge"
if old_autocoder_dir.exists() and old_autocoder_dir.is_dir() and not new_autoforge_dir.exists():
try:
old_autocoder_dir.rename(new_autoforge_dir)
logger.info("Migrated .autocoder/ -> .autoforge/")
migrated: list[str] = [".autocoder/ -> .autoforge/"]
except Exception:
logger.warning("Failed to migrate .autocoder/ -> .autoforge/", exc_info=True)
migrated = []
else:
migrated = []
autoforge_dir = ensure_autoforge_dir(project_dir)
# --- 1. Migrate prompts/ directory -----------------------------------
try:
old_prompts = project_dir / "prompts"
new_prompts = autocoder_dir / "prompts"
new_prompts = autoforge_dir / "prompts"
if old_prompts.exists() and old_prompts.is_dir() and not new_prompts.exists():
shutil.copytree(str(old_prompts), str(new_prompts))
shutil.rmtree(str(old_prompts))
migrated.append("prompts/ -> .autocoder/prompts/")
logger.info("Migrated prompts/ -> .autocoder/prompts/")
migrated.append("prompts/ -> .autoforge/prompts/")
logger.info("Migrated prompts/ -> .autoforge/prompts/")
except Exception:
logger.warning("Failed to migrate prompts/ directory", exc_info=True)
@@ -228,7 +253,7 @@ def migrate_project_layout(project_dir: Path) -> list[str]:
for db_name in db_names:
try:
old_db = project_dir / db_name
new_db = autocoder_dir / db_name
new_db = autoforge_dir / db_name
if old_db.exists() and not new_db.exists():
# Flush WAL to ensure all data is in the main database file
conn = sqlite3.connect(str(old_db))
@@ -263,8 +288,8 @@ def migrate_project_layout(project_dir: Path) -> list[str]:
wal_file = project_dir / f"{db_name}{suffix}"
wal_file.unlink(missing_ok=True)
migrated.append(f"{db_name} -> .autocoder/{db_name}")
logger.info("Migrated %s -> .autocoder/%s", db_name, db_name)
migrated.append(f"{db_name} -> .autoforge/{db_name}")
logger.info("Migrated %s -> .autoforge/%s", db_name, db_name)
except Exception:
logger.warning("Failed to migrate %s", db_name, exc_info=True)
@@ -279,11 +304,11 @@ def migrate_project_layout(project_dir: Path) -> list[str]:
for filename in simple_files:
try:
old_file = project_dir / filename
new_file = autocoder_dir / filename
new_file = autoforge_dir / filename
if old_file.exists() and not new_file.exists():
shutil.move(str(old_file), str(new_file))
migrated.append(f"{filename} -> .autocoder/{filename}")
logger.info("Migrated %s -> .autocoder/%s", filename, filename)
migrated.append(f"{filename} -> .autoforge/{filename}")
logger.info("Migrated %s -> .autoforge/%s", filename, filename)
except Exception:
logger.warning("Failed to migrate %s", filename, exc_info=True)

View File

@@ -221,11 +221,11 @@ def main() -> None:
print("Use an absolute path or register the project first.")
return
# Migrate project layout to .autocoder/ if needed (idempotent, safe)
from autocoder_paths import migrate_project_layout
# Migrate project layout to .autoforge/ if needed (idempotent, safe)
from autoforge_paths import migrate_project_layout
migrated = migrate_project_layout(project_dir)
if migrated:
print(f"Migrated project files to .autocoder/: {', '.join(migrated)}", flush=True)
print(f"Migrated project files to .autoforge/: {', '.join(migrated)}", flush=True)
# Parse batch testing feature IDs (comma-separated string -> list[int])
testing_feature_ids: list[int] | None = None

View File

@@ -382,7 +382,7 @@ def create_client(
project_dir.mkdir(parents=True, exist_ok=True)
# Write settings to a file in the project directory
from autocoder_paths import get_claude_settings_path
from autoforge_paths import get_claude_settings_path
settings_file = get_claude_settings_path(project_dir)
settings_file.parent.mkdir(parents=True, exist_ok=True)
with open(settings_file, "w") as f:
@@ -450,7 +450,7 @@ def create_client(
# Build environment overrides for API endpoint configuration
# These override system env vars for the Claude CLI subprocess,
# allowing AutoCoder to use alternative APIs (e.g., GLM) without
# allowing AutoForge to use alternative APIs (e.g., GLM) without
# affecting the user's global Claude Code settings
sdk_env = {}
for var in API_ENV_VARS:

View File

@@ -7,7 +7,7 @@ subprocesses. Imported by both ``client.py`` (agent sessions) and
``server/services/chat_constants.py`` (chat sessions) to avoid maintaining
duplicate lists.
These allow autocoder to use alternative API endpoints (Ollama, GLM,
These allow autoforge to use alternative API endpoints (Ollama, GLM,
Vertex AI) without affecting the user's global Claude Code settings.
"""

View File

@@ -179,7 +179,7 @@ To see what you can reduce:
```bash
# Count commands by prefix
grep "^ - name:" .autocoder/allowed_commands.yaml | \
grep "^ - name:" .autoforge/allowed_commands.yaml | \
sed 's/^ - name: //' | \
cut -d' ' -f1 | \
sort | uniq -c | sort -rn

View File

@@ -1,4 +1,4 @@
# AutoCoder Security Configuration Examples
# AutoForge Security Configuration Examples
This directory contains example configuration files for controlling which bash commands the autonomous coding agent can execute.
@@ -18,11 +18,11 @@ This directory contains example configuration files for controlling which bash c
### For a Single Project (Most Common)
When you create a new project with AutoCoder, it automatically creates:
When you create a new project with AutoForge, it automatically creates:
```text
my-project/
.autocoder/
.autoforge/
allowed_commands.yaml ← Automatically created from template
```
@@ -34,17 +34,17 @@ If you want commands available across **all projects**, manually create:
```bash
# Copy the example to your home directory
cp examples/org_config.yaml ~/.autocoder/config.yaml
cp examples/org_config.yaml ~/.autoforge/config.yaml
# Edit it to add org-wide commands
nano ~/.autocoder/config.yaml
nano ~/.autoforge/config.yaml
```
---
## Project-Level Configuration
**File:** `{project_dir}/.autocoder/allowed_commands.yaml`
**File:** `{project_dir}/.autoforge/allowed_commands.yaml`
**Purpose:** Define commands needed for THIS specific project.
@@ -82,7 +82,7 @@ commands:
## Organization-Level Configuration
**File:** `~/.autocoder/config.yaml`
**File:** `~/.autoforge/config.yaml`
**Purpose:** Define commands and policies for ALL projects.
@@ -127,13 +127,13 @@ When the agent tries to run a command, the system checks in this order:
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ 2. ORG BLOCKLIST (~/.autocoder/config.yaml) │
│ 2. ORG BLOCKLIST (~/.autoforge/config.yaml) │
│ Commands you block organization-wide │
│ ❌ Projects CANNOT override these │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ 3. ORG ALLOWLIST (~/.autocoder/config.yaml) │
│ 3. ORG ALLOWLIST (~/.autoforge/config.yaml) │
│ Commands available to all projects │
│ ✅ Automatically available │
└─────────────────────────────────────────────────────┘
@@ -145,7 +145,7 @@ When the agent tries to run a command, the system checks in this order:
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ 5. PROJECT ALLOWLIST (.autocoder/allowed_commands) │
│ 5. PROJECT ALLOWLIST (.autoforge/allowed_commands) │
│ Project-specific commands │
│ ✅ Available only to this project │
└─────────────────────────────────────────────────────┘
@@ -195,7 +195,7 @@ Matches:
### iOS Development
**Project config** (`.autocoder/allowed_commands.yaml`):
**Project config** (`.autoforge/allowed_commands.yaml`):
```yaml
version: 1
commands:
@@ -245,7 +245,7 @@ commands:
### Enterprise Organization (Restrictive)
**Org config** (`~/.autocoder/config.yaml`):
**Org config** (`~/.autoforge/config.yaml`):
```yaml
version: 1
@@ -265,7 +265,7 @@ blocked_commands:
### Startup Team (Permissive)
**Org config** (`~/.autocoder/config.yaml`):
**Org config** (`~/.autoforge/config.yaml`):
```yaml
version: 1
@@ -394,7 +394,7 @@ These commands are **NEVER allowed**, even with user approval:
**Solution:** Add the command to your project config:
```yaml
# In .autocoder/allowed_commands.yaml
# In .autoforge/allowed_commands.yaml
commands:
- name: X
description: What this command does
@@ -405,7 +405,7 @@ commands:
**Cause:** The command is in the org blocklist or hardcoded blocklist.
**Solution:**
- If in org blocklist: Edit `~/.autocoder/config.yaml` to remove it
- If in org blocklist: Edit `~/.autoforge/config.yaml` to remove it
- If in hardcoded blocklist: Cannot be allowed (by design)
### Error: "Could not parse YAML config"
@@ -422,8 +422,8 @@ commands:
**Solution:**
1. Restart the agent (changes are loaded on startup)
2. Verify file location:
- Project: `{project}/.autocoder/allowed_commands.yaml`
- Org: `~/.autocoder/config.yaml` (must be manually created)
- Project: `{project}/.autoforge/allowed_commands.yaml`
- Org: `~/.autoforge/config.yaml` (must be manually created)
3. Check YAML is valid (run through a YAML validator)
---
@@ -432,7 +432,7 @@ commands:
### Running the Tests
AutoCoder has comprehensive tests for the security system:
AutoForge has comprehensive tests for the security system:
**Unit Tests** (136 tests - fast):
```bash
@@ -481,7 +481,7 @@ python start.py
cd path/to/security-test
# Edit the config
nano .autocoder/allowed_commands.yaml
nano .autoforge/allowed_commands.yaml
```
**3. Add a test command (e.g., Swift):**
@@ -509,7 +509,7 @@ Or:
```text
Command 'wget' is not allowed.
To allow this command:
1. Add to .autocoder/allowed_commands.yaml for this project, OR
1. Add to .autoforge/allowed_commands.yaml for this project, OR
2. Request mid-session approval (the agent can ask)
```

View File

@@ -1,6 +1,6 @@
# Organization-Level AutoCoder Configuration
# Organization-Level AutoForge Configuration
# ============================================
# Location: ~/.autocoder/config.yaml
# Location: ~/.autoforge/config.yaml
#
# IMPORTANT: This file is OPTIONAL and must be manually created by you.
# It does NOT exist by default.
@@ -22,7 +22,7 @@ version: 1
# Organization-Wide Allowed Commands
# ==========================================
# These commands become available to ALL projects automatically.
# Projects don't need to add them to their own .autocoder/allowed_commands.yaml
# Projects don't need to add them to their own .autoforge/allowed_commands.yaml
#
# By default, this is empty. Uncomment and add commands as needed.
@@ -122,7 +122,7 @@ approval_timeout_minutes: 5
# Default commands: npm, git, curl, ls, cat, etc.
# Always available to all projects.
#
# 5. Project Allowed Commands (.autocoder/allowed_commands.yaml)
# 5. Project Allowed Commands (.autoforge/allowed_commands.yaml)
# Project-specific commands defined in each project.
# LOWEST PRIORITY (can't override blocks above).
#
@@ -165,7 +165,7 @@ approval_timeout_minutes: 5
# ==========================================
# To Create This File
# ==========================================
# 1. Copy this example to: ~/.autocoder/config.yaml
# 1. Copy this example to: ~/.autoforge/config.yaml
# 2. Uncomment and customize the sections you need
# 3. Leave empty lists if you don't need org-level controls
#

View File

@@ -1,12 +1,12 @@
# Project-Specific Allowed Commands
# ==================================
# Location: {project_dir}/.autocoder/allowed_commands.yaml
# Location: {project_dir}/.autoforge/allowed_commands.yaml
#
# This file defines bash commands that the autonomous coding agent can use
# for THIS SPECIFIC PROJECT, beyond the default allowed commands.
#
# When you create a new project, AutoCoder automatically creates this file
# in your project's .autocoder/ directory. You can customize it for your
# When you create a new project, AutoForge automatically creates this file
# in your project's .autoforge/ directory. You can customize it for your
# project's specific needs (iOS, Rust, Python, etc.).
version: 1
@@ -115,7 +115,7 @@ commands: []
# Limits:
# - Maximum 100 commands per project
# - Commands in the blocklist (sudo, dd, shutdown, etc.) can NEVER be allowed
# - Org-level blocked commands (see ~/.autocoder/config.yaml) cannot be overridden
# - Org-level blocked commands (see ~/.autoforge/config.yaml) cannot be overridden
#
# Default Allowed Commands (always available):
# File operations: ls, cat, head, tail, wc, grep, cp, mkdir, mv, rm, touch

View File

@@ -40,11 +40,11 @@ from server.utils.process_utils import kill_process_tree
logger = logging.getLogger(__name__)
# Root directory of autocoder (where this script and autonomous_agent_demo.py live)
AUTOCODER_ROOT = Path(__file__).parent.resolve()
# Root directory of autoforge (where this script and autonomous_agent_demo.py live)
AUTOFORGE_ROOT = Path(__file__).parent.resolve()
# Debug log file path
DEBUG_LOG_FILE = AUTOCODER_ROOT / "orchestrator_debug.log"
DEBUG_LOG_FILE = AUTOFORGE_ROOT / "orchestrator_debug.log"
class DebugLogger:
@@ -823,7 +823,7 @@ class ParallelOrchestrator:
cmd = [
sys.executable,
"-u", # Force unbuffered stdout/stderr
str(AUTOCODER_ROOT / "autonomous_agent_demo.py"),
str(AUTOFORGE_ROOT / "autonomous_agent_demo.py"),
"--project-dir", str(self.project_dir),
"--max-iterations", "1",
"--agent-type", "coding",
@@ -889,7 +889,7 @@ class ParallelOrchestrator:
cmd = [
sys.executable,
"-u",
str(AUTOCODER_ROOT / "autonomous_agent_demo.py"),
str(AUTOFORGE_ROOT / "autonomous_agent_demo.py"),
"--project-dir", str(self.project_dir),
"--max-iterations", "1",
"--agent-type", "coding",
@@ -992,7 +992,7 @@ class ParallelOrchestrator:
cmd = [
sys.executable,
"-u",
str(AUTOCODER_ROOT / "autonomous_agent_demo.py"),
str(AUTOFORGE_ROOT / "autonomous_agent_demo.py"),
"--project-dir", str(self.project_dir),
"--max-iterations", "1",
"--agent-type", "testing",
@@ -1053,7 +1053,7 @@ class ParallelOrchestrator:
cmd = [
sys.executable, "-u",
str(AUTOCODER_ROOT / "autonomous_agent_demo.py"),
str(AUTOFORGE_ROOT / "autonomous_agent_demo.py"),
"--project-dir", str(self.project_dir),
"--agent-type", "initializer",
"--max-iterations", "1",
@@ -1073,7 +1073,7 @@ class ParallelOrchestrator:
"text": True,
"encoding": "utf-8",
"errors": "replace",
"cwd": str(AUTOCODER_ROOT),
"cwd": str(AUTOFORGE_ROOT),
"env": {**os.environ, "PYTHONUNBUFFERED": "1"},
}
if sys.platform == "win32":

View File

@@ -46,7 +46,7 @@ def has_features(project_dir: Path) -> bool:
return True
# Check SQLite database
from autocoder_paths import get_features_db_path
from autoforge_paths import get_features_db_path
db_file = get_features_db_path(project_dir)
if not db_file.exists():
return False
@@ -72,7 +72,7 @@ def count_passing_tests(project_dir: Path) -> tuple[int, int, int]:
Returns:
(passing_count, in_progress_count, total_count)
"""
from autocoder_paths import get_features_db_path
from autoforge_paths import get_features_db_path
db_file = get_features_db_path(project_dir)
if not db_file.exists():
return 0, 0, 0
@@ -122,7 +122,7 @@ def get_all_passing_features(project_dir: Path) -> list[dict]:
Returns:
List of dicts with id, category, name for each passing feature
"""
from autocoder_paths import get_features_db_path
from autoforge_paths import get_features_db_path
db_file = get_features_db_path(project_dir)
if not db_file.exists():
return []
@@ -147,7 +147,7 @@ def send_progress_webhook(passing: int, total: int, project_dir: Path) -> None:
if not WEBHOOK_URL:
return # Webhook not configured
from autocoder_paths import get_progress_cache_path
from autoforge_paths import get_progress_cache_path
cache_file = get_progress_cache_path(project_dir)
previous = 0
previous_passing_ids = set()

View File

@@ -19,7 +19,7 @@ TEMPLATES_DIR = Path(__file__).parent / ".claude" / "templates"
def get_project_prompts_dir(project_dir: Path) -> Path:
"""Get the prompts directory for a specific project."""
from autocoder_paths import get_prompts_dir
from autoforge_paths import get_prompts_dir
return get_prompts_dir(project_dir)
@@ -315,9 +315,9 @@ def scaffold_project_prompts(project_dir: Path) -> Path:
project_prompts = get_project_prompts_dir(project_dir)
project_prompts.mkdir(parents=True, exist_ok=True)
# Create .autocoder directory with .gitignore for runtime files
from autocoder_paths import ensure_autocoder_dir
autocoder_dir = ensure_autocoder_dir(project_dir)
# Create .autoforge directory with .gitignore for runtime files
from autoforge_paths import ensure_autoforge_dir
autoforge_dir = ensure_autoforge_dir(project_dir)
# Define template mappings: (source_template, destination_name)
templates = [
@@ -340,14 +340,14 @@ def scaffold_project_prompts(project_dir: Path) -> Path:
except (OSError, PermissionError) as e:
print(f" Warning: Could not copy {dest_name}: {e}")
# Copy allowed_commands.yaml template to .autocoder/
# Copy allowed_commands.yaml template to .autoforge/
examples_dir = Path(__file__).parent / "examples"
allowed_commands_template = examples_dir / "project_allowed_commands.yaml"
allowed_commands_dest = autocoder_dir / "allowed_commands.yaml"
allowed_commands_dest = autoforge_dir / "allowed_commands.yaml"
if allowed_commands_template.exists() and not allowed_commands_dest.exists():
try:
shutil.copy(allowed_commands_template, allowed_commands_dest)
copied_files.append(".autocoder/allowed_commands.yaml")
copied_files.append(".autoforge/allowed_commands.yaml")
except (OSError, PermissionError) as e:
print(f" Warning: Could not copy allowed_commands.yaml: {e}")

View File

@@ -3,7 +3,7 @@ Project Registry Module
=======================
Cross-platform project registry for storing project name to path mappings.
Uses SQLite database stored at ~/.autocoder/registry.db.
Uses SQLite database stored at ~/.autoforge/registry.db.
"""
import logging
@@ -23,6 +23,22 @@ from sqlalchemy.orm import DeclarativeBase, sessionmaker
logger = logging.getLogger(__name__)
def _migrate_registry_dir() -> None:
"""Migrate ~/.autocoder/ to ~/.autoforge/ if needed.
Provides backward compatibility by automatically renaming the old
config directory to the new location on first access.
"""
old_dir = Path.home() / ".autocoder"
new_dir = Path.home() / ".autoforge"
if old_dir.exists() and not new_dir.exists():
try:
old_dir.rename(new_dir)
logger.info("Migrated registry directory: ~/.autocoder/ -> ~/.autoforge/")
except Exception:
logger.warning("Failed to migrate ~/.autocoder/ to ~/.autoforge/", exc_info=True)
# =============================================================================
# Model Configuration (Single Source of Truth)
# =============================================================================
@@ -120,12 +136,15 @@ _engine_lock = threading.Lock()
def get_config_dir() -> Path:
"""
Get the config directory: ~/.autocoder/
Get the config directory: ~/.autoforge/
Automatically migrates from ~/.autocoder/ if needed.
Returns:
Path to ~/.autocoder/ (created if it doesn't exist)
Path to ~/.autoforge/ (created if it doesn't exist)
"""
config_dir = Path.home() / ".autocoder"
_migrate_registry_dir()
config_dir = Path.home() / ".autoforge"
config_dir.mkdir(parents=True, exist_ok=True)
return config_dir

View File

@@ -553,14 +553,23 @@ def get_org_config_path() -> Path:
Get the organization-level config file path.
Returns:
Path to ~/.autocoder/config.yaml
Path to ~/.autoforge/config.yaml (falls back to ~/.autocoder/config.yaml)
"""
return Path.home() / ".autocoder" / "config.yaml"
new_path = Path.home() / ".autoforge" / "config.yaml"
if new_path.exists():
return new_path
# Backward compatibility: check old location
old_path = Path.home() / ".autocoder" / "config.yaml"
if old_path.exists():
return old_path
return new_path
def load_org_config() -> Optional[dict]:
"""
Load organization-level config from ~/.autocoder/config.yaml.
Load organization-level config from ~/.autoforge/config.yaml.
Falls back to ~/.autocoder/config.yaml for backward compatibility.
Returns:
Dict with parsed org config, or None if file doesn't exist or is invalid
@@ -630,6 +639,9 @@ def load_project_commands(project_dir: Path) -> Optional[dict]:
Returns:
Dict with parsed YAML config, or None if file doesn't exist or is invalid
"""
# Check new location first, fall back to old for backward compatibility
config_path = project_dir.resolve() / ".autoforge" / "allowed_commands.yaml"
if not config_path.exists():
config_path = project_dir.resolve() / ".autocoder" / "allowed_commands.yaml"
if not config_path.exists():
@@ -909,7 +921,7 @@ async def bash_security_hook(input_data, tool_use_id=None, context=None):
# Provide helpful error message with config hint
error_msg = f"Command '{cmd}' is not allowed.\n"
error_msg += "To allow this command:\n"
error_msg += " 1. Add to .autocoder/allowed_commands.yaml for this project, OR\n"
error_msg += " 1. Add to .autoforge/allowed_commands.yaml for this project, OR\n"
error_msg += " 2. Request mid-session approval (the agent can ask)\n"
error_msg += "Note: Some commands are blocked at org-level and cannot be overridden."
return {

View File

@@ -94,7 +94,7 @@ logger = logging.getLogger(__name__)
# Check if remote access is enabled via environment variable
# Set by start_ui.py when --host is not 127.0.0.1
ALLOW_REMOTE = os.environ.get("AUTOCODER_ALLOW_REMOTE", "").lower() in ("1", "true", "yes")
ALLOW_REMOTE = os.environ.get("AUTOFORGE_ALLOW_REMOTE", "").lower() in ("1", "true", "yes")
if ALLOW_REMOTE:
logger.warning(
@@ -133,7 +133,7 @@ else:
if not ALLOW_REMOTE:
@app.middleware("http")
async def require_localhost(request: Request, call_next):
"""Only allow requests from localhost (disabled when AUTOCODER_ALLOW_REMOTE=1)."""
"""Only allow requests from localhost (disabled when AUTOFORGE_ALLOW_REMOTE=1)."""
client_host = request.client.host if request.client else None
# Allow localhost connections

View File

@@ -121,7 +121,7 @@ async def expand_project_websocket(websocket: WebSocket, project_name: str):
return
# Verify project has app_spec.txt
from autocoder_paths import get_prompts_dir
from autoforge_paths import get_prompts_dir
spec_path = get_prompts_dir(project_dir) / "app_spec.txt"
if not spec_path.exists():
await websocket.close(code=4004, reason="Project has no spec. Create spec first.")

View File

@@ -126,7 +126,7 @@ async def list_features(project_name: str):
if not project_dir.exists():
raise HTTPException(status_code=404, detail="Project directory not found")
from autocoder_paths import get_features_db_path
from autoforge_paths import get_features_db_path
db_file = get_features_db_path(project_dir)
if not db_file.exists():
return FeatureListResponse(pending=[], in_progress=[], done=[])
@@ -322,7 +322,7 @@ async def get_dependency_graph(project_name: str):
if not project_dir.exists():
raise HTTPException(status_code=404, detail="Project directory not found")
from autocoder_paths import get_features_db_path
from autoforge_paths import get_features_db_path
db_file = get_features_db_path(project_dir)
if not db_file.exists():
return DependencyGraphResponse(nodes=[], edges=[])
@@ -388,7 +388,7 @@ async def get_feature(project_name: str, feature_id: int):
if not project_dir.exists():
raise HTTPException(status_code=404, detail="Project directory not found")
from autocoder_paths import get_features_db_path
from autoforge_paths import get_features_db_path
db_file = get_features_db_path(project_dir)
if not db_file.exists():
raise HTTPException(status_code=404, detail="No features database found")

View File

@@ -276,7 +276,7 @@ async def delete_project(name: str, delete_files: bool = False):
raise HTTPException(status_code=404, detail=f"Project '{name}' not found")
# Check if agent is running
from autocoder_paths import has_agent_running
from autoforge_paths import has_agent_running
if has_agent_running(project_dir):
raise HTTPException(
status_code=409,
@@ -407,7 +407,7 @@ async def reset_project(name: str, full_reset: bool = False):
raise HTTPException(status_code=404, detail="Project directory not found")
# Check if agent is running
from autocoder_paths import has_agent_running
from autoforge_paths import has_agent_running
if has_agent_running(project_dir):
raise HTTPException(
status_code=409,
@@ -424,7 +424,7 @@ async def reset_project(name: str, full_reset: bool = False):
deleted_files: list[str] = []
from autocoder_paths import (
from autoforge_paths import (
get_assistant_db_path,
get_claude_assistant_settings_path,
get_claude_settings_path,
@@ -466,7 +466,7 @@ async def reset_project(name: str, full_reset: bool = False):
# Full reset: also delete prompts directory
if full_reset:
from autocoder_paths import get_prompts_dir
from autoforge_paths import get_prompts_dir
# Delete prompts from both possible locations
for prompts_dir in [get_prompts_dir(project_dir), project_dir / "prompts"]:
if prompts_dir.exists():

View File

@@ -105,7 +105,7 @@ async def get_spec_file_status(project_name: str):
if not project_dir.exists():
raise HTTPException(status_code=404, detail="Project directory not found")
from autocoder_paths import get_prompts_dir
from autoforge_paths import get_prompts_dir
status_file = get_prompts_dir(project_dir) / ".spec_status.json"
if not status_file.exists():

View File

@@ -64,7 +64,7 @@ def get_system_prompt(project_name: str, project_dir: Path) -> str:
"""Generate the system prompt for the assistant with project context."""
# Try to load app_spec.txt for context
app_spec_content = ""
from autocoder_paths import get_prompts_dir
from autoforge_paths import get_prompts_dir
app_spec_path = get_prompts_dir(project_dir) / "app_spec.txt"
if app_spec_path.exists():
try:
@@ -224,7 +224,7 @@ class AssistantChatSession:
"allow": permissions_list,
},
}
from autocoder_paths import get_claude_assistant_settings_path
from autoforge_paths import get_claude_assistant_settings_path
settings_file = get_claude_assistant_settings_path(self.project_dir)
settings_file.parent.mkdir(parents=True, exist_ok=True)
with open(settings_file, "w") as f:

View File

@@ -64,7 +64,7 @@ class ConversationMessage(Base):
def get_db_path(project_dir: Path) -> Path:
"""Get the path to the assistant database for a project."""
from autocoder_paths import get_assistant_db_path
from autoforge_paths import get_assistant_db_path
return get_assistant_db_path(project_dir)

View File

@@ -14,7 +14,7 @@ from pathlib import Path
from typing import AsyncGenerator
# -------------------------------------------------------------------
# Root directory of the autocoder project (repository root).
# Root directory of the autoforge project (repository root).
# Used throughout the server package whenever the repo root is needed.
# -------------------------------------------------------------------
ROOT_DIR = Path(__file__).parent.parent.parent

View File

@@ -115,7 +115,7 @@ class DevServerProcessManager:
self._callbacks_lock = threading.Lock()
# Lock file to prevent multiple instances (stored in project directory)
from autocoder_paths import get_devserver_lock_path
from autoforge_paths import get_devserver_lock_path
self.lock_file = get_devserver_lock_path(self.project_dir)
@property
@@ -504,10 +504,10 @@ def cleanup_orphaned_devserver_locks() -> int:
continue
# Check both legacy and new locations for lock files
from autocoder_paths import get_autocoder_dir
from autoforge_paths import get_autoforge_dir
lock_locations = [
project_path / ".devserver.lock",
get_autocoder_dir(project_path) / ".devserver.lock",
get_autoforge_dir(project_path) / ".devserver.lock",
]
lock_file = None
for candidate in lock_locations:

View File

@@ -103,7 +103,7 @@ class ExpandChatSession:
return
# Verify project has existing spec
from autocoder_paths import get_prompts_dir
from autoforge_paths import get_prompts_dir
spec_path = get_prompts_dir(self.project_dir) / "app_spec.txt"
if not spec_path.exists():
yield {
@@ -142,7 +142,7 @@ class ExpandChatSession:
],
},
}
from autocoder_paths import get_expand_settings_path
from autoforge_paths import get_expand_settings_path
settings_file = get_expand_settings_path(self.project_dir, uuid.uuid4().hex)
settings_file.parent.mkdir(parents=True, exist_ok=True)
self._settings_file = settings_file

View File

@@ -92,7 +92,7 @@ class AgentProcessManager:
self._callbacks_lock = threading.Lock()
# Lock file to prevent multiple instances (stored in project directory)
from autocoder_paths import get_agent_lock_path
from autoforge_paths import get_agent_lock_path
self.lock_file = get_agent_lock_path(self.project_dir)
@property
@@ -587,10 +587,10 @@ def cleanup_orphaned_locks() -> int:
continue
# Check both legacy and new locations for lock files
from autocoder_paths import get_autocoder_dir
from autoforge_paths import get_autoforge_dir
lock_locations = [
project_path / ".agent.lock",
get_autocoder_dir(project_path) / ".agent.lock",
get_autoforge_dir(project_path) / ".agent.lock",
]
lock_file = None
for candidate in lock_locations:

View File

@@ -6,7 +6,7 @@ Handles project type detection and dev command configuration.
Detects project types by scanning for configuration files and provides
default or custom dev commands for each project.
Configuration is stored in {project_dir}/.autocoder/config.json.
Configuration is stored in {project_dir}/.autoforge/config.json.
"""
import json
@@ -88,13 +88,22 @@ def _get_config_path(project_dir: Path) -> Path:
"""
Get the path to the project config file.
Checks the new .autoforge/ location first, falls back to .autocoder/
for backward compatibility.
Args:
project_dir: Path to the project directory.
Returns:
Path to the .autocoder/config.json file.
Path to the config.json file in the appropriate directory.
"""
return project_dir / ".autocoder" / "config.json"
new_path = project_dir / ".autoforge" / "config.json"
if new_path.exists():
return new_path
old_path = project_dir / ".autocoder" / "config.json"
if old_path.exists():
return old_path
return new_path
def _load_config(project_dir: Path) -> dict:
@@ -137,7 +146,7 @@ def _save_config(project_dir: Path, config: dict) -> None:
"""
Save the project configuration to disk.
Creates the .autocoder directory if it doesn't exist.
Creates the .autoforge directory if it doesn't exist.
Args:
project_dir: Path to the project directory.
@@ -148,7 +157,7 @@ def _save_config(project_dir: Path, config: dict) -> None:
"""
config_path = _get_config_path(project_dir)
# Ensure the .autocoder directory exists
# Ensure the .autoforge directory exists
config_path.parent.mkdir(parents=True, exist_ok=True)
try:
@@ -408,11 +417,11 @@ def clear_dev_command(project_dir: Path) -> None:
config_path.unlink(missing_ok=True)
logger.info("Removed empty config file for %s", project_dir.name)
# Also remove .autocoder directory if empty
autocoder_dir = config_path.parent
if autocoder_dir.exists() and not any(autocoder_dir.iterdir()):
autocoder_dir.rmdir()
logger.debug("Removed empty .autocoder directory for %s", project_dir.name)
# Also remove .autoforge directory if empty
autoforge_dir = config_path.parent
if autoforge_dir.exists() and not any(autoforge_dir.iterdir()):
autoforge_dir.rmdir()
logger.debug("Removed empty .autoforge directory for %s", project_dir.name)
except OSError as e:
logger.warning("Failed to clean up config for %s: %s", project_dir.name, e)
else:

View File

@@ -92,7 +92,7 @@ class SchedulerService:
async def _load_project_schedules(self, project_name: str, project_dir: Path) -> int:
"""Load schedules for a single project. Returns count of schedules loaded."""
from api.database import Schedule, create_database
from autocoder_paths import get_features_db_path
from autoforge_paths import get_features_db_path
db_path = get_features_db_path(project_dir)
if not db_path.exists():
@@ -568,7 +568,7 @@ class SchedulerService:
):
"""Check if a project should be started on server startup."""
from api.database import Schedule, ScheduleOverride, create_database
from autocoder_paths import get_features_db_path
from autoforge_paths import get_features_db_path
db_path = get_features_db_path(project_dir)
if not db_path.exists():

View File

@@ -95,7 +95,7 @@ class SpecChatSession:
# Delete app_spec.txt so Claude can create it fresh
# The SDK requires reading existing files before writing, but app_spec.txt is created new
# Note: We keep initializer_prompt.md so Claude can read and update the template
from autocoder_paths import get_prompts_dir
from autoforge_paths import get_prompts_dir
prompts_dir = get_prompts_dir(self.project_dir)
app_spec_path = prompts_dir / "app_spec.txt"
if app_spec_path.exists():
@@ -116,7 +116,7 @@ class SpecChatSession:
],
},
}
from autocoder_paths import get_claude_settings_path
from autoforge_paths import get_claude_settings_path
settings_file = get_claude_settings_path(self.project_dir)
settings_file.parent.mkdir(parents=True, exist_ok=True)
with open(settings_file, "w") as f:

View File

@@ -3,7 +3,7 @@ cd /d "%~dp0"
echo.
echo ========================================
echo Autonomous Coding Agent
echo AutoForge - Autonomous Coding Agent
echo ========================================
echo.

View File

@@ -82,7 +82,7 @@ def get_existing_projects() -> list[tuple[str, Path]]:
def display_menu(projects: list[tuple[str, Path]]) -> None:
"""Display the main menu."""
print("\n" + "=" * 50)
print(" Autonomous Coding Agent Launcher")
print(" AutoForge - Autonomous Coding Agent")
print("=" * 50)
print("\n[1] Create new project")

View File

@@ -3,7 +3,7 @@ cd "$(dirname "$0")"
echo ""
echo "========================================"
echo " Autonomous Coding Agent"
echo " AutoForge - Autonomous Coding Agent"
echo "========================================"
echo ""

View File

@@ -1,11 +1,11 @@
@echo off
cd /d "%~dp0"
REM AutoCoder UI Launcher for Windows
REM AutoForge UI Launcher for Windows
REM This script launches the web UI for the autonomous coding agent.
echo.
echo ====================================
echo AutoCoder UI
echo AutoForge UI
echo ====================================
echo.

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env python3
"""
AutoCoder UI Launcher
AutoForge UI Launcher
=====================
Automated launcher that handles all setup:
@@ -265,7 +265,7 @@ def start_dev_server(port: int, host: str = "127.0.0.1") -> tuple:
# Set environment for remote access if needed
env = os.environ.copy()
if host != "127.0.0.1":
env["AUTOCODER_ALLOW_REMOTE"] = "1"
env["AUTOFORGE_ALLOW_REMOTE"] = "1"
# Start FastAPI
backend = subprocess.Popen([
@@ -297,7 +297,7 @@ def start_production_server(port: int, host: str = "127.0.0.1"):
# Enable remote access in server if not localhost
if host != "127.0.0.1":
env["AUTOCODER_ALLOW_REMOTE"] = "1"
env["AUTOFORGE_ALLOW_REMOTE"] = "1"
# NOTE: --reload is NOT used because on Windows it breaks asyncio subprocess
# support (uvicorn's reload worker doesn't inherit the ProactorEventLoop policy).
@@ -313,7 +313,7 @@ def start_production_server(port: int, host: str = "127.0.0.1"):
def main() -> None:
"""Main entry point."""
parser = argparse.ArgumentParser(description="AutoCoder UI Launcher")
parser = argparse.ArgumentParser(description="AutoForge UI Launcher")
parser.add_argument("--dev", action="store_true", help="Run in development mode with Vite hot reload")
parser.add_argument("--host", default="127.0.0.1", help="Host to bind to (default: 127.0.0.1)")
parser.add_argument("--port", type=int, default=None, help="Port to bind to (default: auto-detect from 8888)")
@@ -328,7 +328,7 @@ def main() -> None:
print(" SECURITY WARNING")
print("!" * 50)
print(f" Remote access enabled on host: {host}")
print(" The AutoCoder UI will be accessible from other machines.")
print(" The AutoForge UI will be accessible from other machines.")
print(" Ensure you understand the security implications:")
print(" - The agent has file system access to project directories")
print(" - The API can start/stop agents and modify files")
@@ -336,7 +336,7 @@ def main() -> None:
print("!" * 50 + "\n")
print("=" * 50)
print(" AutoCoder UI Setup")
print(" AutoForge UI Setup")
print("=" * 50)
total_steps = 6 if not dev_mode else 5

View File

@@ -1,11 +1,11 @@
#!/bin/bash
cd "$(dirname "$0")"
# AutoCoder UI Launcher for Unix/Linux/macOS
# AutoForge UI Launcher for Unix/Linux/macOS
# This script launches the web UI for the autonomous coding agent.
echo ""
echo "===================================="
echo " AutoCoder UI"
echo " AutoForge UI"
echo "===================================="
echo ""

View File

@@ -273,11 +273,11 @@ def test_yaml_loading():
with tempfile.TemporaryDirectory() as tmpdir:
project_dir = Path(tmpdir)
autocoder_dir = project_dir / ".autocoder"
autocoder_dir.mkdir()
autoforge_dir = project_dir / ".autoforge"
autoforge_dir.mkdir()
# Test 1: Valid YAML
config_path = autocoder_dir / "allowed_commands.yaml"
config_path = autoforge_dir / "allowed_commands.yaml"
config_path.write_text("""version: 1
commands:
- name: swift
@@ -297,7 +297,7 @@ commands:
failed += 1
# Test 2: Missing file returns None
(project_dir / ".autocoder" / "allowed_commands.yaml").unlink()
(project_dir / ".autoforge" / "allowed_commands.yaml").unlink()
config = load_project_commands(project_dir)
if config is None:
print(" PASS: Missing file returns None")
@@ -407,11 +407,11 @@ def test_project_commands():
with tempfile.TemporaryDirectory() as tmpdir:
project_dir = Path(tmpdir)
autocoder_dir = project_dir / ".autocoder"
autocoder_dir.mkdir()
autoforge_dir = project_dir / ".autoforge"
autoforge_dir.mkdir()
# Create a config with Swift commands
config_path = autocoder_dir / "allowed_commands.yaml"
config_path = autoforge_dir / "allowed_commands.yaml"
config_path.write_text("""version: 1
commands:
- name: swift
@@ -482,7 +482,7 @@ def test_org_config_loading():
with tempfile.TemporaryDirectory() as tmpdir:
# Use temporary_home for cross-platform compatibility
with temporary_home(tmpdir):
org_dir = Path(tmpdir) / ".autocoder"
org_dir = Path(tmpdir) / ".autoforge"
org_dir.mkdir()
org_config_path = org_dir / "config.yaml"
@@ -576,7 +576,7 @@ def test_hierarchy_resolution():
with tempfile.TemporaryDirectory() as tmpproject:
# Use temporary_home for cross-platform compatibility
with temporary_home(tmphome):
org_dir = Path(tmphome) / ".autocoder"
org_dir = Path(tmphome) / ".autoforge"
org_dir.mkdir()
org_config_path = org_dir / "config.yaml"
@@ -593,9 +593,9 @@ blocked_commands:
""")
project_dir = Path(tmpproject)
project_autocoder = project_dir / ".autocoder"
project_autocoder.mkdir()
project_config = project_autocoder / "allowed_commands.yaml"
project_autoforge = project_dir / ".autoforge"
project_autoforge.mkdir()
project_config = project_autoforge / "allowed_commands.yaml"
# Create project config
project_config.write_text("""version: 1
@@ -660,7 +660,7 @@ def test_org_blocklist_enforcement():
with tempfile.TemporaryDirectory() as tmpproject:
# Use temporary_home for cross-platform compatibility
with temporary_home(tmphome):
org_dir = Path(tmphome) / ".autocoder"
org_dir = Path(tmphome) / ".autoforge"
org_dir.mkdir()
org_config_path = org_dir / "config.yaml"
@@ -671,8 +671,8 @@ blocked_commands:
""")
project_dir = Path(tmpproject)
project_autocoder = project_dir / ".autocoder"
project_autocoder.mkdir()
project_autoforge = project_dir / ".autoforge"
project_autoforge.mkdir()
# Try to use terraform (should be blocked)
input_data = {"tool_name": "Bash", "tool_input": {"command": "terraform apply"}}
@@ -735,7 +735,7 @@ def test_pkill_extensibility():
with tempfile.TemporaryDirectory() as tmphome:
with tempfile.TemporaryDirectory() as tmpproject:
with temporary_home(tmphome):
org_dir = Path(tmphome) / ".autocoder"
org_dir = Path(tmphome) / ".autoforge"
org_dir.mkdir()
org_config_path = org_dir / "config.yaml"
@@ -762,9 +762,9 @@ pkill_processes:
with tempfile.TemporaryDirectory() as tmpproject:
with temporary_home(tmphome):
project_dir = Path(tmpproject)
project_autocoder = project_dir / ".autocoder"
project_autocoder.mkdir()
project_config = project_autocoder / "allowed_commands.yaml"
project_autoforge = project_dir / ".autoforge"
project_autoforge.mkdir()
project_config = project_autoforge / "allowed_commands.yaml"
# Create project config with extra pkill processes
project_config.write_text("""version: 1
@@ -804,7 +804,7 @@ pkill_processes:
with tempfile.TemporaryDirectory() as tmphome:
with tempfile.TemporaryDirectory() as tmpproject:
with temporary_home(tmphome):
org_dir = Path(tmphome) / ".autocoder"
org_dir = Path(tmphome) / ".autoforge"
org_dir.mkdir()
org_config_path = org_dir / "config.yaml"
@@ -829,7 +829,7 @@ pkill_processes:
with tempfile.TemporaryDirectory() as tmphome:
with tempfile.TemporaryDirectory() as tmpproject:
with temporary_home(tmphome):
org_dir = Path(tmphome) / ".autocoder"
org_dir = Path(tmphome) / ".autoforge"
org_dir.mkdir()
org_config_path = org_dir / "config.yaml"
@@ -851,7 +851,7 @@ pkill_processes:
with tempfile.TemporaryDirectory() as tmphome:
with tempfile.TemporaryDirectory() as tmpproject:
with temporary_home(tmphome):
org_dir = Path(tmphome) / ".autocoder"
org_dir = Path(tmphome) / ".autoforge"
org_dir.mkdir()
org_config_path = org_dir / "config.yaml"
@@ -875,7 +875,7 @@ pkill_processes:
with tempfile.TemporaryDirectory() as tmphome:
with tempfile.TemporaryDirectory() as tmpproject:
with temporary_home(tmphome):
org_dir = Path(tmphome) / ".autocoder"
org_dir = Path(tmphome) / ".autoforge"
org_dir.mkdir()
org_config_path = org_dir / "config.yaml"

View File

@@ -79,9 +79,9 @@ def test_blocked_command_via_hook():
project_dir = Path(tmpdir)
# Create minimal project structure
autocoder_dir = project_dir / ".autocoder"
autocoder_dir.mkdir()
(autocoder_dir / "allowed_commands.yaml").write_text(
autoforge_dir = project_dir / ".autoforge"
autoforge_dir.mkdir()
(autoforge_dir / "allowed_commands.yaml").write_text(
"version: 1\ncommands: []"
)
@@ -114,9 +114,9 @@ def test_allowed_command_via_hook():
project_dir = Path(tmpdir)
# Create minimal project structure
autocoder_dir = project_dir / ".autocoder"
autocoder_dir.mkdir()
(autocoder_dir / "allowed_commands.yaml").write_text(
autoforge_dir = project_dir / ".autoforge"
autoforge_dir.mkdir()
(autoforge_dir / "allowed_commands.yaml").write_text(
"version: 1\ncommands: []"
)
@@ -145,9 +145,9 @@ def test_non_allowed_command_via_hook():
project_dir = Path(tmpdir)
# Create minimal project structure
autocoder_dir = project_dir / ".autocoder"
autocoder_dir.mkdir()
(autocoder_dir / "allowed_commands.yaml").write_text(
autoforge_dir = project_dir / ".autoforge"
autoforge_dir.mkdir()
(autoforge_dir / "allowed_commands.yaml").write_text(
"version: 1\ncommands: []"
)
@@ -179,9 +179,9 @@ def test_project_config_allows_command():
project_dir = Path(tmpdir)
# Create project config with swift allowed
autocoder_dir = project_dir / ".autocoder"
autocoder_dir.mkdir()
(autocoder_dir / "allowed_commands.yaml").write_text("""version: 1
autoforge_dir = project_dir / ".autoforge"
autoforge_dir.mkdir()
(autoforge_dir / "allowed_commands.yaml").write_text("""version: 1
commands:
- name: swift
description: Swift compiler
@@ -214,9 +214,9 @@ def test_pattern_matching():
project_dir = Path(tmpdir)
# Create project config with swift* pattern
autocoder_dir = project_dir / ".autocoder"
autocoder_dir.mkdir()
(autocoder_dir / "allowed_commands.yaml").write_text("""version: 1
autoforge_dir = project_dir / ".autoforge"
autoforge_dir.mkdir()
(autoforge_dir / "allowed_commands.yaml").write_text("""version: 1
commands:
- name: swift*
description: All Swift tools
@@ -247,7 +247,7 @@ def test_org_blocklist_enforcement():
with tempfile.TemporaryDirectory() as tmpproject:
# Use context manager to safely set and restore HOME
with temporary_home(tmphome):
org_dir = Path(tmphome) / ".autocoder"
org_dir = Path(tmphome) / ".autoforge"
org_dir.mkdir()
(org_dir / "config.yaml").write_text("""version: 1
allowed_commands: []
@@ -257,11 +257,11 @@ blocked_commands:
""")
project_dir = Path(tmpproject)
autocoder_dir = project_dir / ".autocoder"
autocoder_dir.mkdir()
autoforge_dir = project_dir / ".autoforge"
autoforge_dir.mkdir()
# Try to allow terraform in project config (should fail - org blocked)
(autocoder_dir / "allowed_commands.yaml").write_text("""version: 1
(autoforge_dir / "allowed_commands.yaml").write_text("""version: 1
commands:
- name: terraform
description: Infrastructure as code
@@ -295,7 +295,7 @@ def test_org_allowlist_inheritance():
with tempfile.TemporaryDirectory() as tmpproject:
# Use context manager to safely set and restore HOME
with temporary_home(tmphome):
org_dir = Path(tmphome) / ".autocoder"
org_dir = Path(tmphome) / ".autoforge"
org_dir.mkdir()
(org_dir / "config.yaml").write_text("""version: 1
allowed_commands:
@@ -305,9 +305,9 @@ blocked_commands: []
""")
project_dir = Path(tmpproject)
autocoder_dir = project_dir / ".autocoder"
autocoder_dir.mkdir()
(autocoder_dir / "allowed_commands.yaml").write_text(
autoforge_dir = project_dir / ".autoforge"
autoforge_dir.mkdir()
(autoforge_dir / "allowed_commands.yaml").write_text(
"version: 1\ncommands: []"
)
@@ -336,9 +336,9 @@ def test_invalid_yaml_ignored():
project_dir = Path(tmpdir)
# Create invalid YAML
autocoder_dir = project_dir / ".autocoder"
autocoder_dir.mkdir()
(autocoder_dir / "allowed_commands.yaml").write_text("invalid: yaml: content:")
autoforge_dir = project_dir / ".autoforge"
autoforge_dir.mkdir()
(autoforge_dir / "allowed_commands.yaml").write_text("invalid: yaml: content:")
# Try to run ls (should still work - falls back to defaults)
input_data = {"tool_name": "Bash", "tool_input": {"command": "ls"}}
@@ -365,13 +365,13 @@ def test_100_command_limit():
project_dir = Path(tmpdir)
# Create config with 101 commands
autocoder_dir = project_dir / ".autocoder"
autocoder_dir.mkdir()
autoforge_dir = project_dir / ".autoforge"
autoforge_dir.mkdir()
commands = [
f" - name: cmd{i}\n description: Command {i}" for i in range(101)
]
(autocoder_dir / "allowed_commands.yaml").write_text(
(autoforge_dir / "allowed_commands.yaml").write_text(
"version: 1\ncommands:\n" + "\n".join(commands)
)

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AutoCoder</title>
<title>AutoForge</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Archivo+Black&family=Work+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&family=DM+Sans:wght@400;500;700&family=Space+Mono:wght@400;700&family=Outfit:wght@400;500;600;700&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">

18
ui/package-lock.json generated
View File

@@ -1,11 +1,11 @@
{
"name": "autocoder",
"name": "autoforge",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "autocoder",
"name": "autoforge",
"version": "1.0.0",
"dependencies": {
"@radix-ui/react-checkbox": "^1.3.3",
@@ -81,7 +81,6 @@
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.5",
@@ -2695,7 +2694,6 @@
"integrity": "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"undici-types": "~6.21.0"
}
@@ -2706,7 +2704,6 @@
"integrity": "sha512-Lpo8kgb/igvMIPeNV2rsYKTgaORYdO1XGVZ4Qz3akwOj0ySGYMPlQWa8BaLn0G63D1aSaAQ5ldR06wCpChQCjA==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
@@ -2717,7 +2714,6 @@
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
"devOptional": true,
"license": "MIT",
"peer": true,
"peerDependencies": {
"@types/react": "^19.2.0"
}
@@ -2767,7 +2763,6 @@
"integrity": "sha512-3xP4XzzDNQOIqBMWogftkwxhg5oMKApqY0BAflmLZiFYHqyhSOxv/cd/zPQLTcCXr4AkaKb25joocY0BD1WC6A==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.51.0",
"@typescript-eslint/types": "8.51.0",
@@ -3072,7 +3067,6 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -3190,7 +3184,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
@@ -3403,7 +3396,6 @@
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"license": "ISC",
"peer": true,
"engines": {
"node": ">=12"
}
@@ -3595,7 +3587,6 @@
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -4579,7 +4570,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -4685,7 +4675,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -4695,7 +4684,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
"license": "MIT",
"peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@@ -5005,7 +4993,6 @@
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -5144,7 +5131,6 @@
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.27.0",
"fdir": "^6.5.0",

View File

@@ -1,5 +1,5 @@
{
"name": "autocoder",
"name": "autoforge",
"private": true,
"version": "1.0.0",
"type": "module",

View File

@@ -34,8 +34,8 @@ import { Button } from '@/components/ui/button'
import { Card, CardContent } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
const STORAGE_KEY = 'autocoder-selected-project'
const VIEW_MODE_KEY = 'autocoder-view-mode'
const STORAGE_KEY = 'autoforge-selected-project'
const VIEW_MODE_KEY = 'autoforge-view-mode'
// Bottom padding for main content when debug panel is collapsed (40px header + 8px margin)
const COLLAPSED_DEBUG_PANEL_CLEARANCE = 48
@@ -264,7 +264,7 @@ function App() {
<div className="flex items-center justify-between">
{/* Logo and Title */}
<h1 className="font-display text-2xl font-bold tracking-tight uppercase">
AutoCoder
AutoForge
</h1>
{/* Controls */}
@@ -376,7 +376,7 @@ function App() {
{!selectedProject ? (
<div className="text-center mt-12">
<h2 className="font-display text-2xl font-bold mb-2">
Welcome to AutoCoder
Welcome to AutoForge
</h2>
<p className="text-muted-foreground mb-4">
Select a project from the dropdown above or create a new one to get started.

View File

@@ -8,7 +8,7 @@ import { Card, CardContent } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
const ACTIVITY_COLLAPSED_KEY = 'autocoder-activity-collapsed'
const ACTIVITY_COLLAPSED_KEY = 'autoforge-activity-collapsed'
interface AgentMissionControlProps {
agents: ActiveAgent[]

View File

@@ -110,7 +110,7 @@ export function DocsPage() {
className="font-display text-xl font-bold tracking-tight uppercase text-foreground
hover:text-primary transition-colors"
>
AutoCoder
AutoForge
</a>
<Badge variant="secondary" className="text-xs font-medium">

View File

@@ -34,7 +34,7 @@ export const DOC_SECTIONS: DocSection[] = [
title: 'Getting Started',
icon: Rocket,
subsections: [
{ id: 'what-is-autocoder', title: 'What is AutoCoder?' },
{ id: 'what-is-autoforge', title: 'What is AutoForge?' },
{ id: 'quick-start', title: 'Quick Start' },
{ id: 'creating-a-project', title: 'Creating a New Project' },
{ id: 'existing-project', title: 'Adding to an Existing Project' },
@@ -60,7 +60,7 @@ export const DOC_SECTIONS: DocSection[] = [
title: 'Target Project Structure',
icon: FolderTree,
subsections: [
{ id: 'autocoder-directory', title: '.autocoder/ Directory Layout' },
{ id: 'autoforge-directory', title: '.autoforge/ Directory Layout' },
{ id: 'features-db', title: 'Features Database' },
{ id: 'prompts-directory', title: 'Prompts Directory' },
{ id: 'allowed-commands-yaml', title: 'Allowed Commands Config' },

View File

@@ -129,7 +129,7 @@ ANTHROPIC_DEFAULT_SONNET_MODEL=qwen3-coder`}</code></pre>
Environment Variables
</h3>
<p className="text-muted-foreground mb-3">
Key environment variables for configuring AutoCoder:
Key environment variables for configuring AutoForge:
</p>
<table className="w-full text-sm mt-3">
<thead>
@@ -193,7 +193,7 @@ ANTHROPIC_DEFAULT_SONNET_MODEL=qwen3-coder`}</code></pre>
Webhook Support
</h3>
<ul className="list-disc space-y-2 ml-4 text-muted-foreground">
<li>AutoCoder can send webhook notifications on feature completion</li>
<li>AutoForge can send webhook notifications on feature completion</li>
<li>Compatible with N8N and similar automation tools</li>
<li>Configure the webhook URL in project settings</li>
<li>
@@ -208,7 +208,7 @@ ANTHROPIC_DEFAULT_SONNET_MODEL=qwen3-coder`}</code></pre>
<ul className="list-disc space-y-2 ml-4 text-muted-foreground">
<li>
All projects are registered in{' '}
<span className="bg-muted px-1.5 py-0.5 rounded text-sm font-mono">~/.autocoder/registry.db</span>{' '}
<span className="bg-muted px-1.5 py-0.5 rounded text-sm font-mono">~/.autoforge/registry.db</span>{' '}
(SQLite)
</li>
<li>Maps project names to filesystem paths</li>

View File

@@ -15,7 +15,7 @@ export function AppSpecSetup() {
<p className="text-muted-foreground mb-3">
The app spec is an XML document that describes the application to be built. It lives at{' '}
<span className="bg-muted px-1.5 py-0.5 rounded text-sm font-mono">
.autocoder/prompts/app_spec.txt
.autoforge/prompts/app_spec.txt
</span>{' '}
and tells the initializer agent what features to create. The spec defines your app&apos;s name,
description, tech stack, and the features that should be implemented.
@@ -56,7 +56,7 @@ export function AppSpecSetup() {
<li>
Create{' '}
<span className="bg-muted px-1.5 py-0.5 rounded text-sm font-mono">
.autocoder/prompts/app_spec.txt
.autoforge/prompts/app_spec.txt
</span>{' '}
in your project directory
</li>
@@ -97,7 +97,7 @@ export function AppSpecSetup() {
<li>
Creates the feature database at{' '}
<span className="bg-muted px-1.5 py-0.5 rounded text-sm font-mono">
.autocoder/features.db
.autoforge/features.db
</span>
</li>
</ul>

View File

@@ -98,7 +98,7 @@ export function AppearanceThemes() {
Themes Overview
</h3>
<p className="text-muted-foreground mb-4">
AutoCoder comes with 6 built-in themes. Each theme provides a complete visual identity including
AutoForge comes with 6 built-in themes. Each theme provides a complete visual identity including
colors, accents, and dark mode variants.
</p>
<div className="space-y-4">

View File

@@ -13,7 +13,7 @@ export function FAQ() {
Starting a New Project
</h3>
<p className="text-muted-foreground italic mb-2">
How do I use AutoCoder on a new project?
How do I use AutoForge on a new project?
</p>
<p className="text-muted-foreground">
From the UI, select &quot;Create New Project&quot; in the project dropdown. Choose a folder and
@@ -27,12 +27,12 @@ export function FAQ() {
Adding to Existing Project
</h3>
<p className="text-muted-foreground italic mb-2">
How do I add AutoCoder to an existing project?
How do I add AutoForge to an existing project?
</p>
<p className="text-muted-foreground">
Register the project folder through the UI project selector using &quot;Add Existing&quot;.
AutoCoder creates a{' '}
<span className="bg-muted px-1.5 py-0.5 rounded text-sm font-mono">.autocoder/</span> directory
AutoForge creates a{' '}
<span className="bg-muted px-1.5 py-0.5 rounded text-sm font-mono">.autoforge/</span> directory
alongside your existing code. Write an app spec describing what to build (new features), and the
agent works within your existing codebase.
</p>
@@ -60,7 +60,7 @@ export function FAQ() {
<p className="text-muted-foreground">
Create{' '}
<span className="bg-muted px-1.5 py-0.5 rounded text-sm font-mono">
.autocoder/allowed_commands.yaml
.autoforge/allowed_commands.yaml
</span>{' '}
in your project with a list of allowed commands. Supports exact names, wildcards (e.g.,{' '}
<span className="bg-muted px-1.5 py-0.5 rounded text-sm font-mono">swift*</span>), and local

View File

@@ -1,7 +1,7 @@
/**
* GettingStarted Documentation Section
*
* Covers what AutoCoder is, quick start commands,
* Covers what AutoForge is, quick start commands,
* creating and adding projects, and system requirements.
*/
@@ -10,12 +10,12 @@ import { Badge } from '@/components/ui/badge'
export function GettingStarted() {
return (
<div>
{/* What is AutoCoder? */}
<h3 id="what-is-autocoder" className="text-lg font-semibold text-foreground mt-8 mb-3">
What is AutoCoder?
{/* What is AutoForge? */}
<h3 id="what-is-autoforge" className="text-lg font-semibold text-foreground mt-8 mb-3">
What is AutoForge?
</h3>
<p className="text-muted-foreground mb-4">
AutoCoder is an autonomous coding agent system that builds complete applications over multiple
AutoForge is an autonomous coding agent system that builds complete applications over multiple
sessions using a two-agent pattern:
</p>
<ol className="list-decimal space-y-2 ml-4 text-muted-foreground">
@@ -38,7 +38,7 @@ export function GettingStarted() {
Quick Start
</h3>
<p className="text-muted-foreground mb-3">
Launch AutoCoder with a single command. The CLI menu lets you create or select a project,
Launch AutoForge with a single command. The CLI menu lets you create or select a project,
while the Web UI provides a full dashboard experience.
</p>
<div className="bg-muted rounded-lg p-4 font-mono text-sm">
@@ -76,12 +76,12 @@ start_ui.bat # Web UI
<ul className="list-disc space-y-2 ml-4 text-muted-foreground">
<li>Register the project folder via the UI project selector</li>
<li>
AutoCoder creates a{' '}
<span className="bg-muted px-1.5 py-0.5 rounded text-sm font-mono">.autocoder/</span>{' '}
AutoForge creates a{' '}
<span className="bg-muted px-1.5 py-0.5 rounded text-sm font-mono">.autoforge/</span>{' '}
directory inside your project
</li>
<li>
Existing code is preserved &mdash; AutoCoder adds its configuration alongside it
Existing code is preserved &mdash; AutoForge adds its configuration alongside it
</li>
<li>Write or generate an app spec describing what to build</li>
</ul>

View File

@@ -1,7 +1,7 @@
/**
* ProjectStructure Documentation Section
*
* Covers the .autocoder/ directory layout, features database,
* Covers the .autoforge/ directory layout, features database,
* prompts directory, allowed commands, CLAUDE.md convention,
* legacy migration, and Claude inheritance.
*/
@@ -9,18 +9,18 @@
export function ProjectStructure() {
return (
<div>
{/* .autocoder/ Directory Layout */}
<h3 id="autocoder-directory" className="text-lg font-semibold text-foreground mt-8 mb-3">
.autocoder/ Directory Layout
{/* .autoforge/ Directory Layout */}
<h3 id="autoforge-directory" className="text-lg font-semibold text-foreground mt-8 mb-3">
.autoforge/ Directory Layout
</h3>
<p className="text-muted-foreground mb-3">
Every AutoCoder project stores its configuration and runtime files in a{' '}
<span className="bg-muted px-1.5 py-0.5 rounded text-sm font-mono">.autocoder/</span>{' '}
Every AutoForge project stores its configuration and runtime files in a{' '}
<span className="bg-muted px-1.5 py-0.5 rounded text-sm font-mono">.autoforge/</span>{' '}
directory at the project root.
</p>
<div className="bg-muted rounded-lg p-4 font-mono text-sm">
<pre><code>{`your-project/
\u251C\u2500\u2500 .autocoder/
\u251C\u2500\u2500 .autoforge/
\u2502 \u251C\u2500\u2500 features.db # SQLite feature database
\u2502 \u251C\u2500\u2500 .agent.lock # Lock file (prevents multiple instances)
\u2502 \u251C\u2500\u2500 .gitignore # Ignores runtime files
@@ -41,7 +41,7 @@ export function ProjectStructure() {
<li>
SQLite database managed by SQLAlchemy, stored at{' '}
<span className="bg-muted px-1.5 py-0.5 rounded text-sm font-mono">
.autocoder/features.db
.autoforge/features.db
</span>
</li>
<li>
@@ -96,7 +96,7 @@ export function ProjectStructure() {
<p className="text-muted-foreground mb-3">
The optional{' '}
<span className="bg-muted px-1.5 py-0.5 rounded text-sm font-mono">
.autocoder/allowed_commands.yaml
.autoforge/allowed_commands.yaml
</span>{' '}
file lets you grant project-specific bash commands to the agent. This is useful when your
project requires tools beyond the default allowlist (e.g., language-specific compilers or
@@ -138,7 +138,7 @@ export function ProjectStructure() {
<ul className="list-disc space-y-2 ml-4 text-muted-foreground">
<li>
On the next agent start, these files are automatically migrated into{' '}
<span className="bg-muted px-1.5 py-0.5 rounded text-sm font-mono">.autocoder/</span>
<span className="bg-muted px-1.5 py-0.5 rounded text-sm font-mono">.autoforge/</span>
</li>
<li>Dual-path resolution ensures both old and new layouts work transparently</li>
<li>No manual migration is needed &mdash; it happens seamlessly</li>

View File

@@ -15,7 +15,7 @@ export function Scheduling() {
What Scheduling Does
</h3>
<p className="text-muted-foreground mb-4">
Scheduling automates agent runs at specific times. Set up a schedule and AutoCoder will automatically
Scheduling automates agent runs at specific times. Set up a schedule and AutoForge will automatically
start agents on your project &mdash; useful for overnight builds, periodic maintenance, or continuous
development.
</p>

View File

@@ -16,7 +16,7 @@ export function Security() {
Command Validation Overview
</h3>
<p className="text-muted-foreground mb-3">
AutoCoder uses a defense-in-depth approach for security. All three layers must pass before any
AutoForge uses a defense-in-depth approach for security. All three layers must pass before any
command is executed:
</p>
<ol className="list-decimal space-y-2 ml-4 text-muted-foreground">
@@ -49,12 +49,12 @@ export function Security() {
</li>
<li>
<strong className="text-foreground">Org Blocklist</strong>{' '}
<span className="bg-muted px-1.5 py-0.5 rounded text-sm font-mono">~/.autocoder/config.yaml</span>{' '}
<span className="bg-muted px-1.5 py-0.5 rounded text-sm font-mono">~/.autoforge/config.yaml</span>{' '}
&mdash; org-wide blocks, cannot be project-overridden
</li>
<li>
<strong className="text-foreground">Org Allowlist</strong>{' '}
<span className="bg-muted px-1.5 py-0.5 rounded text-sm font-mono">~/.autocoder/config.yaml</span>{' '}
<span className="bg-muted px-1.5 py-0.5 rounded text-sm font-mono">~/.autoforge/config.yaml</span>{' '}
&mdash; available to all projects
</li>
<li>
@@ -65,7 +65,7 @@ export function Security() {
<li>
<strong className="text-foreground">Project Allowlist</strong>{' '}
<span className="bg-muted px-1.5 py-0.5 rounded text-sm font-mono">
.autocoder/allowed_commands.yaml
.autoforge/allowed_commands.yaml
</span>{' '}
&mdash; project-specific additions
</li>
@@ -120,12 +120,12 @@ export function Security() {
<p className="text-muted-foreground mb-3">
Each project can define additional allowed commands in{' '}
<span className="bg-muted px-1.5 py-0.5 rounded text-sm font-mono">
.autocoder/allowed_commands.yaml
.autoforge/allowed_commands.yaml
</span>
:
</p>
<div className="bg-muted rounded-lg p-4 font-mono text-sm">
<pre><code>{`# .autocoder/allowed_commands.yaml
<pre><code>{`# .autoforge/allowed_commands.yaml
version: 1
commands:
# Exact command name
@@ -155,10 +155,10 @@ commands:
</h3>
<p className="text-muted-foreground mb-3">
System administrators can set org-wide policies in{' '}
<span className="bg-muted px-1.5 py-0.5 rounded text-sm font-mono">~/.autocoder/config.yaml</span>:
<span className="bg-muted px-1.5 py-0.5 rounded text-sm font-mono">~/.autoforge/config.yaml</span>:
</p>
<div className="bg-muted rounded-lg p-4 font-mono text-sm">
<pre><code>{`# ~/.autocoder/config.yaml
<pre><code>{`# ~/.autoforge/config.yaml
version: 1
# Commands available to ALL projects

View File

@@ -177,7 +177,7 @@ export function SettingsConfig() {
<ul className="list-disc space-y-2 ml-4 text-muted-foreground">
<li>
Global settings stored in SQLite registry at{' '}
<span className="bg-muted px-1.5 py-0.5 rounded text-sm font-mono">~/.autocoder/registry.db</span>
<span className="bg-muted px-1.5 py-0.5 rounded text-sm font-mono">~/.autoforge/registry.db</span>
</li>
<li>Per-project settings (like default concurrency) stored in the project registry entry</li>
<li>UI settings (theme, dark mode) stored in browser localStorage</li>

View File

@@ -52,8 +52,8 @@ export const THEMES: ThemeOption[] = [
}
]
const THEME_STORAGE_KEY = 'autocoder-theme'
const DARK_MODE_STORAGE_KEY = 'autocoder-dark-mode'
const THEME_STORAGE_KEY = 'autoforge-theme'
const DARK_MODE_STORAGE_KEY = 'autoforge-dark-mode'
function getThemeClass(themeId: ThemeId): string {
switch (themeId) {