# AGENTS.md
Guidance for AI coding agents working in `ace-step/ACE-Step-1.5`.
This document is aligned with the intent from:
- Discussion #408: functional decomposition to reduce risk from large mixed-responsibility files.
- Discussion #365: low-risk contribution workflow, minimal scope, and review rigor.
## Primary Objectives
1. Keep changes safe and reviewable.
2. Prefer small, maintainable, decomposed modules.
3. Preserve behavior outside the target fix.
4. Validate with focused Python unit tests.
## Build, Lint, and Test Commands
```bash
# Install dependencies
uv sync
# Run all tests (unittest-based, discovery in */*_test.py and test_*.py)
uv run python -m unittest discover -s . -p "*_test.py"
uv run python -m unittest discover -s . -p "test_*.py"
# Run a single test file
uv run python -m unittest acestep.training.test_lora_utils
# Run a specific test class
uv run python -m unittest acestep.training.test_lora_utils.TestUnwrapDecoder
# Run a single test method
uv run python -m unittest acestep.training.test_lora_utils.TestUnwrapDecoder.test_returns_module_directly
# Run all tests in a directory
uv run python -m unittest discover -s acestep/training -p "*_test.py"
```
## Scope and Change Control (Required)
- Solve one problem per task/PR.
- Keep edits minimal: touch only files/functions required for the requested change.
- Do not make drive-by refactors, formatting sweeps, or opportunistic cleanups.
- Do not alter non-target hardware/runtime paths (CPU/CUDA/MPS/XPU) unless required by the task.
- If any cross-path change is necessary, isolate it and justify it in the PR notes.
- Preserve existing public interfaces unless the task explicitly requires an interface change.
## Decomposition and Module Size Policy
- Prefer single-responsibility modules with clear boundaries.
- Target module size:
- Optimal: `<= 150` LOC
- Hard cap: `200` LOC
- Function decomposition rules:
- Do one thing at a time; if a function description naturally contains "and", split it.
- Split by responsibility, not by convenience.
- Keep data flow explicit (`data in, data out`); side effects must be obvious and deliberate.
- Push decisions up and push work down (orchestration at higher layers, execution details in lower layers).
- The call graph should read clearly from top-level orchestration to leaf operations.
- If a module would exceed `200` LOC:
- Split by responsibility before merging, or
- Add a short justification in PR notes and include a concrete follow-up split plan.
- Keep orchestrator/facade modules thin. Move logic into focused helpers/services.
- Preserve stable facade imports when splitting large files so external callers are not broken.
## Python Unit Testing Expectations
- Add or update tests for every behavior change and bug fix.
- Match repository conventions:
- Use `unittest`-style tests.
- Name test files as `*_test.py` or `test_*.py`.
- Keep tests deterministic, fast, and scoped to changed behavior.
- Use `unittest.mock.MagicMock` and `unittest.mock.patch` for mocking.
- Mock GPU, filesystem, network, and external services where possible.
- If a change requires mocking a large portion of the system to test one unit, treat that as a decomposition smell and refactor boundaries.
- Include at least:
- One success-path test.
- One regression/edge-case test for the bug being fixed.
- One non-target behavior check when relevant.
- Run targeted tests locally before submitting.
## Code Style Guidelines
- **Python version**: 3.11-3.12
- **Indentation**: 4 spaces (no tabs)
- **Line length**: Maximum 100 characters (recommended). See `pyproject.toml` for configured formatter limits. Exceptions allowed for URLs and long strings where wrapping would hurt readability.
- **Strings**: Double quotes `"` preferred
- **Imports**: Group by type (stdlib, third-party, local), sort alphabetically within groups
```python
# Example import ordering
import os
import tempfile
from pathlib import Path
from typing import Any
from unittest.mock import MagicMock, patch
import torch
import torch.nn as nn
from acestep.training.lora_injection import inject_lora_into_dit
```
**Naming conventions**:
- `snake_case` for functions, variables, and module names
- `PascalCase` for classes
- `UPPER_SNAKE_CASE` for constants
- Prefix private/internal names with underscore: `_internal_func`, `_private_var`
**Type hints**: Add type annotations for new/modified functions when practical.
**Docstrings**: Mandatory for all modules, classes, and public functions. Use concise format:
```python
def inject_lora_into_dit(
dit: nn.Module,
config: dict[str, Any],
target_modules: list[str],
) -> nn.Module:
"""Inject LoRA adapters into DiT model for parameter-efficient fine-tuning.
Args:
dit: The Diffusion Transformer model to modify.
config: LoRA configuration dictionary.
target_modules: List of module names to apply LoRA to.
Returns:
The modified DiT model with LoRA adapters injected.
"""
```
**Error handling**:
- Avoid bare `except:` clauses; catch specific exceptions
- Use custom exceptions for domain errors
- Log errors with `loguru.logger` (not `print()`)
- Let exceptions propagate for truly exceptional conditions
**Logging**:
- Use `from loguru import logger` and `logger.info()`, `logger.error()`, etc.
- Keep logs actionable and debug-level for development
- Avoid `print()` in committed code except CLI output
**Multi-platform support** (CUDA, ROCm, Intel XPU, MPS, MLX, CPU):
- Use `gpu_config.py` for hardware detection
- Do not alter non-target platform paths unless explicitly required
- Changes to CUDA code should not break MPS/XPU/CPU paths
## Feature Gating and WIP Safety
- Do not expose unfinished or non-functional user-facing flows by default.
- Gate WIP or unstable UI/API paths behind explicit feature/release flags.
- Keep default behavior stable; "coming soon" paths must not appear as usable functionality unless they are operational and tested.
## Python Coding Best Practices
- Use explicit, readable code over clever shortcuts.
- Docstrings are mandatory for all new or modified Python modules, classes, and functions.
- Docstrings must be concise and include purpose plus key inputs/outputs (and raised exceptions when relevant).
- Add type hints for new/modified functions when practical.
- Keep functions focused and short; extract helpers instead of nesting complexity.
- Use clear names that describe behavior, not implementation trivia.
- Prefer pure functions for logic-heavy paths where possible.
- Avoid duplicated logic, but do not introduce broad abstractions too early; prefer simple local duplication over unstable premature abstraction.
- Handle errors explicitly; avoid bare `except`.
- Keep logging actionable; avoid noisy logs and `print` debugging in committed code.
- Avoid hidden state and unintended side effects.
- Write comments only where intent is non-obvious; keep comments concise and technical.
## AI-Agent Workflow (Recommended)
1. Understand the task and define explicit in-scope/out-of-scope boundaries.
2. Propose a minimal patch plan before editing.
3. Implement the smallest viable change.
4. Add/update focused tests.
5. Self-review only changed hunks for regressions and scope creep.
6. Summarize risk, validation, and non-target impact in PR notes.
## PR Readiness Checklist
- [ ] Change is tightly scoped to one problem.
- [ ] Non-target paths are unchanged, or changes are explicitly justified.
- [ ] New/updated tests cover changed behavior and edge cases.
- [ ] No unrelated refactor/formatting churn.
- [ ] Required docstrings are present for all new/modified modules, classes, and functions.
- [ ] WIP/unstable functionality is feature-flagged and not exposed as default-ready behavior.
- [ ] Module LOC policy is met (`<=150` target, `<=200` hard cap or justified exception).
# Update and Backup Guide
## Overview
All ACE-Step launch scripts check for updates on startup by default. The update check is a lightweight inline operation that runs before the application starts, ensuring you are always notified about new versions without any manual setup.
- **Default behavior**: Update checking is enabled (`CHECK_UPDATE=true`) in every launch script.
- **Platforms supported**: Windows, Linux, and macOS.
- **Graceful failures**: If git is not installed, the network is unreachable, or the project is not a git repository, the check is silently skipped and the application starts normally.
- **User control**: You can disable the check at any time by setting `CHECK_UPDATE=false`.
---
## Update Check Feature
### How It Works
Each launch script contains a lightweight inline update check that runs before the main application starts. The check does not require any external update service -- it uses git directly to compare your local commit with the remote.
**Flow:**
```text
Startup
|
v
CHECK_UPDATE=true? --No--> Skip, start app
|
Yes
v
Git available? --No--> Skip, start app
|
Yes
v
Valid git repo? --No--> Skip, start app
|
Yes
v
Fetch origin (10s timeout) --Timeout/Error--> Skip, start app
|
Success
v
Compare local HEAD vs origin HEAD
|
+-- Same commit --> "Already up to date", start app
|
+-- Different commit --> Show new commits, ask Y/N
|
+-- N --> Skip, start app
|
+-- Y --> Run check_update.bat / check_update.sh for full update
|
v
Start app
```
At every failure point (no git, no network, not a repo), the check exits gracefully and the application starts without interruption.
### Enabling and Disabling
The update check is controlled by the `CHECK_UPDATE` variable near the top of each launch script.
**Windows** (`start_gradio_ui.bat`, `start_api_server.bat`):
```batch
REM Update check on startup (set to false to disable)
set CHECK_UPDATE=true
REM set CHECK_UPDATE=false
```
**Linux / macOS** (`start_gradio_ui.sh`, `start_api_server.sh`, `start_gradio_ui_macos.sh`, `start_api_server_macos.sh`):
```bash
# Update check on startup (set to "false" to disable)
CHECK_UPDATE="true"
# CHECK_UPDATE="false"
```
To disable, change the active line to `false`. To re-enable, change it back to `true`.
### Git Requirements by Platform
The inline update check requires git to be available. How you obtain git depends on your platform.
**Windows:**
- **Option A -- PortableGit** (no installation required): Download from <https://git-scm.com/download/win>, choose the portable version, and extract to a `PortableGit\` folder in the project root. The launch scripts look for `PortableGit\bin\git.exe` first.
- **Option B -- System git**: Install git through any standard method (Git for Windows installer, winget, scoop, etc.). The launch scripts fall back to system git if PortableGit is not found.
```text
Project Root/
├── PortableGit/ <-- Optional, checked first on Windows
│ └── bin/
│ └── git.exe
├── start_gradio_ui.bat
├── check_update.bat
└── ...
```
**Linux:**
Install git through your distribution's package manager:
```bash
# Ubuntu / Debian
sudo apt install git
# CentOS / RHEL / Fedora
sudo yum install git
# or
sudo dnf install git
# Arch Linux
sudo pacman -S git
```
**macOS:**
Install git through Xcode command-line tools or Homebrew:
```bash
# Xcode command-line tools (includes git)
xcode-select --install
# Or via Homebrew
brew install git
```
### Example Output
**Already up to date:**
```text
[Update] Checking for updates...
[Update] Already up to date (abc1234).
Starting ACE-Step Gradio Web UI...
```
**Update available:**
```text
[Update] Checking for updates...
========================================
Update available!
========================================
Current: abc1234 -> Latest: def5678
Recent changes:
* def5678 Fix audio processing bug
* ccc3333 Add new model support
Update now before starting? (Y/N):
```
If you choose **Y**, the script delegates to `check_update.bat` (Windows) or `check_update.sh` (Linux/macOS) for the full update process including backup handling. If you choose **N**, the update is skipped and the application starts with the current version.
**Network unreachable (auto-skip):**
```text
[Update] Checking for updates...
[Update] Network unreachable, skipping.
Starting ACE-Step Gradio Web UI...
```
---
## Manual Update
You can run the update check manually at any time, outside of the launch scripts.
**Windows:**
```batch
check_update.bat
```
**Linux / macOS:**
```bash
./check_update.sh
```
The manual update scripts perform the same 4-step process:
1. Detect git and verify the repository
2. Fetch from origin with a 10-second timeout
3. Compare local and remote commits
4. If an update is available, prompt to apply it (with automatic backup of conflicting files)
---
## File Backup During Updates
### Automatic Backup
When you choose to update and you have locally modified files that also changed on the remote, ACE-Step automatically creates a backup before applying the update.
**Supported file types** (any modified text file is backed up):
- Configuration files: `.bat`, `.sh`, `.yaml`, `.json`, `.ini`
- Python code: `.py`
- Documentation: `.md`, `.txt`
### Backup Process
```text
1. Update detects locally modified files
that also changed on the remote
|
v
2. Creates a timestamped backup directory
.update_backup_YYYYMMDD_HHMMSS/
|
v
3. Copies conflicting files into the backup
(preserves directory structure)
|
v
4. Resets working tree to the remote version
|
v
5. Displays backup location and instructions
```
### Example
**Your local modifications:**
- `start_gradio_ui.bat` -- Changed language to Chinese
- `acestep/handler.py` -- Added debug logging
- `config.yaml` -- Changed model path
**Remote updates:**
- `start_gradio_ui.bat` -- Added new features
- `acestep/handler.py` -- Bug fixes
- `config.yaml` -- New parameters
**Backup created:**
```text
.update_backup_20260205_143022/
├── start_gradio_ui.bat (your version)
├── config.yaml (your version)
└── acestep/
└── handler.py (your version)
```
**Working tree after update:**
```text
start_gradio_ui.bat (new version from GitHub)
config.yaml (new version from GitHub)
acestep/
└── handler.py (new version from GitHub)
```
Your original files are preserved in the backup directory so you can merge your changes back in.
---
## Merging Configurations
After an update that backed up your files, use the merge helper to compare and restore your settings.
### Windows: merge_config.bat
```batch
merge_config.bat
```
When comparing files, this script opens two Notepad windows side by side -- one with the backup version and one with the current version -- so you can manually copy your settings across.
### Linux / macOS: merge_config.sh
```bash
./merge_config.sh
```
When comparing files, this script uses `colordiff` (if installed) or `diff` to display a unified diff in the terminal, showing exactly what changed between your backed-up version and the new version.
To install colordiff for colored output:
```bash
# Ubuntu / Debian
sudo apt install colordiff
# macOS (Homebrew)
brew install colordiff
# Arch Linux
sudo pacman -S colordiff
```
### Menu Options (Both Platforms)
Both `merge_config.bat` and `merge_config.sh` present the same interactive menu:
```text
========================================
ACE-Step Backup Merge Helper
========================================
1. Compare backup with current files
2. Restore a file from backup
3. List all backed up files
4. Delete old backups
5. Exit
```
| Option | Description |
|--------|-------------|
| **1. Compare** | Show differences between your backup and the current (updated) file. On Windows this opens two Notepad windows. On Linux/macOS this prints a unified diff to the terminal. |
| **2. Restore** | Copy a file from the backup back into the project, overwriting the updated version. Use this only if the new version causes problems. |
| **3. List** | Display all files stored in backup directories. |
| **4. Delete** | Permanently remove old backup directories. Only do this after you have finished merging. |
### Merging Common Files
**Launch scripts** (`start_gradio_ui.bat`, `start_gradio_ui.sh`, etc.):
Look for your custom settings in the backup (language, port, download source, etc.) and copy them into the corresponding lines of the new version.
```bash
# Example settings you may want to preserve:
LANGUAGE="zh"
PORT=8080
DOWNLOAD_SOURCE="--download-source modelscope"
```
**Configuration files** (`config.yaml`, `.json`):
Compare the structures. Keep your custom values, add any new keys from the updated version.
```yaml
# Backup (your version)
model_path: "custom/path"
custom_setting: true
# Current (new version)
model_path: "default/path"
new_feature: enabled
# Merged result
model_path: "custom/path" # Keep your setting
custom_setting: true # Keep your setting
new_feature: enabled # Add new feature
```
---
## Testing Update Functionality
Use the test scripts to verify that your git setup and update mechanism are working correctly before relying on them.
**Windows:**
```batch
test_git_update.bat
```
**Linux / macOS:**
```bash
./test_git_update.sh
```
### What the Tests Check
1. **Git availability**: Verifies that git can be found (PortableGit or system git on Windows; system git on Linux/macOS).
2. **Repository validity**: Confirms the project directory is a valid git repository.
3. **Update script presence**: Checks that `check_update.bat` / `check_update.sh` exists.
4. **Network connectivity**: Attempts an actual fetch from the remote (with timeout).
### Example Test Output
```text
========================================
Test Git Update Check
========================================
[Test 1] Checking Git...
[PASS] Git found
git version 2.43.0
[Test 2] Checking git repository...
[PASS] Valid git repository
Branch: main
Commit: a1b2c3d
[Test 3] Checking update script...
[PASS] check_update.sh found
[Test 4] Running update check...
[PASS] Update check completed successfully
[PASS] All tests completed
```
---
## Troubleshooting
### Git not found
The update check is silently skipped if git is not available. To enable it, install git for your platform:
| Platform | Install Command |
|----------|----------------|
| **Windows (PortableGit)** | Download from <https://git-scm.com/download/win> and extract to `PortableGit\` in the project root |
| **Windows (system)** | `winget install --id Git.Git -e` or use the Git for Windows installer |
| **Ubuntu / Debian** | `sudo apt install git` |
| **CentOS / RHEL** | `sudo yum install git` |
| **Arch Linux** | `sudo pacman -S git` |
| **macOS** | `xcode-select --install` or `brew install git` |
### Network timeout
The fetch operation has a 10-second timeout. If it times out, the update check is skipped automatically and the application starts normally. This is expected behavior on slow or restricted networks.
On macOS, the timeout mechanism uses `gtimeout` from GNU coreutils if available, or falls back to a plain fetch without a timeout. To get proper timeout support:
```bash
brew install coreutils
```
### Proxy configuration
**Windows (`check_update.bat`):**
Create a `proxy_config.txt` file in the project root:
```text
PROXY_ENABLED=1
PROXY_URL=http://127.0.0.1:7890
```
Or configure interactively:
```batch
check_update.bat proxy
```
Common proxy formats:
| Type | Example |
|------|---------|
| HTTP proxy | `http://127.0.0.1:7890` |
| HTTPS proxy | `https://proxy.company.com:8080` |
| SOCKS5 proxy | `socks5://127.0.0.1:1080` |
To disable the proxy, set `PROXY_ENABLED=0` in `proxy_config.txt`.
**Linux / macOS:**
Set standard environment variables before running the script:
```bash
export http_proxy="http://127.0.0.1:7890"
export https_proxy="http://127.0.0.1:7890"
./check_update.sh
```
Or add them to your shell profile (`~/.bashrc`, `~/.zshrc`) for persistence.
### Merge conflicts
If the automatic update fails or produces unexpected results:
1. Check for backup directories: look for `.update_backup_*` folders in the project root.
2. Use the merge helper (`merge_config.bat` or `./merge_config.sh`) to compare and restore files.
3. If needed, manually inspect the diff between your backup and the current files.
### Lost configuration after update
1. Find your backup:
- **Windows:** `dir /b .update_backup_*`
- **Linux / macOS:** `ls -d .update_backup_*`
2. Use the merge helper (Option 2) to restore specific files, or manually copy settings from the backup.
---
## Quick Reference
| Action | Windows | Linux / macOS |
|--------|---------|---------------|
| **Enable update check** | `set CHECK_UPDATE=true` (in `.bat`) | `CHECK_UPDATE="true"` (in `.sh`) |
| **Disable update check** | `set CHECK_UPDATE=false` (in `.bat`) | `CHECK_UPDATE="false"` (in `.sh`) |
| **Manual update** | `check_update.bat` | `./check_update.sh` |
| **Configure proxy** | `check_update.bat proxy` or edit `proxy_config.txt` | `export http_proxy=... && ./check_update.sh` |
| **Merge configurations** | `merge_config.bat` | `./merge_config.sh` |
| **Test update setup** | `test_git_update.bat` | `./test_git_update.sh` |
| **List backups** | `dir /b .update_backup_*` | `ls -d .update_backup_*` |
| **Delete a backup** | `rmdir /s /q .update_backup_YYYYMMDD_HHMMSS` | `rm -rf .update_backup_YYYYMMDD_HHMMSS` |